diff --git a/compat_tests/compat_test.go b/compat_tests/compat_test.go index 7ac7e34667..c0aaca1898 100644 --- a/compat_tests/compat_test.go +++ b/compat_tests/compat_test.go @@ -5,12 +5,15 @@ import ( "context" "encoding/json" "fmt" + "math/big" "testing" "time" + "github.com/ethereum/go-ethereum" "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/ethclient" "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/require" ) @@ -29,11 +32,56 @@ func TestCompatibilityOfChain(t *testing.T) { defer cancel() c, err := rpc.DialContext(ctx, "http://localhost:8545") require.NoError(t, err) + + ec := ethclient.NewClient(c) + ctx, cancel = context.WithTimeout(context.Background(), time.Minute*5) + defer cancel() + id, err := ec.ChainID(ctx) + require.NoError(t, err) + require.Greater(t, id.Uint64(), uint64(0)) + + var res json.RawMessage startBlock := uint64(2800) amount := uint64(1000) incrementalLogs := make([]*types.Log, 0) for i := startBlock; i <= startBlock+amount; i++ { - res, err := rpcCall(c, dumpOutput, "eth_getBlockByNumber", hexutil.EncodeUint64(i), true) + ctx, cancel = context.WithTimeout(context.Background(), time.Minute) + defer cancel() + b, err := ec.BlockByNumber(ctx, big.NewInt(int64(i))) + require.NoError(t, err) + h, err := ec.HeaderByNumber(ctx, big.NewInt(int64(i))) + require.NoError(t, err) + + // Comparing the 2 headers directly doesn't work, they differ on unset + // and nil big ints, which represent the same thing but are technically + // not eaual. So instead we compare the marshalled output. + bhMarshalled, err := json.Marshal(b.Header()) + require.NoError(t, err) + hMarshalled, err := json.Marshal(h) + require.NoError(t, err) + require.Equal(t, bhMarshalled, hMarshalled) + + // Now get the header by hash and compare + h2, err := ec.HeaderByHash(ctx, h.Hash()) + require.NoError(t, err) + h2Marshalled, err := json.Marshal(h2) + require.NoError(t, err) + require.Equal(t, hMarshalled, h2Marshalled) + + // Now get the block by hash and compare + b2, err := ec.BlockByHash(ctx, b.Hash()) + require.NoError(t, err) + b2hMarshalled, err := json.Marshal(b2.Header()) + require.NoError(t, err) + require.Equal(t, bhMarshalled, b2hMarshalled) + + bbMarshalled, err := json.Marshal(b.Body()) + require.NoError(t, err) + b2bMarshalled, err := json.Marshal(b2.Body()) + require.NoError(t, err) + require.Equal(t, bbMarshalled, b2bMarshalled) + + res, err = rpcCall(c, dumpOutput, "eth_getBlockByNumber", hexutil.EncodeUint64(i), true) require.NoError(t, err) // Check we got a block require.NotEqual(t, "null", string(res), "block %d should not be null", i) @@ -43,8 +91,20 @@ func TestCompatibilityOfChain(t *testing.T) { txs := blockTransactions{} err = json.Unmarshal(res, &txs) require.NoError(t, err) + incrementalBlockReceipts := types.Receipts{} - for _, tx := range txs.Transactions { + for i, tx := range txs.Transactions { + // Compare transactions decoded from rpcCall with transactions from + // the the block returned by ethclient and those directly retrieved + // by ethclient. Comparing transactions directly does not work + // because of differing private fields. The hash is calculated over + // the data of the transaction and so serves as a good proxy for + // equality. + require.Equal(t, tx.Hash(), b.Transactions()[i].Hash()) + tx2, _, err := ec.TransactionByHash(ctx, tx.Hash()) + require.NoError(t, err) + require.Equal(t, tx.Hash(), tx2.Hash()) + _, err = rpcCall(c, dumpOutput, "eth_getTransactionByHash", tx.Hash()) require.NoError(t, err) res, err = rpcCall(c, dumpOutput, "eth_getTransactionReceipt", tx.Hash()) @@ -52,6 +112,12 @@ func TestCompatibilityOfChain(t *testing.T) { r := types.Receipt{} err = json.Unmarshal(res, &r) require.NoError(t, err) + + // Check receipt decoded from RPC call matches that returned directly from ethclient. + r2, err := ec.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) + require.Equal(t, r, *r2) + incrementalBlockReceipts = append(incrementalBlockReceipts, &r) incrementalLogs = append(incrementalLogs, r.Logs...) } @@ -83,7 +149,7 @@ func TestCompatibilityOfChain(t *testing.T) { // Get all logs for the range and compare with the logs extracted from receipts. from := rpc.BlockNumber(startBlock) to := rpc.BlockNumber(amount + startBlock) - res, err := rpcCall(c, dumpOutput, "eth_getLogs", filterQuery{ + res, err = rpcCall(c, dumpOutput, "eth_getLogs", filterQuery{ FromBlock: &from, ToBlock: &to, }) @@ -93,6 +159,18 @@ func TestCompatibilityOfChain(t *testing.T) { require.NoError(t, err) require.Equal(t, len(incrementalLogs), len(logs)) require.Equal(t, incrementalLogs, logs) + + // Compare logs with those retrived via ethclient + logs2, err := ec.FilterLogs(ctx, ethereum.FilterQuery{ + FromBlock: big.NewInt(int64(startBlock)), + ToBlock: big.NewInt(int64(startBlock + amount)), + }) + // ethclient returns non pointers to logs, convert to pointer form + logPointers := make([]*types.Log, len(logs2)) + for i := range logs2 { + logPointers[i] = &logs2[i] + } + require.Equal(t, logPointers, logs) } type filterQuery struct { diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index e8a201f71b..9596eddb94 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -150,7 +150,10 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 { return nil, errors.New("server returned non-empty uncle list but block header indicates no uncles") } - if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 { + // In celo before the ginerbread hardfork, blocks had no uncles hash and no + // uncles, so in those cases it is legitimate to have an empty uncles hash. + var emptyHash common.Hash + if head.UncleHash != emptyHash && head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 { return nil, errors.New("server returned empty uncle list but block header indicates uncles") } if head.TxHash == types.EmptyTxsHash && len(body.Transactions) > 0 {