diff --git a/go.mod b/go.mod index 1f2bf724b..b167c30bb 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/tendermint/tm-db v0.6.8-0.20220506192307-f628bb5dc95b golang.org/x/crypto v0.18.0 golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 - golang.org/x/mod v0.15.0 + golang.org/x/mod v0.14.0 golang.org/x/sync v0.6.0 golang.org/x/text v0.14.0 golang.org/x/tools v0.17.0 diff --git a/go.sum b/go.sum index 3630eccb6..607a8a222 100644 --- a/go.sum +++ b/go.sum @@ -1904,8 +1904,6 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/vald/evm/evm.go b/vald/evm/evm.go index b4c3e8efd..2941b98a7 100644 --- a/vald/evm/evm.go +++ b/vald/evm/evm.go @@ -21,6 +21,7 @@ import ( "github.com/axelarnetwork/axelar-core/vald/evm/rpc" "github.com/axelarnetwork/axelar-core/x/evm/types" nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported" + vote "github.com/axelarnetwork/axelar-core/x/vote/exported" voteTypes "github.com/axelarnetwork/axelar-core/x/vote/types" "github.com/axelarnetwork/utils/funcs" "github.com/axelarnetwork/utils/log" @@ -38,30 +39,8 @@ var ( TokenSentSig = crypto.Keccak256Hash([]byte("TokenSent(address,string,string,string,uint256)")) ) -// NotFinalizedError contains the block height of the transaction that is not finalized yet -type NotFinalizedError struct { - BlockHeight uint64 -} - -func (e NotFinalizedError) Error() string { - return "not finalized" -} - -// FailedTransactionError contains the block height of the transaction that failed on the source chain -type FailedTransactionError struct { - BlockHeight uint64 -} - -func (e FailedTransactionError) Error() string { - return "failed on source chain" -} - -// NotFoundError is a type-safe wrapper around ethereum.NotFound -type NotFoundError struct{} - -func (e NotFoundError) Error() string { - return ethereum.NotFound.Error() -} +// NotFinalized is returned when a transaction is not finalized +var NotFinalized = goerrors.New("not finalized") // Mgr manages all communication with Ethereum type Mgr struct { @@ -96,21 +75,24 @@ func (mgr Mgr) ProcessNewChain(event *types.ChainAdded) (err error) { // ProcessDepositConfirmation votes on the correctness of an EVM chain token deposit func (mgr Mgr) ProcessDepositConfirmation(event *types.ConfirmDepositStarted) error { - vc := voteContext{ - Participants: event.Participants, - PollMappings: []types.PollMapping{{PollID: event.PollID, TxID: event.TxID}}, - Chain: event.Chain, - ConfirmationHeight: event.ConfirmationHeight, - PollType: "token deposit", - } - return mgr.vote(vc, func(logs []*geth.Log) []types.Event { - return mgr.processTokenDepositLogs(logs, event) - }) -} + if !mgr.isParticipantOf(event.Participants) { + mgr.logger("pollID", event.PollID).Debug("ignoring deposit confirmation poll: not a participant") + return nil + } + + txReceipt, err := mgr.GetTxReceiptIfFinalized(event.Chain, common.Hash(event.TxID), event.ConfirmationHeight) + if err != nil { + return err + } + if txReceipt == nil { + mgr.logger().Infof("broadcasting empty vote for poll %s", event.PollID.String()) + _, err := mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain))) + + return err + } -func (mgr Mgr) processTokenDepositLogs(logs []*geth.Log, event *types.ConfirmDepositStarted) []types.Event { var events []types.Event - for i, log := range logs { + for i, log := range txReceipt.Logs { if log.Topics[0] != ERC20TransferSig { continue } @@ -143,26 +125,33 @@ func (mgr Mgr) processTokenDepositLogs(logs []*geth.Log, event *types.ConfirmDep }, }) } - return events + + mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) + _, err = mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...))) + + return err } // ProcessTokenConfirmation votes on the correctness of an EVM chain token deployment func (mgr Mgr) ProcessTokenConfirmation(event *types.ConfirmTokenStarted) error { - vc := voteContext{ - Participants: event.Participants, - PollMappings: []types.PollMapping{{PollID: event.PollID, TxID: event.TxID}}, - Chain: event.Chain, - ConfirmationHeight: event.ConfirmationHeight, - PollType: "token deployment confirmation", - } - return mgr.vote(vc, func(logs []*geth.Log) []types.Event { - return mgr.processTokenConfirmationLogs(logs, event) - }) -} + if !mgr.isParticipantOf(event.Participants) { + mgr.logger("pollID", event.PollID).Debug("ignoring token confirmation poll: not a participant") + return nil + } + + txReceipt, err := mgr.GetTxReceiptIfFinalized(event.Chain, common.Hash(event.TxID), event.ConfirmationHeight) + if err != nil { + return err + } + if txReceipt == nil { + mgr.logger().Infof("broadcasting empty vote for poll %s", event.PollID.String()) + _, err := mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain))) + + return err + } -func (mgr Mgr) processTokenConfirmationLogs(logs []*geth.Log, event *types.ConfirmTokenStarted) []types.Event { var events []types.Event - for i, log := range logs { + for i, log := range txReceipt.Logs { if log.Topics[0] != ERC20TokenDeploymentSig { continue } @@ -196,29 +185,34 @@ func (mgr Mgr) processTokenConfirmationLogs(logs []*geth.Log, event *types.Confi }) break } - return events + + mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) + _, err = mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...))) + + return err } // ProcessTransferKeyConfirmation votes on the correctness of an EVM chain key transfer func (mgr Mgr) ProcessTransferKeyConfirmation(event *types.ConfirmKeyTransferStarted) error { - vc := voteContext{ - Participants: event.Participants, - PollMappings: []types.PollMapping{{PollID: event.PollID, TxID: event.TxID}}, - Chain: event.Chain, - ConfirmationHeight: event.ConfirmationHeight, - PollType: "key transfer confirmation", - } - return mgr.vote(vc, func(logs []*geth.Log) []types.Event { - return mgr.processKeyTransferLogs(logs, event) - }) -} + if !mgr.isParticipantOf(event.Participants) { + mgr.logger("pollID", event.PollID).Debug("ignoring key transfer confirmation poll: not a participant") + return nil + } -func (mgr Mgr) processKeyTransferLogs(logs []*geth.Log, event *types.ConfirmKeyTransferStarted) []types.Event { + txReceipt, err := mgr.GetTxReceiptIfFinalized(event.Chain, common.Hash(event.TxID), event.ConfirmationHeight) + if err != nil { + return err + } + if txReceipt == nil { + mgr.logger().Infof("broadcasting empty vote for poll %s", event.PollID.String()) + _, err := mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain))) - var events []types.Event + return err + } - for i := len(logs) - 1; i >= 0; i-- { - txlog := logs[i] + var events []types.Event + for i := len(txReceipt.Logs) - 1; i >= 0; i-- { + txlog := txReceipt.Logs[i] if txlog.Topics[0] != MultisigTransferOperatorshipSig { continue @@ -249,81 +243,73 @@ func (mgr Mgr) processKeyTransferLogs(logs []*geth.Log, event *types.ConfirmKeyT }}) break } - return events + + mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) + _, err = mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...))) + + return err } // ProcessGatewayTxConfirmation votes on the correctness of an EVM chain gateway's transactions func (mgr Mgr) ProcessGatewayTxConfirmation(event *types.ConfirmGatewayTxStarted) error { - mappedEvent := &types.ConfirmGatewayTxsStarted{ - PollMappings: []types.PollMapping{{PollID: event.PollID, TxID: event.TxID}}, - Chain: event.Chain, - GatewayAddress: event.GatewayAddress, - ConfirmationHeight: event.ConfirmationHeight, - Participants: event.Participants, - } - return mgr.ProcessGatewayTxsConfirmation(mappedEvent) -} + if !mgr.isParticipantOf(event.Participants) { + mgr.logger("pollID", event.PollID).Debug("ignoring gateway tx confirmation poll: not a participant") + return nil + } -// ProcessGatewayTxsConfirmation votes on the correctness of an EVM chain multiple gateway transactions -func (mgr Mgr) ProcessGatewayTxsConfirmation(event *types.ConfirmGatewayTxsStarted) error { - vc := voteContext{ - Participants: event.Participants, - PollMappings: event.PollMappings, - Chain: event.Chain, - ConfirmationHeight: event.ConfirmationHeight, - PollType: "gateway txs confirmation", - } - return mgr.vote(vc, func(logs []*geth.Log) []types.Event { - return mgr.processGatewayTxLogs(event.Chain, event.GatewayAddress, logs) - }) -} + txReceipt, err := mgr.GetTxReceiptIfFinalized(event.Chain, common.Hash(event.TxID), event.ConfirmationHeight) + if err != nil { + return err + } + if txReceipt == nil { + mgr.logger().Infof("broadcasting empty vote for poll %s", event.PollID.String()) + _, err := mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain))) -type voteContext struct { - PollType string - Participants []sdk.ValAddress - PollMappings []types.PollMapping - Chain nexus.ChainName - ConfirmationHeight uint64 + return err + } + + events := mgr.processGatewayTxLogs(event.Chain, event.GatewayAddress, txReceipt.Logs) + mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) + _, err = mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...))) + + return err } -func (mgr Mgr) vote(voteContext voteContext, logsToVotes func(logs []*geth.Log) []types.Event) error { - if !mgr.isParticipantOf(voteContext.Participants) { - pollIDs := slices.Map(voteContext.PollMappings, types.PollMapping.GetPollID) - mgr.logger("poll_ids", pollIDs).Debug(fmt.Sprintf("ignoring %s poll: not a participant", voteContext.PollType)) +// ProcessGatewayTxsConfirmation votes on the correctness of an EVM chain multiple gateway transactions +func (mgr Mgr) ProcessGatewayTxsConfirmation(event *types.ConfirmGatewayTxsStarted) error { + if !mgr.isParticipantOf(event.Participants) { + pollIDs := slices.Map(event.PollMappings, func(m types.PollMapping) vote.PollID { return m.PollID }) + mgr.logger("poll_ids", pollIDs).Debug("ignoring gateway txs confirmation poll: not a participant") return nil } - txIDs := slices.Map(voteContext.PollMappings, types.PollMapping.GetTxID) - txReceipts, err := mgr.GetTxReceiptsIfFinalized(voteContext.Chain, txIDs, voteContext.ConfirmationHeight) + txIDs := slices.Map(event.PollMappings, func(poll types.PollMapping) common.Hash { return common.Hash(poll.TxID) }) + txReceipts, err := mgr.GetTxReceiptsIfFinalized(event.Chain, txIDs, event.ConfirmationHeight) if err != nil { return err } var votes []sdk.Msg for i, result := range txReceipts { - pollID := voteContext.PollMappings[i].PollID - txID := voteContext.PollMappings[i].TxID + pollID := event.PollMappings[i].PollID + txID := event.PollMappings[i].TxID - logger := mgr.logger("chain", voteContext.Chain, "poll_id", pollID.String(), "tx_id", txID.Hex()) + logger := mgr.logger("chain", event.Chain, "poll_id", pollID.String(), "tx_id", txID.Hex()) // only broadcast empty votes if the tx is not found or not finalized - switch err := result.Err().(type) { + switch result.Err() { case nil: - events := logsToVotes(result.Ok().Logs) + events := mgr.processGatewayTxLogs(event.Chain, event.GatewayAddress, result.Ok().Logs) logger.Infof("broadcasting vote %v", events) - votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(voteContext.Chain, events...))) - case NotFinalizedError: - logger.Debug(fmt.Sprintf("transaction %s in block %v not finalized", txID.Hex(), err.BlockHeight)) + votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain, events...))) + case NotFinalized: + logger.Debug(fmt.Sprintf("transaction %s in block %s not finalized", txID.Hex(), result.Ok().BlockNumber.String())) logger.Infof("broadcasting empty vote due to error: %s", result.Err().Error()) - votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(voteContext.Chain))) - case FailedTransactionError: - logger.Debug(fmt.Sprintf("transaction %s in block %v has failed status", txID.Hex(), err.BlockHeight)) - logger.Infof("broadcasting empty vote due to error: %s", result.Err().Error()) - votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(voteContext.Chain))) - case NotFoundError: + votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain))) + case ethereum.NotFound: logger.Debug(fmt.Sprintf("transaction receipt %s not found", txID.Hex())) logger.Infof("broadcasting empty vote due to error: %s", result.Err().Error()) - votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(voteContext.Chain))) + votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain))) default: logger.Errorf("failed to get tx receipt: %s", result.Err().Error()) } @@ -456,7 +442,41 @@ func (mgr Mgr) isTxReceiptFinalized(chain nexus.ChainName, txReceipt *geth.Recei return true, nil } -// GetTxReceiptsIfFinalized retrieves receipts for provided transaction IDs, only if they're successful and finalized. +func (mgr Mgr) GetTxReceiptIfFinalized(chain nexus.ChainName, txID common.Hash, confHeight uint64) (*geth.Receipt, error) { + client, ok := mgr.rpcs[strings.ToLower(chain.String())] + if !ok { + return nil, fmt.Errorf("rpc client not found for chain %s", chain.String()) + } + + txReceipt, err := client.TransactionReceipt(context.Background(), txID) + keyvals := []interface{}{"chain", chain.String(), "tx_id", txID.Hex()} + logger := mgr.logger(keyvals...) + if err == ethereum.NotFound { + logger.Debug(fmt.Sprintf("transaction receipt %s not found", txID.Hex())) + return nil, nil + } + if err != nil { + return nil, sdkerrors.Wrap(errors.With(err, keyvals...), "failed getting transaction receipt") + } + + if txReceipt.Status != geth.ReceiptStatusSuccessful { + return nil, errors.With(fmt.Errorf("transaction %s failed", txID.Hex()), keyvals...) + } + + isFinalized, err := mgr.isTxReceiptFinalized(chain, txReceipt, confHeight) + if err != nil { + return nil, sdkerrors.Wrapf(errors.With(err, keyvals...), "cannot determine if the transaction %s is finalized", txID.Hex()) + } + if !isFinalized { + logger.Debug(fmt.Sprintf("transaction %s in block %s not finalized", txID.Hex(), txReceipt.BlockNumber.String())) + + return nil, nil + } + + return txReceipt, nil +} + +// GetTxReceiptsIfFinalized retrieves receipts for provided transaction IDs, only if they're finalized. func (mgr Mgr) GetTxReceiptsIfFinalized(chain nexus.ChainName, txIDs []common.Hash, confHeight uint64) ([]rs.Result[*geth.Receipt], error) { client, ok := mgr.rpcs[strings.ToLower(chain.String())] if !ok { @@ -469,16 +489,9 @@ func (mgr Mgr) GetTxReceiptsIfFinalized(chain nexus.ChainName, txIDs []common.Ha "cannot get transaction receipts") } - isFound := func(res rs.Result[*geth.Receipt]) rs.Result[*geth.Receipt] { - if goerrors.Is(res.Err(), ethereum.NotFound) { - return rs.FromErr[*geth.Receipt](NotFoundError{}) - } - return res - } - isFinalized := func(receipt *geth.Receipt) rs.Result[*geth.Receipt] { if receipt.Status != geth.ReceiptStatusSuccessful { - return rs.FromErr[*geth.Receipt](FailedTransactionError{BlockHeight: receipt.BlockNumber.Uint64()}) + return rs.FromErr[*geth.Receipt](fmt.Errorf("transaction %s failed", receipt.TxHash.Hex())) } isFinalized, err := mgr.isTxReceiptFinalized(chain, receipt, confHeight) @@ -489,15 +502,14 @@ func (mgr Mgr) GetTxReceiptsIfFinalized(chain nexus.ChainName, txIDs []common.Ha } if !isFinalized { - return rs.FromErr[*geth.Receipt](NotFinalizedError{BlockHeight: receipt.BlockNumber.Uint64()}) + return rs.FromErr[*geth.Receipt](NotFinalized) } return rs.FromOk(receipt) } return slices.Map(results, func(r rpc.Result) rs.Result[*geth.Receipt] { - res := isFound(rs.Result[*geth.Receipt](r)) - return rs.Pipe(res, isFinalized) + return rs.Pipe(rs.Result[*geth.Receipt](r), isFinalized) }), nil } diff --git a/vald/evm/evm_test.go b/vald/evm/evm_test.go index 312551533..28a4b009b 100644 --- a/vald/evm/evm_test.go +++ b/vald/evm/evm_test.go @@ -1,6 +1,7 @@ package evm_test import ( + "bytes" "context" "fmt" "math/big" @@ -18,7 +19,6 @@ import ( mock2 "github.com/axelarnetwork/axelar-core/sdk-utils/broadcast/mock" "github.com/axelarnetwork/axelar-core/testutils" "github.com/axelarnetwork/axelar-core/testutils/rand" - "github.com/axelarnetwork/axelar-core/utils/errors" "github.com/axelarnetwork/axelar-core/vald/evm" evmmock "github.com/axelarnetwork/axelar-core/vald/evm/mock" evmRpc "github.com/axelarnetwork/axelar-core/vald/evm/rpc" @@ -171,10 +171,132 @@ func TestDecodeErc20TransferEvent_CorrectData(t *testing.T) { assert.Equal(t, expectedAmount, transfer.Amount) } -func TestMgr_ProcessDepositConfirmation(t *testing.T) { +func TestMgr_GetTxReceiptIfFinalized(t *testing.T) { + chain := nexus.ChainName(strings.ToLower(rand.NormalizedStr(5))) + tx := geth.NewTransaction(0, common.BytesToAddress(rand.Bytes(common.HashLength)), big.NewInt(rand.PosI64()), uint64(rand.PosI64()), big.NewInt(rand.PosI64()), rand.Bytes(int(rand.I64Between(100, 1000)))) + + var ( + mgr *evm.Mgr + rpcClient *mock.ClientMock + cache *evmmock.LatestFinalizedBlockCacheMock + confHeight uint64 + latestFinalizedBlockNumber uint64 + ) + + givenMgr := Given("evm mgr", func() { + rpcClient = &mock.ClientMock{} + cache = &evmmock.LatestFinalizedBlockCacheMock{} + confHeight = uint64(rand.I64Between(1, 50)) + latestFinalizedBlockNumber = uint64(rand.I64Between(1000, 10000)) + + mgr = evm.NewMgr(map[string]evmRpc.Client{chain.String(): rpcClient}, nil, rand.ValAddr(), rand.AccAddr(), cache) + }) + + givenMgr. + When("the rpc client determines that the tx failed", func() { + receipt := &geth.Receipt{ + BlockNumber: big.NewInt(int64(latestFinalizedBlockNumber) - rand.I64Between(1, 100)), + TxHash: tx.Hash(), + Status: geth.ReceiptStatusFailed, + } + + rpcClient.TransactionReceiptFunc = func(_ context.Context, txHash common.Hash) (*geth.Receipt, error) { + if bytes.Equal(txHash.Bytes(), tx.Hash().Bytes()) { + return receipt, nil + } + + return nil, fmt.Errorf("not found") + } + }). + Then("tx is considered not finalized", func(t *testing.T) { + txReceipt, err := mgr.GetTxReceiptIfFinalized(chain, tx.Hash(), confHeight) + + assert.ErrorContains(t, err, "failed") + assert.Nil(t, txReceipt) + }). + Run(t) + + givenMgr. + When("the latest finalized block cache does not have the result", func() { + cache.GetFunc = func(_ nexus.ChainName) *big.Int { + return big.NewInt(0) + } + cache.SetFunc = func(_ nexus.ChainName, blockNumber *big.Int) {} + }). + When("the rpc client determines that the tx is finalized", func() { + receipt := &geth.Receipt{ + BlockNumber: big.NewInt(int64(latestFinalizedBlockNumber) - rand.I64Between(1, 100)), + TxHash: tx.Hash(), + Status: geth.ReceiptStatusSuccessful, + } + + rpcClient.TransactionReceiptFunc = func(_ context.Context, txHash common.Hash) (*geth.Receipt, error) { + if bytes.Equal(txHash.Bytes(), tx.Hash().Bytes()) { + return receipt, nil + } + + return nil, fmt.Errorf("not found") + } + rpcClient.HeaderByNumberFunc = func(ctx context.Context, number *big.Int) (*evmRpc.Header, error) { + if number.Cmp(receipt.BlockNumber) == 0 { + return &evmRpc.Header{Transactions: []common.Hash{receipt.TxHash}}, nil + } + + return nil, fmt.Errorf("not found") + } + rpcClient.LatestFinalizedBlockNumberFunc = func(ctx context.Context, confirmations uint64) (*big.Int, error) { + return big.NewInt(int64(latestFinalizedBlockNumber)), nil + } + }). + Then("tx is considered finalized", func(t *testing.T) { + txReceipt, err := mgr.GetTxReceiptIfFinalized(chain, tx.Hash(), confHeight) + + assert.NoError(t, err) + assert.NotNil(t, txReceipt) + }). + Run(t, 5) + + givenMgr. + When("the latest finalized block cache has the result", func() { + cache.GetFunc = func(_ nexus.ChainName) *big.Int { + return big.NewInt(int64(latestFinalizedBlockNumber)) + } + }). + When("the rpc client can find the tx receipt", func() { + receipt := &geth.Receipt{ + BlockNumber: big.NewInt(int64(latestFinalizedBlockNumber) - rand.I64Between(1, 100)), + TxHash: tx.Hash(), + Status: geth.ReceiptStatusSuccessful, + } + + rpcClient.TransactionReceiptFunc = func(_ context.Context, txHash common.Hash) (*geth.Receipt, error) { + if bytes.Equal(txHash.Bytes(), tx.Hash().Bytes()) { + return receipt, nil + } + + return nil, fmt.Errorf("not found") + } + rpcClient.HeaderByNumberFunc = func(ctx context.Context, number *big.Int) (*evmRpc.Header, error) { + if number.Cmp(receipt.BlockNumber) == 0 { + return &evmRpc.Header{Transactions: []common.Hash{receipt.TxHash}}, nil + } + + return nil, fmt.Errorf("not found") + } + }). + Then("tx is considered finalized", func(t *testing.T) { + txReceipt, err := mgr.GetTxReceiptIfFinalized(chain, tx.Hash(), confHeight) + + assert.NoError(t, err) + assert.NotNil(t, txReceipt) + }). + Run(t, 5) +} + +func TestMgr_ProccessDepositConfirmation(t *testing.T) { var ( mgr *evm.Mgr - receipts []*geth.Receipt + receipt *geth.Receipt tokenAddr types.Address depositAddr types.Address amount sdk.Uint @@ -238,19 +360,17 @@ func TestMgr_ProcessDepositConfirmation(t *testing.T) { Data: padToHash(amount.BigInt()).Bytes(), } - receipts = []*geth.Receipt{ - { - TxHash: common.Hash(evmtestutils.RandomHash()), - BlockNumber: big.NewInt(rand.PosI64()), - Logs: []*geth.Log{randomTokenDeposit, validDeposit, randomEvent, invalidDeposit, zeroAmountDeposit}, - Status: 1, - }, + receipt = &geth.Receipt{ + TxHash: common.Hash(evmtestutils.RandomHash()), + BlockNumber: big.NewInt(rand.PosI64()), + Logs: []*geth.Log{randomTokenDeposit, validDeposit, randomEvent, invalidDeposit, zeroAmountDeposit}, + Status: 1, } }) confirmingDeposit := When("confirming the existing deposit", func() { event := evmtestutils.RandomConfirmDepositStarted() - event.TxID = types.Hash(receipts[0].TxHash) + event.TxID = types.Hash(receipt.TxHash) evmMap[strings.ToLower(event.Chain.String())] = rpc event.DepositAddress = depositAddr event.TokenAddress = tokenAddr @@ -290,35 +410,23 @@ func TestMgr_ProcessDepositConfirmation(t *testing.T) { Given("an evm rpc client", func() { rpc = &mock.ClientMock{ HeaderByNumberFunc: func(context.Context, *big.Int) (*evmRpc.Header, error) { - return &evmRpc.Header{Transactions: []common.Hash{receipts[0].TxHash}}, nil + return &evmRpc.Header{Transactions: []common.Hash{receipt.TxHash}}, nil }, - TransactionReceiptsFunc: func(_ context.Context, txIDs []common.Hash) ([]evmRpc.Result, error) { - var res []evmRpc.Result - for _, txID := range txIDs { - found := false - for _, receipt := range receipts { - if txID == receipt.TxHash { - res = append(res, evmRpc.Result(results.FromOk(receipt))) - found = true - break - } - } - if !found { - res = append(res, evmRpc.Result(results.FromErr[*geth.Receipt](ethereum.NotFound))) - } + TransactionReceiptFunc: func(_ context.Context, txID common.Hash) (*geth.Receipt, error) { + if txID != receipt.TxHash { + return nil, ethereum.NotFound } - - return res, nil + return receipt, nil }, LatestFinalizedBlockNumberFunc: func(ctx context.Context, confirmations uint64) (*big.Int, error) { - return receipts[0].BlockNumber, nil + return receipt.BlockNumber, nil }, } }). Branch( Given("no deposit has been made", func() { - rpc.TransactionReceiptsFunc = func(context.Context, []common.Hash) ([]evmRpc.Result, error) { - return []evmRpc.Result{evmRpc.Result(results.FromErr[*geth.Receipt](ethereum.NotFound))}, nil + rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { + return nil, ethereum.NotFound } }). When("confirming a random deposit on the correct chain", func() { @@ -334,7 +442,7 @@ func TestMgr_ProcessDepositConfirmation(t *testing.T) { givenDeposit. When("confirming the deposit on unsupported chain", func() { event := evmtestutils.RandomConfirmDepositStarted() - event.TxID = types.Hash(receipts[0].TxHash) + event.TxID = types.Hash(receipt.TxHash) event.DepositAddress = depositAddr event.TokenAddress = tokenAddr event.Participants = append(event.Participants, valAddr) @@ -349,7 +457,7 @@ func TestMgr_ProcessDepositConfirmation(t *testing.T) { givenDeposit. Given("confirmation height is not reached yet", func() { rpc.LatestFinalizedBlockNumberFunc = func(ctx context.Context, confirmations uint64) (*big.Int, error) { - return sdk.NewIntFromBigInt(receipts[0].BlockNumber).SubRaw(int64(confirmations)).BigInt(), nil + return sdk.NewIntFromBigInt(receipt.BlockNumber).SubRaw(int64(confirmations)).BigInt(), nil } }). When2(confirmingDeposit). @@ -385,7 +493,7 @@ func TestMgr_ProcessDepositConfirmation(t *testing.T) { givenDeposit. When("confirming event with wrong token address", func() { event := evmtestutils.RandomConfirmDepositStarted() - event.TxID = types.Hash(receipts[0].TxHash) + event.TxID = types.Hash(receipt.TxHash) evmMap[strings.ToLower(event.Chain.String())] = rpc event.DepositAddress = depositAddr event.Participants = append(event.Participants, valAddr) @@ -398,7 +506,7 @@ func TestMgr_ProcessDepositConfirmation(t *testing.T) { givenDeposit. When("confirming event with wrong deposit address", func() { event := evmtestutils.RandomConfirmDepositStarted() - event.TxID = types.Hash(receipts[0].TxHash) + event.TxID = types.Hash(receipt.TxHash) evmMap[strings.ToLower(event.Chain.String())] = rpc event.TokenAddress = tokenAddr event.Participants = append(event.Participants, valAddr) @@ -411,7 +519,7 @@ func TestMgr_ProcessDepositConfirmation(t *testing.T) { givenDeposit. When("confirming a deposit without being a participant", func() { event := evmtestutils.RandomConfirmDepositStarted() - event.TxID = types.Hash(receipts[0].TxHash) + event.TxID = types.Hash(receipt.TxHash) evmMap[strings.ToLower(event.Chain.String())] = rpc event.DepositAddress = depositAddr event.TokenAddress = tokenAddr @@ -436,11 +544,11 @@ func TestMgr_ProcessDepositConfirmation(t *testing.T) { }, Data: padToHash(additionalAmount.BigInt()).Bytes(), } - receipts[0].Logs = append(receipts[0].Logs, additionalDeposit) + receipt.Logs = append(receipt.Logs, additionalDeposit) }). When("confirming the deposits", func() { event := evmtestutils.RandomConfirmDepositStarted() - event.TxID = types.Hash(receipts[0].TxHash) + event.TxID = types.Hash(receipt.TxHash) evmMap[strings.ToLower(event.Chain.String())] = rpc event.DepositAddress = depositAddr event.TokenAddress = tokenAddr @@ -468,7 +576,7 @@ func TestMgr_ProcessDepositConfirmation(t *testing.T) { } -func TestMgr_ProcessTokenConfirmation(t *testing.T) { +func TestMgr_ProccessTokenConfirmation(t *testing.T) { var ( mgr *evm.Mgr event *types.ConfirmTokenStarted @@ -517,8 +625,8 @@ func TestMgr_ProcessTokenConfirmation(t *testing.T) { HeaderByNumberFunc: func(ctx context.Context, number *big.Int) (*evmRpc.Header, error) { return &evmRpc.Header{Transactions: []common.Hash{receipt.TxHash}}, nil }, - TransactionReceiptsFunc: func(context.Context, []common.Hash) ([]evmRpc.Result, error) { - return []evmRpc.Result{evmRpc.Result(results.FromOk(receipt))}, nil + TransactionReceiptFunc: func(context.Context, common.Hash) (*geth.Receipt, error) { + return receipt, nil }, LatestFinalizedBlockNumberFunc: func(ctx context.Context, confirmations uint64) (*big.Int, error) { return receipt.BlockNumber, nil @@ -552,9 +660,7 @@ func TestMgr_ProcessTokenConfirmation(t *testing.T) { t.Run("no tx receipt", testutils.Func(func(t *testing.T) { setup() - rpc.TransactionReceiptsFunc = func(context.Context, []common.Hash) ([]evmRpc.Result, error) { - return []evmRpc.Result{evmRpc.Result(results.FromErr[*geth.Receipt](ethereum.NotFound))}, nil - } + rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { return nil, ethereum.NotFound } err := mgr.ProcessTokenConfirmation(event) @@ -569,13 +675,7 @@ func TestMgr_ProcessTokenConfirmation(t *testing.T) { t.Run("no deploy event", testutils.Func(func(t *testing.T) { setup() - res, _ := rpc.TransactionReceipts(context.Background(), []common.Hash{}) - - assert.Len(t, res, 1) - result := results.Result[*geth.Receipt](res[0]) - assert.NoError(t, result.Err()) - receipt := result.Ok() - + receipt, _ := rpc.TransactionReceipt(context.Background(), common.Hash{}) var correctLogIdx int for i, l := range receipt.Logs { if l.Address == common.BytesToAddress(gatewayAddrBytes) { @@ -585,9 +685,7 @@ func TestMgr_ProcessTokenConfirmation(t *testing.T) { } // remove the deploy event receipt.Logs = append(receipt.Logs[:correctLogIdx], receipt.Logs[correctLogIdx+1:]...) - rpc.TransactionReceiptsFunc = func(context.Context, []common.Hash) ([]evmRpc.Result, error) { - return []evmRpc.Result{evmRpc.Result(results.FromOk(receipt))}, nil - } + rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { return receipt, nil } err := mgr.ProcessTokenConfirmation(event) @@ -603,20 +701,14 @@ func TestMgr_ProcessTokenConfirmation(t *testing.T) { t.Run("wrong deploy event", testutils.Func(func(t *testing.T) { setup() - res, _ := rpc.TransactionReceipts(context.Background(), []common.Hash{}) - assert.Len(t, res, 1) - result := results.Result[*geth.Receipt](res[0]) - assert.NoError(t, result.Err()) - receipt := result.Ok() + receipt, _ := rpc.TransactionReceipt(context.Background(), common.Hash{}) for _, l := range receipt.Logs { if l.Address == common.BytesToAddress(gatewayAddrBytes) { l.Data = rand.Bytes(int(rand.I64Between(0, 1000))) break } } - rpc.TransactionReceiptsFunc = func(context.Context, []common.Hash) ([]evmRpc.Result, error) { - return []evmRpc.Result{evmRpc.Result(results.FromOk(receipt))}, nil - } + rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { return receipt, nil } err := mgr.ProcessTokenConfirmation(event) @@ -669,10 +761,9 @@ func TestMgr_ProcessTransferKeyConfirmation(t *testing.T) { Status: 1, } - rpc.TransactionReceiptsFunc = func(_ context.Context, txHashs []common.Hash) ([]evmRpc.Result, error) { - assert.Len(t, txHashs, 1) - if txHashs[0] == common.Hash(txID) { - return []evmRpc.Result{evmRpc.Result(results.FromOk(txReceipt))}, nil + rpc.TransactionReceiptFunc = func(ctx context.Context, txHash common.Hash) (*geth.Receipt, error) { + if txHash == common.Hash(txID) { + return txReceipt, nil } return nil, fmt.Errorf("not found") @@ -829,9 +920,7 @@ func TestMgr_GetTxReceiptsIfFinalized(t *testing.T) { Then("should not retrieve receipts", func(t *testing.T) { receipts, err := mgr.GetTxReceiptsIfFinalized(chain, txHashes, confHeight) assert.NoError(t, err) - slices.ForEach(receipts, func(result results.Result[*geth.Receipt]) { - assert.True(t, errors.Is[evm.FailedTransactionError](result.Err())) - }) + slices.ForEach(receipts, func(result results.Result[*geth.Receipt]) { assert.ErrorContains(t, result.Err(), "failed") }) }), When("transactions are finalized", func() { @@ -882,9 +971,7 @@ func TestMgr_GetTxReceiptsIfFinalized(t *testing.T) { notFinalized := receipts[len(txHashes)/2:] assert.True(t, slices.All(finalized, func(result results.Result[*geth.Receipt]) bool { return result.Err() == nil })) - assert.True(t, slices.All(notFinalized, func(result results.Result[*geth.Receipt]) bool { - return errors.Is[evm.NotFinalizedError](result.Err()) - })) + assert.True(t, slices.All(notFinalized, func(result results.Result[*geth.Receipt]) bool { return result.Err() == evm.NotFinalized })) }), ). Run(t, 5) diff --git a/vald/evm/rpc/client.go b/vald/evm/rpc/client.go index 3fe654c31..f789bb503 100644 --- a/vald/evm/rpc/client.go +++ b/vald/evm/rpc/client.go @@ -20,6 +20,8 @@ type Result results.Result[*types.Receipt] // Client provides calls to EVM JSON-RPC endpoints type Client interface { + // TransactionReceipt returns the transaction receipt for the given transaction hash + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) // TransactionReceipts returns transaction receipts for the given transaction hashes TransactionReceipts(ctx context.Context, txHashes []common.Hash) ([]Result, error) // HeaderByNumber returns the block header for the given block number diff --git a/vald/evm/rpc/ethereum.go b/vald/evm/rpc/ethereum.go index 676ebdecb..109b134fb 100644 --- a/vald/evm/rpc/ethereum.go +++ b/vald/evm/rpc/ethereum.go @@ -22,6 +22,7 @@ import ( type EthereumJSONRPCClient interface { BlockNumber(ctx context.Context) (uint64, error) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) Close() } @@ -73,49 +74,33 @@ func (c *EthereumClient) LatestFinalizedBlockNumber(ctx context.Context, confirm return sdk.NewIntFromUint64(blockNumber).SubRaw(int64(confirmations)).AddRaw(1).BigInt(), nil } -// TransactionReceipts returns transaction receipts for the given transaction hashes. Returns an error if the call fails. -// If a transaction is not found, the corresponding result will be in the error case with a type of NotFound. func (c *EthereumClient) TransactionReceipts(ctx context.Context, txHashes []common.Hash) ([]Result, error) { - switch len(txHashes) { - case 0: - return nil, nil - case 1: + batch := slices.Map(txHashes, func(txHash common.Hash) rpc.BatchElem { var receipt *types.Receipt - err := c.rpc.CallContext(ctx, receipt, "eth_getTransactionReceipt", txHashes[0]) - if err != nil { - return nil, err + return rpc.BatchElem{ + Method: "eth_getTransactionReceipt", + Args: []interface{}{txHash}, + Result: &receipt, } - if receipt == nil { - return []Result{Result(results.FromErr[*types.Receipt](ethereum.NotFound))}, nil - } - return []Result{Result(results.FromOk(receipt))}, nil - default: - batch := slices.Map(txHashes, func(txHash common.Hash) rpc.BatchElem { - var receipt *types.Receipt - return rpc.BatchElem{ - Method: "eth_getTransactionReceipt", - Args: []interface{}{txHash}, - Result: &receipt, - } - }) - - if err := c.rpc.BatchCallContext(ctx, batch); err != nil { - return nil, fmt.Errorf("unable to send batch request: %v", err) + }) + + if err := c.rpc.BatchCallContext(ctx, batch); err != nil { + return nil, fmt.Errorf("unable to send batch request: %v", err) + } + + return slices.Map(batch, func(elem rpc.BatchElem) Result { + if elem.Error != nil { + return Result(results.FromErr[*types.Receipt](elem.Error)) } - return slices.Map(batch, func(elem rpc.BatchElem) Result { - if elem.Error != nil { - return Result(results.FromErr[*types.Receipt](elem.Error)) - } + receipt := elem.Result.(**types.Receipt) + if *receipt == nil { + return Result(results.FromErr[*types.Receipt](ethereum.NotFound)) + } - receipt := elem.Result.(**types.Receipt) - if *receipt == nil { - return Result(results.FromErr[*types.Receipt](ethereum.NotFound)) - } + return Result(results.FromOk(*receipt)) + }), nil - return Result(results.FromOk(*receipt)) - }), nil - } } // copied from https://github.com/ethereum/go-ethereum/blob/69568c554880b3567bace64f8848ff1be27d084d/ethclient/ethclient.go#L565 diff --git a/vald/evm/rpc/mock/client.go b/vald/evm/rpc/mock/client.go index 26681d5c8..79e05931d 100644 --- a/vald/evm/rpc/mock/client.go +++ b/vald/evm/rpc/mock/client.go @@ -7,6 +7,7 @@ import ( "context" "github.com/axelarnetwork/axelar-core/vald/evm/rpc" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "math/big" "sync" ) @@ -30,6 +31,9 @@ var _ rpc.Client = &ClientMock{} // LatestFinalizedBlockNumberFunc: func(ctx context.Context, confirmations uint64) (*big.Int, error) { // panic("mock out the LatestFinalizedBlockNumber method") // }, +// TransactionReceiptFunc: func(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { +// panic("mock out the TransactionReceipt method") +// }, // TransactionReceiptsFunc: func(ctx context.Context, txHashes []common.Hash) ([]rpc.Result, error) { // panic("mock out the TransactionReceipts method") // }, @@ -49,6 +53,9 @@ type ClientMock struct { // LatestFinalizedBlockNumberFunc mocks the LatestFinalizedBlockNumber method. LatestFinalizedBlockNumberFunc func(ctx context.Context, confirmations uint64) (*big.Int, error) + // TransactionReceiptFunc mocks the TransactionReceipt method. + TransactionReceiptFunc func(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + // TransactionReceiptsFunc mocks the TransactionReceipts method. TransactionReceiptsFunc func(ctx context.Context, txHashes []common.Hash) ([]rpc.Result, error) @@ -71,6 +78,13 @@ type ClientMock struct { // Confirmations is the confirmations argument value. Confirmations uint64 } + // TransactionReceipt holds details about calls to the TransactionReceipt method. + TransactionReceipt []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // TxHash is the txHash argument value. + TxHash common.Hash + } // TransactionReceipts holds details about calls to the TransactionReceipts method. TransactionReceipts []struct { // Ctx is the ctx argument value. @@ -82,6 +96,7 @@ type ClientMock struct { lockClose sync.RWMutex lockHeaderByNumber sync.RWMutex lockLatestFinalizedBlockNumber sync.RWMutex + lockTransactionReceipt sync.RWMutex lockTransactionReceipts sync.RWMutex } @@ -184,6 +199,42 @@ func (mock *ClientMock) LatestFinalizedBlockNumberCalls() []struct { return calls } +// TransactionReceipt calls TransactionReceiptFunc. +func (mock *ClientMock) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + if mock.TransactionReceiptFunc == nil { + panic("ClientMock.TransactionReceiptFunc: method is nil but Client.TransactionReceipt was just called") + } + callInfo := struct { + Ctx context.Context + TxHash common.Hash + }{ + Ctx: ctx, + TxHash: txHash, + } + mock.lockTransactionReceipt.Lock() + mock.calls.TransactionReceipt = append(mock.calls.TransactionReceipt, callInfo) + mock.lockTransactionReceipt.Unlock() + return mock.TransactionReceiptFunc(ctx, txHash) +} + +// TransactionReceiptCalls gets all the calls that were made to TransactionReceipt. +// Check the length with: +// +// len(mockedClient.TransactionReceiptCalls()) +func (mock *ClientMock) TransactionReceiptCalls() []struct { + Ctx context.Context + TxHash common.Hash +} { + var calls []struct { + Ctx context.Context + TxHash common.Hash + } + mock.lockTransactionReceipt.RLock() + calls = mock.calls.TransactionReceipt + mock.lockTransactionReceipt.RUnlock() + return calls +} + // TransactionReceipts calls TransactionReceiptsFunc. func (mock *ClientMock) TransactionReceipts(ctx context.Context, txHashes []common.Hash) ([]rpc.Result, error) { if mock.TransactionReceiptsFunc == nil { diff --git a/vald/evm/rpc/mock/ethereum.go b/vald/evm/rpc/mock/ethereum.go index b61ff7904..855ddd29d 100644 --- a/vald/evm/rpc/mock/ethereum.go +++ b/vald/evm/rpc/mock/ethereum.go @@ -7,6 +7,7 @@ import ( "context" evmrpc "github.com/axelarnetwork/axelar-core/vald/evm/rpc" "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ethereumrpc "github.com/ethereum/go-ethereum/rpc" "math/big" @@ -35,6 +36,9 @@ var _ evmrpc.EthereumJSONRPCClient = &EthereumJSONRPCClientMock{} // FilterLogsFunc: func(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { // panic("mock out the FilterLogs method") // }, +// TransactionReceiptFunc: func(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { +// panic("mock out the TransactionReceipt method") +// }, // } // // // use mockedEthereumJSONRPCClient in code that requires evmrpc.EthereumJSONRPCClient @@ -54,6 +58,9 @@ type EthereumJSONRPCClientMock struct { // FilterLogsFunc mocks the FilterLogs method. FilterLogsFunc func(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) + // TransactionReceiptFunc mocks the TransactionReceipt method. + TransactionReceiptFunc func(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + // calls tracks calls to the methods. calls struct { // BlockNumber holds details about calls to the BlockNumber method. @@ -80,11 +87,19 @@ type EthereumJSONRPCClientMock struct { // Q is the q argument value. Q ethereum.FilterQuery } + // TransactionReceipt holds details about calls to the TransactionReceipt method. + TransactionReceipt []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // TxHash is the txHash argument value. + TxHash common.Hash + } } - lockBlockNumber sync.RWMutex - lockCallContract sync.RWMutex - lockClose sync.RWMutex - lockFilterLogs sync.RWMutex + lockBlockNumber sync.RWMutex + lockCallContract sync.RWMutex + lockClose sync.RWMutex + lockFilterLogs sync.RWMutex + lockTransactionReceipt sync.RWMutex } // BlockNumber calls BlockNumberFunc. @@ -222,6 +237,42 @@ func (mock *EthereumJSONRPCClientMock) FilterLogsCalls() []struct { return calls } +// TransactionReceipt calls TransactionReceiptFunc. +func (mock *EthereumJSONRPCClientMock) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + if mock.TransactionReceiptFunc == nil { + panic("EthereumJSONRPCClientMock.TransactionReceiptFunc: method is nil but EthereumJSONRPCClient.TransactionReceipt was just called") + } + callInfo := struct { + Ctx context.Context + TxHash common.Hash + }{ + Ctx: ctx, + TxHash: txHash, + } + mock.lockTransactionReceipt.Lock() + mock.calls.TransactionReceipt = append(mock.calls.TransactionReceipt, callInfo) + mock.lockTransactionReceipt.Unlock() + return mock.TransactionReceiptFunc(ctx, txHash) +} + +// TransactionReceiptCalls gets all the calls that were made to TransactionReceipt. +// Check the length with: +// +// len(mockedEthereumJSONRPCClient.TransactionReceiptCalls()) +func (mock *EthereumJSONRPCClientMock) TransactionReceiptCalls() []struct { + Ctx context.Context + TxHash common.Hash +} { + var calls []struct { + Ctx context.Context + TxHash common.Hash + } + mock.lockTransactionReceipt.RLock() + calls = mock.calls.TransactionReceipt + mock.lockTransactionReceipt.RUnlock() + return calls +} + // Ensure, that JSONRPCClientMock does implement evmrpc.JSONRPCClient. // If this is not the case, regenerate this file with moq. var _ evmrpc.JSONRPCClient = &JSONRPCClientMock{} diff --git a/x/evm/types/events.go b/x/evm/types/events.go index e718a2c73..7c2f17134 100644 --- a/x/evm/types/events.go +++ b/x/evm/types/events.go @@ -1,8 +1,6 @@ package types import ( - "github.com/ethereum/go-ethereum/common" - nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported" vote "github.com/axelarnetwork/axelar-core/x/vote/exported" ) @@ -71,13 +69,3 @@ func NewCommandBatchSigned(chain nexus.ChainName, batchID []byte) *CommandBatchS func NewCommandBatchAborted(chain nexus.ChainName, batchID []byte) *CommandBatchAborted { return &CommandBatchAborted{Chain: chain, CommandBatchID: batchID} } - -// GetTxID is a getter for the transaction ID for convenience when used in mappings -func (m PollMapping) GetTxID() common.Hash { - return common.Hash(m.TxID) -} - -// GetPollID is a getter for the poll ID for convenience when used in mappings -func (m PollMapping) GetPollID() vote.PollID { - return m.PollID -}