From 48e9f983b89e8bde99750ecf5d5d0586b11715ce Mon Sep 17 00:00:00 2001 From: Sammy Date: Fri, 15 Mar 2024 00:08:21 +0800 Subject: [PATCH] feat(vald): unify GetTransactionReceiptsIfFinalized and GetTransactionReceiptIfFinalized, and unify the behaviours of single and batch voting (#2120) * feat(vald): unify GetTransactionReceiptsIfFinalized and GetTransactionReceiptIfFinalized, and unify the behaviours of single and batch voting * remove commented-out code --- vald/evm/deposit_confirmation.go | 19 ++-- vald/evm/deposit_confirmation_test.go | 23 +++-- vald/evm/evm.go | 111 +++++++++++---------- vald/evm/evm_test.go | 82 ++++++++------- vald/evm/gateway_tx_confirmation.go | 19 ++-- vald/evm/gateway_txs_confirmation.go | 36 ++----- vald/evm/key_transfer_confirmation.go | 38 ++++--- vald/evm/key_transfer_confirmation_test.go | 16 ++- vald/evm/rpc/client.go | 10 +- vald/evm/rpc/ethereum.go | 10 +- vald/evm/rpc/mock/client.go | 57 +---------- vald/evm/token_confirmation.go | 21 ++-- vald/evm/token_confirmation_test.go | 54 ++++++---- 13 files changed, 246 insertions(+), 250 deletions(-) diff --git a/vald/evm/deposit_confirmation.go b/vald/evm/deposit_confirmation.go index 417f50081..da66ef7ba 100644 --- a/vald/evm/deposit_confirmation.go +++ b/vald/evm/deposit_confirmation.go @@ -19,21 +19,24 @@ func (mgr Mgr) ProcessDepositConfirmation(event *types.ConfirmDepositStarted) er return nil } + var vote *voteTypes.VoteRequest + 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))) + if txReceipt.Err() != nil { + vote = voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain)) - return err - } + mgr.logger().Infof("broadcasting empty vote for poll %s: %s", event.PollID.String(), txReceipt.Err().Error()) + } else { + events := mgr.processDepositConfirmationLogs(event, txReceipt.Ok().Logs) + vote = voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...)) - events := mgr.processDepositConfirmationLogs(event, txReceipt.Logs) + mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) + } - 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...))) + _, err = mgr.broadcaster.Broadcast(context.TODO(), vote) return err } diff --git a/vald/evm/deposit_confirmation_test.go b/vald/evm/deposit_confirmation_test.go index 0fc50d1cd..c0179a193 100644 --- a/vald/evm/deposit_confirmation_test.go +++ b/vald/evm/deposit_confirmation_test.go @@ -1,6 +1,7 @@ package evm_test import ( + "bytes" "context" "math/big" "strings" @@ -22,6 +23,8 @@ import ( "github.com/axelarnetwork/axelar-core/x/evm/types/testutils" nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported" votetypes "github.com/axelarnetwork/axelar-core/x/vote/types" + "github.com/axelarnetwork/utils/monads/results" + "github.com/axelarnetwork/utils/slices" . "github.com/axelarnetwork/utils/test" ) @@ -144,11 +147,14 @@ func TestMgr_ProccessDepositConfirmation(t *testing.T) { HeaderByNumberFunc: func(context.Context, *big.Int) (*evmRpc.Header, error) { return &evmRpc.Header{Transactions: []common.Hash{receipt.TxHash}}, nil }, - TransactionReceiptFunc: func(_ context.Context, txID common.Hash) (*geth.Receipt, error) { - if txID != receipt.TxHash { - return nil, ethereum.NotFound - } - return receipt, nil + TransactionReceiptsFunc: func(ctx context.Context, txHashes []common.Hash) ([]evmRpc.TxReceiptResult, error) { + return slices.Map(txHashes, func(txHash common.Hash) evmRpc.TxReceiptResult { + if bytes.Equal(txHash.Bytes(), receipt.TxHash.Bytes()) { + return evmRpc.TxReceiptResult(results.FromOk(*receipt)) + } + + return evmRpc.TxReceiptResult(results.FromErr[geth.Receipt](ethereum.NotFound)) + }), nil }, LatestFinalizedBlockNumberFunc: func(ctx context.Context, confirmations uint64) (*big.Int, error) { return receipt.BlockNumber, nil @@ -157,8 +163,10 @@ func TestMgr_ProccessDepositConfirmation(t *testing.T) { }). Branch( Given("no deposit has been made", func() { - rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { - return nil, ethereum.NotFound + rpc.TransactionReceiptsFunc = func(ctx context.Context, txHashes []common.Hash) ([]evmRpc.TxReceiptResult, error) { + return slices.Map(txHashes, func(hash common.Hash) evmRpc.TxReceiptResult { + return evmRpc.TxReceiptResult(results.FromErr[geth.Receipt](ethereum.NotFound)) + }), nil } }). When("confirming a random deposit on the correct chain", func() { @@ -180,7 +188,6 @@ func TestMgr_ProccessDepositConfirmation(t *testing.T) { event.Participants = append(event.Participants, valAddr) err = mgr.ProcessDepositConfirmation(&event) - }). Then("return error", func(t *testing.T) { assert.Error(t, err) diff --git a/vald/evm/evm.go b/vald/evm/evm.go index ccf19e885..f008eb3e2 100644 --- a/vald/evm/evm.go +++ b/vald/evm/evm.go @@ -8,7 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" geth "github.com/ethereum/go-ethereum/core/types" @@ -18,7 +17,7 @@ import ( "github.com/axelarnetwork/axelar-core/x/evm/types" nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported" "github.com/axelarnetwork/utils/log" - rs "github.com/axelarnetwork/utils/monads/results" + "github.com/axelarnetwork/utils/monads/results" "github.com/axelarnetwork/utils/slices" ) @@ -59,7 +58,7 @@ func (mgr Mgr) ProcessNewChain(event *types.ChainAdded) (err error) { return nil } -func (mgr Mgr) isTxReceiptFinalized(chain nexus.ChainName, txReceipt *geth.Receipt, confHeight uint64) (bool, error) { +func (mgr Mgr) isFinalized(chain nexus.ChainName, txReceipt geth.Receipt, confHeight uint64) (bool, error) { client, ok := mgr.rpcs[strings.ToLower(chain.String())] if !ok { return false, fmt.Errorf("rpc client not found for chain %s", chain.String()) @@ -83,74 +82,78 @@ func (mgr Mgr) isTxReceiptFinalized(chain nexus.ChainName, txReceipt *geth.Recei return true, nil } -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, nil - } - - isFinalized, err := mgr.isTxReceiptFinalized(chain, txReceipt, confHeight) +// GetTxReceiptIfFinalized retrieves receipt for provided transaction ID. +// +// # Result is +// +// - Ok(receipt) if the transaction is finalized and successful +// +// - Err(ethereum.NotFound) if the transaction is not found +// +// - Err(ErrTxFailed) if the transaction is finalized but failed +// +// - Err(ErrNotFinalized) if the transaction is not finalized +// +// - Err(err) otherwise +func (mgr Mgr) GetTxReceiptIfFinalized(chain nexus.ChainName, txID common.Hash, confHeight uint64) (results.Result[geth.Receipt], error) { + txReceipts, err := mgr.GetTxReceiptsIfFinalized(chain, []common.Hash{txID}, 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 results.Result[geth.Receipt]{}, err } - return txReceipt, nil + return txReceipts[0], err } -// 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) { +// GetTxReceiptsIfFinalized retrieves receipts for provided transaction IDs. +// +// # Individual result is +// +// - Ok(receipt) if the transaction is finalized and successful +// +// - Err(ethereum.NotFound) if the transaction is not found +// +// - Err(ErrTxFailed) if the transaction is finalized but failed +// +// - Err(ErrNotFinalized) if the transaction is not finalized +// +// - Err(err) otherwise +func (mgr Mgr) GetTxReceiptsIfFinalized(chain nexus.ChainName, txIDs []common.Hash, confHeight uint64) ([]results.Result[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()) } - results, err := client.TransactionReceipts(context.Background(), txIDs) + receipts, err := client.TransactionReceipts(context.Background(), txIDs) if err != nil { - return nil, sdkerrors.Wrapf(errors.With(err, "chain", chain.String(), "tx_ids", txIDs), - "cannot get transaction receipts") + return slices.Map(txIDs, func(_ common.Hash) results.Result[geth.Receipt] { + return results.FromErr[geth.Receipt]( + sdkerrors.Wrapf( + errors.With(err, "chain", chain.String(), "tx_ids", txIDs), + "cannot get transaction receipts"), + ) + }), nil } - isFinalized := func(receipt *geth.Receipt) rs.Result[*geth.Receipt] { - if receipt.Status != geth.ReceiptStatusSuccessful { - return rs.FromErr[*geth.Receipt](ErrTxFailed) - } + return slices.Map(receipts, func(receipt rpc.TxReceiptResult) results.Result[geth.Receipt] { + return results.Pipe(results.Result[geth.Receipt](receipt), func(receipt geth.Receipt) results.Result[geth.Receipt] { - isFinalized, err := mgr.isTxReceiptFinalized(chain, receipt, confHeight) - if err != nil { - return rs.FromErr[*geth.Receipt](sdkerrors.Wrapf(errors.With(err, "chain", chain.String()), - "cannot determine if the transaction %s is finalized", receipt.TxHash.Hex()), - ) - } + isFinalized, err := mgr.isFinalized(chain, receipt, confHeight) + if err != nil { + return results.FromErr[geth.Receipt](sdkerrors.Wrapf(errors.With(err, "chain", chain.String()), + "cannot determine if the transaction %s is finalized", receipt.TxHash.Hex()), + ) + } - if !isFinalized { - return rs.FromErr[*geth.Receipt](ErrNotFinalized) - } + if !isFinalized { + return results.FromErr[geth.Receipt](ErrNotFinalized) + } - return rs.FromOk(receipt) - } + if receipt.Status != geth.ReceiptStatusSuccessful { + return results.FromErr[geth.Receipt](ErrTxFailed) + } - return slices.Map(results, func(r rpc.Result) rs.Result[*geth.Receipt] { - return rs.Pipe(rs.Result[*geth.Receipt](r), isFinalized) + return results.FromOk[geth.Receipt](receipt) + }) }), nil } diff --git a/vald/evm/evm_test.go b/vald/evm/evm_test.go index 960740216..febb2d671 100644 --- a/vald/evm/evm_test.go +++ b/vald/evm/evm_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" geth "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/assert" @@ -52,19 +53,24 @@ func TestMgr_GetTxReceiptIfFinalized(t *testing.T) { 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") + cache.GetFunc = func(_ nexus.ChainName) *big.Int { + return receipt.BlockNumber + } + rpcClient.TransactionReceiptsFunc = func(ctx context.Context, txHashes []common.Hash) ([]evmRpc.TxReceiptResult, error) { + return slices.Map(txHashes, func(hash common.Hash) evmRpc.TxReceiptResult { + if bytes.Equal(hash.Bytes(), tx.Hash().Bytes()) { + return evmRpc.TxReceiptResult(results.FromOk(*receipt)) + } + + return evmRpc.TxReceiptResult(results.FromErr[geth.Receipt](ethereum.NotFound)) + }), nil } }). - Then("tx is considered not finalized", func(t *testing.T) { + Then("tx is considered failed", func(t *testing.T) { txReceipt, err := mgr.GetTxReceiptIfFinalized(chain, tx.Hash(), confHeight) assert.NoError(t, err) - assert.Nil(t, txReceipt) + assert.Equal(t, txReceipt.Err(), evm.ErrTxFailed) }). Run(t) @@ -82,12 +88,14 @@ func TestMgr_GetTxReceiptIfFinalized(t *testing.T) { 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 - } + rpcClient.TransactionReceiptsFunc = func(ctx context.Context, txHashes []common.Hash) ([]evmRpc.TxReceiptResult, error) { + return slices.Map(txHashes, func(hash common.Hash) evmRpc.TxReceiptResult { + if bytes.Equal(hash.Bytes(), tx.Hash().Bytes()) { + return evmRpc.TxReceiptResult(results.FromOk(*receipt)) + } - return nil, fmt.Errorf("not found") + return evmRpc.TxReceiptResult(results.FromErr[geth.Receipt](ethereum.NotFound)) + }), nil } rpcClient.HeaderByNumberFunc = func(ctx context.Context, number *big.Int) (*evmRpc.Header, error) { if number.Cmp(receipt.BlockNumber) == 0 { @@ -104,7 +112,8 @@ func TestMgr_GetTxReceiptIfFinalized(t *testing.T) { txReceipt, err := mgr.GetTxReceiptIfFinalized(chain, tx.Hash(), confHeight) assert.NoError(t, err) - assert.NotNil(t, txReceipt) + assert.NoError(t, txReceipt.Err()) + assert.NotNil(t, txReceipt.Ok()) }). Run(t, 5) @@ -121,12 +130,14 @@ func TestMgr_GetTxReceiptIfFinalized(t *testing.T) { 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 - } + rpcClient.TransactionReceiptsFunc = func(ctx context.Context, txHashes []common.Hash) ([]evmRpc.TxReceiptResult, error) { + return slices.Map(txHashes, func(hash common.Hash) evmRpc.TxReceiptResult { + if bytes.Equal(hash.Bytes(), tx.Hash().Bytes()) { + return evmRpc.TxReceiptResult(results.FromOk(*receipt)) + } - return nil, fmt.Errorf("not found") + return evmRpc.TxReceiptResult(results.FromErr[geth.Receipt](ethereum.NotFound)) + }), nil } rpcClient.HeaderByNumberFunc = func(ctx context.Context, number *big.Int) (*evmRpc.Header, error) { if number.Cmp(receipt.BlockNumber) == 0 { @@ -140,7 +151,8 @@ func TestMgr_GetTxReceiptIfFinalized(t *testing.T) { txReceipt, err := mgr.GetTxReceiptIfFinalized(chain, tx.Hash(), confHeight) assert.NoError(t, err) - assert.NotNil(t, txReceipt) + assert.NoError(t, txReceipt.Err()) + assert.NotNil(t, txReceipt.Ok()) }). Run(t, 5) } @@ -177,9 +189,9 @@ func TestMgr_GetTxReceiptsIfFinalized(t *testing.T) { When("transactions failed", func() { latestFinalizedBlockNumber = rand.I64Between(1000, 10000) - evmClient.TransactionReceiptsFunc = func(_ context.Context, _ []common.Hash) ([]evmRpc.Result, error) { - return slices.Map(txHashes, func(hash common.Hash) evmRpc.Result { - return evmRpc.Result(results.FromOk(&geth.Receipt{ + evmClient.TransactionReceiptsFunc = func(_ context.Context, _ []common.Hash) ([]evmRpc.TxReceiptResult, error) { + return slices.Map(txHashes, func(hash common.Hash) evmRpc.TxReceiptResult { + return evmRpc.TxReceiptResult(results.FromOk(geth.Receipt{ BlockNumber: big.NewInt(latestFinalizedBlockNumber - rand.I64Between(1, 100)), TxHash: hash, Status: geth.ReceiptStatusFailed, @@ -189,16 +201,17 @@ 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.Equal(t, result.Err(), evm.ErrTxFailed) }) + slices.ForEach(receipts, func(result results.Result[geth.Receipt]) { assert.Equal(t, result.Err(), evm.ErrTxFailed) }) }), When("transactions are finalized", func() { latestFinalizedBlockNumber = rand.I64Between(1000, 10000) - evmClient.TransactionReceiptsFunc = func(_ context.Context, _ []common.Hash) ([]evmRpc.Result, error) { - return slices.Map(txHashes, func(hash common.Hash) evmRpc.Result { - return evmRpc.Result(results.FromOk(&geth.Receipt{ + evmClient.TransactionReceiptsFunc = func(_ context.Context, _ []common.Hash) ([]evmRpc.TxReceiptResult, error) { + return slices.Map(txHashes, func(hash common.Hash) evmRpc.TxReceiptResult { + return evmRpc.TxReceiptResult(results.FromOk(geth.Receipt{ BlockNumber: big.NewInt(latestFinalizedBlockNumber - rand.I64Between(1, 100)), TxHash: hash, Status: geth.ReceiptStatusSuccessful, @@ -208,14 +221,15 @@ func TestMgr_GetTxReceiptsIfFinalized(t *testing.T) { }). Then("should return receipt results", func(t *testing.T) { receipts, err := mgr.GetTxReceiptsIfFinalized(chain, txHashes, confHeight) + assert.NoError(t, err) - assert.True(t, slices.All(receipts, func(result results.Result[*geth.Receipt]) bool { return result.Err() == nil })) + assert.True(t, slices.All(receipts, func(result results.Result[geth.Receipt]) bool { return result.Err() == nil })) }), When("some transactions are not finalized", func() { - evmClient.TransactionReceiptsFunc = func(_ context.Context, _ []common.Hash) ([]evmRpc.Result, error) { + evmClient.TransactionReceiptsFunc = func(_ context.Context, _ []common.Hash) ([]evmRpc.TxReceiptResult, error) { i := 0 - return slices.Map(txHashes, func(hash common.Hash) evmRpc.Result { + return slices.Map(txHashes, func(hash common.Hash) evmRpc.TxReceiptResult { var blockNumber *big.Int // half of the transactions are finalized if i < len(txHashes)/2 { @@ -225,7 +239,7 @@ func TestMgr_GetTxReceiptsIfFinalized(t *testing.T) { } i++ - return evmRpc.Result(results.FromOk(&geth.Receipt{ + return evmRpc.TxReceiptResult(results.FromOk(geth.Receipt{ BlockNumber: blockNumber, TxHash: hash, Status: geth.ReceiptStatusSuccessful, @@ -235,13 +249,13 @@ func TestMgr_GetTxReceiptsIfFinalized(t *testing.T) { }). Then("should return error results for not found", func(t *testing.T) { receipts, err := mgr.GetTxReceiptsIfFinalized(chain, txHashes, confHeight) - assert.NoError(t, err) + assert.NoError(t, err) finalized := receipts[:len(txHashes)/2] 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 result.Err() == evm.ErrNotFinalized })) + 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 result.Err() == evm.ErrNotFinalized })) }), ). Run(t, 5) diff --git a/vald/evm/gateway_tx_confirmation.go b/vald/evm/gateway_tx_confirmation.go index cfb296d62..9ae5e684c 100644 --- a/vald/evm/gateway_tx_confirmation.go +++ b/vald/evm/gateway_tx_confirmation.go @@ -20,21 +20,24 @@ func (mgr Mgr) ProcessGatewayTxConfirmation(event *types.ConfirmGatewayTxStarted return nil } + var vote *voteTypes.VoteRequest + 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))) + if txReceipt.Err() != nil { + vote = voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain)) - return err - } + mgr.logger().Infof("broadcasting empty vote for poll %s: %s", event.PollID.String(), txReceipt.Err().Error()) + } else { + events := mgr.processGatewayTxLogs(event.Chain, event.GatewayAddress, txReceipt.Ok().Logs) + vote = voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...)) - events := mgr.processGatewayTxLogs(event.Chain, event.GatewayAddress, txReceipt.Logs) + mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) + } - 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...))) + _, err = mgr.broadcaster.Broadcast(context.TODO(), vote) return err } diff --git a/vald/evm/gateway_txs_confirmation.go b/vald/evm/gateway_txs_confirmation.go index 2f79e4741..4d9e65d5b 100644 --- a/vald/evm/gateway_txs_confirmation.go +++ b/vald/evm/gateway_txs_confirmation.go @@ -2,15 +2,13 @@ package evm import ( "context" - "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/axelarnetwork/axelar-core/x/evm/types" vote "github.com/axelarnetwork/axelar-core/x/vote/exported" - votetypes "github.com/axelarnetwork/axelar-core/x/vote/types" + voteTypes "github.com/axelarnetwork/axelar-core/x/vote/types" "github.com/axelarnetwork/utils/slices" ) @@ -29,34 +27,22 @@ func (mgr Mgr) ProcessGatewayTxsConfirmation(event *types.ConfirmGatewayTxsStart } var votes []sdk.Msg - for i, result := range txReceipts { + for i, txReceipt := range txReceipts { pollID := event.PollMappings[i].PollID txID := event.PollMappings[i].TxID 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 result.Err() { - case nil: - 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(event.Chain, events...))) - case ErrNotFinalized: - logger.Debug(fmt.Sprintf("transaction %s not finalized", txID.Hex())) - logger.Infof("broadcasting empty vote due to error: %s", result.Err().Error()) - votes = append(votes, votetypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain))) - case ErrTxFailed: - logger.Debug(fmt.Sprintf("transaction %s failed", txID.Hex())) - logger.Infof("broadcasting empty vote due to error: %s", result.Err().Error()) - 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(event.Chain))) - default: - logger.Errorf("failed to get tx receipt: %s", result.Err().Error()) - } + if txReceipt.Err() != nil { + votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain))) + + logger.Infof("broadcasting empty vote for poll %s: %s", pollID.String(), txReceipt.Err().Error()) + } else { + events := mgr.processGatewayTxLogs(event.Chain, event.GatewayAddress, txReceipt.Ok().Logs) + votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain, events...))) + logger.Infof("broadcasting vote %v for poll %s", events, pollID.String()) + } } _, err = mgr.broadcaster.Broadcast(context.TODO(), votes...) diff --git a/vald/evm/key_transfer_confirmation.go b/vald/evm/key_transfer_confirmation.go index ec684f727..a0bdf2bb2 100644 --- a/vald/evm/key_transfer_confirmation.go +++ b/vald/evm/key_transfer_confirmation.go @@ -5,6 +5,7 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/common" + geth "github.com/ethereum/go-ethereum/core/types" "github.com/axelarnetwork/axelar-core/x/evm/types" voteTypes "github.com/axelarnetwork/axelar-core/x/vote/types" @@ -17,20 +18,31 @@ func (mgr Mgr) ProcessTransferKeyConfirmation(event *types.ConfirmKeyTransferSta return nil } + var vote *voteTypes.VoteRequest + 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))) + if txReceipt.Err() != nil { + vote = voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain)) - return err + mgr.logger().Infof("broadcasting empty vote for poll %s: %s", event.PollID.String(), txReceipt.Err().Error()) + } else { + events := mgr.processTransferKeyLogs(event, txReceipt.Ok().Logs) + vote = voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...)) + + mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) } - var events []types.Event - for i := len(txReceipt.Logs) - 1; i >= 0; i-- { - txlog := txReceipt.Logs[i] + _, err = mgr.broadcaster.Broadcast(context.TODO(), vote) + + return err +} + +func (mgr Mgr) processTransferKeyLogs(event *types.ConfirmKeyTransferStarted, logs []*geth.Log) []types.Event { + for i := len(logs) - 1; i >= 0; i-- { + txlog := logs[i] if txlog.Topics[0] != MultisigTransferOperatorshipSig { continue @@ -52,18 +64,14 @@ func (mgr Mgr) ProcessTransferKeyConfirmation(event *types.ConfirmKeyTransferSta continue } - events = append(events, types.Event{ - Chain: event.Chain, + return []types.Event{{Chain: event.Chain, TxID: event.TxID, Index: uint64(i), Event: &types.Event_MultisigOperatorshipTransferred{ MultisigOperatorshipTransferred: &transferOperatorshipEvent, - }}) - break + }, + }} } - 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 + return []types.Event{} } diff --git a/vald/evm/key_transfer_confirmation_test.go b/vald/evm/key_transfer_confirmation_test.go index 0ff31be06..3c0e0471a 100644 --- a/vald/evm/key_transfer_confirmation_test.go +++ b/vald/evm/key_transfer_confirmation_test.go @@ -1,12 +1,14 @@ package evm_test import ( + "bytes" "context" "fmt" "math/big" "testing" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -25,6 +27,8 @@ import ( 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/monads/results" + "github.com/axelarnetwork/utils/slices" . "github.com/axelarnetwork/utils/test" ) @@ -67,12 +71,14 @@ func TestMgr_ProcessTransferKeyConfirmation(t *testing.T) { Status: 1, } - rpc.TransactionReceiptFunc = func(ctx context.Context, txHash common.Hash) (*geth.Receipt, error) { - if txHash == common.Hash(txID) { - return txReceipt, nil - } + rpc.TransactionReceiptsFunc = func(ctx context.Context, txHashes []common.Hash) ([]evmrpc.TxReceiptResult, error) { + return slices.Map(txHashes, func(hash common.Hash) evmrpc.TxReceiptResult { + if bytes.Equal(hash.Bytes(), txID.Bytes()) { + return evmrpc.TxReceiptResult(results.FromOk(*txReceipt)) + } - return nil, fmt.Errorf("not found") + return evmrpc.TxReceiptResult(results.FromErr[geth.Receipt](ethereum.NotFound)) + }), nil } rpc.HeaderByNumberFunc = func(ctx context.Context, number *big.Int) (*evmrpc.Header, error) { if number.Cmp(txReceipt.BlockNumber) == 0 { diff --git a/vald/evm/rpc/client.go b/vald/evm/rpc/client.go index f789bb503..acdd01450 100644 --- a/vald/evm/rpc/client.go +++ b/vald/evm/rpc/client.go @@ -14,16 +14,14 @@ import ( //go:generate moq -out ./mock/client.go -pkg mock . Client -// Result is a custom type that allows moq to correctly generate the mock for -// results.Result with *types.Receipt. -type Result results.Result[*types.Receipt] +// TxReceiptResult is a custom type that allows moq to correctly generate the mock for +// results.TxReceiptResult with *types.Receipt. +type TxReceiptResult 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) + TransactionReceipts(ctx context.Context, txHashes []common.Hash) ([]TxReceiptResult, error) // HeaderByNumber returns the block header for the given block number HeaderByNumber(ctx context.Context, number *big.Int) (*Header, error) // LatestFinalizedBlockNumber returns the latest finalized block number diff --git a/vald/evm/rpc/ethereum.go b/vald/evm/rpc/ethereum.go index 109b134fb..474962d76 100644 --- a/vald/evm/rpc/ethereum.go +++ b/vald/evm/rpc/ethereum.go @@ -74,7 +74,7 @@ func (c *EthereumClient) LatestFinalizedBlockNumber(ctx context.Context, confirm return sdk.NewIntFromUint64(blockNumber).SubRaw(int64(confirmations)).AddRaw(1).BigInt(), nil } -func (c *EthereumClient) TransactionReceipts(ctx context.Context, txHashes []common.Hash) ([]Result, error) { +func (c *EthereumClient) TransactionReceipts(ctx context.Context, txHashes []common.Hash) ([]TxReceiptResult, error) { batch := slices.Map(txHashes, func(txHash common.Hash) rpc.BatchElem { var receipt *types.Receipt return rpc.BatchElem{ @@ -88,17 +88,17 @@ func (c *EthereumClient) TransactionReceipts(ctx context.Context, txHashes []com return nil, fmt.Errorf("unable to send batch request: %v", err) } - return slices.Map(batch, func(elem rpc.BatchElem) Result { + return slices.Map(batch, func(elem rpc.BatchElem) TxReceiptResult { if elem.Error != nil { - return Result(results.FromErr[*types.Receipt](elem.Error)) + return TxReceiptResult(results.FromErr[types.Receipt](elem.Error)) } receipt := elem.Result.(**types.Receipt) if *receipt == nil { - return Result(results.FromErr[*types.Receipt](ethereum.NotFound)) + return TxReceiptResult(results.FromErr[types.Receipt](ethereum.NotFound)) } - return Result(results.FromOk(*receipt)) + return TxReceiptResult(results.FromOk(**receipt)) }), nil } diff --git a/vald/evm/rpc/mock/client.go b/vald/evm/rpc/mock/client.go index 79e05931d..8a0c499a2 100644 --- a/vald/evm/rpc/mock/client.go +++ b/vald/evm/rpc/mock/client.go @@ -7,7 +7,6 @@ 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" ) @@ -31,10 +30,7 @@ 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) { +// TransactionReceiptsFunc: func(ctx context.Context, txHashes []common.Hash) ([]rpc.TxReceiptResult, error) { // panic("mock out the TransactionReceipts method") // }, // } @@ -53,11 +49,8 @@ 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) + TransactionReceiptsFunc func(ctx context.Context, txHashes []common.Hash) ([]rpc.TxReceiptResult, error) // calls tracks calls to the methods. calls struct { @@ -78,13 +71,6 @@ 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. @@ -96,7 +82,6 @@ type ClientMock struct { lockClose sync.RWMutex lockHeaderByNumber sync.RWMutex lockLatestFinalizedBlockNumber sync.RWMutex - lockTransactionReceipt sync.RWMutex lockTransactionReceipts sync.RWMutex } @@ -199,44 +184,8 @@ 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) { +func (mock *ClientMock) TransactionReceipts(ctx context.Context, txHashes []common.Hash) ([]rpc.TxReceiptResult, error) { if mock.TransactionReceiptsFunc == nil { panic("ClientMock.TransactionReceiptsFunc: method is nil but Client.TransactionReceipts was just called") } diff --git a/vald/evm/token_confirmation.go b/vald/evm/token_confirmation.go index 7eb307a49..6e8687b79 100644 --- a/vald/evm/token_confirmation.go +++ b/vald/evm/token_confirmation.go @@ -3,6 +3,7 @@ package evm import ( "bytes" "context" + "fmt" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/common" @@ -19,21 +20,25 @@ func (mgr Mgr) ProcessTokenConfirmation(event *types.ConfirmTokenStarted) error return nil } + var vote *voteTypes.VoteRequest + 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))) + if txReceipt.Err() != nil { + vote = voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain)) - return err - } + fmt.Printf("txReceipt.Err().Error() %#v\n", txReceipt.Err().Error()) + mgr.logger().Infof("broadcasting empty vote for poll %s: %s", event.PollID.String(), txReceipt.Err().Error()) + } else { + events := mgr.processTokenConfirmationLogs(event, txReceipt.Ok().Logs) + vote = voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...)) - events := mgr.processTokenConfirmationLogs(event, txReceipt.Logs) + mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) + } - 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...))) + _, err = mgr.broadcaster.Broadcast(context.TODO(), vote) return err } diff --git a/vald/evm/token_confirmation_test.go b/vald/evm/token_confirmation_test.go index 97e934d32..e1e47f76e 100644 --- a/vald/evm/token_confirmation_test.go +++ b/vald/evm/token_confirmation_test.go @@ -1,6 +1,7 @@ package evm_test import ( + "bytes" "context" "math/big" "testing" @@ -23,6 +24,8 @@ import ( 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/monads/results" + "github.com/axelarnetwork/utils/slices" ) func TestMgr_ProccessTokenConfirmation(t *testing.T) { @@ -33,7 +36,9 @@ func TestMgr_ProccessTokenConfirmation(t *testing.T) { broadcaster *broadcastmock.BroadcasterMock gatewayAddrBytes []byte valAddr sdk.ValAddress + receipt *geth.Receipt ) + setup := func() { pollID := vote.PollID(rand.I64Between(10, 100)) @@ -44,21 +49,9 @@ func TestMgr_ProccessTokenConfirmation(t *testing.T) { symbol := rand.Denom(5, 20) valAddr = rand.ValAddr() - event = &types.ConfirmTokenStarted{ - TxID: types.Hash(common.BytesToHash(rand.Bytes(common.HashLength))), - Chain: "Ethereum", - GatewayAddress: types.Address(common.BytesToAddress(gatewayAddrBytes)), - TokenAddress: types.Address(common.BytesToAddress(tokenAddrBytes)), - TokenDetails: types.TokenDetails{Symbol: symbol}, - ConfirmationHeight: uint64(confHeight), - PollParticipants: vote.PollParticipants{ - PollID: pollID, - Participants: []sdk.ValAddress{valAddr}, - }, - } tx := geth.NewTransaction(0, common.BytesToAddress(rand.Bytes(common.HashLength)), big.NewInt(0), 21000, big.NewInt(1), []byte{}) - receipt := &geth.Receipt{ + receipt = &geth.Receipt{ TxHash: tx.Hash(), BlockNumber: big.NewInt(rand.I64Between(0, blockNumber-confHeight)), Logs: createTokenLogs( @@ -70,12 +63,31 @@ func TestMgr_ProccessTokenConfirmation(t *testing.T) { ), Status: 1, } + event = &types.ConfirmTokenStarted{ + TxID: types.Hash(receipt.TxHash), + Chain: "Ethereum", + GatewayAddress: types.Address(common.BytesToAddress(gatewayAddrBytes)), + TokenAddress: types.Address(common.BytesToAddress(tokenAddrBytes)), + TokenDetails: types.TokenDetails{Symbol: symbol}, + ConfirmationHeight: uint64(confHeight), + PollParticipants: vote.PollParticipants{ + PollID: pollID, + Participants: []sdk.ValAddress{valAddr}, + }, + } + rpc = &mock.ClientMock{ HeaderByNumberFunc: func(ctx context.Context, number *big.Int) (*evmrpc.Header, error) { return &evmrpc.Header{Transactions: []common.Hash{receipt.TxHash}}, nil }, - TransactionReceiptFunc: func(context.Context, common.Hash) (*geth.Receipt, error) { - return receipt, nil + TransactionReceiptsFunc: func(ctx context.Context, txHashes []common.Hash) ([]evmrpc.TxReceiptResult, error) { + return slices.Map(txHashes, func(txHash common.Hash) evmrpc.TxReceiptResult { + if bytes.Equal(txHash.Bytes(), receipt.TxHash.Bytes()) { + return evmrpc.TxReceiptResult(results.FromOk(*receipt)) + } + + return evmrpc.TxReceiptResult(results.FromErr[geth.Receipt](ethereum.NotFound)) + }), nil }, LatestFinalizedBlockNumberFunc: func(ctx context.Context, confirmations uint64) (*big.Int, error) { return receipt.BlockNumber, nil @@ -110,7 +122,11 @@ func TestMgr_ProccessTokenConfirmation(t *testing.T) { t.Run("no tx receipt", testutils.Func(func(t *testing.T) { setup() - rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { return nil, ethereum.NotFound } + rpc.TransactionReceiptsFunc = func(ctx context.Context, txHashes []common.Hash) ([]evmrpc.TxReceiptResult, error) { + return slices.Map(txHashes, func(txHash common.Hash) evmrpc.TxReceiptResult { + return evmrpc.TxReceiptResult(results.FromErr[geth.Receipt](ethereum.NotFound)) + }), nil + } err := mgr.ProcessTokenConfirmation(event) @@ -126,7 +142,7 @@ func TestMgr_ProccessTokenConfirmation(t *testing.T) { t.Run("no deploy event", testutils.Func(func(t *testing.T) { setup() - receipt, _ := rpc.TransactionReceipt(context.Background(), common.Hash{}) + var correctLogIdx int for i, l := range receipt.Logs { if l.Address == common.BytesToAddress(gatewayAddrBytes) { @@ -136,7 +152,6 @@ func TestMgr_ProccessTokenConfirmation(t *testing.T) { } // remove the deploy event receipt.Logs = append(receipt.Logs[:correctLogIdx], receipt.Logs[correctLogIdx+1:]...) - rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { return receipt, nil } err := mgr.ProcessTokenConfirmation(event) @@ -153,14 +168,13 @@ func TestMgr_ProccessTokenConfirmation(t *testing.T) { t.Run("wrong deploy event", testutils.Func(func(t *testing.T) { setup() - 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.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { return receipt, nil } err := mgr.ProcessTokenConfirmation(event)