From f051ea3a49e6a5029768e2896b1caf43cb4729b6 Mon Sep 17 00:00:00 2001 From: Robert Pirtle Date: Wed, 1 Mar 2023 17:05:53 -0800 Subject: [PATCH] Add EVM signer to e2e test SigningAccounts (#1482) * rename cosmos-sdk specific signers * add evm_signer util * add utilities for converting between addresses * rename signers * dont include e2e tests in docker image * add evmsigner to e2e SigningAccount * add new whale account that is an EthAccount * use ethsecp256k1 for e2e SigningAccounts * wait for evm tx to be committed to block also add example evm tx tests! :tada: * check remainined balance is expected * check balance via evm --- .dockerignore | 1 + Makefile | 2 +- tests/e2e/e2e_test.go | 70 ++++++++- tests/e2e/generated/kava-1/config/app.toml | 4 +- .../e2e/generated/kava-1/config/genesis.json | 53 ++++++- tests/e2e/testutil/account.go | 141 ++++++++++++++---- tests/e2e/testutil/suite.go | 29 +++- tests/util/addresses.go | 14 ++ tests/util/addresses_test.go | 21 +++ tests/util/evmsigner.go | 103 +++++++++++++ tests/util/{signing.go => sdksigner.go} | 34 ++--- 11 files changed, 415 insertions(+), 57 deletions(-) create mode 100644 tests/util/addresses.go create mode 100644 tests/util/addresses_test.go create mode 100644 tests/util/evmsigner.go rename tests/util/{signing.go => sdksigner.go} (94%) diff --git a/.dockerignore b/.dockerignore index 6c2f0586..816fbd68 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,3 +2,4 @@ out/ **/node_modules/ .git/ docs/ +tests/ diff --git a/Makefile b/Makefile index bd369d6e..910d3416 100644 --- a/Makefile +++ b/Makefile @@ -291,7 +291,7 @@ test-basic: test # run end-to-end tests (local docker container must be built, see docker-build) test-e2e: - export E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC='season bone lucky dog depth pond royal decide unknown device fruit inch clock trap relief horse morning taxi bird session throw skull avocado private'; \ + export E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC='tent fitness boat among census primary pipe nose dream glance cave turtle electric fabric jacket shaft easy myself genuine this sibling pulse word unfold'; \ go test -failfast -count=1 -v ./tests/e2e/... test: diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index e4d4e280..d22d701a 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -8,10 +8,18 @@ import ( "github.com/stretchr/testify/suite" "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + ethtypes "github.com/ethereum/go-ethereum/core/types" + emtypes "github.com/tharsis/ethermint/types" + + "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/tests/e2e/testutil" + "github.com/kava-labs/kava/tests/util" +) + +var ( + minEvmGasPrice = big.NewInt(1e10) // akava ) func ukava(amt int64) sdk.Coin { @@ -26,14 +34,18 @@ func TestIntegrationTestSuite(t *testing.T) { suite.Run(t, new(IntegrationTestSuite)) } -// example test that queries kava chain & kava's EVM +// example test that queries kava via SDK and EVM func (suite *IntegrationTestSuite) TestChainID() { - // TODO: make chain agnostic, don't hardcode expected chain ids + // TODO: make chain agnostic, don't hardcode expected chain ids (in testutil) + expectedEvmNetworkId, err := emtypes.ParseChainID(testutil.ChainId) + suite.NoError(err) + // EVM query evmNetworkId, err := suite.EvmClient.NetworkID(context.Background()) suite.NoError(err) - suite.Equal(big.NewInt(8888), evmNetworkId) + suite.Equal(expectedEvmNetworkId, evmNetworkId) + // SDK query nodeInfo, err := suite.Tm.GetNodeInfo(context.Background(), &tmservice.GetNodeInfoRequest{}) suite.NoError(err) suite.Equal(testutil.ChainId, nodeInfo.DefaultNodeInfo.Network) @@ -43,9 +55,55 @@ func (suite *IntegrationTestSuite) TestChainID() { func (suite *IntegrationTestSuite) TestFundedAccount() { funds := ukava(1e7) acc := suite.NewFundedAccount("example-acc", sdk.NewCoins(funds)) + + // check that the sdk & evm signers are for the same account + suite.Equal(acc.SdkAddress.String(), util.EvmToSdkAddress(acc.EvmAddress).String()) + suite.Equal(acc.EvmAddress.Hex(), util.SdkToEvmAddress(acc.SdkAddress).Hex()) + + // check balance via SDK query res, err := suite.Bank.Balance(context.Background(), banktypes.NewQueryBalanceRequest( - acc.Address, "ukava", + acc.SdkAddress, "ukava", )) suite.NoError(err) suite.Equal(funds, *res.Balance) + + // check balance via EVM query + akavaBal, err := suite.EvmClient.BalanceAt(context.Background(), acc.EvmAddress, nil) + suite.NoError(err) + suite.Equal(funds.Amount.MulRaw(1e12).BigInt(), akavaBal) +} + +// example test that signs & broadcasts an EVM tx +func (suite *IntegrationTestSuite) TestTransferOverEVM() { + // fund an account that can perform the transfer + initialFunds := ukava(1e7) // 10 KAVA + acc := suite.NewFundedAccount("evm-test-transfer", sdk.NewCoins(initialFunds)) + + // get a rando account to send kava to + randomAddr := app.RandomAddress() + to := util.SdkToEvmAddress(randomAddr) + + // example fetching of nonce (account sequence) + nonce, err := suite.EvmClient.PendingNonceAt(context.Background(), acc.EvmAddress) + suite.NoError(err) + suite.Equal(uint64(0), nonce) // sanity check. the account should have no prior txs + + // transfer kava over EVM + kavaToTransfer := big.NewInt(1e18) // 1 KAVA; akava has 18 decimals. + req := util.EvmTxRequest{ + Tx: ethtypes.NewTransaction(nonce, to, kavaToTransfer, 1e5, minEvmGasPrice, nil), + Data: "any ol' data to track this through the system", + } + res := acc.SignAndBroadcastEvmTx(req) + suite.NoError(res.Err) + suite.Equal(ethtypes.ReceiptStatusSuccessful, res.Receipt.Status) + + // evm txs refund unused gas. so to know the expected balance we need to know how much gas was used. + ukavaUsedForGas := sdk.NewIntFromBigInt(minEvmGasPrice). + Mul(sdk.NewIntFromUint64(res.Receipt.GasUsed)). + QuoRaw(1e12) // convert akava to ukava + + // expect (9 - gas used) KAVA remaining in account. + balance := suite.QuerySdkForBalances(acc.SdkAddress) + suite.Equal(sdk.NewInt(9e6).Sub(ukavaUsedForGas), balance.AmountOf("ukava")) } diff --git a/tests/e2e/generated/kava-1/config/app.toml b/tests/e2e/generated/kava-1/config/app.toml index 03877fa0..32a23ee3 100644 --- a/tests/e2e/generated/kava-1/config/app.toml +++ b/tests/e2e/generated/kava-1/config/app.toml @@ -60,11 +60,11 @@ inter-block-cache = true # ["message.sender", "message.recipient"] index-events = [] -# IavlCacheSize set the size of the iavl tree cache. +# IavlCacheSize set the size of the iavl tree cache. # Default cache size is 50mb. iavl-cache-size = 781250 trace = true -# IAVLDisableFastNode enables or disables the fast node feature of IAVL. +# IAVLDisableFastNode enables or disables the fast node feature of IAVL. # Default is true. iavl-disable-fastnode = true diff --git a/tests/e2e/generated/kava-1/config/genesis.json b/tests/e2e/generated/kava-1/config/genesis.json index b9ad04b7..d88ca888 100644 --- a/tests/e2e/generated/kava-1/config/genesis.json +++ b/tests/e2e/generated/kava-1/config/genesis.json @@ -1,5 +1,5 @@ { - "genesis_time": "2023-02-15T18:28:01.981711Z", + "genesis_time": "2023-02-28T20:05:58.764023Z", "chain_id": "kavalocalnet_8888-1", "initial_height": "1", "consensus_params": { @@ -187,6 +187,16 @@ } ] }, + { + "@type": "/ethermint.types.v1.EthAccount", + "base_account": { + "address": "kava1q0dkky0505r555etn6u2nz4h4kjcg5y8dg863a", + "pub_key": null, + "account_number": "0", + "sequence": "0" + }, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + }, { "@type": "/ethermint.types.v1.EthAccount", "base_account": { @@ -268,6 +278,47 @@ "default_send_enabled": true }, "balances": [ + { + "address": "kava1q0dkky0505r555etn6u2nz4h4kjcg5y8dg863a", + "coins": [ + { + "denom": "bkava-kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42", + "amount": "10000000000000000" + }, + { + "denom": "bnb", + "amount": "10000000000000000" + }, + { + "denom": "btcb", + "amount": "10000000000000000" + }, + { + "denom": "busd", + "amount": "10000000000000000" + }, + { + "denom": "hard", + "amount": "1000000000000000000" + }, + { + "denom": "swp", + "amount": "1000000000000000000" + }, + { + "denom": "ukava", + "amount": "1000000000000" + }, + { + "denom": "usdx", + "amount": "10000000000000000" + }, + { + "denom": "xrpb", + "amount": "10000000000000000" + } + ] + }, { "address": "kava1z3ytjpr6ancl8gw80z6f47z9smug7986x29vtj", "coins": [ diff --git a/tests/e2e/testutil/account.go b/tests/e2e/testutil/account.go index 3e5082e5..e3f2e318 100644 --- a/tests/e2e/testutil/account.go +++ b/tests/e2e/testutil/account.go @@ -1,28 +1,45 @@ package testutil import ( + "context" + "encoding/hex" + "errors" "fmt" "log" "os" + "time" "github.com/cosmos/cosmos-sdk/crypto/hd" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/go-bip39" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/tharsis/ethermint/crypto/ethsecp256k1" + emtypes "github.com/tharsis/ethermint/types" "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/tests/util" ) -type SigningAccount struct { - name string - mnemonic string - signer *util.Signer - requests chan<- util.MsgRequest - responses <-chan util.MsgResponse +var BroadcastTimeoutErr = errors.New("timed out waiting for tx to be committed to block") - Address sdk.AccAddress +type SigningAccount struct { + name string + mnemonic string + + evmSigner *util.EvmSigner + evmReqChan chan<- util.EvmTxRequest + evmResChan <-chan util.EvmTxResponse + + kavaSigner *util.KavaSigner + sdkReqChan chan<- util.KavaMsgRequest + sdkResChan <-chan util.KavaMsgResponse + + EvmAddress common.Address + SdkAddress sdk.AccAddress l *log.Logger } @@ -42,11 +59,12 @@ func (suite *E2eTestSuite) AddNewSigningAccount(name string, hdPath *hd.BIP44Par suite.Failf("can't create signing account", "account with name %s already exists", name) } + // Kava signing account for SDK side privKeyBytes, err := hd.Secp256k1.Derive()(mnemonic, "", hdPath.String()) suite.NoErrorf(err, "failed to derive private key from mnemonic for %s: %s", name, err) - privKey := &secp256k1.PrivKey{Key: privKeyBytes} + privKey := ðsecp256k1.PrivKey{Key: privKeyBytes} - signer := util.NewSigner( + kavaSigner := util.NewKavaSigner( chainId, suite.encodingConfig, suite.Auth, @@ -55,36 +73,58 @@ func (suite *E2eTestSuite) AddNewSigningAccount(name string, hdPath *hd.BIP44Par 100, ) - requests := make(chan util.MsgRequest) - responses, err := signer.Run(requests) + sdkReqChan := make(chan util.KavaMsgRequest) + sdkResChan, err := kavaSigner.Run(sdkReqChan) suite.NoErrorf(err, "failed to start signer for account %s: %s", name, err) + // Kava signing account for EVM side + evmChainId, err := emtypes.ParseChainID(chainId) + suite.NoErrorf(err, "unable to parse ethermint-compatible chain id from %s", chainId) + ecdsaPrivKey, err := crypto.HexToECDSA(hex.EncodeToString(privKeyBytes)) + suite.NoError(err, "failed to generate ECDSA private key from bytes") + + evmSigner, err := util.NewEvmSigner( + suite.EvmClient, + ecdsaPrivKey, + evmChainId, + ) + suite.NoErrorf(err, "failed to create evm signer") + + evmReqChan := make(chan util.EvmTxRequest) + evmResChan := evmSigner.Run(evmReqChan) + logger := log.New(os.Stdout, fmt.Sprintf("[%s] ", name), log.LstdFlags) - // TODO: authenticated eth client. suite.accounts[name] = &SigningAccount{ - name: name, - mnemonic: mnemonic, - signer: signer, - requests: requests, - responses: responses, - l: logger, + name: name, + mnemonic: mnemonic, + l: logger, - Address: signer.Address(), + evmSigner: evmSigner, + evmReqChan: evmReqChan, + evmResChan: evmResChan, + + kavaSigner: kavaSigner, + sdkReqChan: sdkReqChan, + sdkResChan: sdkResChan, + + EvmAddress: evmSigner.Address(), + SdkAddress: kavaSigner.Address(), } return suite.accounts[name] } // SignAndBroadcastKavaTx sends a request to the signer and awaits its response. -func (a *SigningAccount) SignAndBroadcastKavaTx(req util.MsgRequest) util.MsgResponse { - a.l.Printf("broadcasting tx %+v\n", req.Data) +func (a *SigningAccount) SignAndBroadcastKavaTx(req util.KavaMsgRequest) util.KavaMsgResponse { + a.l.Printf("broadcasting sdk tx %+v\n", req.Data) // send the request to signer - a.requests <- req + a.sdkReqChan <- req + // TODO: timeout awaiting the response. // block and await response // response is not returned until the msg is committed to a block - res := <-a.responses + res := <-a.sdkResChan // error will be set if response is not Code 0 (success) or Code 19 (already in mempool) if res.Err != nil { @@ -96,6 +136,50 @@ func (a *SigningAccount) SignAndBroadcastKavaTx(req util.MsgRequest) util.MsgRes return res } +// EvmTxResponse is util.EvmTxResponse that also includes the Receipt, if available +type EvmTxResponse struct { + util.EvmTxResponse + Receipt *ethtypes.Receipt +} + +// SignAndBroadcastEvmTx sends a request to the signer and awaits its response. +func (a *SigningAccount) SignAndBroadcastEvmTx(req util.EvmTxRequest) EvmTxResponse { + a.l.Printf("broadcasting evm tx %+v\n", req.Data) + // send the request to signer + a.evmReqChan <- req + + // block and await response + // response occurs once tx is submitted to pending tx pool. + // poll for the receipt to wait for it to be included in a block + res := <-a.evmResChan + response := EvmTxResponse{ + EvmTxResponse: res, + } + // if failed during signing or broadcast, there will never be a receipt. + if res.Err != nil { + return response + } + + // if we don't have a tx receipt within a given timeout, fail the request + timeout := time.After(10 * time.Second) + for { + select { + case <-timeout: + response.Err = BroadcastTimeoutErr + default: + response.Receipt, response.Err = a.evmSigner.EvmClient.TransactionReceipt(context.Background(), res.TxHash) + if errors.Is(response.Err, ethereum.NotFound) { + // tx still not committed to a block. retry! + time.Sleep(100 * time.Millisecond) + continue + } + } + break + } + + return response +} + func (suite *E2eTestSuite) NewFundedAccount(name string, funds sdk.Coins) *SigningAccount { entropy, err := bip39.NewEntropy(128) suite.NoErrorf(err, "failed to generate entropy for account %s: %s", name, err) @@ -110,12 +194,13 @@ func (suite *E2eTestSuite) NewFundedAccount(name string, funds sdk.Coins) *Signi ) whale := suite.GetAccount(FundedAccountName) + whale.l.Printf("attempting to fund created account (%s=%s)\n", name, acc.SdkAddress.String()) res := whale.SignAndBroadcastKavaTx( - util.MsgRequest{ + util.KavaMsgRequest{ Msgs: []sdk.Msg{ - banktypes.NewMsgSend(whale.Address, acc.Address, funds), + banktypes.NewMsgSend(whale.SdkAddress, acc.SdkAddress, funds), }, - GasLimit: 1e5, + GasLimit: 2e5, FeeAmount: sdk.NewCoins(sdk.NewCoin(StakingDenom, sdk.NewInt(75000))), Data: fmt.Sprintf("initial funding of account %s", name), }, @@ -123,5 +208,7 @@ func (suite *E2eTestSuite) NewFundedAccount(name string, funds sdk.Coins) *Signi suite.NoErrorf(res.Err, "failed to fund new account %s: %s", name, res.Err) + whale.l.Printf("successfully funded [%s]\n", name) + return acc } diff --git a/tests/e2e/testutil/suite.go b/tests/e2e/testutil/suite.go index 1bea3bba..a2883cf8 100644 --- a/tests/e2e/testutil/suite.go +++ b/tests/e2e/testutil/suite.go @@ -1,6 +1,7 @@ package testutil import ( + "context" "fmt" "os" "path/filepath" @@ -10,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" "github.com/cosmos/cosmos-sdk/crypto/hd" + sdk "github.com/cosmos/cosmos-sdk/types" txtypes "github.com/cosmos/cosmos-sdk/types/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -25,6 +27,10 @@ const ( ChainId = "kavalocalnet_8888-1" FundedAccountName = "whale" StakingDenom = "ukava" + // use coin type 60 so we are compatible with accounts from `kava add keys --eth ` + // these accounts use the ethsecp256k1 signing algorithm that allows the signing client + // to manage both sdk & evm txs. + Bip44CoinType = 60 ) type E2eTestSuite struct { @@ -44,6 +50,7 @@ type E2eTestSuite struct { } func (suite *E2eTestSuite) SetupSuite() { + fmt.Println("setting up test suite.") app.SetSDKConfig() suite.encodingConfig = app.MakeEncodingConfig() @@ -96,19 +103,35 @@ func (suite *E2eTestSuite) SetupSuite() { // initialize accounts map suite.accounts = make(map[string]*SigningAccount) // setup the signing account for the initially funded account (used to fund all other accounts) - suite.AddNewSigningAccount( + whale := suite.AddNewSigningAccount( FundedAccountName, - hd.CreateHDPath(app.Bip44CoinType, 0, 0), + hd.CreateHDPath(Bip44CoinType, 0, 0), ChainId, fundedAccountMnemonic, ) + + // check that funded account is actually funded. + fmt.Printf("account used for funding (%s) address: %s\n", FundedAccountName, whale.SdkAddress) + whaleFunds := suite.QuerySdkForBalances(whale.SdkAddress) + if whaleFunds.IsZero() { + suite.FailNow("no available funds.", "funded account mnemonic is for account with no funds") + } } func (suite *E2eTestSuite) TearDownSuite() { + fmt.Println("tearing down test suite.") // close all account request channels for _, a := range suite.accounts { - close(a.requests) + close(a.sdkReqChan) } // gracefully shutdown docker container(s) suite.runner.Shutdown() } + +func (suite *E2eTestSuite) QuerySdkForBalances(addr sdk.AccAddress) sdk.Coins { + res, err := suite.Bank.AllBalances(context.Background(), &banktypes.QueryAllBalancesRequest{ + Address: addr.String(), + }) + suite.NoError(err) + return res.Balances +} diff --git a/tests/util/addresses.go b/tests/util/addresses.go new file mode 100644 index 00000000..d8118d41 --- /dev/null +++ b/tests/util/addresses.go @@ -0,0 +1,14 @@ +package util + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" +) + +func SdkToEvmAddress(addr sdk.AccAddress) common.Address { + return common.BytesToAddress(addr.Bytes()) +} + +func EvmToSdkAddress(addr common.Address) sdk.AccAddress { + return sdk.AccAddress(addr.Bytes()) +} diff --git a/tests/util/addresses_test.go b/tests/util/addresses_test.go new file mode 100644 index 00000000..85574d7e --- /dev/null +++ b/tests/util/addresses_test.go @@ -0,0 +1,21 @@ +package util_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/tests/util" +) + +func TestAddressConversion(t *testing.T) { + app.SetSDKConfig() + bech32Addr := sdk.MustAccAddressFromBech32("kava17d2wax0zhjrrecvaszuyxdf5wcu5a0p4qlx3t5") + hexAddr := common.HexToAddress("0xf354ee99e2bc863cE19d80b843353476394EbC35") + require.Equal(t, bech32Addr, util.EvmToSdkAddress(hexAddr)) + require.Equal(t, hexAddr, util.SdkToEvmAddress(bech32Addr)) +} diff --git a/tests/util/evmsigner.go b/tests/util/evmsigner.go new file mode 100644 index 00000000..484ae560 --- /dev/null +++ b/tests/util/evmsigner.go @@ -0,0 +1,103 @@ +package util + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" +) + +type EvmTxRequest struct { + Tx *ethtypes.Transaction + Data interface{} +} +type EvmTxResponse struct { + Request EvmTxRequest + TxHash common.Hash + Err error +} + +type EvmFailedToSignError struct{ Err error } + +func (e EvmFailedToSignError) Error() string { + return fmt.Sprintf("failed to sign tx: %s", e.Err) +} + +type EvmFailedToBroadcastError struct{ Err error } + +func (e EvmFailedToBroadcastError) Error() string { + return fmt.Sprintf("failed to broadcast tx: %s", e.Err) +} + +// EvmSigner manages signing and broadcasting requests to transfer Erc20 tokens +// Will work for calling all contracts that have func signature `transfer(address,uint256)` +type EvmSigner struct { + auth *bind.TransactOpts + signerAddress common.Address + EvmClient *ethclient.Client +} + +func NewEvmSigner( + evmClient *ethclient.Client, + privKey *ecdsa.PrivateKey, + chainId *big.Int, +) (*EvmSigner, error) { + auth, err := bind.NewKeyedTransactorWithChainID(privKey, chainId) + if err != nil { + return &EvmSigner{}, err + } + + publicKey := privKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return &EvmSigner{}, fmt.Errorf("cannot assert type: publicKey is not of type *ecdsa.PublicKey") + } + + return &EvmSigner{ + auth: auth, + signerAddress: crypto.PubkeyToAddress(*publicKeyECDSA), + EvmClient: evmClient, + }, nil +} + +func (s *EvmSigner) Run(requests <-chan EvmTxRequest) <-chan EvmTxResponse { + responses := make(chan EvmTxResponse) + + // receive tx requests, sign & broadcast them. + // Responses are sent once the tx is added to the pending tx pool. + // To see result, use TransactionReceipt after tx has been included in a block. + go func() { + for { + // wait for incoming request + req := <-requests + + signedTx, err := s.auth.Signer(s.signerAddress, req.Tx) + if err != nil { + err = EvmFailedToSignError{Err: err} + } else { + err = s.EvmClient.SendTransaction(context.Background(), signedTx) + if err != nil { + err = EvmFailedToBroadcastError{Err: err} + } + } + + responses <- EvmTxResponse{ + Request: req, + TxHash: signedTx.Hash(), + Err: err, + } + } + }() + + return responses +} + +func (s *EvmSigner) Address() common.Address { + return s.signerAddress +} diff --git a/tests/util/signing.go b/tests/util/sdksigner.go similarity index 94% rename from tests/util/signing.go rename to tests/util/sdksigner.go index 6f50c06b..0f3ea9da 100644 --- a/tests/util/signing.go +++ b/tests/util/sdksigner.go @@ -18,18 +18,18 @@ import ( tmmempool "github.com/tendermint/tendermint/mempool" ) -type MsgRequest struct { +type KavaMsgRequest struct { Msgs []sdk.Msg GasLimit uint64 FeeAmount sdk.Coins Memo string - // Arbitrary data to be referenced in the corresponding MsgResponse, unused - // in signing. This is mostly useful to match MsgResponses with MsgRequests. + // Arbitrary data to be referenced in the corresponding KavaMsgResponse, unused + // in signing. This is mostly useful to match KavaMsgResponses with KavaMsgRequests. Data interface{} } -type MsgResponse struct { - Request MsgRequest +type KavaMsgResponse struct { + Request KavaMsgRequest Tx authsigning.Tx TxBytes []byte Result sdk.TxResponse @@ -46,8 +46,8 @@ const ( txResetSequence ) -// Signer broadcasts msgs to a single kava node -type Signer struct { +// KavaSigner broadcasts msgs to a single kava node +type KavaSigner struct { chainID string encodingConfig params.EncodingConfig authClient authtypes.QueryClient @@ -56,15 +56,15 @@ type Signer struct { inflightTxLimit uint64 } -func NewSigner( +func NewKavaSigner( chainID string, encodingConfig params.EncodingConfig, authClient authtypes.QueryClient, txClient txtypes.ServiceClient, privKey cryptotypes.PrivKey, - inflightTxLimit uint64) *Signer { + inflightTxLimit uint64) *KavaSigner { - return &Signer{ + return &KavaSigner{ chainID: chainID, encodingConfig: encodingConfig, authClient: authClient, @@ -74,7 +74,7 @@ func NewSigner( } } -func (s *Signer) pollAccountState() <-chan authtypes.AccountI { +func (s *KavaSigner) pollAccountState() <-chan authtypes.AccountI { accountState := make(chan authtypes.AccountI) go func() { @@ -100,7 +100,7 @@ func (s *Signer) pollAccountState() <-chan authtypes.AccountI { return accountState } -func (s *Signer) Run(requests <-chan MsgRequest) (<-chan MsgResponse, error) { +func (s *KavaSigner) Run(requests <-chan KavaMsgRequest) (<-chan KavaMsgResponse, error) { // poll account state in it's own goroutine // and send status updates to the signing goroutine // @@ -108,15 +108,15 @@ func (s *Signer) Run(requests <-chan MsgRequest) (<-chan MsgResponse, error) { // websocket events with a fallback to polling accountState := s.pollAccountState() - responses := make(chan MsgResponse) + responses := make(chan KavaMsgResponse) go func() { // wait until account is loaded to start signing account := <-accountState // store current request waiting to be broadcasted - var currentRequest *MsgRequest + var currentRequest *KavaMsgRequest // keep track of all successfully broadcasted txs // index is sequence % inflightTxLimit - inflight := make([]*MsgResponse, s.inflightTxLimit) + inflight := make([]*KavaMsgResponse, s.inflightTxLimit) // used for confirming sent txs only prevDeliverTxSeq := account.GetSequence() // tx sequence of already signed messages @@ -243,7 +243,7 @@ func (s *Signer) Run(requests <-chan MsgRequest) (<-chan MsgResponse, error) { tx, txBytes, err := Sign(s.encodingConfig.TxConfig, s.privKey, txBuilder, signerData) - response = &MsgResponse{ + response = &KavaMsgResponse{ Request: *currentRequest, Tx: tx, TxBytes: txBytes, @@ -367,7 +367,7 @@ func (s *Signer) Run(requests <-chan MsgRequest) (<-chan MsgResponse, error) { } // Address returns the address of the Signer -func (s *Signer) Address() sdk.AccAddress { +func (s *KavaSigner) Address() sdk.AccAddress { return GetAccAddress(s.privKey) }