diff --git a/DEVELOPMENT_GUIDE.md b/DEVELOPMENT_GUIDE.md index 59e5f2499..c6f3aa226 100644 --- a/DEVELOPMENT_GUIDE.md +++ b/DEVELOPMENT_GUIDE.md @@ -19,6 +19,10 @@ To compile and run the package locally, you need to run the following command in 1. Navigate into the directory of the repository 2. Run: `go run cmd/mailchain/main.go account add --protocol=ethereum --key-type="secp256k1" --private-key=YOUR_PRIVATE_KEY` +**To run substrate** + +1. Run: `docker-compose -f docker-compose.substrate.yml up --build` + ### Inbox vs Inbox Staging From time to time, there may be pending breaking changes that have not been released. This means that if you use the web inbox, it may not work as desired. diff --git a/Makefile b/Makefile index a9b8bd733..a76a37e21 100644 --- a/Makefile +++ b/Makefile @@ -76,3 +76,6 @@ lint: golangci-lint run --fix .FORCE: + +indexer-database-up: + go run cmd/indexer/main.go database up --master-postgres-password=mailchain --master-postgres-user=mailchain --indexer-postgres-password=indexer --envelope-postgres-password=envelope --pubkey-postgres-password=pubkey \ No newline at end of file diff --git a/cmd/indexer/commands/block-num.go b/cmd/indexer/commands/block-num.go new file mode 100644 index 000000000..d44316cef --- /dev/null +++ b/cmd/indexer/commands/block-num.go @@ -0,0 +1,16 @@ +package commands + +import ( + "context" + "strconv" + + "github.com/mailchain/mailchain/cmd/indexer/internal/clients" +) + +func getBlockNumber(blockNumber string, client clients.Block) (uint64, error) { + if blockNumber == "latest" { + return client.LatestBlockNumber(context.Background()) + } + + return strconv.ParseUint(blockNumber, 0, 64) +} diff --git a/cmd/indexer/commands/ethereum.go b/cmd/indexer/commands/ethereum.go index a0371db6c..f333a24c0 100644 --- a/cmd/indexer/commands/ethereum.go +++ b/cmd/indexer/commands/ethereum.go @@ -23,7 +23,7 @@ func ethereumCmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { network, _ := cmd.Flags().GetString("network") protocol, _ := cmd.Flags().GetString("protocol") - blockNumber, _ := cmd.Flags().GetUint64("start-block") + blockNumber, _ := cmd.Flags().GetString("start-block") addressRPC, _ := cmd.Flags().GetString("rpc-address") if addressRPC == "" { @@ -62,7 +62,7 @@ func ethereumCmd() *cobra.Command { }, } - cmd.Flags().Uint64("start-block", 0, "Block number from which the indexer will start") + cmd.Flags().String("start-block", "latest", "Block number from which the indexer will start, e.g. 10000, or 'latest'") cmd.Flags().String("protocol", protocols.Ethereum, "Protocol to run against") cmd.Flags().String("network", ethereum.Mainnet, "Network to run against") cmd.Flags().String("rpc-address", "", "Ethereum RPC-JSON address") @@ -70,7 +70,7 @@ func ethereumCmd() *cobra.Command { return cmd } -func createEthereumProcessor(connIndexer, connPublicKey, connEnvelope *sqlx.DB, blockNumber uint64, protocol, network, rawStorePath, addressRPC string) (*processor.Sequential, error) { +func createEthereumProcessor(connIndexer, connPublicKey, connEnvelope *sqlx.DB, blockNumber, protocol, network, rawStorePath, addressRPC string) (*processor.Sequential, error) { ctx := context.Background() ethClient, err := eth.NewRPC(addressRPC) @@ -78,6 +78,11 @@ func createEthereumProcessor(connIndexer, connPublicKey, connEnvelope *sqlx.DB, return nil, err } + blockNo, err := getBlockNumber(blockNumber, ethClient) + if err != nil { + return nil, err + } + networkID, err := ethClient.NetworkID(ctx) if err != nil { return nil, err @@ -119,7 +124,7 @@ func createEthereumProcessor(connIndexer, connPublicKey, connEnvelope *sqlx.DB, chainCfg, ) - if err := syncStore.PutBlockNumber(ctx, protocol, network, blockNumber); err != nil { + if err := syncStore.PutBlockNumber(ctx, protocol, network, blockNo); err != nil { return nil, err } diff --git a/cmd/indexer/commands/sequenial.go b/cmd/indexer/commands/sequenial.go index 66af14a3d..378dad7c6 100644 --- a/cmd/indexer/commands/sequenial.go +++ b/cmd/indexer/commands/sequenial.go @@ -4,16 +4,24 @@ import ( "context" "fmt" + "github.com/cenkalti/backoff" "github.com/mailchain/mailchain/cmd/indexer/internal/processor" "github.com/spf13/cobra" ) func doSequential(cmd *cobra.Command, p *processor.Sequential) { for { - err := p.NextBlock(context.Background()) + operation := func() error { + err := p.NextBlock(context.Background()) + if err != nil { + fmt.Fprintf(cmd.ErrOrStderr(), "%+v\n", err) + } - if err != nil { - fmt.Fprintf(cmd.ErrOrStderr(), "%+v", err) + return err + } + + if err := backoff.Retry(operation, backoff.NewExponentialBackOff()); err != nil { + fmt.Fprintf(cmd.ErrOrStderr(), "%+v\n", err) } } } diff --git a/cmd/indexer/commands/substrate.go b/cmd/indexer/commands/substrate.go index f818023a7..be131f665 100644 --- a/cmd/indexer/commands/substrate.go +++ b/cmd/indexer/commands/substrate.go @@ -6,7 +6,7 @@ import ( "github.com/jmoiron/sqlx" "github.com/mailchain/mailchain/cmd/indexer/internal/processor" sub "github.com/mailchain/mailchain/cmd/indexer/internal/substrate" - "github.com/mailchain/mailchain/cmd/internal/datastore/os" + "github.com/mailchain/mailchain/cmd/internal/datastore/noop" "github.com/mailchain/mailchain/cmd/internal/datastore/pq" "github.com/mailchain/mailchain/internal/protocols" "github.com/mailchain/mailchain/internal/protocols/substrate" @@ -22,15 +22,13 @@ func substrateCmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { network, _ := cmd.Flags().GetString("network") protocol, _ := cmd.Flags().GetString("protocol") - blockNumber, _ := cmd.Flags().GetUint64("start-block") + blockNumber, _ := cmd.Flags().GetString("start-block") addressRPC, _ := cmd.Flags().GetString("rpc-address") if addressRPC == "" { return errors.Errorf("rpc-address must not be empty") } - rawStorePath, _ := cmd.Flags().GetString("raw-store-path") - connIndexer, err := newPostgresConnection(cmd, "indexer") if err != nil { return err @@ -50,7 +48,7 @@ func substrateCmd() *cobra.Command { defer connPublicKey.Close() defer connEnvelope.Close() - seqProcessor, err := createSubstrateProcessor(connIndexer, connPublicKey, connEnvelope, blockNumber, protocol, network, rawStorePath, addressRPC) + seqProcessor, err := createSubstrateProcessor(connIndexer, connPublicKey, connEnvelope, blockNumber, protocol, network, addressRPC) if err != nil { return err } @@ -61,7 +59,7 @@ func substrateCmd() *cobra.Command { }, } - cmd.Flags().Uint64("start-block", 0, "Block number from which the indexer will start") + cmd.Flags().String("start-block", "latest", "Block number from which the indexer will start, e.g. 10000, or 'latest'") cmd.Flags().String("protocol", protocols.Substrate, "Protocol to run against") cmd.Flags().String("network", substrate.EdgewareMainnet, "Network to run against") cmd.Flags().String("rpc-address", "", "Substrate RPC-JSON address") @@ -69,7 +67,7 @@ func substrateCmd() *cobra.Command { return cmd } -func createSubstrateProcessor(connIndexer, connPublicKey, connEnvelope *sqlx.DB, blockNumber uint64, protocol, network, rawStorePath, addressRPC string) (*processor.Sequential, error) { +func createSubstrateProcessor(connIndexer, connPublicKey, connEnvelope *sqlx.DB, blockNumber, protocol, network, addressRPC string) (*processor.Sequential, error) { ctx := context.Background() subClient, err := sub.NewRPC(addressRPC) @@ -77,6 +75,11 @@ func createSubstrateProcessor(connIndexer, connPublicKey, connEnvelope *sqlx.DB, return nil, err } + blockNo, err := getBlockNumber(blockNumber, subClient) + if err != nil { + return nil, err + } + syncStore, err := pq.NewSyncStore(connIndexer) if err != nil { return nil, err @@ -92,18 +95,14 @@ func createSubstrateProcessor(connIndexer, connPublicKey, connEnvelope *sqlx.DB, return nil, err } - rawStore, err := os.NewRawTransactionStore(rawStorePath) + rawStore, err := noop.NewRawTransactionStore() if err != nil { return nil, err } - processorTransaction := sub.NewExtrinsicProcessor( - transactionStore, - rawStore, - pubKeyStore, - ) + processorTransaction := sub.NewExtrinsicProcessor(transactionStore, rawStore, pubKeyStore) - if err := syncStore.PutBlockNumber(ctx, protocol, network, blockNumber); err != nil { + if err := syncStore.PutBlockNumber(ctx, protocol, network, blockNo); err != nil { return nil, err } diff --git a/cmd/indexer/internal/substrate/extrinsic.go b/cmd/indexer/internal/substrate/extrinsic.go index b2c2a92e1..9075cb08d 100644 --- a/cmd/indexer/internal/substrate/extrinsic.go +++ b/cmd/indexer/internal/substrate/extrinsic.go @@ -53,15 +53,6 @@ func (t *Extrinsic) Run(ctx context.Context, protocol, network string, tx interf return actions.StoreTransaction(ctx, t.txStore, t.rawTxStore, protocol, network, storeTx, subEx) } -// func (t *Transaction) From(blockNo *big.Int, tx *types.Transaction) ([]byte, error) { -// msg, err := tx.AsMessage(types.MakeSigner(t.chainConfig, blockNo)) -// if err != nil { -// return nil, err -// } - -// return msg.From().Bytes(), nil -// } - func getFromPublicKey(sig *types.ExtrinsicSignatureV4) (crypto.PublicKey, error) { if !sig.Signer.IsAccountID { return nil, errors.Errorf("must be signed by account ID") @@ -119,11 +110,6 @@ func (t *Extrinsic) ToTransaction(network string, blk *types.Block, ex *types.Ex txInfo, data := getParts(w.Bytes()) - decodedData, err := encoding.DecodeHex(string(data)) - if err != nil { - return nil, err - } - from, err := getFromAddress(network, &ex.Signature) if err != nil { return nil, err @@ -143,7 +129,7 @@ func (t *Extrinsic) ToTransaction(network string, blk *types.Block, ex *types.Ex From: from, // BlockHash: blk.Hash().Bytes(), Hash: hash, - Data: decodedData, + Data: data, To: to, // Value: *value, // GasUsed: *gasUsed, @@ -154,13 +140,13 @@ func (t *Extrinsic) ToTransaction(network string, blk *types.Block, ex *types.Ex func getParts(data []byte) (txInfo, dataField []byte) { // mailchain hex encoded -> 0x6d61696c636861696e // 0x6d61696c636861696e hex encoded -> 3078366436313639366336333638363136393665 - pieces := bytes.Split(data, []byte{0x30, 0x78, 0x36, 0x64, 0x36, 0x31, 0x36, 0x39, 0x36, 0x63, 0x36, 0x33, 0x36, 0x38, 0x36, 0x31, 0x36, 0x39, 0x36, 0x65}) + pieces := bytes.Split(data, encoding.DataPrefix()) txInfo = pieces[0] if len(pieces) == 2 { dataField = append( // 0x30, 0x78 -> 0x - []byte{0x36, 0x64, 0x36, 0x31, 0x36, 0x39, 0x36, 0x63, 0x36, 0x33, 0x36, 0x38, 0x36, 0x31, 0x36, 0x39, 0x36, 0x65}, + encoding.DataPrefix(), pieces[1]..., ) } diff --git a/cmd/indexer/internal/substrate/extrinsic_test.go b/cmd/indexer/internal/substrate/extrinsic_test.go index 8253ab1bf..c26801e97 100644 --- a/cmd/indexer/internal/substrate/extrinsic_test.go +++ b/cmd/indexer/internal/substrate/extrinsic_test.go @@ -60,7 +60,7 @@ func TestExtrinsic_Run(t *testing.T) { "substrate", "edgeware-berlin", func() *types.Extrinsic { - b := getBlock(t, "0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971") + b := getBlock(t, "0x61c83ac28eec9ac530df6f117edc761e3c3a0861f73dc8534f2e0682f1d9ef75") e := b.Extrinsics[2] return &e }(), @@ -98,36 +98,36 @@ func Test_ExtrinsicHash(t *testing.T) { "success-timestamp-set", args{ func() *types.Extrinsic { - b := getBlock(t, "0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971") + b := getBlock(t, "0x61c83ac28eec9ac530df6f117edc761e3c3a0861f73dc8534f2e0682f1d9ef75") e := b.Extrinsics[0] return &e }(), }, - []byte{0x94, 0x6c, 0x96, 0xef, 0x50, 0x2b, 0x4f, 0x4, 0x6e, 0x4f, 0xae, 0x52, 0x9, 0x52, 0x3a, 0xe3, 0xaa, 0x59, 0xa5, 0x66, 0x49, 0x2d, 0xf6, 0x4e, 0xdf, 0x38, 0x10, 0xd0, 0x35, 0x7c, 0x35, 0x51}, + []byte{0xc7, 0x6e, 0xcb, 0x62, 0x2e, 0xca, 0x6e, 0x4d, 0xdb, 0xab, 0xd3, 0xfc, 0x50, 0xe, 0x59, 0x9c, 0x7e, 0xce, 0xb, 0x87, 0x16, 0x9d, 0x39, 0x2f, 0xa1, 0xab, 0xc6, 0xce, 0x8f, 0xca, 0x20, 0x32}, false, }, { "success-final-hint", args{ func() *types.Extrinsic { - b := getBlock(t, "0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971") + b := getBlock(t, "0x61c83ac28eec9ac530df6f117edc761e3c3a0861f73dc8534f2e0682f1d9ef75") e := b.Extrinsics[1] return &e }(), }, - []byte{0x49, 0xf0, 0xb1, 0x75, 0x20, 0xe9, 0x77, 0x4f, 0x8d, 0x86, 0xf4, 0xba, 0xae, 0x6, 0x15, 0xe9, 0x2c, 0x54, 0xbd, 0x9f, 0x87, 0x35, 0xd6, 0xe8, 0xa7, 0xab, 0xeb, 0x8c, 0x76, 0x2a, 0x67, 0xd4}, + []byte{0x18, 0xd0, 0x2a, 0x17, 0x69, 0x9d, 0x50, 0xba, 0xe, 0x42, 0xa2, 0x7a, 0xa3, 0x52, 0x6d, 0x36, 0x4a, 0x9, 0xbe, 0x30, 0x54, 0xe8, 0x49, 0xa5, 0x74, 0x47, 0x8d, 0xa5, 0x50, 0x5a, 0x4c, 0xc2}, false, }, { "success-contract", args{ func() *types.Extrinsic { - b := getBlock(t, "0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971") + b := getBlock(t, "0x61c83ac28eec9ac530df6f117edc761e3c3a0861f73dc8534f2e0682f1d9ef75") e := b.Extrinsics[2] return &e }(), }, - []byte{0xdc, 0xd9, 0x48, 0x65, 0x81, 0x56, 0x13, 0x1f, 0x43, 0xb7, 0x8a, 0x4c, 0x85, 0xe1, 0x63, 0xa8, 0xa1, 0xaa, 0x33, 0x65, 0xa3, 0x92, 0xf4, 0x9e, 0x51, 0x43, 0x65, 0x20, 0x57, 0xfc, 0xf5, 0xe9}, + []byte{0x3f, 0xcb, 0x9c, 0x48, 0x7d, 0x1a, 0xc, 0x28, 0xfe, 0x27, 0xa7, 0x73, 0xcd, 0x9d, 0x20, 0x12, 0x48, 0x25, 0x33, 0xd9, 0x6, 0x4a, 0xed, 0x91, 0xb2, 0x98, 0x3, 0xaa, 0xcc, 0x3d, 0x4a, 0x9e}, false, }, } diff --git a/cmd/indexer/internal/substrate/testdata/blocks/0x61c83ac28eec9ac530df6f117edc761e3c3a0861f73dc8534f2e0682f1d9ef75.json b/cmd/indexer/internal/substrate/testdata/blocks/0x61c83ac28eec9ac530df6f117edc761e3c3a0861f73dc8534f2e0682f1d9ef75.json new file mode 100644 index 000000000..99a41d53a --- /dev/null +++ b/cmd/indexer/internal/substrate/testdata/blocks/0x61c83ac28eec9ac530df6f117edc761e3c3a0861f73dc8534f2e0682f1d9ef75.json @@ -0,0 +1,26 @@ +{ + "jsonrpc": "2.0", + "result": { + "block": { + "extrinsics": [ + "0x280402000b40f1a9737301", + "0x1c040b00eada8e00", + "0x490484ff169a11721851f5dff3541dd5c4b0b478ac1cd092c9d5976e83daa0d03f26620c0164abcbfe0a217090472c5c173222e9f9f35dc17c8cce8db860a29571eef574288545e2d09772f043aa6e2858bd0421640b25f666af8ff16535ccc6af398f01850030000e02ff84623e7252e41138af6904e1b02304c941625f39e5762589125dc1a2f2cf2e300002f4010009026d61696c636861696e010a6e2ae344da21829b0b22ab9cfead2ada180ee30318ae9279988275688b059de8d2e6487a7000bf9de65744ccf32dfa2a1f834a6e4f444b45f1257f922ec726b19485a5bbb83bee0c206a786516110d05f583b1c4dd8a3826369c999611705be7ffb419363e2c9797ec5f32b6661a4612062204a3b3ba0d" + ], + "header": { + "digest": { + "logs": [ + "0x0661757261208c3ed90f00000000", + "0x0561757261010149a7f5bdc38daac2f75942f0c543eda761241284f4b737c9c98125ac52187c5fdfc017a24564aed5018a75fc131e3e9f21f35a419befd128d7abe642a126780e" + ] + }, + "extrinsicsRoot": "0x607f22bb3237dc3aa4e705402f346f661c0a479004e9ca50f3e0a26e5f70a34f", + "number": "0x23b6bd", + "parentHash": "0x52626c79f0e7008112ab337595597950174e08cbae0cef1a65fbf80e8698cde9", + "stateRoot": "0x08554277fafec2afe218c66f29b2986cd000e438b36b65168471353221b5134a" + } + }, + "justification": null + }, + "id": 22 +} \ No newline at end of file diff --git a/cmd/indexer/internal/substrate/testdata/blocks/0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971 2.json b/cmd/indexer/internal/substrate/testdata/blocks/0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971 2.json deleted file mode 100644 index cf7d6775e..000000000 --- a/cmd/indexer/internal/substrate/testdata/blocks/0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971 2.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "jsonrpc": "2.0", - "result": { - "block": { - "extrinsics": [ - "0x280402000ba0fa77987101", - "0x1c040b007ab03f00", - "0xd10384fff49a6b6deb3cdb00d86e735ced8f48b649273fc6ff6a3bbba021a6b4a1c5d06701b6f94ea763c250533409727e26eda6f67661c8da4190dbf0988c4e0c4ee14465efe0f64267e836aeee21a7d44aec1c82a2013c8dd064aebe27559db0cece9b83007c000e02ff2a9aff809089623c64ccd7ca79b8fffb114cef2c251b2b3e4f2d5144eee962fb0002f40100910130783664363136393663363336383631363936653031306132353230303130313136323065666639663438333061373038653364373938333635363861306161346136343063353034643034383938343139363738333361306439633965353162366533" - ], - "header": { - "digest": { - "logs": [ - "0x06617572612026f8c40f00000000", - "0x0561757261010135748ca2ac77a329c7053b38bff94a18a504df23aea9a9c8e03e06f479b334b384996a0021447b5a4afebd32f2ee6ceb8cc832b107f7cd35400e5a17854d0407" - ] - }, - "extrinsicsRoot": "0x8765cd86b8d08b63fb315855894f5c357c4b25670c131f96b5bbad5aab6971a5", - "number": "0xfec21", - "parentHash": "0xf145d056d93bc8e1661cf334763b475f32ab3ee0155e3d6a33c825904509023d", - "stateRoot": "0x0a2164eece05fab911adfd891ea1e02e9e11a0a1f18e05f61ffefecc99b5dabf" - } - }, - "justification": null - }, - "id": 23 -} \ No newline at end of file diff --git a/cmd/indexer/internal/substrate/testdata/blocks/0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971.json b/cmd/indexer/internal/substrate/testdata/blocks/0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971.json deleted file mode 100644 index cf7d6775e..000000000 --- a/cmd/indexer/internal/substrate/testdata/blocks/0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "jsonrpc": "2.0", - "result": { - "block": { - "extrinsics": [ - "0x280402000ba0fa77987101", - "0x1c040b007ab03f00", - "0xd10384fff49a6b6deb3cdb00d86e735ced8f48b649273fc6ff6a3bbba021a6b4a1c5d06701b6f94ea763c250533409727e26eda6f67661c8da4190dbf0988c4e0c4ee14465efe0f64267e836aeee21a7d44aec1c82a2013c8dd064aebe27559db0cece9b83007c000e02ff2a9aff809089623c64ccd7ca79b8fffb114cef2c251b2b3e4f2d5144eee962fb0002f40100910130783664363136393663363336383631363936653031306132353230303130313136323065666639663438333061373038653364373938333635363861306161346136343063353034643034383938343139363738333361306439633965353162366533" - ], - "header": { - "digest": { - "logs": [ - "0x06617572612026f8c40f00000000", - "0x0561757261010135748ca2ac77a329c7053b38bff94a18a504df23aea9a9c8e03e06f479b334b384996a0021447b5a4afebd32f2ee6ceb8cc832b107f7cd35400e5a17854d0407" - ] - }, - "extrinsicsRoot": "0x8765cd86b8d08b63fb315855894f5c357c4b25670c131f96b5bbad5aab6971a5", - "number": "0xfec21", - "parentHash": "0xf145d056d93bc8e1661cf334763b475f32ab3ee0155e3d6a33c825904509023d", - "stateRoot": "0x0a2164eece05fab911adfd891ea1e02e9e11a0a1f18e05f61ffefecc99b5dabf" - } - }, - "justification": null - }, - "id": 23 -} \ No newline at end of file diff --git a/cmd/internal/datastore/noop/transaction.go b/cmd/internal/datastore/noop/transaction.go new file mode 100644 index 000000000..f6a9baef3 --- /dev/null +++ b/cmd/internal/datastore/noop/transaction.go @@ -0,0 +1,21 @@ +package noop + +import ( + "context" + + "github.com/mailchain/mailchain/cmd/internal/datastore" +) + +// NewRawTransactionStore that does nothing. +func NewRawTransactionStore() (datastore.RawTransactionStore, error) { + return &RawTransactionStore{}, nil +} + +// RawTransactionStore object. +type RawTransactionStore struct { +} + +// PutRawTransaction writes nothing. +func (s RawTransactionStore) PutRawTransaction(ctx context.Context, protocol, network string, hash []byte, tx interface{}) error { + return nil +} diff --git a/cmd/internal/datastore/pq/transactions.go b/cmd/internal/datastore/pq/transactions.go index ac18f0f14..f5e05d17b 100644 --- a/cmd/internal/datastore/pq/transactions.go +++ b/cmd/internal/datastore/pq/transactions.go @@ -71,7 +71,7 @@ func (s *TransactionStore) getTransactions(ctx context.Context, protocol, networ return nil, errors.WithStack(err) } - sql, args, err := squirrel.Select("tx_from", "tx_to", "tx_data", "tx_block_hash", "tx_value", "tx_gas_used", "tx_gas_price"). + sql, args, err := squirrel.Select("hash", "tx_from", "tx_to", "tx_data", "tx_block_hash", "tx_value", "tx_gas_used", "tx_gas_price"). From("transactions"). PlaceholderFormat(squirrel.Dollar). Where(squirrel.Eq{"protocol": p}). diff --git a/cmd/internal/datastore/pq/transactions_test.go b/cmd/internal/datastore/pq/transactions_test.go index b8466beff..61227a904 100644 --- a/cmd/internal/datastore/pq/transactions_test.go +++ b/cmd/internal/datastore/pq/transactions_test.go @@ -16,6 +16,8 @@ import ( ) var ( + hash1 = []byte{0x01, 0x90, 0x6b, 0x5c, 0x89, 0x1, 0xfd, 0xb2, 0xab, 0x4c, 0xe3, 0xdb, 0x60, 0x9b, 0x60, 0x5c, 0xd5, 0xef, 0x7f, 0xe6} + hash2 = []byte{0x02, 0xab, 0x9b, 0xe3, 0x60, 0x5c, 0x6b, 0x5c, 0x4c, 0xd5, 0x90, 0xdb, 0x60, 0x89, 0x1, 0xfd, 0xb2, 0xef, 0x7f, 0xe6} fromAddress = []byte{0x54, 0xab, 0x4c, 0xe3, 0x60, 0x5c, 0xd5, 0x90, 0xdb, 0x60, 0x9b, 0x6b, 0x5c, 0x89, 0x1, 0xfd, 0xb2, 0xef, 0x7f, 0xe6} toAddress = []byte{0x55, 0xab, 0x4c, 0xe3, 0x60, 0x5c, 0xd5, 0x90, 0xdb, 0x60, 0x9b, 0x6b, 0x5c, 0x89, 0x1, 0xfd, 0xb2, 0xef, 0x7f, 0xe6} toAddress2 = []byte{0x57, 0xab, 0x4c, 0xe3, 0x60, 0x5c, 0xd5, 0x90, 0xdb, 0x60, 0x9b, 0x6b, 0x5c, 0x89, 0x1, 0xfd, 0xb2, 0xef, 0x7f, 0xe6} @@ -56,7 +58,7 @@ func TestTransactionStore_GetTransactionsFrom(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - m.ExpectQuery(regexp.QuoteMeta(`SELECT tx_from, tx_to, tx_data, tx_block_hash, tx_value, tx_gas_used, tx_gas_price FROM transactions WHERE protocol = $1 AND network = $2 AND tx_from = $3`)). + m.ExpectQuery(regexp.QuoteMeta(`SELECT hash, tx_from, tx_to, tx_data, tx_block_hash, tx_value, tx_gas_used, tx_gas_price FROM transactions WHERE protocol = $1 AND network = $2 AND tx_from = $3`)). WithArgs(uint8(1), uint8(1), fromAddress). WillReturnError(sql.ErrNoRows) @@ -81,12 +83,12 @@ func TestTransactionStore_GetTransactionsFrom(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - m.ExpectQuery(regexp.QuoteMeta(`SELECT tx_from, tx_to, tx_data, tx_block_hash, tx_value, tx_gas_used, tx_gas_price FROM transactions WHERE protocol = $1 AND network = $2 AND tx_from = $3`)). + m.ExpectQuery(regexp.QuoteMeta(`SELECT hash, tx_from, tx_to, tx_data, tx_block_hash, tx_value, tx_gas_used, tx_gas_price FROM transactions WHERE protocol = $1 AND network = $2 AND tx_from = $3`)). WithArgs(uint8(1), uint8(1), fromAddress). WillReturnRows( - sqlmock.NewRows([]string{"tx_from", "tx_to", "tx_data", "tx_block_hash", "tx_value", "tx_gas_used", "tx_gas_price"}). - AddRow(fromAddress, toAddress, []byte{0x1}, []byte{0x1}, []byte{0x1}, []byte{0x1}, []byte{0x1}). - AddRow(fromAddress, toAddress2, []byte{0x2}, []byte{0x2}, []byte{0x2}, []byte{0x2}, []byte{0x2}), + sqlmock.NewRows([]string{"hash", "tx_from", "tx_to", "tx_data", "tx_block_hash", "tx_value", "tx_gas_used", "tx_gas_price"}). + AddRow(hash1, fromAddress, toAddress, []byte{0x1}, []byte{0x1}, []byte{0x1}, []byte{0x1}, []byte{0x1}). + AddRow(hash2, fromAddress, toAddress2, []byte{0x2}, []byte{0x2}, []byte{0x2}, []byte{0x2}, []byte{0x2}), ) return mock{db, m} @@ -101,6 +103,7 @@ func TestTransactionStore_GetTransactionsFrom(t *testing.T) { Value: *big.NewInt(1), GasUsed: *big.NewInt(1), GasPrice: *big.NewInt(1), + Hash: hash1, }, datastore.Transaction{ From: fromAddress, @@ -110,6 +113,7 @@ func TestTransactionStore_GetTransactionsFrom(t *testing.T) { Value: *big.NewInt(2), GasUsed: *big.NewInt(2), GasPrice: *big.NewInt(2), + Hash: hash2, }, }, false, @@ -195,7 +199,7 @@ func TestTransactionStore_GetTransactionsTo(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - m.ExpectQuery(regexp.QuoteMeta(`SELECT tx_from, tx_to, tx_data, tx_block_hash, tx_value, tx_gas_used, tx_gas_price FROM transactions WHERE protocol = $1 AND network = $2 AND tx_to = $3`)). + m.ExpectQuery(regexp.QuoteMeta(`SELECT hash, tx_from, tx_to, tx_data, tx_block_hash, tx_value, tx_gas_used, tx_gas_price FROM transactions WHERE protocol = $1 AND network = $2 AND tx_to = $3`)). WithArgs(uint8(1), uint8(1), toAddress). WillReturnError(sql.ErrNoRows) @@ -220,11 +224,11 @@ func TestTransactionStore_GetTransactionsTo(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - m.ExpectQuery(regexp.QuoteMeta(`SELECT tx_from, tx_to, tx_data, tx_block_hash, tx_value, tx_gas_used, tx_gas_price FROM transactions WHERE protocol = $1 AND network = $2 AND tx_to = $3`)). + m.ExpectQuery(regexp.QuoteMeta(`SELECT hash, tx_from, tx_to, tx_data, tx_block_hash, tx_value, tx_gas_used, tx_gas_price FROM transactions WHERE protocol = $1 AND network = $2 AND tx_to = $3`)). WithArgs(uint8(1), uint8(1), toAddress). WillReturnRows( - sqlmock.NewRows([]string{"tx_from", "tx_to", "tx_data", "tx_block_hash", "tx_value", "tx_gas_used", "tx_gas_price"}). - AddRow(fromAddress, toAddress, []byte{0x1}, []byte{0x1}, []byte{0x1}, []byte{0x1}, []byte{0x1}), + sqlmock.NewRows([]string{"hash", "tx_from", "tx_to", "tx_data", "tx_block_hash", "tx_value", "tx_gas_used", "tx_gas_price"}). + AddRow(hash1, fromAddress, toAddress, []byte{0x1}, []byte{0x1}, []byte{0x1}, []byte{0x1}, []byte{0x1}), ) return mock{db, m} @@ -239,6 +243,7 @@ func TestTransactionStore_GetTransactionsTo(t *testing.T) { Value: *big.NewInt(1), GasUsed: *big.NewInt(1), GasPrice: *big.NewInt(1), + Hash: hash1, }, }, false, @@ -258,11 +263,11 @@ func TestTransactionStore_GetTransactionsTo(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - m.ExpectQuery(regexp.QuoteMeta(`SELECT tx_from, tx_to, tx_data, tx_block_hash, tx_value, tx_gas_used, tx_gas_price FROM transactions WHERE protocol = $1 AND network = $2 AND tx_to = $3`)). + m.ExpectQuery(regexp.QuoteMeta(`SELECT hash, tx_from, tx_to, tx_data, tx_block_hash, tx_value, tx_gas_used, tx_gas_price FROM transactions WHERE protocol = $1 AND network = $2 AND tx_to = $3`)). WithArgs(uint8(1), uint8(1), toAddress2). WillReturnRows( - sqlmock.NewRows([]string{"tx_from", "tx_to", "tx_data", "tx_block_hash", "tx_value", "tx_gas_used", "tx_gas_price"}). - AddRow(fromAddress, toAddress2, []byte{0x2}, []byte{0x2}, []byte{0x2}, []byte{0x2}, []byte{0x2}), + sqlmock.NewRows([]string{"hash", "tx_from", "tx_to", "tx_data", "tx_block_hash", "tx_value", "tx_gas_used", "tx_gas_price"}). + AddRow(hash2, fromAddress, toAddress2, []byte{0x2}, []byte{0x2}, []byte{0x2}, []byte{0x2}, []byte{0x2}), ) return mock{db, m} @@ -277,6 +282,7 @@ func TestTransactionStore_GetTransactionsTo(t *testing.T) { Value: *big.NewInt(2), GasUsed: *big.NewInt(2), GasPrice: *big.NewInt(2), + Hash: hash2, }, }, false, diff --git a/cmd/mailchain/internal/http/handlers/messages.go b/cmd/mailchain/internal/http/handlers/messages.go index 01f26e438..f8fceb1f5 100644 --- a/cmd/mailchain/internal/http/handlers/messages.go +++ b/cmd/mailchain/internal/http/handlers/messages.go @@ -21,10 +21,10 @@ import ( "time" "github.com/mailchain/mailchain/cmd/internal/http/params" - "github.com/mailchain/mailchain/crypto/cipher" "github.com/mailchain/mailchain/encoding" "github.com/mailchain/mailchain/errs" "github.com/mailchain/mailchain/internal/address" + "github.com/mailchain/mailchain/internal/envelope" "github.com/mailchain/mailchain/internal/keystore" "github.com/mailchain/mailchain/internal/keystore/kdf/multi" "github.com/mailchain/mailchain/internal/mailbox" @@ -32,7 +32,7 @@ import ( "github.com/pkg/errors" ) -// GetMessages returns a handler get spec +// GetMessages returns a handler get spec. func GetMessages(inbox stores.State, receivers map[string]mailbox.Receiver, ks keystore.Store, deriveKeyOptions multi.OptionsBuilders) func(w http.ResponseWriter, r *http.Request) { //nolint: funlen, gocyclo // Get swagger:route GET /messages Messages GetMessages @@ -79,14 +79,26 @@ func GetMessages(inbox stores.State, receivers map[string]mailbox.Receiver, ks k return } - decrypter, err := ks.GetDecrypter(req.addressBytes, req.Protocol, req.Network, cipher.AES256CBC, deriveKeyOptions) - if err != nil { - errs.JSONWriter(w, http.StatusInternalServerError, errors.WithMessage(err, "could not get `decrypter`")) - return - } - messages := make([]getMessage, 0) for _, transactionData := range transactions { //nolint TODO: thats an arbitrary limit + env, err := envelope.Unmarshal(transactionData.Data) + if err != nil { + errs.JSONWriter(w, http.StatusInternalServerError, errors.WithMessage(err, "failed to unmarshal envelope")) + return + } + + decrypterKind, err := env.DecrypterKind() + if err != nil { + errs.JSONWriter(w, http.StatusInternalServerError, errors.WithMessage(err, "failed to find decrypter type")) + return + } + + decrypter, err := ks.GetDecrypter(req.addressBytes, req.Protocol, req.Network, decrypterKind, deriveKeyOptions) + if err != nil { + errs.JSONWriter(w, http.StatusInternalServerError, errors.WithMessage(err, "could not get `decrypter`")) + return + } + message, err := mailbox.ReadMessage(transactionData.Data, decrypter) if err != nil { messages = append(messages, getMessage{ @@ -97,6 +109,7 @@ func GetMessages(inbox stores.State, receivers map[string]mailbox.Receiver, ks k } readStatus, _ := inbox.GetReadStatus(message.ID) + messages = append(messages, getMessage{ Body: string(message.Body), Headers: &getHeaders{ diff --git a/cmd/mailchain/internal/http/handlers/messages_test.go b/cmd/mailchain/internal/http/handlers/messages_test.go index 4ac863136..0fc5271be 100644 --- a/cmd/mailchain/internal/http/handlers/messages_test.go +++ b/cmd/mailchain/internal/http/handlers/messages_test.go @@ -16,6 +16,7 @@ package handlers import ( "context" + "fmt" "io/ioutil" "net/http" "net/http/httptest" @@ -51,36 +52,32 @@ func Test_GetMessages(t *testing.T) { name string args args req *http.Request - wantBody string wantStatus int }{ { - "err-empty-address", + "422-empty-address", args{}, httptest.NewRequest("GET", "/?address=&network=mainnet&protocol=ethereum", nil), - "{\"code\":422,\"message\":\"'address' must not be empty\"}\n", http.StatusUnprocessableEntity, }, { - "err-receiver-not-supported", + "422-receiver-not-supported", args{}, httptest.NewRequest("GET", "/?address=0x5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761&network=mainnet&protocol=ethereum", nil), - "{\"code\":422,\"message\":\"receiver not supported on \\\"ethereum/mainnet\\\"\"}\n", http.StatusUnprocessableEntity, }, { - "err-receiver-no-configured", + "422-receiver-no-configured", args{ receivers: map[string]mailbox.Receiver{ "ethereum/mainnet": nil, }, }, httptest.NewRequest("GET", "/?address=0x5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761&network=mainnet&protocol=ethereum", nil), - "{\"code\":422,\"message\":\"no receiver configured for \\\"ethereum/mainnet\\\"\"}\n", http.StatusUnprocessableEntity, }, { - "err-no-private-key-found", + "406-no-private-key-found", args{ receivers: map[string]mailbox.Receiver{ "ethereum/mainnet": etherscan.APIClient{}, @@ -92,11 +89,10 @@ func Test_GetMessages(t *testing.T) { }(), }, httptest.NewRequest("GET", "/?address=0x5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761&network=mainnet&protocol=ethereum", nil).WithContext(context.Background()), - "{\"code\":406,\"message\":\"no private key found for address\"}\n", http.StatusNotAcceptable, }, { - "err-receiver-network-error", + "406-receiver-network-error", args{ receivers: func() map[string]mailbox.Receiver { return map[string]mailbox.Receiver{ @@ -115,11 +111,10 @@ func Test_GetMessages(t *testing.T) { }(), }, httptest.NewRequest("GET", "/?address=0x5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761&network=mainnet&protocol=ethereum", nil), - "{\"code\":406,\"message\":\"network `mainnet` does not have etherscan client configured\"}\n", http.StatusNotAcceptable, }, { - "err-receiver-internal-error", + "500-receiver-internal-error", args{ receivers: func() map[string]mailbox.Receiver { return map[string]mailbox.Receiver{ @@ -138,18 +133,23 @@ func Test_GetMessages(t *testing.T) { }(), }, httptest.NewRequest("GET", "/?address=0x5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761&network=mainnet&protocol=ethereum", nil), - "{\"code\":500,\"message\":\"internal error\"}\n", http.StatusInternalServerError, }, { - "err-decrypter-error", + "500-decrypter-error", args{ receivers: func() map[string]mailbox.Receiver { return map[string]mailbox.Receiver{ "ethereum/mainnet": func() mailbox.Receiver { receiver := mailboxtest.NewMockReceiver(mockCtrl) receiver.EXPECT().Receive(context.Background(), "ethereum", "mainnet", []byte{0x56, 0x2, 0xea, 0x95, 0x54, 0xb, 0xee, 0x46, 0xd0, 0x3b, 0xa3, 0x35, 0xee, 0xd6, 0xf4, 0x9d, 0x11, 0x7e, 0xab, 0x95, 0xc8, 0xab, 0x8b, 0x71, 0xba, 0xe2, 0xcd, 0xd1, 0xe5, 0x64, 0xa7, 0x61}). - Return([]mailbox.Transaction{}, nil).Times(1) + Return([]mailbox.Transaction{ + { + Data: encodingtest.MustDecodeHex("500801120f7365637265742d6c6f636174696f6e1a221620d3c47ef741473ebf42773d25687b7540a3d96429aec07dd1ce66c0d4fd16ea13"), + BlockID: []byte("YS1ibG9jay1udW1iZXI="), + Hash: []byte("YS1oYXNo"), + }, + }, nil).Times(1) return receiver }(), } @@ -157,16 +157,15 @@ func Test_GetMessages(t *testing.T) { ks: func() keystore.Store { store := keystoretest.NewMockStore(mockCtrl) store.EXPECT().HasAddress([]byte{0x56, 0x2, 0xea, 0x95, 0x54, 0xb, 0xee, 0x46, 0xd0, 0x3b, 0xa3, 0x35, 0xee, 0xd6, 0xf4, 0x9d, 0x11, 0x7e, 0xab, 0x95, 0xc8, 0xab, 0x8b, 0x71, 0xba, 0xe2, 0xcd, 0xd1, 0xe5, 0x64, 0xa7, 0x61}, "ethereum", "mainnet").Return(true).Times(1) - store.EXPECT().GetDecrypter([]byte{0x56, 0x2, 0xea, 0x95, 0x54, 0xb, 0xee, 0x46, 0xd0, 0x3b, 0xa3, 0x35, 0xee, 0xd6, 0xf4, 0x9d, 0x11, 0x7e, 0xab, 0x95, 0xc8, 0xab, 0x8b, 0x71, 0xba, 0xe2, 0xcd, 0xd1, 0xe5, 0x64, 0xa7, 0x61}, "ethereum", "mainnet", cipher.AES256CBC, multi.OptionsBuilders{}).Return(nil, errors.New("not found")) + store.EXPECT().GetDecrypter([]byte{0x56, 0x2, 0xea, 0x95, 0x54, 0xb, 0xee, 0x46, 0xd0, 0x3b, 0xa3, 0x35, 0xee, 0xd6, 0xf4, 0x9d, 0x11, 0x7e, 0xab, 0x95, 0xc8, 0xab, 0x8b, 0x71, 0xba, 0xe2, 0xcd, 0xd1, 0xe5, 0x64, 0xa7, 0x61}, "ethereum", "mainnet", byte(0x73), multi.OptionsBuilders{}).Return(nil, errors.New("not found")) return store }(), }, httptest.NewRequest("GET", "/?address=0x5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761&network=mainnet&protocol=ethereum", nil), - "{\"code\":500,\"message\":\"could not get `decrypter`: not found\"}\n", http.StatusInternalServerError, }, { - "success", + "200-message", args{ inbox: func() stores.State { inbox := storestest.NewMockState(mockCtrl) @@ -184,10 +183,6 @@ func Test_GetMessages(t *testing.T) { BlockID: []byte("YS1ibG9jay1udW1iZXI="), Hash: []byte("YS1oYXNo"), }, - { - // invalid transaction, will be added as - // {status: failed to unmarshal: buffer is empty} in the response - }, }, nil).Times(1) return receiver }(), @@ -204,16 +199,15 @@ func Test_GetMessages(t *testing.T) { store := keystoretest.NewMockStore(mockCtrl) store.EXPECT().HasAddress([]byte{0x56, 0x2, 0xea, 0x95, 0x54, 0xb, 0xee, 0x46, 0xd0, 0x3b, 0xa3, 0x35, 0xee, 0xd6, 0xf4, 0x9d, 0x11, 0x7e, 0xab, 0x95, 0xc8, 0xab, 0x8b, 0x71, 0xba, 0xe2, 0xcd, 0xd1, 0xe5, 0x64, 0xa7, 0x61}, "ethereum", "mainnet").Return(true).Times(1) - store.EXPECT().GetDecrypter([]byte{0x56, 0x2, 0xea, 0x95, 0x54, 0xb, 0xee, 0x46, 0xd0, 0x3b, 0xa3, 0x35, 0xee, 0xd6, 0xf4, 0x9d, 0x11, 0x7e, 0xab, 0x95, 0xc8, 0xab, 0x8b, 0x71, 0xba, 0xe2, 0xcd, 0xd1, 0xe5, 0x64, 0xa7, 0x61}, "ethereum", "mainnet", cipher.AES256CBC, multi.OptionsBuilders{}).Return(decrypter, nil) + store.EXPECT().GetDecrypter([]byte{0x56, 0x2, 0xea, 0x95, 0x54, 0xb, 0xee, 0x46, 0xd0, 0x3b, 0xa3, 0x35, 0xee, 0xd6, 0xf4, 0x9d, 0x11, 0x7e, 0xab, 0x95, 0xc8, 0xab, 0x8b, 0x71, 0xba, 0xe2, 0xcd, 0xd1, 0xe5, 0x64, 0xa7, 0x61}, "ethereum", "mainnet", byte(0x73), multi.OptionsBuilders{}).Return(decrypter, nil) return store }(), }, httptest.NewRequest("GET", "/?address=0x5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761&network=mainnet&protocol=ethereum", nil), - "{\"messages\":[{\"headers\":{\"date\":\"2019-03-12T20:23:13Z\",\"from\":\"\\u003c5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761@ropsten.ethereum\\u003e\",\"to\":\"\\u003c4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2@ropsten.ethereum\\u003e\",\"message-id\":\"47eca011e32b52c71005ad8a8f75e1b44c92c99fd12e43bccfe571e3c2d13d2e9a826a550f5ff63b247af471\",\"content-type\":\"text/plain; charset=\\\"UTF-8\\\"\"},\"body\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur maximus metus ante, sit amet ullamcorper dui hendrerit ac. Sed vestibulum dui lectus, quis eleifend urna mollis eu. Integer dictum metus ut sem rutrum aliquet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus eget euismod nibh. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer bibendum venenatis sem sed auctor. Ut aliquam eu diam nec fermentum. Sed turpis nulla, viverra ac efficitur ac, fermentum vel sapien. Curabitur vehicula risus id odio congue tempor. Mauris tincidunt feugiat risus, eget auctor magna blandit sit amet. Curabitur consectetur, dolor eu imperdiet varius, dui neque mattis neque, vel fringilla magna tortor ut risus. Cras cursus sem et nisl interdum molestie. Aliquam auctor sodales blandit.\\r\\n\",\"subject\":\"Hello world\",\"status\":\"ok\",\"status-code\":\"\",\"read\":false,\"block-id\":\"YS1ibG9jay1udW1iZXI=\",\"block-id-encoding\":\"hex/0x-prefix\",\"transaction-hash\":\"YS1oYXNo\",\"transaction-hash-encoding\":\"hex/0x-prefix\"},{\"status\":\"failed to unmarshal: buffer is empty\",\"status-code\":\"\",\"read\":false}]}\n", http.StatusOK, }, { - "success with empty messages", + "200-zero-messages", args{ inbox: func() stores.State { return storestest.NewMockState(mockCtrl) @@ -229,19 +223,17 @@ func Test_GetMessages(t *testing.T) { } }(), ks: func() keystore.Store { - decrypter := ciphertest.NewMockDecrypter(mockCtrl) store := keystoretest.NewMockStore(mockCtrl) store.EXPECT().HasAddress([]byte{0x56, 0x2, 0xea, 0x95, 0x54, 0xb, 0xee, 0x46, 0xd0, 0x3b, 0xa3, 0x35, 0xee, 0xd6, 0xf4, 0x9d, 0x11, 0x7e, 0xab, 0x95, 0xc8, 0xab, 0x8b, 0x71, 0xba, 0xe2, 0xcd, 0xd1, 0xe5, 0x64, 0xa7, 0x61}, "ethereum", "mainnet").Return(true).Times(1) - store.EXPECT().GetDecrypter([]byte{0x56, 0x2, 0xea, 0x95, 0x54, 0xb, 0xee, 0x46, 0xd0, 0x3b, 0xa3, 0x35, 0xee, 0xd6, 0xf4, 0x9d, 0x11, 0x7e, 0xab, 0x95, 0xc8, 0xab, 0x8b, 0x71, 0xba, 0xe2, 0xcd, 0xd1, 0xe5, 0x64, 0xa7, 0x61}, "ethereum", "mainnet", cipher.AES256CBC, multi.OptionsBuilders{}).Return(decrypter, nil) return store }(), }, httptest.NewRequest("GET", "/?address=0x5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761&network=mainnet&protocol=ethereum", nil), - "{\"messages\":[]}\n", http.StatusOK, }, } for _, tt := range tests { + testName := t.Name() t.Run(tt.name, func(t *testing.T) { // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. rr := httptest.NewRecorder() @@ -256,9 +248,15 @@ func Test_GetMessages(t *testing.T) { t.Errorf("handler returned wrong status code: got %v want %v", rr.Code, tt.wantStatus) } - if !assert.Equal(t, tt.wantBody, rr.Body.String()) { + + goldenResponse, err := ioutil.ReadFile(fmt.Sprintf("./testdata/%s/response-%s.json", testName, tt.name)) + if err != nil { + assert.FailNow(t, err.Error()) + } + + if !assert.JSONEq(t, string(goldenResponse), rr.Body.String()) { t.Errorf("handler returned unexpected body: got %v want %v", - rr.Body.String(), tt.wantBody) + rr.Body.String(), goldenResponse) } }) @@ -292,6 +290,18 @@ func Test_parseGetMessagesRequest(t *testing.T) { }, false, }, + { + "err-invalid-protocol", + args{ + map[string]string{ + "address": "0x5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761", + "network": "mainnet", + "protocol": "invalid", + }, + }, + nil, + true, + }, { "err-empty-address", args{ diff --git a/cmd/mailchain/internal/http/handlers/send_message.go b/cmd/mailchain/internal/http/handlers/send_message.go index f25ad08f5..25ddab046 100644 --- a/cmd/mailchain/internal/http/handlers/send_message.go +++ b/cmd/mailchain/internal/http/handlers/send_message.go @@ -81,6 +81,7 @@ func SendMessage(sent stores.Sent, senders map[string]sender.Message, ks keystor errs.JSONWriter(w, http.StatusInternalServerError, errors.WithMessage(err, "failed to decode address")) return } + if !ks.HasAddress(from, req.Protocol, req.Network) { errs.JSONWriter(w, http.StatusNotAcceptable, errors.Errorf("no private key found for `%s` from address", req.Body.Message.Headers.From)) return @@ -241,7 +242,7 @@ type PostRequestBody struct { ContentType string `json:"content-type"` } -func checkForEmpties(msg PostMessage) error { +func checkForEmpties(msg *PostMessage) error { if msg.Headers == nil { return errors.Errorf("headers must not be nil") } @@ -270,7 +271,7 @@ func isValid(p *PostRequestBody, protocol, network string) error { return errors.New("PostRequestBody must not be nil") } - if err := checkForEmpties(p.Message); err != nil { + if err := checkForEmpties(&p.Message); err != nil { return err } diff --git a/cmd/mailchain/internal/http/handlers/send_message_test.go b/cmd/mailchain/internal/http/handlers/send_message_test.go index e73abdeaa..4ccac9bf3 100644 --- a/cmd/mailchain/internal/http/handlers/send_message_test.go +++ b/cmd/mailchain/internal/http/handlers/send_message_test.go @@ -33,7 +33,7 @@ import ( func Test_checkForEmpties(t *testing.T) { type args struct { - msg PostMessage + msg *PostMessage } tests := []struct { name string @@ -43,7 +43,7 @@ func Test_checkForEmpties(t *testing.T) { { "success", args{ - PostMessage{ + &PostMessage{ Headers: &PostHeaders{}, Subject: "subject-value", Body: "body-value", @@ -57,7 +57,7 @@ func Test_checkForEmpties(t *testing.T) { { "empty-headers", args{ - PostMessage{ + &PostMessage{ Subject: "subject-value", Body: "body-value", PublicKey: "public-key-value", @@ -70,7 +70,7 @@ func Test_checkForEmpties(t *testing.T) { { "empty-subject", args{ - PostMessage{ + &PostMessage{ Headers: &PostHeaders{}, Body: "body-value", PublicKey: "public-key-value", @@ -83,7 +83,7 @@ func Test_checkForEmpties(t *testing.T) { { "empty-body", args{ - PostMessage{ + &PostMessage{ Headers: &PostHeaders{}, Subject: "subject-value", PublicKey: "public-key-value", @@ -96,7 +96,7 @@ func Test_checkForEmpties(t *testing.T) { { "empty-public-key", args{ - PostMessage{ + &PostMessage{ Headers: &PostHeaders{}, Subject: "subject-value", Body: "body-value", diff --git a/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-200-message.json b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-200-message.json new file mode 100644 index 000000000..f4882031b --- /dev/null +++ b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-200-message.json @@ -0,0 +1,22 @@ +{ + "messages": [ + { + "headers": { + "date": "2019-03-12T20:23:13Z", + "from": "\u003c5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761@ropsten.ethereum\u003e", + "to": "\u003c4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2@ropsten.ethereum\u003e", + "message-id": "47eca011e32b52c71005ad8a8f75e1b44c92c99fd12e43bccfe571e3c2d13d2e9a826a550f5ff63b247af471", + "content-type": "text/plain; charset=\"UTF-8\"" + }, + "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur maximus metus ante, sit amet ullamcorper dui hendrerit ac. Sed vestibulum dui lectus, quis eleifend urna mollis eu. Integer dictum metus ut sem rutrum aliquet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus eget euismod nibh. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer bibendum venenatis sem sed auctor. Ut aliquam eu diam nec fermentum. Sed turpis nulla, viverra ac efficitur ac, fermentum vel sapien. Curabitur vehicula risus id odio congue tempor. Mauris tincidunt feugiat risus, eget auctor magna blandit sit amet. Curabitur consectetur, dolor eu imperdiet varius, dui neque mattis neque, vel fringilla magna tortor ut risus. Cras cursus sem et nisl interdum molestie. Aliquam auctor sodales blandit.\r\n", + "subject": "Hello world", + "status": "ok", + "status-code": "", + "read": false, + "block-id": "YS1ibG9jay1udW1iZXI=", + "block-id-encoding": "hex/0x-prefix", + "transaction-hash": "YS1oYXNo", + "transaction-hash-encoding": "hex/0x-prefix" + } + ] +} \ No newline at end of file diff --git a/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-200-zero-messages.json b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-200-zero-messages.json new file mode 100644 index 000000000..b798f5328 --- /dev/null +++ b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-200-zero-messages.json @@ -0,0 +1,3 @@ +{ + "messages": [] +} \ No newline at end of file diff --git a/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-406-no-private-key-found.json b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-406-no-private-key-found.json new file mode 100644 index 000000000..2538c49e0 --- /dev/null +++ b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-406-no-private-key-found.json @@ -0,0 +1,4 @@ +{ + "code": 406, + "message": "no private key found for address" +} \ No newline at end of file diff --git a/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-406-receiver-network-error.json b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-406-receiver-network-error.json new file mode 100644 index 000000000..5ea8cc77a --- /dev/null +++ b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-406-receiver-network-error.json @@ -0,0 +1,4 @@ +{ + "code": 406, + "message": "network `mainnet` does not have etherscan client configured" +} \ No newline at end of file diff --git a/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-422-empty-address.json b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-422-empty-address.json new file mode 100644 index 000000000..cba61b708 --- /dev/null +++ b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-422-empty-address.json @@ -0,0 +1,4 @@ +{ + "code": 422, + "message": "'address' must not be empty" +} \ No newline at end of file diff --git a/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-422-receiver-no-configured.json b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-422-receiver-no-configured.json new file mode 100644 index 000000000..c68cdf1ac --- /dev/null +++ b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-422-receiver-no-configured.json @@ -0,0 +1,4 @@ +{ + "code": 422, + "message": "no receiver configured for \"ethereum/mainnet\"" +} \ No newline at end of file diff --git a/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-422-receiver-not-supported.json b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-422-receiver-not-supported.json new file mode 100644 index 000000000..d8170f701 --- /dev/null +++ b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-422-receiver-not-supported.json @@ -0,0 +1,4 @@ +{ + "code": 422, + "message": "receiver not supported on \"ethereum/mainnet\"" +} \ No newline at end of file diff --git a/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-500-decrypter-error.json b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-500-decrypter-error.json new file mode 100644 index 000000000..2b0163bba --- /dev/null +++ b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-500-decrypter-error.json @@ -0,0 +1,4 @@ +{ + "code": 500, + "message": "could not get `decrypter`: not found" +} \ No newline at end of file diff --git a/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-500-receiver-internal-error.json b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-500-receiver-internal-error.json new file mode 100644 index 000000000..cf440a471 --- /dev/null +++ b/cmd/mailchain/internal/http/handlers/testdata/Test_GetMessages/response-500-receiver-internal-error.json @@ -0,0 +1,4 @@ +{ + "code": 500, + "message": "internal error" +} \ No newline at end of file diff --git a/docker-compose.substrate.yml b/docker-compose.substrate.yml new file mode 100644 index 000000000..a86f8d3a4 --- /dev/null +++ b/docker-compose.substrate.yml @@ -0,0 +1,32 @@ +version: '3.7' + +services: + database: + image: postgres + environment: + - POSTGRES_USER=mailchain + - POSTGRES_PASSWORD=mailchain + ports: + - 5432:5432 + volumes: + - db_data:/var/lib/postgresql/data + indexer-migration: + image: mailchain/indexer + entrypoint: ["/indexer", "database", "up", "--postgres-host=database", "--master-postgres-password=mailchain", "--master-postgres-user=mailchain", "--indexer-postgres-password=indexer", "--envelope-postgres-password=envelope", "--pubkey-postgres-password=pubkey"] + depends_on: + - database + indexer-substrate-berlin: + image: mailchain/indexer + entrypoint: ["/indexer", "substrate", "--start-block=latest", "--network=edgeware-berlin", "--rpc-address=ws://berlin1.edgewa.re:9944", "--postgres-host=database", "--envelope-postgres-password=envelope", "--indexer-postgres-password=indexer", "--indexer-postgres-password=indexer", "--envelope-postgres-password=envelope", "--pubkey-postgres-password=pubkey", "--raw-store-path=./tmp/tx"] + depends_on: + - database + receiver: + image: mailchain/receiver + entrypoint: ["/receiver", "--postgres-password=receiver", "--postgres-host=database", "--postgres-password=envelope"] + depends_on: + - database + ports: + - 8081:8080 + +volumes: + db_data: diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 7a22f081c..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: '3.7' - -services: - db: - image: postgres - environment: - - POSTGRES_USER=mailchain - - POSTGRES_PASSWORD=mailchain - ports: - - 5432:5432 - volumes: - - db_data:/var/lib/postgresql/data - -volumes: - db_data: diff --git a/go.mod b/go.mod index ae3177fe7..5617510ce 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 github.com/andreburgaud/crypt2go v0.11.0 github.com/aws/aws-sdk-go v1.33.1 + github.com/cenkalti/backoff v2.2.1+incompatible github.com/centrifuge/go-substrate-rpc-client v1.1.0 // indirect github.com/dgraph-io/badger/v2 v2.0.3 diff --git a/go.sum b/go.sum index 5007b86b2..1c41a129e 100644 --- a/go.sum +++ b/go.sum @@ -93,6 +93,7 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/centrifuge/go-substrate-rpc-client v1.1.0 h1:cpfG7KKwy+n7FRb1LkiMYwi6e4gwzaooGfPIzXXQj6w= diff --git a/internal/address/encode.go b/internal/address/encode.go index c57433987..f3a256d57 100644 --- a/internal/address/encode.go +++ b/internal/address/encode.go @@ -26,6 +26,9 @@ func EncodeByProtocol(in []byte, protocol string) (encoded, encodingKind string, case protocols.Ethereum: encodingKind = encoding.KindHex0XPrefix encoded = encoding.EncodeHexZeroX(in) + case protocols.Substrate: + encodingKind = encoding.KindBase58 + encoded = encoding.EncodeBase58(in) default: err = errors.Errorf("%q unsupported protocol", protocol) } diff --git a/internal/envelope/0x01.go b/internal/envelope/0x01.go index 4f2dcf641..ef4a7d735 100644 --- a/internal/envelope/0x01.go +++ b/internal/envelope/0x01.go @@ -103,9 +103,8 @@ func (x *ZeroX01) ContentsHash(decrypter cipher.Decrypter) ([]byte, error) { if err != nil { return nil, errors.WithStack(err) } - locationHash := UInt64Bytes(decrypted) - return locationHash.Bytes() + return UInt64Bytes(decrypted).Bytes() } // IntegrityHash returns a hash of the encrypted content. This can be used to validate the integrity of the contents before decrypting. @@ -122,3 +121,11 @@ func (x *ZeroX01) Valid() error { return nil } + +func (x *ZeroX01) DecrypterKind() (byte, error) { + if len(x.UIBEncryptedLocationHash) == 0 { + return 0x0, errors.Errorf("`EncryptedLocationHash` must not be empty") + } + + return x.UIBEncryptedLocationHash[0], nil +} diff --git a/internal/envelope/0x01_test.go b/internal/envelope/0x01_test.go index e1589f80e..0c3d22abe 100644 --- a/internal/envelope/0x01_test.go +++ b/internal/envelope/0x01_test.go @@ -466,3 +466,47 @@ func TestNewZeroX01(t *testing.T) { }) } } + +func TestZeroX01_DecrypterKind(t *testing.T) { + type fields struct { + UIBEncryptedLocationHash []byte + } + tests := []struct { + name string + fields fields + want byte + wantErr bool + }{ + { + "success", + fields{ + encodingtest.MustDecodeHexZeroX("0x2ee10c59024c836d7ca12470b5ac74673002127ddedadbc6fc4375a8c086b650060ede199f603a158bc7884a903eadf97a2dd0fbe69ac81c216830f94e56b847d924b51a7d8227c80714219e6821a51bc7cba922f291a47bdffe29e7c3f67ad908ff377bfcc0b603007ead4bfd87ff0acc272528ca03d6381e6d0e1e2c5dfd24d521"), + }, + cipher.AES256CBC, + false, + }, + { + "err-empty", + fields{ + []byte{}, + }, + 0x0, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := &ZeroX01{ + UIBEncryptedLocationHash: tt.fields.UIBEncryptedLocationHash, + } + got, err := x.DecrypterKind() + if (err != nil) != tt.wantErr { + t.Errorf("ZeroX01.DecrypterKind() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ZeroX01.DecrypterKind() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/envelope/0x02.go b/internal/envelope/0x02.go index cedae611d..d49439ef8 100644 --- a/internal/envelope/0x02.go +++ b/internal/envelope/0x02.go @@ -134,3 +134,11 @@ func (x *ZeroX02) Valid() error { return nil } + +func (x *ZeroX02) DecrypterKind() (byte, error) { + if len(x.UIBEncryptedLocationHash) == 0 { + return 0x0, errors.Errorf("`EncryptedLocationHash` must not be empty") + } + + return x.UIBEncryptedLocationHash[0], nil +} diff --git a/internal/envelope/0x02_test.go b/internal/envelope/0x02_test.go index 293138f14..ab82d1b7d 100644 --- a/internal/envelope/0x02_test.go +++ b/internal/envelope/0x02_test.go @@ -263,11 +263,11 @@ func TestZeroX02_IntegrityHash(t *testing.T) { } got, err := d.IntegrityHash(tt.args.decrypter) if (err != nil) != tt.wantErr { - t.Errorf("ZeroX01.IntegrityHash() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("ZeroX02.IntegrityHash() error = %v, wantErr %v", err, tt.wantErr) return } if !assert.Equal(t, tt.want, got) { - t.Errorf("ZeroX01.IntegrityHash() = %v, want %v", got, tt.want) + t.Errorf("ZeroX02.IntegrityHash() = %v, want %v", got, tt.want) } }) } @@ -507,3 +507,47 @@ func TestNewZeroX02(t *testing.T) { }) } } + +func TestZeroX02_DecrypterKind(t *testing.T) { + type fields struct { + UIBEncryptedLocationHash []byte + } + tests := []struct { + name string + fields fields + want byte + wantErr bool + }{ + { + "success", + fields{ + encodingtest.MustDecodeHexZeroX("0x2ee10c59024c836d7ca12470b5ac74673002127ddedadbc6fc4375a8c086b650060ede199f603a158bc7884a903eadf97a2dd0fbe69ac81c216830f94e56b847d924b51a7d8227c80714219e6821a51bc7cba922f291a47bdffe29e7c3f67ad908ff377bfcc0b603007ead4bfd87ff0acc272528ca03d6381e6d0e1e2c5dfd24d521"), + }, + cipher.AES256CBC, + false, + }, + { + "err-empty", + fields{ + []byte{}, + }, + 0x0, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := &ZeroX02{ + UIBEncryptedLocationHash: tt.fields.UIBEncryptedLocationHash, + } + got, err := x.DecrypterKind() + if (err != nil) != tt.wantErr { + t.Errorf("ZeroX02.DecrypterKind() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ZeroX02.DecrypterKind() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/envelope/0x50.go b/internal/envelope/0x50.go index 4e88c9682..604e273c7 100644 --- a/internal/envelope/0x50.go +++ b/internal/envelope/0x50.go @@ -69,3 +69,11 @@ func (d *ZeroX50) Valid() error { return nil } + +func (x *ZeroX50) DecrypterKind() (byte, error) { + if len(x.EncryptedURL) == 0 { + return 0x0, errors.Errorf("`EncryptedURL` must not be empty") + } + + return x.EncryptedURL[0], nil +} diff --git a/internal/envelope/0x50_test.go b/internal/envelope/0x50_test.go index 4788b136f..63f2787c9 100644 --- a/internal/envelope/0x50_test.go +++ b/internal/envelope/0x50_test.go @@ -339,3 +339,47 @@ func TestZeroX50_Valid(t *testing.T) { }) } } + +func TestZeroX50_DecrypterKind(t *testing.T) { + type fields struct { + EncryptedURL []byte + } + tests := []struct { + name string + fields fields + want byte + wantErr bool + }{ + { + "success", + fields{ + encodingtest.MustDecodeHexZeroX("0x2ee10c59024c836d7ca12470b5ac74673002127ddedadbc6fc4375a8c086b650060ede199f603a158bc7884a903eadf97a2dd0fbe69ac81c216830f94e56b847d924b51a7d8227c80714219e6821a51bc7cba922f291a47bdffe29e7c3f67ad908ff377bfcc0b603007ead4bfd87ff0acc272528ca03d6381e6d0e1e2c5dfd24d521"), + }, + cipher.AES256CBC, + false, + }, + { + "err-empty", + fields{ + []byte{}, + }, + 0x0, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := &ZeroX50{ + EncryptedURL: tt.fields.EncryptedURL, + } + got, err := x.DecrypterKind() + if (err != nil) != tt.wantErr { + t.Errorf("ZeroX50.DecrypterKind() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ZeroX50.DecrypterKind() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/envelope/data.go b/internal/envelope/data.go index fc0b05faa..0c35b6fc3 100644 --- a/internal/envelope/data.go +++ b/internal/envelope/data.go @@ -52,4 +52,6 @@ type Data interface { // Valid will verify the contents of the envelope. // Checks the envelopes contents for no integrity issues which would prevent the envelope from being read. Valid() error + // DecrypterKind returns the byte ID for the decrypter needed for the envelope + DecrypterKind() (byte, error) } diff --git a/internal/keystore/decrypters.go b/internal/keystore/decrypters.go index b48b927c0..010c8439e 100644 --- a/internal/keystore/decrypters.go +++ b/internal/keystore/decrypters.go @@ -19,6 +19,7 @@ import ( "github.com/mailchain/mailchain/crypto/cipher" "github.com/mailchain/mailchain/crypto/cipher/aes256cbc" "github.com/mailchain/mailchain/crypto/cipher/nacl" + "github.com/mailchain/mailchain/crypto/cipher/noop" "github.com/pkg/errors" ) @@ -29,6 +30,8 @@ func Decrypter(cipherType byte, pk crypto.PrivateKey) (cipher.Decrypter, error) return aes256cbc.NewDecrypter(pk) case cipher.NACLECDH: return nacl.NewDecrypter(pk) + case cipher.NoOperation: + return noop.NewDecrypter(), nil default: return nil, errors.Errorf("unsupported decrypter type") } diff --git a/internal/keystore/decrypters_test.go b/internal/keystore/decrypters_test.go index baf1b06bc..161f9ac19 100644 --- a/internal/keystore/decrypters_test.go +++ b/internal/keystore/decrypters_test.go @@ -22,6 +22,7 @@ import ( "github.com/mailchain/mailchain/crypto/cipher" "github.com/mailchain/mailchain/crypto/cipher/aes256cbc" "github.com/mailchain/mailchain/crypto/cipher/nacl" + "github.com/mailchain/mailchain/crypto/cipher/noop" "github.com/mailchain/mailchain/crypto/cryptotest" "github.com/mailchain/mailchain/crypto/ed25519/ed25519test" "github.com/mailchain/mailchain/crypto/secp256k1/secp256k1test" @@ -66,6 +67,18 @@ func TestDecrypter(t *testing.T) { }(), false, }, + { + "noop", + args{ + cipher.NoOperation, + ed25519test.CharlottePrivateKey, + }, + func() cipher.Decrypter { + m := noop.NewDecrypter() + return m + }(), + false, + }, { "err-invalid-key-type", args{ diff --git a/sender/substraterpc/client.go b/sender/substraterpc/client.go index 500740348..ab0ed43aa 100644 --- a/sender/substraterpc/client.go +++ b/sender/substraterpc/client.go @@ -6,7 +6,6 @@ import ( gsrpc "github.com/mailchain/go-substrate-rpc-client" "github.com/mailchain/go-substrate-rpc-client/types" - "github.com/mailchain/mailchain/encoding" "github.com/mailchain/mailchain/internal/protocols/substrate" ) @@ -52,7 +51,7 @@ func (s SubstrateClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) } func (s SubstrateClient) Call(metadata *types.Metadata, to types.Address, gas *big.Int, data []byte) (types.Call, error) { - return types.NewCall(metadata, "Contracts.call", to, types.UCompact(0), types.UCompact(gas.Uint64()), encoding.EncodeHexZeroX(data)) + return types.NewCall(metadata, "Contracts.call", to, types.UCompact(0), types.UCompact(gas.Uint64()), data) } func (s SubstrateClient) NewExtrinsic(call types.Call) types.Extrinsic { diff --git a/sender/substraterpc/client_test.go b/sender/substraterpc/client_test.go index c884d5aeb..81e8632fd 100644 --- a/sender/substraterpc/client_test.go +++ b/sender/substraterpc/client_test.go @@ -171,7 +171,7 @@ func TestSubstrateClient_Call(t *testing.T) { MethodIndex: 0x2, }, Args: types.Args{ - 0xff, 0x84, 0x62, 0x3e, 0x72, 0x52, 0xe4, 0x11, 0x38, 0xaf, 0x69, 0x4, 0xe1, 0xb0, 0x23, 0x4, 0xc9, 0x41, 0x62, 0x5f, 0x39, 0xe5, 0x76, 0x25, 0x89, 0x12, 0x5d, 0xc1, 0xa2, 0xf2, 0xcf, 0x2e, 0x30, 0x0, 0x2, 0xf4, 0x1, 0x0, 0x40, 0x30, 0x78, 0x36, 0x64, 0x36, 0x35, 0x37, 0x33, 0x37, 0x33, 0x36, 0x31, 0x36, 0x37, 0x36, 0x35, + 0xff, 0x84, 0x62, 0x3e, 0x72, 0x52, 0xe4, 0x11, 0x38, 0xaf, 0x69, 0x4, 0xe1, 0xb0, 0x23, 0x4, 0xc9, 0x41, 0x62, 0x5f, 0x39, 0xe5, 0x76, 0x25, 0x89, 0x12, 0x5d, 0xc1, 0xa2, 0xf2, 0xcf, 0x2e, 0x30, 0x0, 0x2, 0xf4, 0x1, 0x0, 0x1c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, }, }, false, diff --git a/sender/substraterpc/sender.go b/sender/substraterpc/sender.go index 3cf00280c..ff72ec265 100644 --- a/sender/substraterpc/sender.go +++ b/sender/substraterpc/sender.go @@ -25,7 +25,7 @@ func (s SubstrateRPC) Send(ctx context.Context, network string, to, from, data [ return errors.WithMessage(err, "could not get gas price") } - addressTo := client.GetAddress(to) + addressTo := client.GetAddress(to[1:]) c, err := client.Call(meta, addressTo, gasPrice, data) if err != nil { diff --git a/sender/substraterpc/sender_test.go b/sender/substraterpc/sender_test.go index fecfedead..46eaa56df 100644 --- a/sender/substraterpc/sender_test.go +++ b/sender/substraterpc/sender_test.go @@ -2,6 +2,7 @@ package substraterpc import ( "context" + "errors" "math/big" "testing" @@ -15,7 +16,6 @@ import ( "github.com/mailchain/mailchain/internal/protocols/substrate" "github.com/mailchain/mailchain/sender" "github.com/mailchain/mailchain/sender/substraterpc/substraterpctest" - "github.com/pkg/errors" ) func TestSubstrateRPC_Send(t *testing.T) { @@ -68,7 +68,7 @@ func TestSubstrateRPC_Send(t *testing.T) { IsAccountIndex: false, AsAccountIndex: 0, } - m.EXPECT().GetAddress(to).Return(addressTo) + m.EXPECT().GetAddress(to[1:]).Return(addressTo) m.EXPECT().SuggestGasPrice(context.Background()).Return(big.NewInt(SuggestedGas), nil) call := types.Call{} m.EXPECT().Call(metadata, addressTo, big.NewInt(SuggestedGas), []byte("transactionDataValue")).Return(call, nil) @@ -192,7 +192,7 @@ func TestSubstrateRPC_Send(t *testing.T) { IsAccountIndex: false, AsAccountIndex: 0, } - m.EXPECT().GetAddress(to).Return(addressTo) + m.EXPECT().GetAddress(to[1:]).Return(addressTo) m.EXPECT().SuggestGasPrice(context.Background()).Return(big.NewInt(SuggestedGas), nil) m.EXPECT().Call(metadata, addressTo, big.NewInt(SuggestedGas), []byte("transactionDataValue")).Return(types.Call{}, errors.New("Call error")) return m @@ -235,7 +235,7 @@ func TestSubstrateRPC_Send(t *testing.T) { IsAccountIndex: false, AsAccountIndex: 0, } - m.EXPECT().GetAddress(to).Return(addressTo) + m.EXPECT().GetAddress(to[1:]).Return(addressTo) m.EXPECT().SuggestGasPrice(context.Background()).Return(big.NewInt(SuggestedGas), nil) call := types.Call{} m.EXPECT().Call(metadata, addressTo, big.NewInt(SuggestedGas), []byte("transactionDataValue")).Return(call, nil) @@ -282,7 +282,7 @@ func TestSubstrateRPC_Send(t *testing.T) { IsAccountIndex: false, AsAccountIndex: 0, } - m.EXPECT().GetAddress(to).Return(addressTo) + m.EXPECT().GetAddress(to[1:]).Return(addressTo) m.EXPECT().SuggestGasPrice(context.Background()).Return(big.NewInt(SuggestedGas), nil) call := types.Call{} m.EXPECT().Call(metadata, addressTo, big.NewInt(SuggestedGas), []byte("transactionDataValue")).Return(call, nil) @@ -330,7 +330,7 @@ func TestSubstrateRPC_Send(t *testing.T) { IsAccountIndex: false, AsAccountIndex: 0, } - m.EXPECT().GetAddress(to).Return(addressTo) + m.EXPECT().GetAddress(to[1:]).Return(addressTo) m.EXPECT().SuggestGasPrice(context.Background()).Return(big.NewInt(SuggestedGas), nil) call := types.Call{} m.EXPECT().Call(metadata, addressTo, big.NewInt(SuggestedGas), []byte("transactionDataValue")).Return(call, nil) @@ -379,7 +379,7 @@ func TestSubstrateRPC_Send(t *testing.T) { IsAccountIndex: false, AsAccountIndex: 0, } - m.EXPECT().GetAddress(to).Return(addressTo) + m.EXPECT().GetAddress(to[1:]).Return(addressTo) m.EXPECT().SuggestGasPrice(context.Background()).Return(big.NewInt(SuggestedGas), nil) call := types.Call{} m.EXPECT().Call(metadata, addressTo, big.NewInt(SuggestedGas), []byte("transactionDataValue")).Return(call, nil) @@ -443,7 +443,7 @@ func TestSubstrateRPC_Send(t *testing.T) { IsAccountIndex: false, AsAccountIndex: 0, } - m.EXPECT().GetAddress(to).Return(addressTo) + m.EXPECT().GetAddress(to[1:]).Return(addressTo) m.EXPECT().SuggestGasPrice(context.Background()).Return(big.NewInt(SuggestedGas), nil) call := types.Call{} m.EXPECT().Call(metadata, addressTo, big.NewInt(SuggestedGas), []byte("transactionDataValue")).Return(call, nil)