diff --git a/core/chains/evm/bulletprooftxmanager/eth_confirmer.go b/core/chains/evm/bulletprooftxmanager/eth_confirmer.go index 7fa9bc7fac1..6d6b3d38627 100644 --- a/core/chains/evm/bulletprooftxmanager/eth_confirmer.go +++ b/core/chains/evm/bulletprooftxmanager/eth_confirmer.go @@ -380,13 +380,13 @@ func (ec *EthConfirmer) getNonceForLatestBlock(ctx context.Context, from gethCom // Note this function will increment promRevertedTxCount upon receiving // a reverted transaction receipt. Should only be called with unconfirmed attempts. -func (ec *EthConfirmer) batchFetchReceipts(ctx context.Context, attempts []EthTxAttempt) (receipts []Receipt, err error) { +func (ec *EthConfirmer) batchFetchReceipts(ctx context.Context, attempts []EthTxAttempt) (receipts []evmtypes.Receipt, err error) { var reqs []rpc.BatchElem for _, attempt := range attempts { req := rpc.BatchElem{ Method: "eth_getTransactionReceipt", Args: []interface{}{attempt.Hash}, - Result: &Receipt{}, + Result: &evmtypes.Receipt{}, } reqs = append(reqs, req) } @@ -402,9 +402,9 @@ func (ec *EthConfirmer) batchFetchReceipts(ctx context.Context, attempts []EthTx attempt := attempts[i] result, err := req.Result, req.Error - receipt, is := result.(*Receipt) + receipt, is := result.(*evmtypes.Receipt) if !is { - return nil, errors.Errorf("expected result to be a %T, got %T", (*Receipt)(nil), result) + return nil, errors.Errorf("expected result to be a %T, got %T", (*evmtypes.Receipt)(nil), result) } l := lggr.With( @@ -461,7 +461,7 @@ func (ec *EthConfirmer) batchFetchReceipts(ctx context.Context, attempts []EthTx return } -func (ec *EthConfirmer) saveFetchedReceipts(receipts []Receipt) (err error) { +func (ec *EthConfirmer) saveFetchedReceipts(receipts []evmtypes.Receipt) (err error) { if len(receipts) == 0 { return nil } diff --git a/core/chains/evm/bulletprooftxmanager/eth_confirmer_test.go b/core/chains/evm/bulletprooftxmanager/eth_confirmer_test.go index cda99f57236..f62eab71db8 100644 --- a/core/chains/evm/bulletprooftxmanager/eth_confirmer_test.go +++ b/core/chains/evm/bulletprooftxmanager/eth_confirmer_test.go @@ -199,7 +199,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { return len(b) == 1 && cltest.BatchElemMatchesHash(b[0], attempt1_1.Hash) })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &bulletprooftxmanager.Receipt{} + elems[0].Result = &evmtypes.Receipt{} }).Once() // Do the thing @@ -217,7 +217,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }) t.Run("saves nothing if returned receipt does not match the attempt", func(t *testing.T) { - bptxmReceipt := bulletprooftxmanager.Receipt{ + bptxmReceipt := evmtypes.Receipt{ TxHash: utils.NewHash(), BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), @@ -244,7 +244,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }) t.Run("saves nothing if query returns error", func(t *testing.T) { - bptxmReceipt := bulletprooftxmanager.Receipt{ + bptxmReceipt := evmtypes.Receipt{ TxHash: attempt1_1.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), @@ -277,7 +277,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { require.Len(t, attempt2_1.EthReceipts, 0) t.Run("saves eth_receipt and marks eth_tx as confirmed when geth client returns valid receipt", func(t *testing.T) { - bptxmReceipt := bulletprooftxmanager.Receipt{ + bptxmReceipt := evmtypes.Receipt{ TxHash: attempt1_1.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), @@ -295,7 +295,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { // First transaction confirmed elems[0].Result = &bptxmReceipt // Second transaction still unconfirmed - elems[1].Result = &bulletprooftxmanager.Receipt{} + elems[1].Result = &evmtypes.Receipt{} }).Once() // Do the thing @@ -336,7 +336,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { require.NoError(t, borm.InsertEthTxAttempt(&attempt2_3)) require.NoError(t, borm.InsertEthTxAttempt(&attempt2_2)) - bptxmReceipt := bulletprooftxmanager.Receipt{ + bptxmReceipt := evmtypes.Receipt{ TxHash: attempt2_2.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), @@ -353,11 +353,11 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) // Most expensive attempt still unconfirmed - elems[2].Result = &bulletprooftxmanager.Receipt{} + elems[2].Result = &evmtypes.Receipt{} // Second most expensive attempt is confirmed elems[1].Result = &bptxmReceipt // Cheapest attempt still unconfirmed - elems[0].Result = &bulletprooftxmanager.Receipt{} + elems[0].Result = &evmtypes.Receipt{} }).Once() // Do the thing @@ -379,7 +379,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { t.Run("ignores receipt missing BlockHash that comes from querying parity too early", func(t *testing.T) { ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(10), nil) - receipt := bulletprooftxmanager.Receipt{ + receipt := evmtypes.Receipt{ TxHash: attempt3_1.Hash, } ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { @@ -404,7 +404,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { t.Run("does not panic if receipt has BlockHash but is missing some other fields somehow", func(t *testing.T) { // NOTE: This should never happen, but we shouldn't panic regardless - receipt := bulletprooftxmanager.Receipt{ + receipt := evmtypes.Receipt{ TxHash: attempt3_1.Hash, BlockHash: utils.NewHash(), } @@ -431,7 +431,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { t.Run("handles case where eth_receipt already exists somehow", func(t *testing.T) { ethReceipt := cltest.MustInsertEthReceipt(t, borm, 42, utils.NewHash(), attempt3_1.Hash) - bptxmReceipt := bulletprooftxmanager.Receipt{ + bptxmReceipt := evmtypes.Receipt{ TxHash: attempt3_1.Hash, BlockHash: ethReceipt.BlockHash, BlockNumber: big.NewInt(ethReceipt.BlockNumber), @@ -477,7 +477,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { require.NoError(t, borm.InsertEthTxAttempt(&attempt4_2)) - bptxmReceipt := bulletprooftxmanager.Receipt{ + bptxmReceipt := evmtypes.Receipt{ TxHash: attempt4_2.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), @@ -492,7 +492,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) // First attempt still unconfirmed - elems[1].Result = &bulletprooftxmanager.Receipt{} + elems[1].Result = &evmtypes.Receipt{} // Second attempt is confirmed elems[0].Result = &bptxmReceipt }).Once() @@ -560,8 +560,8 @@ func TestEthConfirmer_CheckForReceipts_batching(t *testing.T) { cltest.BatchElemMatchesHash(b[1], attempts[3].Hash) })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &bulletprooftxmanager.Receipt{} - elems[1].Result = &bulletprooftxmanager.Receipt{} + elems[0].Result = &evmtypes.Receipt{} + elems[1].Result = &evmtypes.Receipt{} }).Once() ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && @@ -569,15 +569,15 @@ func TestEthConfirmer_CheckForReceipts_batching(t *testing.T) { cltest.BatchElemMatchesHash(b[1], attempts[1].Hash) })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &bulletprooftxmanager.Receipt{} - elems[1].Result = &bulletprooftxmanager.Receipt{} + elems[0].Result = &evmtypes.Receipt{} + elems[1].Result = &evmtypes.Receipt{} }).Once() ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 1 && cltest.BatchElemMatchesHash(b[0], attempts[0].Hash) })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) - elems[0].Result = &bulletprooftxmanager.Receipt{} + elems[0].Result = &evmtypes.Receipt{} }).Once() require.NoError(t, ec.CheckForReceipts(ctx, 42)) @@ -628,10 +628,10 @@ func TestEthConfirmer_CheckForReceipts_only_likely_confirmed(t *testing.T) { })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) captured = append(captured, elems...) - elems[0].Result = &bulletprooftxmanager.Receipt{} - elems[1].Result = &bulletprooftxmanager.Receipt{} - elems[2].Result = &bulletprooftxmanager.Receipt{} - elems[3].Result = &bulletprooftxmanager.Receipt{} + elems[0].Result = &evmtypes.Receipt{} + elems[1].Result = &evmtypes.Receipt{} + elems[2].Result = &evmtypes.Receipt{} + elems[3].Result = &evmtypes.Receipt{} }).Once() require.NoError(t, ec.CheckForReceipts(ctx, 42)) @@ -729,13 +729,13 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { pgtest.MustExec(t, db, `UPDATE eth_tx_attempts SET broadcast_before_block_num = 41 WHERE broadcast_before_block_num IS NULL`) t.Run("marks buried eth_txes as 'confirmed_missing_receipt'", func(t *testing.T) { - bptxmReceipt0 := bulletprooftxmanager.Receipt{ + bptxmReceipt0 := evmtypes.Receipt{ TxHash: attempt0_2.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), TransactionIndex: uint(1), } - bptxmReceipt3 := bulletprooftxmanager.Receipt{ + bptxmReceipt3 := evmtypes.Receipt{ TxHash: attempt3_1.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(42), @@ -755,12 +755,12 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { elems := args.Get(1).([]rpc.BatchElem) // First transaction confirmed elems[0].Result = &bptxmReceipt0 - elems[1].Result = &bulletprooftxmanager.Receipt{} + elems[1].Result = &evmtypes.Receipt{} // Second transaction stil unconfirmed - elems[2].Result = &bulletprooftxmanager.Receipt{} - elems[3].Result = &bulletprooftxmanager.Receipt{} + elems[2].Result = &evmtypes.Receipt{} + elems[3].Result = &evmtypes.Receipt{} // Third transaction still unconfirmed - elems[4].Result = &bulletprooftxmanager.Receipt{} + elems[4].Result = &evmtypes.Receipt{} // Fourth transaction is confirmed elems[5].Result = &bptxmReceipt3 }).Once() @@ -804,7 +804,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { // eth_txes with nonce 3 is confirmed t.Run("marks eth_txes with state 'confirmed_missing_receipt' as 'confirmed' if a receipt finally shows up", func(t *testing.T) { - bptxmReceipt := bulletprooftxmanager.Receipt{ + bptxmReceipt := evmtypes.Receipt{ TxHash: attempt2_1.Hash, BlockHash: utils.NewHash(), BlockNumber: big.NewInt(43), @@ -820,8 +820,8 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) // First transaction still unconfirmed - elems[0].Result = &bulletprooftxmanager.Receipt{} - elems[1].Result = &bulletprooftxmanager.Receipt{} + elems[0].Result = &evmtypes.Receipt{} + elems[1].Result = &evmtypes.Receipt{} // Second transaction confirmed elems[2].Result = &bptxmReceipt }).Once() @@ -868,8 +868,8 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) // Both attempts still unconfirmed - elems[0].Result = &bulletprooftxmanager.Receipt{} - elems[1].Result = &bulletprooftxmanager.Receipt{} + elems[0].Result = &evmtypes.Receipt{} + elems[1].Result = &evmtypes.Receipt{} }).Once() // PERFORM @@ -910,8 +910,8 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { })).Return(nil).Run(func(args mock.Arguments) { elems := args.Get(1).([]rpc.BatchElem) // Both attempts still unconfirmed - elems[0].Result = &bulletprooftxmanager.Receipt{} - elems[1].Result = &bulletprooftxmanager.Receipt{} + elems[0].Result = &evmtypes.Receipt{} + elems[1].Result = &evmtypes.Receipt{} }).Once() // PERFORM @@ -1501,7 +1501,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { require.Greater(t, expectedBumpedGasPrice.Int64(), attempt1_2.GasPrice.ToInt().Int64()) ethTx := *types.NewTx(&types.LegacyTx{}) - receipt := bulletprooftxmanager.Receipt{BlockNumber: big.NewInt(40)} + receipt := evmtypes.Receipt{BlockNumber: big.NewInt(40)} kst.On("SignTx", fromAddress, mock.MatchedBy(func(tx *types.Transaction) bool { @@ -2541,7 +2541,7 @@ func TestEthConfirmer_ResumePendingRuns(t *testing.T) { case data := <-ch: require.IsType(t, []byte{}, data) - var r bulletprooftxmanager.Receipt + var r evmtypes.Receipt err := json.Unmarshal(data.([]byte), &r) require.NoError(t, err) require.Equal(t, receipt.TxHash, r.TxHash) diff --git a/core/chains/evm/bulletprooftxmanager/types.go b/core/chains/evm/bulletprooftxmanager/types.go deleted file mode 100644 index a99dc411ede..00000000000 --- a/core/chains/evm/bulletprooftxmanager/types.go +++ /dev/null @@ -1,264 +0,0 @@ -package bulletprooftxmanager - -import ( - "encoding/json" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - gethTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/utils" -) - -// Receipt represents an ethereum receipt. -// -// Copied from go-ethereum: https://github.com/ethereum/go-ethereum/blob/ce9a289fa48e0d2593c4aaa7e207c8a5dd3eaa8a/core/types/receipt.go#L50 -// -// We use our own version because Geth's version specifies various -// gencodec:"required" fields which cause unhelpful errors when unmarshalling -// from an empty JSON object which can happen in the batch fetcher. -type Receipt struct { - PostState []byte `json:"root"` - Status uint64 `json:"status"` - CumulativeGasUsed uint64 `json:"cumulativeGasUsed"` - Bloom gethTypes.Bloom `json:"logsBloom"` - Logs []*Log `json:"logs"` - TxHash common.Hash `json:"transactionHash"` - ContractAddress common.Address `json:"contractAddress"` - GasUsed uint64 `json:"gasUsed"` - BlockHash common.Hash `json:"blockHash,omitempty"` - BlockNumber *big.Int `json:"blockNumber,omitempty"` - TransactionIndex uint `json:"transactionIndex"` -} - -// FromGethReceipt converts a gethTypes.Receipt to a Receipt -func FromGethReceipt(gr *gethTypes.Receipt) *Receipt { - if gr == nil { - return nil - } - logs := make([]*Log, len(gr.Logs)) - for i, glog := range gr.Logs { - logs[i] = FromGethLog(glog) - } - return &Receipt{ - gr.PostState, - gr.Status, - gr.CumulativeGasUsed, - gr.Bloom, - logs, - gr.TxHash, - gr.ContractAddress, - gr.GasUsed, - gr.BlockHash, - gr.BlockNumber, - gr.TransactionIndex, - } -} - -// IsZero returns true if receipt is the zero receipt -// Batch calls to the RPC will return a pointer to an empty Receipt struct -// Easiest way to check if the receipt was missing is to see if the hash is 0x0 -// Real receipts will always have the TxHash set -func (r Receipt) IsZero() bool { - return r.TxHash == utils.EmptyHash -} - -// IsUnmined returns true if the receipt is for a TX that has not been mined yet. -// Supposedly according to the spec this should never happen, but Parity does -// it anyway. -func (r Receipt) IsUnmined() bool { - return r.BlockHash == utils.EmptyHash -} - -// MarshalJSON marshals Receipt as JSON. -// Copied from: https://github.com/ethereum/go-ethereum/blob/ce9a289fa48e0d2593c4aaa7e207c8a5dd3eaa8a/core/types/gen_receipt_json.go -func (r Receipt) MarshalJSON() ([]byte, error) { - type Receipt struct { - PostState hexutil.Bytes `json:"root"` - Status hexutil.Uint64 `json:"status"` - CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed"` - Bloom gethTypes.Bloom `json:"logsBloom"` - Logs []*Log `json:"logs"` - TxHash common.Hash `json:"transactionHash"` - ContractAddress common.Address `json:"contractAddress"` - GasUsed hexutil.Uint64 `json:"gasUsed"` - BlockHash common.Hash `json:"blockHash,omitempty"` - BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` - TransactionIndex hexutil.Uint `json:"transactionIndex"` - } - var enc Receipt - enc.PostState = r.PostState - enc.Status = hexutil.Uint64(r.Status) - enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed) - enc.Bloom = r.Bloom - enc.Logs = r.Logs - enc.TxHash = r.TxHash - enc.ContractAddress = r.ContractAddress - enc.GasUsed = hexutil.Uint64(r.GasUsed) - enc.BlockHash = r.BlockHash - enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) - enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (r *Receipt) UnmarshalJSON(input []byte) error { - type Receipt struct { - PostState *hexutil.Bytes `json:"root"` - Status *hexutil.Uint64 `json:"status"` - CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed"` - Bloom *gethTypes.Bloom `json:"logsBloom"` - Logs []*Log `json:"logs"` - TxHash *common.Hash `json:"transactionHash"` - ContractAddress *common.Address `json:"contractAddress"` - GasUsed *hexutil.Uint64 `json:"gasUsed"` - BlockHash *common.Hash `json:"blockHash,omitempty"` - BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` - TransactionIndex *hexutil.Uint `json:"transactionIndex"` - } - var dec Receipt - if err := json.Unmarshal(input, &dec); err != nil { - return errors.Wrap(err, "could not unmarshal receipt") - } - if dec.PostState != nil { - r.PostState = *dec.PostState - } - if dec.Status != nil { - r.Status = uint64(*dec.Status) - } - if dec.CumulativeGasUsed != nil { - r.CumulativeGasUsed = uint64(*dec.CumulativeGasUsed) - } - if dec.Bloom != nil { - r.Bloom = *dec.Bloom - } - r.Logs = dec.Logs - if dec.TxHash != nil { - r.TxHash = *dec.TxHash - } - if dec.ContractAddress != nil { - r.ContractAddress = *dec.ContractAddress - } - if dec.GasUsed != nil { - r.GasUsed = uint64(*dec.GasUsed) - } - if dec.BlockHash != nil { - r.BlockHash = *dec.BlockHash - } - if dec.BlockNumber != nil { - r.BlockNumber = (*big.Int)(dec.BlockNumber) - } - if dec.TransactionIndex != nil { - r.TransactionIndex = uint(*dec.TransactionIndex) - } - return nil -} - -// Log represents a contract log event. -// -// Copied from go-ethereum: https://github.com/ethereum/go-ethereum/blob/ce9a289fa48e0d2593c4aaa7e207c8a5dd3eaa8a/core/types/log.go -// -// We use our own version because Geth's version specifies various -// gencodec:"required" fields which cause unhelpful errors when unmarshalling -// from an empty JSON object which can happen in the batch fetcher. -type Log struct { - Address common.Address `json:"address"` - Topics []common.Hash `json:"topics"` - Data []byte `json:"data"` - BlockNumber uint64 `json:"blockNumber"` - TxHash common.Hash `json:"transactionHash"` - TxIndex uint `json:"transactionIndex"` - BlockHash common.Hash `json:"blockHash"` - Index uint `json:"logIndex"` - Removed bool `json:"removed"` -} - -// FromGethLog converts a gethTypes.Log to a Log -func FromGethLog(gl *gethTypes.Log) *Log { - if gl == nil { - return nil - } - return &Log{ - gl.Address, - gl.Topics, - gl.Data, - gl.BlockNumber, - gl.TxHash, - gl.TxIndex, - gl.BlockHash, - gl.Index, - gl.Removed, - } -} - -// MarshalJSON marshals as JSON. -func (l Log) MarshalJSON() ([]byte, error) { - type Log struct { - Address common.Address `json:"address"` - Topics []common.Hash `json:"topics"` - Data hexutil.Bytes `json:"data"` - BlockNumber hexutil.Uint64 `json:"blockNumber"` - TxHash common.Hash `json:"transactionHash"` - TxIndex hexutil.Uint `json:"transactionIndex"` - BlockHash common.Hash `json:"blockHash"` - Index hexutil.Uint `json:"logIndex"` - Removed bool `json:"removed"` - } - var enc Log - enc.Address = l.Address - enc.Topics = l.Topics - enc.Data = l.Data - enc.BlockNumber = hexutil.Uint64(l.BlockNumber) - enc.TxHash = l.TxHash - enc.TxIndex = hexutil.Uint(l.TxIndex) - enc.BlockHash = l.BlockHash - enc.Index = hexutil.Uint(l.Index) - enc.Removed = l.Removed - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (l *Log) UnmarshalJSON(input []byte) error { - type Log struct { - Address *common.Address `json:"address"` - Topics []common.Hash `json:"topics"` - Data *hexutil.Bytes `json:"data"` - BlockNumber *hexutil.Uint64 `json:"blockNumber"` - TxHash *common.Hash `json:"transactionHash"` - TxIndex *hexutil.Uint `json:"transactionIndex"` - BlockHash *common.Hash `json:"blockHash"` - Index *hexutil.Uint `json:"logIndex"` - Removed *bool `json:"removed"` - } - var dec Log - if err := json.Unmarshal(input, &dec); err != nil { - return errors.Wrap(err, "could not unmarshal log") - } - if dec.Address != nil { - l.Address = *dec.Address - } - l.Topics = dec.Topics - if dec.Data != nil { - l.Data = *dec.Data - } - if dec.BlockNumber != nil { - l.BlockNumber = uint64(*dec.BlockNumber) - } - if dec.TxHash != nil { - l.TxHash = *dec.TxHash - } - if dec.TxIndex != nil { - l.TxIndex = uint(*dec.TxIndex) - } - if dec.BlockHash != nil { - l.BlockHash = *dec.BlockHash - } - if dec.Index != nil { - l.Index = uint(*dec.Index) - } - if dec.Removed != nil { - l.Removed = *dec.Removed - } - return nil -} diff --git a/core/chains/evm/client/simulated_backend.go b/core/chains/evm/client/simulated_backend.go new file mode 100644 index 00000000000..34e235945f8 --- /dev/null +++ b/core/chains/evm/client/simulated_backend.go @@ -0,0 +1,406 @@ +package client + +import ( + "bytes" + "context" + "fmt" + "math/big" + "strings" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink/core/assets" + evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/utils" +) + +var _ Client = (*SimulatedBackendClient)(nil) + +// SimulatedBackendClient is an Client implementation using a simulated +// blockchain backend. Note that not all RPC methods are implemented here. +type SimulatedBackendClient struct { + b *backends.SimulatedBackend + t testing.TB + chainId *big.Int +} + +// NewSimulatedBackendClient creates an eth client backed by a simulated backend. +func NewSimulatedBackendClient(t testing.TB, b *backends.SimulatedBackend, chainId *big.Int) *SimulatedBackendClient { + return &SimulatedBackendClient{ + b: b, + t: t, + chainId: chainId, + } +} + +// Dial noop for the sim. +func (c *SimulatedBackendClient) Dial(context.Context) error { + return nil +} + +// Close does nothing. We ought not close the underlying backend here since +// other simulated clients might still be using it +func (c *SimulatedBackendClient) Close() {} + +// checkEthCallArgs extracts and verifies the arguments for an eth_call RPC +func (c *SimulatedBackendClient) checkEthCallArgs( + args []interface{}) (*CallArgs, *big.Int, error) { + if len(args) != 2 { + return nil, nil, fmt.Errorf( + "should have two arguments after \"eth_call\", got %d", len(args)) + } + callArgs, ok := args[0].(CallArgs) + if !ok { + return nil, nil, fmt.Errorf("third arg to SimulatedBackendClient.Call "+ + "must be an eth.CallArgs, got %+#v", args[0]) + } + blockNumber, err := c.blockNumber(args[1]) + if err != nil || blockNumber.Cmp(c.currentBlockNumber()) != 0 { + return nil, nil, fmt.Errorf("fourth arg to SimulatedBackendClient.Call "+ + "must be the string \"latest\", or a *big.Int equal to current "+ + "blocknumber, got %#+v", args[1]) + } + return &callArgs, blockNumber, nil +} + +// CallContext mocks the ethereum client RPC calls used by chainlink, copying the +// return value into result. +func (c *SimulatedBackendClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { + switch method { + case "eth_call": + callArgs, _, err := c.checkEthCallArgs(args) + if err != nil { + return err + } + callMsg := ethereum.CallMsg{To: &callArgs.To, Data: callArgs.Data} + b, err := c.b.CallContract(ctx, callMsg, nil /* always latest block */) + if err != nil { + return errors.Wrapf(err, "while calling contract at address %x with "+ + "data %x", callArgs.To, callArgs.Data) + } + switch r := result.(type) { + case *hexutil.Bytes: + *r = append(*r, b...) + if !bytes.Equal(*r, b) { + return fmt.Errorf("was passed a non-empty array, or failed to copy "+ + "answer. Expected %x = %x", *r, b) + } + return nil + default: + return fmt.Errorf("first arg to SimulatedBackendClient.Call is an "+ + "unrecognized type: %T; add processing logic for it here", result) + } + default: + return fmt.Errorf("second arg to SimulatedBackendClient.Call is an RPC "+ + "API method which has not yet been implemented: %s. Add processing for "+ + "it here", method) + } +} + +// FilterLogs returns all logs that respect the passed filter query. +func (c *SimulatedBackendClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) (logs []types.Log, err error) { + return c.b.FilterLogs(ctx, q) +} + +// SubscribeFilterLogs registers a subscription for push notifications of logs +// from a given address. +func (c *SimulatedBackendClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, channel chan<- types.Log) (ethereum.Subscription, error) { + return c.b.SubscribeFilterLogs(ctx, q, channel) +} + +// GetEthBalance helper to get eth balance +func (c *SimulatedBackendClient) GetEthBalance(ctx context.Context, account common.Address, blockNumber *big.Int) (*assets.Eth, error) { + panic("not implemented") +} + +// currentBlockNumber returns index of *pending* block in simulated blockchain +func (c *SimulatedBackendClient) currentBlockNumber() *big.Int { + return c.b.Blockchain().CurrentBlock().Number() +} + +var balanceOfABIString = `[ + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +]` + +var balanceOfABI abi.ABI + +func init() { + var err error + balanceOfABI, err = abi.JSON(strings.NewReader(balanceOfABIString)) + if err != nil { + panic(errors.Wrapf(err, "while parsing erc20ABI")) + } +} + +// GetERC20Balance returns the balance of the given address for the token +// contract address. +func (c *SimulatedBackendClient) GetERC20Balance(address common.Address, contractAddress common.Address) (balance *big.Int, err error) { + callData, err := balanceOfABI.Pack("balanceOf", address) + if err != nil { + return nil, errors.Wrapf(err, "while seeking the ERC20 balance of %s on %s", + address, contractAddress) + } + b, err := c.b.CallContract(context.Background(), ethereum.CallMsg{ + To: &contractAddress, Data: callData}, + c.currentBlockNumber()) + if err != nil { + return nil, errors.Wrapf(err, "while calling ERC20 balanceOf method on %s "+ + "for balance of %s", contractAddress, address) + } + err = balanceOfABI.UnpackIntoInterface(balance, "balanceOf", b) + if err != nil { + return nil, errors.New("unable to unpack balance") + } + return balance, nil +} + +// GetLINKBalance get link balance. +func (c *SimulatedBackendClient) GetLINKBalance(linkAddress common.Address, address common.Address) (*assets.Link, error) { + panic("not implemented") +} + +// TransactionReceipt returns the transaction receipt for the given transaction hash. +func (c *SimulatedBackendClient) TransactionReceipt(ctx context.Context, receipt common.Hash) (*types.Receipt, error) { + return c.b.TransactionReceipt(ctx, receipt) +} + +func (c *SimulatedBackendClient) blockNumber(number interface{}) (blockNumber *big.Int, err error) { + switch n := number.(type) { + case string: + switch n { + case "latest": + return c.currentBlockNumber(), nil + case "earliest": + return big.NewInt(0), nil + case "pending": + panic("not implemented") // I don't understand the semantics of this. + // return big.NewInt(0).Add(c.currentBlockNumber(), big.NewInt(1)), nil + default: + blockNumber, err = utils.HexToUint256(n) + if err != nil { + return nil, errors.Wrapf(err, "while parsing '%s' as hex-encoded"+ + "block number", n) + } + return blockNumber, nil + } + case *big.Int: + if n.Sign() < 0 { + return nil, fmt.Errorf("block number must be non-negative") + } + return n, nil + } + panic("can never reach here") +} + +// HeadByNumber returns our own header type. +func (c *SimulatedBackendClient) HeadByNumber(ctx context.Context, n *big.Int) (*evmtypes.Head, error) { + if n == nil { + n = c.currentBlockNumber() + } + header, err := c.b.HeaderByNumber(ctx, n) + if err != nil { + return nil, err + } else if header == nil { + return nil, ethereum.NotFound + } + return &evmtypes.Head{ + EVMChainID: utils.NewBigI(c.chainId.Int64()), + Hash: header.Hash(), + Number: header.Number.Int64(), + ParentHash: header.ParentHash, + }, nil +} + +// BlockByNumber returns a geth block type. +func (c *SimulatedBackendClient) BlockByNumber(ctx context.Context, n *big.Int) (*types.Block, error) { + return c.b.BlockByNumber(ctx, n) +} + +// ChainID returns the ethereum ChainID. +func (c *SimulatedBackendClient) ChainID() *big.Int { + return c.chainId +} + +// PendingNonceAt gets pending nonce i.e. mempool nonce. +func (c *SimulatedBackendClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + return c.b.PendingNonceAt(ctx, account) +} + +// NonceAt gets nonce as of a specified block. +func (c *SimulatedBackendClient) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { + return c.b.NonceAt(ctx, account, blockNumber) +} + +// BalanceAt gets balance as of a specified block. +func (c *SimulatedBackendClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { + return c.b.BalanceAt(ctx, account, blockNumber) +} + +type headSubscription struct { + unSub chan chan struct{} + subscription ethereum.Subscription +} + +var _ ethereum.Subscription = (*headSubscription)(nil) + +func (h *headSubscription) Unsubscribe() { + done := make(chan struct{}) + h.unSub <- done + <-done +} + +// Err returns err channel +func (h *headSubscription) Err() <-chan error { return h.subscription.Err() } + +// SubscribeNewHead registers a subscription for push notifications of new blocks. +// Note the sim's API only accepts types.Head so we have this goroutine +// to convert those into evmtypes.Head. +func (c *SimulatedBackendClient) SubscribeNewHead( + ctx context.Context, + channel chan<- *evmtypes.Head, +) (ethereum.Subscription, error) { + subscription := &headSubscription{unSub: make(chan chan struct{})} + ch := make(chan *types.Header) + + var err error + subscription.subscription, err = c.b.SubscribeNewHead(ctx, ch) + if err != nil { + return nil, errors.Wrapf(err, "could not subscribe to new heads on "+ + "simulated backend") + } + go func() { + var lastHead *evmtypes.Head + for { + select { + case h := <-ch: + var head *evmtypes.Head + if h != nil { + head = &evmtypes.Head{Number: h.Number.Int64(), Hash: h.Hash(), ParentHash: h.ParentHash, Parent: lastHead, EVMChainID: utils.NewBig(c.chainId)} + lastHead = head + } + select { + case channel <- head: + case done := <-subscription.unSub: + subscription.subscription.Unsubscribe() + close(done) + return + } + + case done := <-subscription.unSub: + subscription.subscription.Unsubscribe() + close(done) + return + } + } + }() + return subscription, err +} + +// HeaderByNumber returns the geth header type. +func (c *SimulatedBackendClient) HeaderByNumber(ctx context.Context, n *big.Int) (*types.Header, error) { + return c.b.HeaderByNumber(ctx, n) +} + +// SendTransaction sends a transaction. +func (c *SimulatedBackendClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { + sender, err := types.Sender(types.NewLondonSigner(c.chainId), tx) + if err != nil { + logger.TestLogger(c.t).Panic(fmt.Errorf("invalid transaction: %v (tx: %#v)", err, tx)) + } + pendingNonce, err := c.b.PendingNonceAt(ctx, sender) + if err != nil { + panic(fmt.Errorf("unable to determine nonce for account %s: %v", sender.Hex(), err)) + } + // the simulated backend does not gracefully handle tx rebroadcasts (gas bumping) so just + // ignore the situation where nonces are reused + // github.com/ethereum/go-ethereum/blob/fb2c79df1995b4e8dfe79f9c75464d29d23aaaf4/accounts/abi/bind/backends/simulated.go#L556 + if tx.Nonce() < pendingNonce { + return nil + } + + err = c.b.SendTransaction(ctx, tx) + return err +} + +// Call makes a call. +func (c *SimulatedBackendClient) Call(result interface{}, method string, args ...interface{}) error { + return c.CallContext(context.Background(), result, method, args) +} + +// CallContract calls a contract. +func (c *SimulatedBackendClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + return c.b.CallContract(ctx, msg, blockNumber) +} + +// CodeAt gets the code associated with an account as of a specified block. +func (c *SimulatedBackendClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { + return c.b.CodeAt(ctx, account, blockNumber) +} + +// PendingCodeAt gets the latest code. +func (c *SimulatedBackendClient) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + return c.b.PendingCodeAt(ctx, account) +} + +// EstimateGas estimates gas for a msg. +func (c *SimulatedBackendClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { + return c.b.EstimateGas(ctx, call) +} + +// SuggestGasPrice recommends a gas price. +func (c *SimulatedBackendClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + panic("unimplemented") +} + +// BatchCallContext makes a batch rpc call. +func (c *SimulatedBackendClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { + for i, elem := range b { + if elem.Method != "eth_getTransactionReceipt" || len(elem.Args) != 1 { + return errors.New("SimulatedBackendClient BatchCallContext only supports eth_getTransactionReceipt") + } + switch v := elem.Result.(type) { + case *evmtypes.Receipt: + hash, is := elem.Args[0].(common.Hash) + if !is { + return errors.Errorf("SimulatedBackendClient expected arg to be a hash, got: %T", elem.Args[0]) + } + receipt, err := c.b.TransactionReceipt(ctx, hash) + b[i].Result = evmtypes.FromGethReceipt(receipt) + b[i].Error = err + default: + return errors.Errorf("SimulatedBackendClient unsupported elem.Result type %T", v) + } + } + return nil +} + +// SuggestGasTipCap suggests a gas tip cap. +func (c *SimulatedBackendClient) SuggestGasTipCap(ctx context.Context) (tipCap *big.Int, err error) { + return nil, nil +} diff --git a/core/chains/evm/types/types.go b/core/chains/evm/types/types.go index 98c0ef32cfd..cc323103b74 100644 --- a/core/chains/evm/types/types.go +++ b/core/chains/evm/types/types.go @@ -3,10 +3,14 @@ package types import ( "database/sql/driver" "encoding/json" - "errors" "math/big" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + gethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/assets" @@ -88,6 +92,7 @@ func (c *ChainCfg) Scan(value interface{}) error { return json.Unmarshal(b, c) } + func (c ChainCfg) Value() (driver.Value, error) { return json.Marshal(c) } @@ -100,6 +105,7 @@ type Chain struct { UpdatedAt time.Time Enabled bool } + type Node struct { ID int32 Name string @@ -110,3 +116,255 @@ type Node struct { CreatedAt time.Time UpdatedAt time.Time } + +// Receipt represents an ethereum receipt. +// +// Copied from go-ethereum: https://github.com/ethereum/go-ethereum/blob/ce9a289fa48e0d2593c4aaa7e207c8a5dd3eaa8a/core/types/receipt.go#L50 +// +// We use our own version because Geth's version specifies various +// gencodec:"required" fields which cause unhelpful errors when unmarshalling +// from an empty JSON object which can happen in the batch fetcher. +type Receipt struct { + PostState []byte `json:"root"` + Status uint64 `json:"status"` + CumulativeGasUsed uint64 `json:"cumulativeGasUsed"` + Bloom gethTypes.Bloom `json:"logsBloom"` + Logs []*Log `json:"logs"` + TxHash common.Hash `json:"transactionHash"` + ContractAddress common.Address `json:"contractAddress"` + GasUsed uint64 `json:"gasUsed"` + BlockHash common.Hash `json:"blockHash,omitempty"` + BlockNumber *big.Int `json:"blockNumber,omitempty"` + TransactionIndex uint `json:"transactionIndex"` +} + +// FromGethReceipt converts a gethTypes.Receipt to a Receipt +func FromGethReceipt(gr *gethTypes.Receipt) *Receipt { + if gr == nil { + return nil + } + logs := make([]*Log, len(gr.Logs)) + for i, glog := range gr.Logs { + logs[i] = FromGethLog(glog) + } + return &Receipt{ + gr.PostState, + gr.Status, + gr.CumulativeGasUsed, + gr.Bloom, + logs, + gr.TxHash, + gr.ContractAddress, + gr.GasUsed, + gr.BlockHash, + gr.BlockNumber, + gr.TransactionIndex, + } +} + +// IsZero returns true if receipt is the zero receipt +// Batch calls to the RPC will return a pointer to an empty Receipt struct +// Easiest way to check if the receipt was missing is to see if the hash is 0x0 +// Real receipts will always have the TxHash set +func (r Receipt) IsZero() bool { + return r.TxHash == utils.EmptyHash +} + +// IsUnmined returns true if the receipt is for a TX that has not been mined yet. +// Supposedly according to the spec this should never happen, but Parity does +// it anyway. +func (r Receipt) IsUnmined() bool { + return r.BlockHash == utils.EmptyHash +} + +// MarshalJSON marshals Receipt as JSON. +// Copied from: https://github.com/ethereum/go-ethereum/blob/ce9a289fa48e0d2593c4aaa7e207c8a5dd3eaa8a/core/types/gen_receipt_json.go +func (r Receipt) MarshalJSON() ([]byte, error) { + type Receipt struct { + PostState hexutil.Bytes `json:"root"` + Status hexutil.Uint64 `json:"status"` + CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed"` + Bloom gethTypes.Bloom `json:"logsBloom"` + Logs []*Log `json:"logs"` + TxHash common.Hash `json:"transactionHash"` + ContractAddress common.Address `json:"contractAddress"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + BlockHash common.Hash `json:"blockHash,omitempty"` + BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` + TransactionIndex hexutil.Uint `json:"transactionIndex"` + } + var enc Receipt + enc.PostState = r.PostState + enc.Status = hexutil.Uint64(r.Status) + enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed) + enc.Bloom = r.Bloom + enc.Logs = r.Logs + enc.TxHash = r.TxHash + enc.ContractAddress = r.ContractAddress + enc.GasUsed = hexutil.Uint64(r.GasUsed) + enc.BlockHash = r.BlockHash + enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) + enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (r *Receipt) UnmarshalJSON(input []byte) error { + type Receipt struct { + PostState *hexutil.Bytes `json:"root"` + Status *hexutil.Uint64 `json:"status"` + CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed"` + Bloom *gethTypes.Bloom `json:"logsBloom"` + Logs []*Log `json:"logs"` + TxHash *common.Hash `json:"transactionHash"` + ContractAddress *common.Address `json:"contractAddress"` + GasUsed *hexutil.Uint64 `json:"gasUsed"` + BlockHash *common.Hash `json:"blockHash,omitempty"` + BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` + TransactionIndex *hexutil.Uint `json:"transactionIndex"` + } + var dec Receipt + if err := json.Unmarshal(input, &dec); err != nil { + return errors.Wrap(err, "could not unmarshal receipt") + } + if dec.PostState != nil { + r.PostState = *dec.PostState + } + if dec.Status != nil { + r.Status = uint64(*dec.Status) + } + if dec.CumulativeGasUsed != nil { + r.CumulativeGasUsed = uint64(*dec.CumulativeGasUsed) + } + if dec.Bloom != nil { + r.Bloom = *dec.Bloom + } + r.Logs = dec.Logs + if dec.TxHash != nil { + r.TxHash = *dec.TxHash + } + if dec.ContractAddress != nil { + r.ContractAddress = *dec.ContractAddress + } + if dec.GasUsed != nil { + r.GasUsed = uint64(*dec.GasUsed) + } + if dec.BlockHash != nil { + r.BlockHash = *dec.BlockHash + } + if dec.BlockNumber != nil { + r.BlockNumber = (*big.Int)(dec.BlockNumber) + } + if dec.TransactionIndex != nil { + r.TransactionIndex = uint(*dec.TransactionIndex) + } + return nil +} + +// Log represents a contract log event. +// +// Copied from go-ethereum: https://github.com/ethereum/go-ethereum/blob/ce9a289fa48e0d2593c4aaa7e207c8a5dd3eaa8a/core/types/log.go +// +// We use our own version because Geth's version specifies various +// gencodec:"required" fields which cause unhelpful errors when unmarshalling +// from an empty JSON object which can happen in the batch fetcher. +type Log struct { + Address common.Address `json:"address"` + Topics []common.Hash `json:"topics"` + Data []byte `json:"data"` + BlockNumber uint64 `json:"blockNumber"` + TxHash common.Hash `json:"transactionHash"` + TxIndex uint `json:"transactionIndex"` + BlockHash common.Hash `json:"blockHash"` + Index uint `json:"logIndex"` + Removed bool `json:"removed"` +} + +// FromGethLog converts a gethTypes.Log to a Log +func FromGethLog(gl *gethTypes.Log) *Log { + if gl == nil { + return nil + } + return &Log{ + gl.Address, + gl.Topics, + gl.Data, + gl.BlockNumber, + gl.TxHash, + gl.TxIndex, + gl.BlockHash, + gl.Index, + gl.Removed, + } +} + +// MarshalJSON marshals as JSON. +func (l Log) MarshalJSON() ([]byte, error) { + type Log struct { + Address common.Address `json:"address"` + Topics []common.Hash `json:"topics"` + Data hexutil.Bytes `json:"data"` + BlockNumber hexutil.Uint64 `json:"blockNumber"` + TxHash common.Hash `json:"transactionHash"` + TxIndex hexutil.Uint `json:"transactionIndex"` + BlockHash common.Hash `json:"blockHash"` + Index hexutil.Uint `json:"logIndex"` + Removed bool `json:"removed"` + } + var enc Log + enc.Address = l.Address + enc.Topics = l.Topics + enc.Data = l.Data + enc.BlockNumber = hexutil.Uint64(l.BlockNumber) + enc.TxHash = l.TxHash + enc.TxIndex = hexutil.Uint(l.TxIndex) + enc.BlockHash = l.BlockHash + enc.Index = hexutil.Uint(l.Index) + enc.Removed = l.Removed + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (l *Log) UnmarshalJSON(input []byte) error { + type Log struct { + Address *common.Address `json:"address"` + Topics []common.Hash `json:"topics"` + Data *hexutil.Bytes `json:"data"` + BlockNumber *hexutil.Uint64 `json:"blockNumber"` + TxHash *common.Hash `json:"transactionHash"` + TxIndex *hexutil.Uint `json:"transactionIndex"` + BlockHash *common.Hash `json:"blockHash"` + Index *hexutil.Uint `json:"logIndex"` + Removed *bool `json:"removed"` + } + var dec Log + if err := json.Unmarshal(input, &dec); err != nil { + return errors.Wrap(err, "could not unmarshal log") + } + if dec.Address != nil { + l.Address = *dec.Address + } + l.Topics = dec.Topics + if dec.Data != nil { + l.Data = *dec.Data + } + if dec.BlockNumber != nil { + l.BlockNumber = uint64(*dec.BlockNumber) + } + if dec.TxHash != nil { + l.TxHash = *dec.TxHash + } + if dec.TxIndex != nil { + l.TxIndex = uint(*dec.TxIndex) + } + if dec.BlockHash != nil { + l.BlockHash = *dec.BlockHash + } + if dec.Index != nil { + l.Index = uint(*dec.Index) + } + if dec.Removed != nil { + l.Removed = *dec.Removed + } + return nil +} diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index 645c8550fac..375d05b0c12 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -329,7 +329,7 @@ func NewDynamicFeeEthTxAttempt(t *testing.T, etxID int64) bulletprooftxmanager.E func NewEthReceipt(t *testing.T, blockNumber int64, blockHash common.Hash, txHash common.Hash) bulletprooftxmanager.EthReceipt { transactionIndex := uint(NewRandomInt64()) - receipt := bulletprooftxmanager.Receipt{ + receipt := evmtypes.Receipt{ BlockNumber: big.NewInt(blockNumber), BlockHash: blockHash, TxHash: txHash, diff --git a/core/internal/cltest/simulated_backend.go b/core/internal/cltest/simulated_backend.go index 6c227d564c9..29b0290501e 100644 --- a/core/internal/cltest/simulated_backend.go +++ b/core/internal/cltest/simulated_backend.go @@ -1,33 +1,21 @@ package cltest import ( - "bytes" - "context" "crypto/ecdsa" - "fmt" "math/big" - "strings" "sync" "testing" "time" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rpc" - "github.com/pkg/errors" uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/require" null "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/assets" - "github.com/smartcontractkit/chainlink/core/chains/evm/bulletprooftxmanager" evmclient "github.com/smartcontractkit/chainlink/core/chains/evm/client" evmtypes "github.com/smartcontractkit/chainlink/core/chains/evm/types" "github.com/smartcontractkit/chainlink/core/internal/testutils/configtest" @@ -66,7 +54,7 @@ func NewApplicationWithConfigAndKeyOnSimulatedBlockchain( chainId := backend.Blockchain().Config().ChainID cfg.Overrides.DefaultChainID = chainId - client := &SimulatedBackendClient{b: backend, t: t, chainId: chainId} + client := evmclient.NewSimulatedBackendClient(t, backend, chainId) eventBroadcaster := pg.NewEventBroadcaster(cfg.DatabaseURL(), 0, 0, logger.TestLogger(t), uuid.NewV4()) zero := models.MustMakeDuration(0 * time.Millisecond) @@ -105,361 +93,6 @@ func MustNewKeyedTransactor(t *testing.T, key *ecdsa.PrivateKey, chainID int64) return transactor } -// SimulatedBackendClient is an evmclient.Client implementation using a simulated -// blockchain backend. Note that not all RPC methods are implemented here. -type SimulatedBackendClient struct { - b *backends.SimulatedBackend - t testing.TB - chainId *big.Int -} - -var _ evmclient.Client = (*SimulatedBackendClient)(nil) - -func (c *SimulatedBackendClient) Dial(context.Context) error { - return nil -} - -// Close does nothing. We ought not close the underlying backend here since -// other simulated clients might still be using it -func (c *SimulatedBackendClient) Close() {} - -// checkEthCallArgs extracts and verifies the arguments for an eth_call RPC -func (c *SimulatedBackendClient) checkEthCallArgs( - args []interface{}) (*evmclient.CallArgs, *big.Int, error) { - if len(args) != 2 { - return nil, nil, fmt.Errorf( - "should have two arguments after \"eth_call\", got %d", len(args)) - } - callArgs, ok := args[0].(evmclient.CallArgs) - if !ok { - return nil, nil, fmt.Errorf("third arg to SimulatedBackendClient.Call "+ - "must be an eth.CallArgs, got %+#v", args[0]) - } - blockNumber, err := c.blockNumber(args[1]) - if err != nil || blockNumber.Cmp(c.currentBlockNumber()) != 0 { - return nil, nil, fmt.Errorf("fourth arg to SimulatedBackendClient.Call "+ - "must be the string \"latest\", or a *big.Int equal to current "+ - "blocknumber, got %#+v", args[1]) - } - return &callArgs, blockNumber, nil -} - -// Call mocks the ethereum client RPC calls used by chainlink, copying the -// return value into result. -func (c *SimulatedBackendClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { - switch method { - case "eth_call": - callArgs, _, err := c.checkEthCallArgs(args) - if err != nil { - return err - } - callMsg := ethereum.CallMsg{To: &callArgs.To, Data: callArgs.Data} - b, err := c.b.CallContract(ctx, callMsg, nil /* always latest block */) - if err != nil { - return errors.Wrapf(err, "while calling contract at address %x with "+ - "data %x", callArgs.To, callArgs.Data) - } - switch r := result.(type) { - case *hexutil.Bytes: - *r = append(*r, b...) - if !bytes.Equal(*r, b) { - return fmt.Errorf("was passed a non-empty array, or failed to copy "+ - "answer. Expected %x = %x", *r, b) - } - return nil - default: - return fmt.Errorf("first arg to SimulatedBackendClient.Call is an "+ - "unrecognized type: %T; add processing logic for it here", result) - } - default: - return fmt.Errorf("second arg to SimulatedBackendClient.Call is an RPC "+ - "API method which has not yet been implemented: %s. Add processing for "+ - "it here", method) - } -} - -// FilterLogs returns all logs that respect the passed filter query. -func (c *SimulatedBackendClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) (logs []types.Log, err error) { - return c.b.FilterLogs(ctx, q) -} - -// SubscribeToLogs registers a subscription for push notifications of logs -// from a given address. -func (c *SimulatedBackendClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, channel chan<- types.Log) (ethereum.Subscription, error) { - return c.b.SubscribeFilterLogs(ctx, q, channel) -} - -func (c *SimulatedBackendClient) GetEthBalance(ctx context.Context, account common.Address, blockNumber *big.Int) (*assets.Eth, error) { - panic("not implemented") -} - -// currentBlockNumber returns index of *pending* block in simulated blockchain -func (c *SimulatedBackendClient) currentBlockNumber() *big.Int { - return c.b.Blockchain().CurrentBlock().Number() -} - -var balanceOfABIString = `[ - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "balance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } -]` - -var balanceOfABI abi.ABI - -func init() { - var err error - balanceOfABI, err = abi.JSON(strings.NewReader(balanceOfABIString)) - if err != nil { - panic(errors.Wrapf(err, "while parsing erc20ABI")) - } -} - -// GetERC20Balance returns the balance of the given address for the token -// contract address. -func (c *SimulatedBackendClient) GetERC20Balance(address common.Address, contractAddress common.Address) (balance *big.Int, err error) { - callData, err := balanceOfABI.Pack("balanceOf", address) - if err != nil { - return nil, errors.Wrapf(err, "while seeking the ERC20 balance of %s on %s", - address, contractAddress) - } - b, err := c.b.CallContract(context.Background(), ethereum.CallMsg{ - To: &contractAddress, Data: callData}, - c.currentBlockNumber()) - if err != nil { - return nil, errors.Wrapf(err, "while calling ERC20 balanceOf method on %s "+ - "for balance of %s", contractAddress, address) - } - err = balanceOfABI.UnpackIntoInterface(balance, "balanceOf", b) - if err != nil { - return nil, errors.New("unable to unpack balance") - } - return balance, nil -} - -func (c *SimulatedBackendClient) GetLINKBalance(linkAddress common.Address, address common.Address) (*assets.Link, error) { - panic("not implemented") -} - -// TransactionReceipt returns the transaction receipt for the given transaction hash. -func (c *SimulatedBackendClient) TransactionReceipt(ctx context.Context, receipt common.Hash) (*types.Receipt, error) { - return c.b.TransactionReceipt(ctx, receipt) -} - -func (c *SimulatedBackendClient) blockNumber(number interface{}) (blockNumber *big.Int, err error) { - switch n := number.(type) { - case string: - switch n { - case "latest": - return c.currentBlockNumber(), nil - case "earliest": - return big.NewInt(0), nil - case "pending": - panic("not implemented") // I don't understand the semantics of this. - // return big.NewInt(0).Add(c.currentBlockNumber(), big.NewInt(1)), nil - default: - blockNumber, err = utils.HexToUint256(n) - if err != nil { - return nil, errors.Wrapf(err, "while parsing '%s' as hex-encoded"+ - "block number", n) - } - return blockNumber, nil - } - case *big.Int: - if n.Sign() < 0 { - return nil, fmt.Errorf("block number must be non-negative") - } - return n, nil - } - panic("can never reach here") -} - -func (c *SimulatedBackendClient) HeadByNumber(ctx context.Context, n *big.Int) (*evmtypes.Head, error) { - if n == nil { - n = c.currentBlockNumber() - } - header, err := c.b.HeaderByNumber(ctx, n) - if err != nil { - return nil, err - } else if header == nil { - return nil, ethereum.NotFound - } - return &evmtypes.Head{ - EVMChainID: utils.NewBigI(SimulatedBackendEVMChainID), - Hash: header.Hash(), - Number: header.Number.Int64(), - ParentHash: header.ParentHash, - }, nil -} - -func (c *SimulatedBackendClient) BlockByNumber(ctx context.Context, n *big.Int) (*types.Block, error) { - return c.b.BlockByNumber(ctx, n) -} - -// GetChainID returns the ethereum ChainID. -func (c *SimulatedBackendClient) ChainID() *big.Int { - return c.chainId -} - -func (c *SimulatedBackendClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { - return c.b.PendingNonceAt(ctx, account) -} - -func (c *SimulatedBackendClient) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { - return c.b.NonceAt(ctx, account, blockNumber) -} - -func (c *SimulatedBackendClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { - return c.b.BalanceAt(ctx, account, blockNumber) -} - -type headSubscription struct { - unSub chan chan struct{} - subscription ethereum.Subscription -} - -var _ ethereum.Subscription = (*headSubscription)(nil) - -func (h *headSubscription) Unsubscribe() { - done := make(chan struct{}) - h.unSub <- done - <-done -} - -func (h *headSubscription) Err() <-chan error { return h.subscription.Err() } - -// SubscribeNewHead registers a subscription for push notifications of new blocks. -// Note the sim's API only accepts types.Head so we have this goroutine -// to convert those into evmtypes.Head. -func (c *SimulatedBackendClient) SubscribeNewHead( - ctx context.Context, - channel chan<- *evmtypes.Head, -) (ethereum.Subscription, error) { - subscription := &headSubscription{unSub: make(chan chan struct{})} - ch := make(chan *types.Header) - - var err error - subscription.subscription, err = c.b.SubscribeNewHead(ctx, ch) - if err != nil { - return nil, errors.Wrapf(err, "could not subscribe to new heads on "+ - "simulated backend") - } - go func() { - var lastHead *evmtypes.Head - for { - select { - case h := <-ch: - var head *evmtypes.Head - if h != nil { - head = &evmtypes.Head{Number: h.Number.Int64(), Hash: h.Hash(), ParentHash: h.ParentHash, Parent: lastHead, EVMChainID: utils.NewBig(c.chainId)} - lastHead = head - } - select { - case channel <- head: - case done := <-subscription.unSub: - subscription.subscription.Unsubscribe() - close(done) - return - } - - case done := <-subscription.unSub: - subscription.subscription.Unsubscribe() - close(done) - return - } - } - }() - return subscription, err -} - -func (c *SimulatedBackendClient) HeaderByNumber(ctx context.Context, n *big.Int) (*types.Header, error) { - return c.b.HeaderByNumber(ctx, n) -} - -func (c *SimulatedBackendClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { - sender, err := types.Sender(types.NewLondonSigner(c.chainId), tx) - if err != nil { - logger.TestLogger(c.t).Panic(fmt.Errorf("invalid transaction: %v (tx: %#v)", err, tx)) - } - pendingNonce, err := c.b.PendingNonceAt(ctx, sender) - if err != nil { - panic(fmt.Errorf("unable to determine nonce for account %s: %v", sender.Hex(), err)) - } - // the simulated backend does not gracefully handle tx rebroadcasts (gas bumping) so just - // ignore the situation where nonces are reused - // github.com/ethereum/go-ethereum/blob/fb2c79df1995b4e8dfe79f9c75464d29d23aaaf4/accounts/abi/bind/backends/simulated.go#L556 - if tx.Nonce() < pendingNonce { - return nil - } - - err = c.b.SendTransaction(ctx, tx) - return err -} - -func (c *SimulatedBackendClient) Call(result interface{}, method string, args ...interface{}) error { - return c.CallContext(context.Background(), result, method, args) -} - -func (c *SimulatedBackendClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { - return c.b.CallContract(ctx, msg, blockNumber) -} - -func (c *SimulatedBackendClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { - return c.b.CodeAt(ctx, account, blockNumber) -} - -func (c *SimulatedBackendClient) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { - return c.b.PendingCodeAt(ctx, account) -} - -func (c *SimulatedBackendClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { - return c.b.EstimateGas(ctx, call) -} - -func (c *SimulatedBackendClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { - panic("unimplemented") -} - -func (c *SimulatedBackendClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { - for i, elem := range b { - if elem.Method != "eth_getTransactionReceipt" || len(elem.Args) != 1 { - return errors.New("SimulatedBackendClient BatchCallContext only supports eth_getTransactionReceipt") - } - switch v := elem.Result.(type) { - case *bulletprooftxmanager.Receipt: - hash, is := elem.Args[0].(common.Hash) - if !is { - return errors.Errorf("SimulatedBackendClient expected arg to be a hash, got: %T", elem.Args[0]) - } - receipt, err := c.b.TransactionReceipt(ctx, hash) - b[i].Result = bulletprooftxmanager.FromGethReceipt(receipt) - b[i].Error = err - default: - return errors.Errorf("SimulatedBackendClient unsupported elem.Result type %T", v) - } - } - return nil -} - -func (c *SimulatedBackendClient) SuggestGasTipCap(ctx context.Context) (tipCap *big.Int, err error) { - return nil, nil -} - // Mine forces the simulated backend to produce a new block every 2 seconds func Mine(backend *backends.SimulatedBackend, blockTime time.Duration) (stopMining func()) { timer := time.NewTicker(blockTime)