eth_client_test.go 6.95 KB
Newer Older
1 2 3 4
package sources

import (
	"context"
5
	crand "crypto/rand"
6
	"math/big"
7
	"math/rand"
8 9
	"testing"

10 11 12
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"

13
	"github.com/ethereum/go-ethereum"
14 15 16 17
	"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"
18 19

	"github.com/ethereum-optimism/optimism/op-node/rollup"
Sabnock01's avatar
Sabnock01 committed
20
	"github.com/ethereum-optimism/optimism/op-service/client"
21
	"github.com/ethereum-optimism/optimism/op-service/eth"
22
	"github.com/ethereum-optimism/optimism/op-service/sources/caching"
23 24 25 26 27 28 29 30 31 32
)

type mockRPC struct {
	mock.Mock
}

func (m *mockRPC) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
	return m.MethodCalled("BatchCallContext", ctx, b).Get(0).([]error)[0]
}

33
func (m *mockRPC) CallContext(ctx context.Context, result any, method string, args ...any) error {
34 35 36
	return m.MethodCalled("CallContext", ctx, result, method, args).Get(0).([]error)[0]
}

37
func (m *mockRPC) EthSubscribe(ctx context.Context, channel any, args ...any) (ethereum.Subscription, error) {
38 39 40 41 42 43 44 45 46 47 48 49 50 51
	called := m.MethodCalled("EthSubscribe", channel, args)
	return called.Get(0).(*rpc.ClientSubscription), called.Get(1).([]error)[0]
}

func (m *mockRPC) Close() {
	m.MethodCalled("Close")
}

var _ client.RPC = (*mockRPC)(nil)

var testEthClientConfig = &EthClientConfig{
	ReceiptsCacheSize:     10,
	TransactionsCacheSize: 10,
	HeadersCacheSize:      10,
52
	PayloadsCacheSize:     10,
53 54 55
	MaxRequestsPerBatch:   20,
	MaxConcurrentRequests: 10,
	TrustRPC:              false,
56
	MustBePostMerge:       false,
57
	RPCProviderKind:       RPCKindStandard,
58 59 60
}

func randHash() (out common.Hash) {
61
	_, _ = crand.Read(out[:])
62 63 64
	return out
}

65
func randHeader() (*types.Header, *RPCHeader) {
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
	hdr := &types.Header{
		ParentHash:  randHash(),
		UncleHash:   randHash(),
		Coinbase:    common.Address{},
		Root:        randHash(),
		TxHash:      randHash(),
		ReceiptHash: randHash(),
		Bloom:       types.Bloom{},
		Difficulty:  big.NewInt(42),
		Number:      big.NewInt(1234),
		GasLimit:    0,
		GasUsed:     0,
		Time:        123456,
		Extra:       make([]byte, 0),
		MixDigest:   randHash(),
		Nonce:       types.BlockNonce{},
		BaseFee:     big.NewInt(100),
	}
84
	rhdr := &RPCHeader{
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
		ParentHash:  hdr.ParentHash,
		UncleHash:   hdr.UncleHash,
		Coinbase:    hdr.Coinbase,
		Root:        hdr.Root,
		TxHash:      hdr.TxHash,
		ReceiptHash: hdr.ReceiptHash,
		Bloom:       eth.Bytes256(hdr.Bloom),
		Difficulty:  *(*hexutil.Big)(hdr.Difficulty),
		Number:      hexutil.Uint64(hdr.Number.Uint64()),
		GasLimit:    hexutil.Uint64(hdr.GasLimit),
		GasUsed:     hexutil.Uint64(hdr.GasUsed),
		Time:        hexutil.Uint64(hdr.Time),
		Extra:       hdr.Extra,
		MixDigest:   hdr.MixDigest,
		Nonce:       hdr.Nonce,
		BaseFee:     (*hexutil.Big)(hdr.BaseFee),
		Hash:        hdr.Hash(),
102 103 104 105 106 107 108
	}
	return hdr, rhdr
}

func TestEthClient_InfoByHash(t *testing.T) {
	m := new(mockRPC)
	_, rhdr := randHeader()
109
	expectedInfo, _ := rhdr.Info(true, false)
110
	ctx := context.Background()
111
	m.On("CallContext", ctx, new(*RPCHeader),
112
		"eth_getBlockByHash", []any{rhdr.Hash, false}).Run(func(args mock.Arguments) {
113
		*args[1].(**RPCHeader) = rhdr
114 115 116
	}).Return([]error{nil})
	s, err := NewEthClient(m, nil, nil, testEthClientConfig)
	require.NoError(t, err)
117
	info, err := s.InfoByHash(ctx, rhdr.Hash)
118 119 120 121
	require.NoError(t, err)
	require.Equal(t, info, expectedInfo)
	m.Mock.AssertExpectations(t)
	// Again, without expecting any calls from the mock, the cache will return the block
122
	info, err = s.InfoByHash(ctx, rhdr.Hash)
123 124 125 126 127 128 129 130
	require.NoError(t, err)
	require.Equal(t, info, expectedInfo)
	m.Mock.AssertExpectations(t)
}

func TestEthClient_InfoByNumber(t *testing.T) {
	m := new(mockRPC)
	_, rhdr := randHeader()
131 132
	expectedInfo, _ := rhdr.Info(true, false)
	n := rhdr.Number
133
	ctx := context.Background()
134
	m.On("CallContext", ctx, new(*RPCHeader),
135
		"eth_getBlockByNumber", []any{n.String(), false}).Run(func(args mock.Arguments) {
136
		*args[1].(**RPCHeader) = rhdr
137
	}).Return([]error{nil})
138
	s, err := NewL1Client(m, nil, nil, L1ClientDefaultConfig(&rollup.Config{SeqWindowSize: 10}, true, RPCKindStandard))
139
	require.NoError(t, err)
140
	info, err := s.InfoByNumber(ctx, uint64(n))
141 142 143 144
	require.NoError(t, err)
	require.Equal(t, info, expectedInfo)
	m.Mock.AssertExpectations(t)
}
145 146 147 148 149 150 151 152

func TestEthClient_WrongInfoByNumber(t *testing.T) {
	m := new(mockRPC)
	_, rhdr := randHeader()
	rhdr2 := *rhdr
	rhdr2.Number += 1
	n := rhdr.Number
	ctx := context.Background()
153
	m.On("CallContext", ctx, new(*RPCHeader),
154
		"eth_getBlockByNumber", []any{n.String(), false}).Run(func(args mock.Arguments) {
155
		*args[1].(**RPCHeader) = &rhdr2
156
	}).Return([]error{nil})
157
	s, err := NewL1Client(m, nil, nil, L1ClientDefaultConfig(&rollup.Config{SeqWindowSize: 10}, true, RPCKindStandard))
158 159 160 161 162 163 164 165 166 167 168 169 170 171
	require.NoError(t, err)
	_, err = s.InfoByNumber(ctx, uint64(n))
	require.Error(t, err, "cannot accept the wrong block")
	m.Mock.AssertExpectations(t)
}

func TestEthClient_WrongInfoByHash(t *testing.T) {
	m := new(mockRPC)
	_, rhdr := randHeader()
	rhdr2 := *rhdr
	rhdr2.Root[0] += 1
	rhdr2.Hash = rhdr2.computeBlockHash()
	k := rhdr.Hash
	ctx := context.Background()
172
	m.On("CallContext", ctx, new(*RPCHeader),
173
		"eth_getBlockByHash", []any{k, false}).Run(func(args mock.Arguments) {
174
		*args[1].(**RPCHeader) = &rhdr2
175
	}).Return([]error{nil})
176
	s, err := NewL1Client(m, nil, nil, L1ClientDefaultConfig(&rollup.Config{SeqWindowSize: 10}, true, RPCKindStandard))
177 178 179 180 181
	require.NoError(t, err)
	_, err = s.InfoByHash(ctx, k)
	require.Error(t, err, "cannot accept the wrong block")
	m.Mock.AssertExpectations(t)
}
182

183 184 185 186 187
func newEthClientWithCaches(metrics caching.Metrics, cacheSize int) *EthClient {
	return &EthClient{
		transactionsCache: caching.NewLRUCache[common.Hash, types.Transactions](metrics, "txs", cacheSize),
		headersCache:      caching.NewLRUCache[common.Hash, eth.BlockInfo](metrics, "headers", cacheSize),
		payloadsCache:     caching.NewLRUCache[common.Hash, *eth.ExecutionPayloadEnvelope](metrics, "payloads", cacheSize),
188 189 190
	}
}

191 192
// TestReceiptValidation tests that the receipt validation is performed by the underlying RPCReceiptsFetcher
func TestReceiptValidation(t *testing.T) {
193 194
	require := require.New(t)
	mrpc := new(mockRPC)
195 196 197 198
	rp := NewRPCReceiptsFetcher(mrpc, nil, RPCReceiptsConfig{})
	const numTxs = 1
	block, _ := randomRpcBlockAndReceipts(rand.New(rand.NewSource(420)), numTxs)
	//txHashes := receiptTxHashes(receipts)
199 200
	ctx := context.Background()

201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
	mrpc.On("CallContext",
		ctx,
		mock.Anything,
		"eth_getTransactionReceipt",
		mock.Anything).
		Run(func(args mock.Arguments) {
		}).
		Return([]error{nil})

	// when the block is requested, the block is returned
	mrpc.On("CallContext",
		ctx,
		mock.Anything,
		"eth_getBlockByHash",
		mock.Anything).
216
		Run(func(args mock.Arguments) {
217
			*(args[1].(**RPCBlock)) = block
218
		}).
219
		Return([]error{nil})
220 221 222

	ethcl := newEthClientWithCaches(nil, numTxs)
	ethcl.client = mrpc
223 224
	ethcl.recProvider = rp
	ethcl.trustRPC = true
225

226 227
	_, _, err := ethcl.FetchReceipts(ctx, block.Hash)
	require.ErrorContains(err, "unexpected nil block number")
228
}