diff --git a/cmd/indexer/internal/substrate/extrinsic.go b/cmd/indexer/internal/substrate/extrinsic.go index d92b32cd4..b2c2a92e1 100644 --- a/cmd/indexer/internal/substrate/extrinsic.go +++ b/cmd/indexer/internal/substrate/extrinsic.go @@ -14,6 +14,7 @@ import ( "github.com/mailchain/mailchain/crypto/sr25519" "github.com/mailchain/mailchain/encoding" "github.com/mailchain/mailchain/internal/protocols/substrate" + "github.com/minio/blake2b-simd" "github.com/pkg/errors" ) @@ -95,11 +96,24 @@ func getToAddress(network string, dataPart []byte) ([]byte, error) { return substrate.SS58AddressFormat(network, toPubKey) } -func (t *Extrinsic) ToTransaction(network string, blk *types.Block, tx *types.Extrinsic) (*datastore.Transaction, error) { +func ExtrinsicHash(ex *types.Extrinsic) ([]byte, error) { w := bytes.NewBuffer([]byte{}) encoder := scale.NewEncoder(w) - if err := tx.Method.Args.Encode(*encoder); err != nil { + if err := ex.Encode(*encoder); err != nil { + return nil, err + } + + h := blake2b.Sum256(w.Bytes()) + + return h[:], nil +} + +func (t *Extrinsic) ToTransaction(network string, blk *types.Block, ex *types.Extrinsic) (*datastore.Transaction, error) { + w := bytes.NewBuffer([]byte{}) + encoder := scale.NewEncoder(w) + + if err := ex.Method.Args.Encode(*encoder); err != nil { return nil, err } @@ -110,7 +124,7 @@ func (t *Extrinsic) ToTransaction(network string, blk *types.Block, tx *types.Ex return nil, err } - from, err := getFromAddress(network, &tx.Signature) + from, err := getFromAddress(network, &ex.Signature) if err != nil { return nil, err } @@ -120,10 +134,15 @@ func (t *Extrinsic) ToTransaction(network string, blk *types.Block, tx *types.Ex return nil, err } + hash, err := ExtrinsicHash(ex) + if err != nil { + return nil, err + } + return &datastore.Transaction{ From: from, // BlockHash: blk.Hash().Bytes(), - // Hash: tx.Hash().Bytes(), + Hash: hash, Data: decodedData, To: to, // Value: *value, diff --git a/cmd/indexer/internal/substrate/extrinsic_test.go b/cmd/indexer/internal/substrate/extrinsic_test.go index 2de9b957d..8253ab1bf 100644 --- a/cmd/indexer/internal/substrate/extrinsic_test.go +++ b/cmd/indexer/internal/substrate/extrinsic_test.go @@ -7,11 +7,12 @@ import ( "github.com/golang/mock/gomock" "github.com/mailchain/go-substrate-rpc-client/types" "github.com/mailchain/mailchain/cmd/indexer/internal/actions" - "github.com/mailchain/mailchain/cmd/indexer/internal/substrate" + target "github.com/mailchain/mailchain/cmd/indexer/internal/substrate" "github.com/mailchain/mailchain/cmd/internal/datastore" "github.com/mailchain/mailchain/cmd/internal/datastore/datastoretest" "github.com/mailchain/mailchain/internal/protocols" networks "github.com/mailchain/mailchain/internal/protocols/substrate" + "github.com/stretchr/testify/assert" ) func TestExtrinsic_Run(t *testing.T) { @@ -63,14 +64,14 @@ func TestExtrinsic_Run(t *testing.T) { e := b.Extrinsics[2] return &e }(), - &substrate.TxOptions{}, + &target.TxOptions{}, }, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - extrinsic := substrate.NewExtrinsicProcessor( + extrinsic := target.NewExtrinsicProcessor( tt.fields.txStore, tt.fields.rawTxStore, tt.fields.pkStore, @@ -82,3 +83,64 @@ func TestExtrinsic_Run(t *testing.T) { }) } } + +func Test_ExtrinsicHash(t *testing.T) { + type args struct { + ex *types.Extrinsic + } + tests := []struct { + name string + args args + want []byte + wantErr bool + }{ + { + "success-timestamp-set", + args{ + func() *types.Extrinsic { + b := getBlock(t, "0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971") + 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}, + false, + }, + { + "success-final-hint", + args{ + func() *types.Extrinsic { + b := getBlock(t, "0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971") + 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}, + false, + }, + { + "success-contract", + args{ + func() *types.Extrinsic { + b := getBlock(t, "0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971") + 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}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := target.ExtrinsicHash(tt.args.ex) + if (err != nil) != tt.wantErr { + t.Errorf("extrinsicHash() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !assert.Equal(t, tt.want, got) { + t.Errorf("extrinsicHash() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/cmd/indexer/internal/substrate/testdata/blocks/0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971 2.json b/cmd/indexer/internal/substrate/testdata/blocks/0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971 2.json new file mode 100644 index 000000000..cf7d6775e --- /dev/null +++ b/cmd/indexer/internal/substrate/testdata/blocks/0x7ce3d93396dac53f1ae5fba268afcaa623e224e359507d581439dea791bab971 2.json @@ -0,0 +1,26 @@ +{ + "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/mailchain/internal/http/handlers/messages.go b/cmd/mailchain/internal/http/handlers/messages.go index 33c874f8e..01f26e438 100644 --- a/cmd/mailchain/internal/http/handlers/messages.go +++ b/cmd/mailchain/internal/http/handlers/messages.go @@ -45,16 +45,19 @@ func GetMessages(inbox stores.State, receivers map[string]mailbox.Receiver, ks k // 422: ValidationError return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + req, err := parseGetMessagesRequest(r) if err != nil { errs.JSONWriter(w, http.StatusUnprocessableEntity, errors.WithStack(err)) return } + receiver, ok := receivers[fmt.Sprintf("%s/%s", req.Protocol, req.Network)] if !ok { errs.JSONWriter(w, http.StatusUnprocessableEntity, errors.Errorf("receiver not supported on \"%s/%s\"", req.Protocol, req.Network)) return } + if receiver == nil { errs.JSONWriter(w, http.StatusUnprocessableEntity, errors.Errorf("no receiver configured for \"%s/%s\"", req.Protocol, req.Network)) return @@ -64,20 +67,24 @@ func GetMessages(inbox stores.State, receivers map[string]mailbox.Receiver, ks k errs.JSONWriter(w, http.StatusNotAcceptable, errors.Errorf("no private key found for address")) return } - transactions, err := receiver.Receive(ctx, req.Network, req.addressBytes) + + transactions, err := receiver.Receive(ctx, req.Protocol, req.Network, req.addressBytes) if mailbox.IsNetworkNotSupportedError(err) { errs.JSONWriter(w, http.StatusNotAcceptable, errors.Errorf("network `%s` does not have etherscan client configured", req.Network)) return } + if err != nil { errs.JSONWriter(w, http.StatusInternalServerError, errors.WithStack(err)) 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 message, err := mailbox.ReadMessage(transactionData.Data, decrypter) @@ -85,8 +92,10 @@ func GetMessages(inbox stores.State, receivers map[string]mailbox.Receiver, ks k messages = append(messages, getMessage{ Status: err.Error(), }) + continue } + readStatus, _ := inbox.GetReadStatus(message.ID) messages = append(messages, getMessage{ Body: string(message.Body), @@ -163,11 +172,6 @@ func parseGetMessagesRequest(r *http.Request) (*GetMessagesRequest, error) { return nil, err } - // TODO: validate address - // if !ethereum.IsAddressValid(addr) { - // return nil, errors.Errorf("'address' is invalid") - // } - addressBytes, err := address.DecodeByProtocol(addr, protocol) if err != nil { return nil, err diff --git a/cmd/mailchain/internal/http/handlers/messages_test.go b/cmd/mailchain/internal/http/handlers/messages_test.go index 24f2ae900..4ac863136 100644 --- a/cmd/mailchain/internal/http/handlers/messages_test.go +++ b/cmd/mailchain/internal/http/handlers/messages_test.go @@ -102,7 +102,7 @@ func Test_GetMessages(t *testing.T) { return map[string]mailbox.Receiver{ "ethereum/mainnet": func() mailbox.Receiver { receiver := mailboxtest.NewMockReceiver(mockCtrl) - receiver.EXPECT().Receive(context.Background(), "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}). + 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(nil, errors.New("network not supported")).Times(1) return receiver }(), @@ -125,7 +125,7 @@ func Test_GetMessages(t *testing.T) { return map[string]mailbox.Receiver{ "ethereum/mainnet": func() mailbox.Receiver { receiver := mailboxtest.NewMockReceiver(mockCtrl) - receiver.EXPECT().Receive(context.Background(), "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}). + 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(nil, errors.New("internal error")).Times(1) return receiver }(), @@ -148,7 +148,7 @@ func Test_GetMessages(t *testing.T) { return map[string]mailbox.Receiver{ "ethereum/mainnet": func() mailbox.Receiver { receiver := mailboxtest.NewMockReceiver(mockCtrl) - receiver.EXPECT().Receive(context.Background(), "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}). + 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 receiver }(), @@ -177,7 +177,7 @@ func Test_GetMessages(t *testing.T) { return map[string]mailbox.Receiver{ "ethereum/mainnet": func() mailbox.Receiver { receiver := mailboxtest.NewMockReceiver(mockCtrl) - receiver.EXPECT().Receive(context.Background(), "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}). + 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{ { Data: encodingtest.MustDecodeHex("500801120f7365637265742d6c6f636174696f6e1a221620d3c47ef741473ebf42773d25687b7540a3d96429aec07dd1ce66c0d4fd16ea13"), @@ -222,7 +222,7 @@ func Test_GetMessages(t *testing.T) { return map[string]mailbox.Receiver{ "ethereum/mainnet": func() mailbox.Receiver { receiver := mailboxtest.NewMockReceiver(mockCtrl) - receiver.EXPECT().Receive(context.Background(), "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}). + 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 receiver }(), diff --git a/cmd/mailchain/internal/settings/defaults/networks.go b/cmd/mailchain/internal/settings/defaults/networks.go index 1b460b668..d57e2692c 100644 --- a/cmd/mailchain/internal/settings/defaults/networks.go +++ b/cmd/mailchain/internal/settings/defaults/networks.go @@ -28,8 +28,8 @@ func SubstrateNetworkAny(network string) *NetworkDefaults { // NameServiceAddress: NameServiceAddressKind, // NameServiceDomainName: NameServiceDomainNameKind, PublicKeyFinder: SubstratePublicKeyFinder, - // Receiver: mailchain.ClientEtherscanNoAuth, - Sender: "substrate-rpc-" + network, - Disabled: false, + Receiver: Mailchain, + Sender: "substrate-rpc-" + network, + Disabled: false, } } diff --git a/cmd/mailchain/internal/settings/blockscout_receiver.go b/cmd/mailchain/internal/settings/receiver_blockscout.go similarity index 100% rename from cmd/mailchain/internal/settings/blockscout_receiver.go rename to cmd/mailchain/internal/settings/receiver_blockscout.go diff --git a/cmd/mailchain/internal/settings/blockscout_receiver_test.go b/cmd/mailchain/internal/settings/receiver_blockscout_test.go similarity index 100% rename from cmd/mailchain/internal/settings/blockscout_receiver_test.go rename to cmd/mailchain/internal/settings/receiver_blockscout_test.go diff --git a/cmd/mailchain/internal/settings/etherscan_receiver.go b/cmd/mailchain/internal/settings/receiver_etherscan.go similarity index 100% rename from cmd/mailchain/internal/settings/etherscan_receiver.go rename to cmd/mailchain/internal/settings/receiver_etherscan.go diff --git a/cmd/mailchain/internal/settings/etherscan_receiver_test.go b/cmd/mailchain/internal/settings/receiver_etherscan_test.go similarity index 100% rename from cmd/mailchain/internal/settings/etherscan_receiver_test.go rename to cmd/mailchain/internal/settings/receiver_etherscan_test.go diff --git a/cmd/mailchain/internal/settings/receiver_mailchain.go b/cmd/mailchain/internal/settings/receiver_mailchain.go new file mode 100644 index 000000000..c30abed32 --- /dev/null +++ b/cmd/mailchain/internal/settings/receiver_mailchain.go @@ -0,0 +1,56 @@ +package settings //nolint: dupl + +import ( + "github.com/mailchain/mailchain/cmd/internal/settings/output" + "github.com/mailchain/mailchain/cmd/internal/settings/values" + "github.com/mailchain/mailchain/internal/clients/mailchain" + "github.com/mailchain/mailchain/internal/mailbox" + "github.com/mailchain/mailchain/internal/protocols" + "github.com/mailchain/mailchain/internal/protocols/substrate" +) + +// MailchainReceiver configuration element. +type MailchainReceiver struct { + EnabledProtocolNetworks values.StringSlice + Address values.String +} + +func mailchainReceiverNoAuth(s values.Store) *MailchainReceiver { + return &MailchainReceiver{ + Address: values.NewDefaultString("http://localhost:8081", s, "receivers.mailchain.address"), + EnabledProtocolNetworks: values.NewDefaultStringSlice( + []string{ + protocols.Substrate + "/" + substrate.EdgewareBerlin, + protocols.Substrate + "/" + substrate.EdgewareMainnet, + }, + s, + "receivers.mailchain.enabled-networks", + ), + } +} + +// Supports a map of what protocol and network combinations are supported. +func (r MailchainReceiver) Supports() map[string]bool { + m := map[string]bool{} + for _, np := range r.EnabledProtocolNetworks.Get() { + m[np] = true + } + + return m +} + +// Produce `mailbox.Receiver` based on configuration settings. +func (r MailchainReceiver) Produce() (mailbox.Receiver, error) { + return mailchain.NewReceiver(r.Address.Get()) +} + +// Output configuration as an `output.Element` for use in exporting configuration. +func (r MailchainReceiver) Output() output.Element { + return output.Element{ + FullName: "mailchain", + Attributes: []output.Attribute{ + r.EnabledProtocolNetworks.Attribute(), + r.Address.Attribute(), + }, + } +} diff --git a/cmd/mailchain/internal/settings/receivers.go b/cmd/mailchain/internal/settings/receivers.go index ee871f80b..37d8163e7 100644 --- a/cmd/mailchain/internal/settings/receivers.go +++ b/cmd/mailchain/internal/settings/receivers.go @@ -14,6 +14,7 @@ func receivers(s values.Store) *Receivers { defaults.ClientEtherscanNoAuth: etherscanReceiverNoAuth(s), defaults.ClientEtherscan: etherscanReceiver(s), defaults.ClientBlockscoutNoAuth: blockscoutReceiverNoAuth(s), + defaults.Mailchain: mailchainReceiverNoAuth(s), }, } } diff --git a/cmd/mailchain/internal/settings/testdata/TestRoot_ToYaml/comment-defaults.comment-defaults.golden.yaml b/cmd/mailchain/internal/settings/testdata/TestRoot_ToYaml/comment-defaults.comment-defaults.golden.yaml index 32d5b6406..857876137 100644 --- a/cmd/mailchain/internal/settings/testdata/TestRoot_ToYaml/comment-defaults.comment-defaults.golden.yaml +++ b/cmd/mailchain/internal/settings/testdata/TestRoot_ToYaml/comment-defaults.comment-defaults.golden.yaml @@ -45,14 +45,14 @@ # nameservice-address: "" # nameservice-domain-name: "" # public-key-finder: "substrate-public-key-finder" -# receiver: "" +# receiver: "mailchain" # sender: "substrate-rpc-edgeware-berlin" # edgeware-mainnet: # disabled: false # nameservice-address: "" # nameservice-domain-name: "" # public-key-finder: "substrate-public-key-finder" -# receiver: "" +# receiver: "mailchain" # sender: "substrate-rpc-edgeware-mainnet" # nameservice-address: # mailchain: @@ -117,6 +117,11 @@ # - "ethereum/mainnet" # - "ethereum/rinkeby" # - "ethereum/ropsten" +# mailchain: +# address: "http://localhost:8081" +# enabled-networks: +# - "substrate/edgeware-berlin" +# - "substrate/edgeware-mainnet" # senders: # ethereum-relay: # base-url: "https://relay.mailchain.xyz/" diff --git a/cmd/mailchain/internal/settings/testdata/TestRoot_ToYaml/include-defaults.include-defaults.golden.yaml b/cmd/mailchain/internal/settings/testdata/TestRoot_ToYaml/include-defaults.include-defaults.golden.yaml index b1fa20bb3..499e2363d 100644 --- a/cmd/mailchain/internal/settings/testdata/TestRoot_ToYaml/include-defaults.include-defaults.golden.yaml +++ b/cmd/mailchain/internal/settings/testdata/TestRoot_ToYaml/include-defaults.include-defaults.golden.yaml @@ -45,14 +45,14 @@ protocols: nameservice-address: "" nameservice-domain-name: "" public-key-finder: "substrate-public-key-finder" - receiver: "" + receiver: "mailchain" sender: "substrate-rpc-edgeware-berlin" edgeware-mainnet: disabled: false nameservice-address: "" nameservice-domain-name: "" public-key-finder: "substrate-public-key-finder" - receiver: "" + receiver: "mailchain" sender: "substrate-rpc-edgeware-mainnet" nameservice-address: mailchain: @@ -117,6 +117,11 @@ receivers: - "ethereum/mainnet" - "ethereum/rinkeby" - "ethereum/ropsten" + mailchain: + address: "http://localhost:8081" + enabled-networks: + - "substrate/edgeware-berlin" + - "substrate/edgeware-mainnet" senders: ethereum-relay: base-url: "https://relay.mailchain.xyz/" diff --git a/go.sum b/go.sum index ff04ea386..5007b86b2 100644 --- a/go.sum +++ b/go.sum @@ -898,6 +898,7 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/clients/blockscout/receiver.go b/internal/clients/blockscout/receiver.go index 6c14b11bc..e2873eb2e 100644 --- a/internal/clients/blockscout/receiver.go +++ b/internal/clients/blockscout/receiver.go @@ -24,7 +24,7 @@ import ( ) // Receive check ethereum transactions for mailchain messages -func (c APIClient) Receive(ctx context.Context, network string, address []byte) ([]mailbox.Transaction, error) { +func (c APIClient) Receive(ctx context.Context, protocol, network string, address []byte) ([]mailbox.Transaction, error) { if !c.isNetworkSupported(network) { return nil, errors.Errorf("network not supported") } diff --git a/internal/clients/blockscout/receiver_test.go b/internal/clients/blockscout/receiver_test.go index 69e4a6055..680ba2aa0 100644 --- a/internal/clients/blockscout/receiver_test.go +++ b/internal/clients/blockscout/receiver_test.go @@ -30,8 +30,9 @@ import ( func TestReceive(t *testing.T) { type args struct { - ctx context.Context - network string + ctx context.Context + protocol string + network string } tests := []struct { name string @@ -44,6 +45,7 @@ func TestReceive(t *testing.T) { "err-network-not-supported", args{ context.Background(), + "ethereum", "InvalidNetwork", }, errors.New("network not supported"), @@ -54,6 +56,7 @@ func TestReceive(t *testing.T) { "err-unmarshal", args{ context.Background(), + "ethereum", "TestNetwork", }, errors.New("invalid character 'i' looking for beginning of object key string"), @@ -64,6 +67,7 @@ func TestReceive(t *testing.T) { "err-get", args{ context.Background(), + "ethereum", "TestNetwork", }, nil, @@ -74,6 +78,7 @@ func TestReceive(t *testing.T) { "success-remove-invalid-tx", args{ context.Background(), + "ethereum", "TestNetwork", }, nil, @@ -90,6 +95,7 @@ func TestReceive(t *testing.T) { "success-remove-empty-input-tx", args{ context.Background(), + "ethereum", "TestNetwork", }, nil, @@ -106,6 +112,7 @@ func TestReceive(t *testing.T) { "success-removes-duplicated-tx", args{ context.Background(), + "ethereum", "TestNetwork", }, nil, @@ -135,7 +142,7 @@ func TestReceive(t *testing.T) { client := &APIClient{ networkConfigs: map[string]networkConfig{"TestNetwork": {url: server.URL}}, } - got, err := client.Receive(tt.args.ctx, tt.args.network, []byte{}) + got, err := client.Receive(tt.args.ctx, tt.args.protocol, tt.args.network, []byte{}) if (err != nil) && err.Error() != tt.wantErr.Error() { t.Errorf("APIClient.Receive() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/internal/clients/etherscan/receiver.go b/internal/clients/etherscan/receiver.go index fb9512573..0dd4e37e9 100644 --- a/internal/clients/etherscan/receiver.go +++ b/internal/clients/etherscan/receiver.go @@ -24,7 +24,7 @@ import ( ) // Receive check ethereum transactions for mailchain messages -func (c APIClient) Receive(ctx context.Context, network string, address []byte) ([]mailbox.Transaction, error) { +func (c APIClient) Receive(ctx context.Context, protocol, network string, address []byte) ([]mailbox.Transaction, error) { if !c.isNetworkSupported(network) { return nil, errors.Errorf("network not supported") } diff --git a/internal/clients/etherscan/receiver_test.go b/internal/clients/etherscan/receiver_test.go index 2efe8c0ea..3c74a4922 100644 --- a/internal/clients/etherscan/receiver_test.go +++ b/internal/clients/etherscan/receiver_test.go @@ -30,8 +30,9 @@ import ( func TestReceive(t *testing.T) { type args struct { - ctx context.Context - network string + ctx context.Context + protocol string + network string } tests := []struct { name string @@ -44,6 +45,7 @@ func TestReceive(t *testing.T) { "err-network-not-supported", args{ context.Background(), + "ethereum", "InvalidNetwork", }, errors.New("network not supported"), @@ -54,6 +56,7 @@ func TestReceive(t *testing.T) { "err-unmarshal", args{ context.Background(), + "ethereum", "TestNetwork", }, errors.New("{invalid}: invalid character 'i' looking for beginning of object key string"), @@ -64,6 +67,7 @@ func TestReceive(t *testing.T) { "err-get", args{ context.Background(), + "ethereum", "TestNetwork", }, nil, @@ -74,6 +78,7 @@ func TestReceive(t *testing.T) { "success-remove-invalid-tx", args{ context.Background(), + "ethereum", "TestNetwork", }, nil, @@ -90,6 +95,7 @@ func TestReceive(t *testing.T) { "success-remove-empty-input-tx", args{ context.Background(), + "ethereum", "TestNetwork", }, nil, @@ -106,6 +112,7 @@ func TestReceive(t *testing.T) { "success-removes-duplicated-tx", args{ context.Background(), + "ethereum", "TestNetwork", }, nil, @@ -136,7 +143,7 @@ func TestReceive(t *testing.T) { key: "api-key", networkConfigs: map[string]networkConfig{"TestNetwork": {url: server.URL}}, } - got, err := client.Receive(tt.args.ctx, tt.args.network, []byte{}) + got, err := client.Receive(tt.args.ctx, tt.args.protocol, tt.args.network, []byte{}) if (err != nil) && !assert.Equal(t, tt.wantErr.Error(), err.Error()) { t.Errorf("APIClient.Receive() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/internal/clients/mailchain/receiver.go b/internal/clients/mailchain/receiver.go new file mode 100644 index 000000000..c6a9c32f7 --- /dev/null +++ b/internal/clients/mailchain/receiver.go @@ -0,0 +1,121 @@ +// Copyright 2019 Finobo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mailchain + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/mailchain/mailchain/encoding" + "github.com/mailchain/mailchain/internal/address" + "github.com/mailchain/mailchain/internal/mailbox" + "github.com/pkg/errors" + "gopkg.in/resty.v1" +) + +// NewReceiver create new API client. +func NewReceiver(address string) (*Receiver, error) { + return &Receiver{ + address: address, + }, nil +} + +// Receiver for talking to etherscan. +type Receiver struct { + address string +} + +// GetTransactionsByAddress get transactions from address via etherscan. +func (c Receiver) getTransactionsByAddress(protocol, network string, addr []byte) (*envelopeList, error) { + encodedAddress, _, err := address.EncodeByProtocol(addr, protocol) + if err != nil { + return nil, errors.WithStack(err) + } + + envelopeListResponse, err := resty.R(). + SetQueryParams(map[string]string{ + "protocol": protocol, + "network": network, + "address": encodedAddress, + }). + Get(fmt.Sprintf("%s/to", strings.Trim(c.address, "/"))) + if err != nil { + return nil, errors.WithStack(err) + } + + txResult := &envelopeList{} + if err := json.Unmarshal(envelopeListResponse.Body(), txResult); err != nil { + return nil, errors.WithMessage(err, string(envelopeListResponse.Body())) + } + + return txResult, nil +} + +// Receive check ethereum transactions for mailchain messages +func (c Receiver) Receive(ctx context.Context, protocol, network string, address []byte) ([]mailbox.Transaction, error) { + txResult, err := c.getTransactionsByAddress(protocol, network, address) + if err != nil { + return nil, errors.WithStack(err) + } + + res := []mailbox.Transaction{} + txHashes := map[string]bool{} + + for i := range txResult.Envelopes { //nolint TODO: paging + x := txResult.Envelopes[i] + + _, ok := txHashes[x.Hash] + if ok { + continue + } + + txHashes[x.Hash] = true + + encryptedTransactionData, err := encoding.DecodeHexZeroX(x.Data) + if err != nil { + continue // invalid data should move to next record + } + + if !bytes.HasPrefix(encryptedTransactionData, encoding.DataPrefix()) { + continue + } + + res = append(res, mailbox.Transaction{ + Data: encryptedTransactionData[len(encoding.DataPrefix()):], + Hash: []byte(x.Hash), + }) + } + + return res, nil +} + +type envelopeList struct { + Envelopes []Envelope `json:"envelopes"` +} + +type Envelope struct { + From string `json:"from"` + To string `json:"to"` + Data string `json:"data"` + BlockHash string `json:"block-hash"` + Hash string `json:"hash"` + + Value string `json:"value"` + GasUsed string `json:"gas-used"` + GasPrice string `json:"gas-price"` +} diff --git a/internal/mailbox/mailboxtest/receiver_mock.go b/internal/mailbox/mailboxtest/receiver_mock.go index e5fc59763..8610864a5 100644 --- a/internal/mailbox/mailboxtest/receiver_mock.go +++ b/internal/mailbox/mailboxtest/receiver_mock.go @@ -49,16 +49,16 @@ func (m *MockReceiver) EXPECT() *MockReceiverMockRecorder { } // Receive mocks base method -func (m *MockReceiver) Receive(ctx context.Context, network string, address []byte) ([]mailbox.Transaction, error) { +func (m *MockReceiver) Receive(ctx context.Context, protocol, network string, address []byte) ([]mailbox.Transaction, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Receive", ctx, network, address) + ret := m.ctrl.Call(m, "Receive", ctx, protocol, network, address) ret0, _ := ret[0].([]mailbox.Transaction) ret1, _ := ret[1].(error) return ret0, ret1 } // Receive indicates an expected call of Receive -func (mr *MockReceiverMockRecorder) Receive(ctx, network, address interface{}) *gomock.Call { +func (mr *MockReceiverMockRecorder) Receive(ctx, protocol, network, address interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Receive", reflect.TypeOf((*MockReceiver)(nil).Receive), ctx, network, address) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Receive", reflect.TypeOf((*MockReceiver)(nil).Receive), ctx, protocol, network, address) } diff --git a/internal/mailbox/receiver.go b/internal/mailbox/receiver.go index 84bfeb258..32b6b5344 100644 --- a/internal/mailbox/receiver.go +++ b/internal/mailbox/receiver.go @@ -29,7 +29,7 @@ type Transaction struct { //go:generate mockgen -source=receiver.go -package=mailboxtest -destination=./mailboxtest/receiver_mock.go // Receiver gets encrypted data from blockchain. type Receiver interface { - Receive(ctx context.Context, network string, address []byte) ([]Transaction, error) + Receive(ctx context.Context, protocol, network string, address []byte) ([]Transaction, error) } // type ReceiverOpts interface{}