Commit 9d2e72cb authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into jg/ci

parents 71611d2c 6e524df2
...@@ -58,12 +58,14 @@ type Metrics struct { ...@@ -58,12 +58,14 @@ type Metrics struct {
PendingBlocksCount prometheus.GaugeVec PendingBlocksCount prometheus.GaugeVec
BlocksAddedCount prometheus.Gauge BlocksAddedCount prometheus.Gauge
ChannelInputBytes prometheus.GaugeVec ChannelInputBytes prometheus.GaugeVec
ChannelReadyBytes prometheus.Gauge ChannelReadyBytes prometheus.Gauge
ChannelOutputBytes prometheus.Gauge ChannelOutputBytes prometheus.Gauge
ChannelClosedReason prometheus.Gauge ChannelClosedReason prometheus.Gauge
ChannelNumFrames prometheus.Gauge ChannelNumFrames prometheus.Gauge
ChannelComprRatio prometheus.Histogram ChannelComprRatio prometheus.Histogram
ChannelInputBytesTotal prometheus.Counter
ChannelOutputBytesTotal prometheus.Counter
BatcherTxEvs opmetrics.EventVec BatcherTxEvs opmetrics.EventVec
} }
...@@ -144,6 +146,16 @@ func NewMetrics(procName string) *Metrics { ...@@ -144,6 +146,16 @@ func NewMetrics(procName string) *Metrics {
Help: "Compression ratios of closed channel.", Help: "Compression ratios of closed channel.",
Buckets: append([]float64{0.1, 0.2}, prometheus.LinearBuckets(0.3, 0.05, 14)...), Buckets: append([]float64{0.1, 0.2}, prometheus.LinearBuckets(0.3, 0.05, 14)...),
}), }),
ChannelInputBytesTotal: factory.NewCounter(prometheus.CounterOpts{
Namespace: ns,
Name: "input_bytes_total",
Help: "Total number of bytes to a channel.",
}),
ChannelOutputBytesTotal: factory.NewCounter(prometheus.CounterOpts{
Namespace: ns,
Name: "output_bytes_total",
Help: "Total number of compressed output bytes from a channel.",
}),
BatcherTxEvs: opmetrics.NewEventVec(factory, ns, "", "batcher_tx", "BatcherTx", []string{"stage"}), BatcherTxEvs: opmetrics.NewEventVec(factory, ns, "", "batcher_tx", "BatcherTx", []string{"stage"}),
} }
...@@ -219,6 +231,8 @@ func (m *Metrics) RecordChannelClosed(id derive.ChannelID, numPendingBlocks int, ...@@ -219,6 +231,8 @@ func (m *Metrics) RecordChannelClosed(id derive.ChannelID, numPendingBlocks int,
m.ChannelNumFrames.Set(float64(numFrames)) m.ChannelNumFrames.Set(float64(numFrames))
m.ChannelInputBytes.WithLabelValues(StageClosed).Set(float64(inputBytes)) m.ChannelInputBytes.WithLabelValues(StageClosed).Set(float64(inputBytes))
m.ChannelOutputBytes.Set(float64(outputComprBytes)) m.ChannelOutputBytes.Set(float64(outputComprBytes))
m.ChannelInputBytesTotal.Add(float64(inputBytes))
m.ChannelOutputBytesTotal.Add(float64(outputComprBytes))
var comprRatio float64 var comprRatio float64
if inputBytes > 0 { if inputBytes > 0 {
......
...@@ -191,7 +191,7 @@ func TestL2EngineAPIFail(gt *testing.T) { ...@@ -191,7 +191,7 @@ func TestL2EngineAPIFail(gt *testing.T) {
} }
func TestEngineAPITests(t *testing.T) { func TestEngineAPITests(t *testing.T) {
test.RunEngineAPITests(t, func() engineapi.EngineBackend { test.RunEngineAPITests(t, func(t *testing.T) engineapi.EngineBackend {
jwtPath := e2eutils.WriteDefaultJWT(t) jwtPath := e2eutils.WriteDefaultJWT(t)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
......
...@@ -24,6 +24,7 @@ type IterativeBatchCall[K any, V any] struct { ...@@ -24,6 +24,7 @@ type IterativeBatchCall[K any, V any] struct {
makeRequest func(K) (V, rpc.BatchElem) makeRequest func(K) (V, rpc.BatchElem)
getBatch BatchCallContextFn getBatch BatchCallContextFn
getSingle CallContextFn
requestsValues []V requestsValues []V
scheduled chan rpc.BatchElem scheduled chan rpc.BatchElem
...@@ -35,6 +36,7 @@ func NewIterativeBatchCall[K any, V any]( ...@@ -35,6 +36,7 @@ func NewIterativeBatchCall[K any, V any](
requestsKeys []K, requestsKeys []K,
makeRequest func(K) (V, rpc.BatchElem), makeRequest func(K) (V, rpc.BatchElem),
getBatch BatchCallContextFn, getBatch BatchCallContextFn,
getSingle CallContextFn,
batchSize int) *IterativeBatchCall[K, V] { batchSize int) *IterativeBatchCall[K, V] {
if len(requestsKeys) < batchSize { if len(requestsKeys) < batchSize {
...@@ -47,6 +49,7 @@ func NewIterativeBatchCall[K any, V any]( ...@@ -47,6 +49,7 @@ func NewIterativeBatchCall[K any, V any](
out := &IterativeBatchCall[K, V]{ out := &IterativeBatchCall[K, V]{
completed: 0, completed: 0,
getBatch: getBatch, getBatch: getBatch,
getSingle: getSingle,
requestsKeys: requestsKeys, requestsKeys: requestsKeys,
batchSize: batchSize, batchSize: batchSize,
makeRequest: makeRequest, makeRequest: makeRequest,
...@@ -119,11 +122,23 @@ func (ibc *IterativeBatchCall[K, V]) Fetch(ctx context.Context) error { ...@@ -119,11 +122,23 @@ func (ibc *IterativeBatchCall[K, V]) Fetch(ctx context.Context) error {
break break
} }
if err := ibc.getBatch(ctx, batch); err != nil { if len(batch) == 0 {
for _, r := range batch { return nil
ibc.scheduled <- r }
if ibc.batchSize == 1 {
first := batch[0]
if err := ibc.getSingle(ctx, &first.Result, first.Method, first.Args...); err != nil {
ibc.scheduled <- first
return err
}
} else {
if err := ibc.getBatch(ctx, batch); err != nil {
for _, r := range batch {
ibc.scheduled <- r
}
return fmt.Errorf("failed batch-retrieval: %w", err)
} }
return fmt.Errorf("failed batch-retrieval: %w", err)
} }
var result error var result error
for _, elem := range batch { for _, elem := range batch {
......
...@@ -34,7 +34,8 @@ type batchTestCase struct { ...@@ -34,7 +34,8 @@ type batchTestCase struct {
batchSize int batchSize int
batchCalls []batchCall batchCalls []batchCall
singleCalls []elemCall
mock.Mock mock.Mock
} }
...@@ -53,7 +54,14 @@ func (tc *batchTestCase) GetBatch(ctx context.Context, b []rpc.BatchElem) error ...@@ -53,7 +54,14 @@ func (tc *batchTestCase) GetBatch(ctx context.Context, b []rpc.BatchElem) error
if ctx.Err() != nil { if ctx.Err() != nil {
return ctx.Err() return ctx.Err()
} }
return tc.Mock.MethodCalled("get", b).Get(0).([]error)[0] return tc.Mock.MethodCalled("getBatch", b).Get(0).([]error)[0]
}
func (tc *batchTestCase) GetSingle(ctx context.Context, result any, method string, args ...any) error {
if ctx.Err() != nil {
return ctx.Err()
}
return tc.Mock.MethodCalled("getSingle", (*(result.(*interface{}))).(*string), method, args[0]).Get(0).([]error)[0]
} }
var mockErr = errors.New("mockErr") var mockErr = errors.New("mockErr")
...@@ -64,7 +72,7 @@ func (tc *batchTestCase) Run(t *testing.T) { ...@@ -64,7 +72,7 @@ func (tc *batchTestCase) Run(t *testing.T) {
keys[i] = i keys[i] = i
} }
makeMock := func(bci int, bc batchCall) func(args mock.Arguments) { makeBatchMock := func(bci int, bc batchCall) func(args mock.Arguments) {
return func(args mock.Arguments) { return func(args mock.Arguments) {
batch := args[0].([]rpc.BatchElem) batch := args[0].([]rpc.BatchElem)
for i, elem := range batch { for i, elem := range batch {
...@@ -94,10 +102,30 @@ func (tc *batchTestCase) Run(t *testing.T) { ...@@ -94,10 +102,30 @@ func (tc *batchTestCase) Run(t *testing.T) {
}) })
} }
if len(bc.elems) > 0 { if len(bc.elems) > 0 {
tc.On("get", batch).Once().Run(makeMock(bci, bc)).Return([]error{bc.rpcErr}) // wrap to preserve nil as type of error tc.On("getBatch", batch).Once().Run(makeBatchMock(bci, bc)).Return([]error{bc.rpcErr}) // wrap to preserve nil as type of error
}
}
makeSingleMock := func(eci int, ec elemCall) func(args mock.Arguments) {
return func(args mock.Arguments) {
result := args[0].(*string)
id := args[2].(int)
require.Equal(t, ec.id, id, "element should match expected element")
if ec.err {
*result = ""
} else {
*result = fmt.Sprintf("mock result id %d", id)
}
} }
} }
iter := NewIterativeBatchCall[int, *string](keys, makeTestRequest, tc.GetBatch, tc.batchSize) // mock the results of unbatched calls
for eci, ec := range tc.singleCalls {
var ret error
if ec.err {
ret = mockErr
}
tc.On("getSingle", new(string), "testing_foobar", ec.id).Once().Run(makeSingleMock(eci, ec)).Return([]error{ret})
}
iter := NewIterativeBatchCall[int, *string](keys, makeTestRequest, tc.GetBatch, tc.GetSingle, tc.batchSize)
for i, bc := range tc.batchCalls { for i, bc := range tc.batchCalls {
ctx := context.Background() ctx := context.Background()
if bc.makeCtx != nil { if bc.makeCtx != nil {
...@@ -116,6 +144,20 @@ func (tc *batchTestCase) Run(t *testing.T) { ...@@ -116,6 +144,20 @@ func (tc *batchTestCase) Run(t *testing.T) {
} }
} }
} }
for i, ec := range tc.singleCalls {
ctx := context.Background()
err := iter.Fetch(ctx)
if err == io.EOF {
require.Equal(t, i, len(tc.singleCalls)-1, "EOF only on last call")
} else {
require.False(t, iter.Complete())
if ec.err {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}
}
require.True(t, iter.Complete(), "batch iter should be complete after the expected calls") require.True(t, iter.Complete(), "batch iter should be complete after the expected calls")
out, err := iter.Result() out, err := iter.Result()
require.NoError(t, err) require.NoError(t, err)
...@@ -154,6 +196,37 @@ func TestFetchBatched(t *testing.T) { ...@@ -154,6 +196,37 @@ func TestFetchBatched(t *testing.T) {
}, },
}, },
}, },
{
name: "single element",
items: 1,
batchSize: 4,
singleCalls: []elemCall{
{id: 0, err: false},
},
},
{
name: "unbatched",
items: 4,
batchSize: 1,
singleCalls: []elemCall{
{id: 0, err: false},
{id: 1, err: false},
{id: 2, err: false},
{id: 3, err: false},
},
},
{
name: "unbatched with retry",
items: 4,
batchSize: 1,
singleCalls: []elemCall{
{id: 0, err: false},
{id: 1, err: true},
{id: 2, err: false},
{id: 3, err: false},
{id: 1, err: false},
},
},
{ {
name: "split", name: "split",
items: 5, items: 5,
...@@ -240,7 +313,7 @@ func TestFetchBatched(t *testing.T) { ...@@ -240,7 +313,7 @@ func TestFetchBatched(t *testing.T) {
}, },
{ {
name: "context timeout", name: "context timeout",
items: 1, items: 2,
batchSize: 3, batchSize: 3,
batchCalls: []batchCall{ batchCalls: []batchCall{
{ {
...@@ -255,6 +328,7 @@ func TestFetchBatched(t *testing.T) { ...@@ -255,6 +328,7 @@ func TestFetchBatched(t *testing.T) {
{ {
elems: []elemCall{ elems: []elemCall{
{id: 0, err: false}, {id: 0, err: false},
{id: 1, err: false},
}, },
err: "", err: "",
}, },
......
...@@ -373,6 +373,7 @@ func (job *receiptsFetchingJob) runFetcher(ctx context.Context) error { ...@@ -373,6 +373,7 @@ func (job *receiptsFetchingJob) runFetcher(ctx context.Context) error {
job.txHashes, job.txHashes,
makeReceiptRequest, makeReceiptRequest,
job.client.BatchCallContext, job.client.BatchCallContext,
job.client.CallContext,
job.maxBatchSize, job.maxBatchSize,
) )
} }
......
...@@ -18,14 +18,15 @@ import ( ...@@ -18,14 +18,15 @@ import (
) )
type OracleBackedL2Chain struct { type OracleBackedL2Chain struct {
log log.Logger log log.Logger
oracle Oracle oracle Oracle
chainCfg *params.ChainConfig chainCfg *params.ChainConfig
engine consensus.Engine engine consensus.Engine
head *types.Header oracleHead *types.Header
safe *types.Header head *types.Header
finalized *types.Header safe *types.Header
vmCfg vm.Config finalized *types.Header
vmCfg vm.Config
// Inserted blocks // Inserted blocks
blocks map[common.Hash]*types.Block blocks map[common.Hash]*types.Block
...@@ -44,11 +45,12 @@ func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, chainCfg *params.C ...@@ -44,11 +45,12 @@ func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, chainCfg *params.C
engine: beacon.New(nil), engine: beacon.New(nil),
// Treat the agreed starting head as finalized - nothing before it can be disputed // Treat the agreed starting head as finalized - nothing before it can be disputed
head: head.Header(), head: head.Header(),
safe: head.Header(), safe: head.Header(),
finalized: head.Header(), finalized: head.Header(),
blocks: make(map[common.Hash]*types.Block), oracleHead: head.Header(),
db: NewOracleBackedDB(oracle), blocks: make(map[common.Hash]*types.Block),
db: NewOracleBackedDB(oracle),
}, nil }, nil
} }
...@@ -82,11 +84,7 @@ func (o *OracleBackedL2Chain) CurrentFinalBlock() *types.Header { ...@@ -82,11 +84,7 @@ func (o *OracleBackedL2Chain) CurrentFinalBlock() *types.Header {
} }
func (o *OracleBackedL2Chain) GetHeaderByHash(hash common.Hash) *types.Header { func (o *OracleBackedL2Chain) GetHeaderByHash(hash common.Hash) *types.Header {
block := o.GetBlockByHash(hash) return o.GetBlockByHash(hash).Header()
if block == nil {
return nil
}
return block.Header()
} }
func (o *OracleBackedL2Chain) GetBlockByHash(hash common.Hash) *types.Block { func (o *OracleBackedL2Chain) GetBlockByHash(hash common.Hash) *types.Block {
...@@ -96,15 +94,18 @@ func (o *OracleBackedL2Chain) GetBlockByHash(hash common.Hash) *types.Block { ...@@ -96,15 +94,18 @@ func (o *OracleBackedL2Chain) GetBlockByHash(hash common.Hash) *types.Block {
return block return block
} }
// Retrieve from the oracle // Retrieve from the oracle
block = o.oracle.BlockByHash(hash) return o.oracle.BlockByHash(hash)
if block == nil {
return nil
}
return block
} }
func (o *OracleBackedL2Chain) GetBlock(hash common.Hash, number uint64) *types.Block { func (o *OracleBackedL2Chain) GetBlock(hash common.Hash, number uint64) *types.Block {
block := o.GetBlockByHash(hash) var block *types.Block
if o.oracleHead.Number.Uint64() < number {
// For blocks above the chain head, only consider newly built blocks
// Avoids requesting an unknown block from the oracle which would panic.
block = o.blocks[hash]
} else {
block = o.GetBlockByHash(hash)
}
if block == nil { if block == nil {
return nil return nil
} }
...@@ -116,9 +117,6 @@ func (o *OracleBackedL2Chain) GetBlock(hash common.Hash, number uint64) *types.B ...@@ -116,9 +117,6 @@ func (o *OracleBackedL2Chain) GetBlock(hash common.Hash, number uint64) *types.B
func (o *OracleBackedL2Chain) GetHeader(hash common.Hash, u uint64) *types.Header { func (o *OracleBackedL2Chain) GetHeader(hash common.Hash, u uint64) *types.Header {
block := o.GetBlock(hash, u) block := o.GetBlock(hash, u)
if block == nil {
return nil
}
return block.Header() return block.Header()
} }
......
...@@ -42,17 +42,6 @@ func TestGetBlocks(t *testing.T) { ...@@ -42,17 +42,6 @@ func TestGetBlocks(t *testing.T) {
} }
} }
func TestUnknownBlock(t *testing.T) {
_, chain := setupOracleBackedChain(t, 1)
hash := common.HexToHash("0x556677881122")
blockNumber := uint64(1)
require.Nil(t, chain.GetBlockByHash(hash))
require.Nil(t, chain.GetHeaderByHash(hash))
require.Nil(t, chain.GetBlock(hash, blockNumber))
require.Nil(t, chain.GetHeader(hash, blockNumber))
require.False(t, chain.HasBlockAndState(hash, blockNumber))
}
func TestCanonicalHashNotFoundPastChainHead(t *testing.T) { func TestCanonicalHashNotFoundPastChainHead(t *testing.T) {
blocks, chain := setupOracleBackedChainWithLowerHead(t, 5, 3) blocks, chain := setupOracleBackedChainWithLowerHead(t, 5, 3)
...@@ -69,7 +58,7 @@ func TestCanonicalHashNotFoundPastChainHead(t *testing.T) { ...@@ -69,7 +58,7 @@ func TestCanonicalHashNotFoundPastChainHead(t *testing.T) {
func TestAppendToChain(t *testing.T) { func TestAppendToChain(t *testing.T) {
blocks, chain := setupOracleBackedChainWithLowerHead(t, 4, 3) blocks, chain := setupOracleBackedChainWithLowerHead(t, 4, 3)
newBlock := blocks[4] newBlock := blocks[4]
require.Nil(t, chain.GetBlockByHash(newBlock.Hash()), "block unknown before being added") require.Nil(t, chain.GetBlock(newBlock.Hash(), newBlock.NumberU64()), "block unknown before being added")
require.NoError(t, chain.InsertBlockWithoutSetHead(newBlock)) require.NoError(t, chain.InsertBlockWithoutSetHead(newBlock))
require.Equal(t, blocks[3].Header(), chain.CurrentHeader(), "should not update chain head yet") require.Equal(t, blocks[3].Header(), chain.CurrentHeader(), "should not update chain head yet")
...@@ -113,9 +102,7 @@ func TestUpdateStateDatabaseWhenImportingBlock(t *testing.T) { ...@@ -113,9 +102,7 @@ func TestUpdateStateDatabaseWhenImportingBlock(t *testing.T) {
require.NotEqual(t, blocks[1].Root(), newBlock.Root(), "block should have modified world state") require.NotEqual(t, blocks[1].Root(), newBlock.Root(), "block should have modified world state")
require.Panics(t, func() { require.False(t, chain.HasBlockAndState(newBlock.Root(), newBlock.NumberU64()), "state from non-imported block should not be available")
_, _ = chain.StateAt(newBlock.Root())
}, "state from non-imported block should not be available")
err = chain.InsertBlockWithoutSetHead(newBlock) err = chain.InsertBlockWithoutSetHead(newBlock)
require.NoError(t, err) require.NoError(t, err)
...@@ -181,7 +168,7 @@ func setupOracle(t *testing.T, blockCount int, headBlockNumber int) (*params.Cha ...@@ -181,7 +168,7 @@ func setupOracle(t *testing.T, blockCount int, headBlockNumber int) (*params.Cha
genesisBlock := l2Genesis.MustCommit(db) genesisBlock := l2Genesis.MustCommit(db)
blocks, _ := core.GenerateChain(chainCfg, genesisBlock, consensus, db, blockCount, func(i int, gen *core.BlockGen) {}) blocks, _ := core.GenerateChain(chainCfg, genesisBlock, consensus, db, blockCount, func(i int, gen *core.BlockGen) {})
blocks = append([]*types.Block{genesisBlock}, blocks...) blocks = append([]*types.Block{genesisBlock}, blocks...)
oracle := newStubBlockOracle(blocks[:headBlockNumber+1], db) oracle := newStubBlockOracle(t, blocks[:headBlockNumber+1], db)
return chainCfg, blocks, oracle return chainCfg, blocks, oracle
} }
...@@ -213,23 +200,27 @@ type stubBlockOracle struct { ...@@ -213,23 +200,27 @@ type stubBlockOracle struct {
kvStateOracle kvStateOracle
} }
func newStubBlockOracle(chain []*types.Block, db ethdb.Database) *stubBlockOracle { func newStubBlockOracle(t *testing.T, chain []*types.Block, db ethdb.Database) *stubBlockOracle {
blocks := make(map[common.Hash]*types.Block, len(chain)) blocks := make(map[common.Hash]*types.Block, len(chain))
for _, block := range chain { for _, block := range chain {
blocks[block.Hash()] = block blocks[block.Hash()] = block
} }
return &stubBlockOracle{ return &stubBlockOracle{
blocks: blocks, blocks: blocks,
kvStateOracle: kvStateOracle{source: db}, kvStateOracle: kvStateOracle{t: t, source: db},
} }
} }
func (o stubBlockOracle) BlockByHash(blockHash common.Hash) *types.Block { func (o stubBlockOracle) BlockByHash(blockHash common.Hash) *types.Block {
return o.blocks[blockHash] block, ok := o.blocks[blockHash]
if !ok {
o.t.Fatalf("requested unknown block %s", blockHash)
}
return block
} }
func TestEngineAPITests(t *testing.T) { func TestEngineAPITests(t *testing.T) {
test.RunEngineAPITests(t, func() engineapi.EngineBackend { test.RunEngineAPITests(t, func(t *testing.T) engineapi.EngineBackend {
_, chain := setupOracleBackedChain(t, 0) _, chain := setupOracleBackedChain(t, 0)
return chain return chain
}) })
......
...@@ -301,7 +301,7 @@ func (ea *L2EngineAPI) NewPayloadV1(ctx context.Context, payload *eth.ExecutionP ...@@ -301,7 +301,7 @@ func (ea *L2EngineAPI) NewPayloadV1(ctx context.Context, payload *eth.ExecutionP
} }
// If we already have the block locally, ignore the entire execution and just // If we already have the block locally, ignore the entire execution and just
// return a fake success. // return a fake success.
if block := ea.backend.GetBlockByHash(payload.BlockHash); block != nil { if block := ea.backend.GetBlock(payload.BlockHash, uint64(payload.BlockNumber)); block != nil {
ea.log.Warn("Ignoring already known beacon payload", "number", payload.BlockNumber, "hash", payload.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) ea.log.Warn("Ignoring already known beacon payload", "number", payload.BlockNumber, "hash", payload.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
hash := block.Hash() hash := block.Hash()
return &eth.PayloadStatusV1{Status: eth.ExecutionValid, LatestValidHash: &hash}, nil return &eth.PayloadStatusV1{Status: eth.ExecutionValid, LatestValidHash: &hash}, nil
......
...@@ -18,7 +18,7 @@ import ( ...@@ -18,7 +18,7 @@ import (
var gasLimit = eth.Uint64Quantity(30_000_000) var gasLimit = eth.Uint64Quantity(30_000_000)
var feeRecipient = common.Address{} var feeRecipient = common.Address{}
func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBackend) { func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.EngineBackend) {
t.Run("CreateBlock", func(t *testing.T) { t.Run("CreateBlock", func(t *testing.T) {
api := newTestHelper(t, createBackend) api := newTestHelper(t, createBackend)
...@@ -292,10 +292,10 @@ type testHelper struct { ...@@ -292,10 +292,10 @@ type testHelper struct {
assert *require.Assertions assert *require.Assertions
} }
func newTestHelper(t *testing.T, createBackend func() engineapi.EngineBackend) *testHelper { func newTestHelper(t *testing.T, createBackend func(t *testing.T) engineapi.EngineBackend) *testHelper {
logger := testlog.Logger(t, log.LvlDebug) logger := testlog.Logger(t, log.LvlDebug)
ctx := context.Background() ctx := context.Background()
backend := createBackend() backend := createBackend(t)
api := engineapi.NewL2EngineAPI(logger, backend) api := engineapi.NewL2EngineAPI(logger, backend)
test := &testHelper{ test := &testHelper{
t: t, t: t,
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
...@@ -24,19 +25,25 @@ type CallContext interface { ...@@ -24,19 +25,25 @@ type CallContext interface {
type FetchingL2Oracle struct { type FetchingL2Oracle struct {
ctx context.Context ctx context.Context
logger log.Logger logger log.Logger
head eth.BlockInfo
blockSource BlockSource blockSource BlockSource
callContext CallContext callContext CallContext
} }
func NewFetchingL2Oracle(ctx context.Context, logger log.Logger, l2Url string) (*FetchingL2Oracle, error) { func NewFetchingL2Oracle(ctx context.Context, logger log.Logger, l2Url string, l2Head common.Hash) (*FetchingL2Oracle, error) {
rpcClient, err := rpc.Dial(l2Url) rpcClient, err := rpc.Dial(l2Url)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ethClient := ethclient.NewClient(rpcClient) ethClient := ethclient.NewClient(rpcClient)
head, err := ethClient.HeaderByHash(ctx, l2Head)
if err != nil {
return nil, fmt.Errorf("retrieve l2 head %v: %w", l2Head, err)
}
return &FetchingL2Oracle{ return &FetchingL2Oracle{
ctx: ctx, ctx: ctx,
logger: logger, logger: logger,
head: eth.HeaderBlockInfo(head),
blockSource: ethClient, blockSource: ethClient,
callContext: rpcClient, callContext: rpcClient,
}, nil }, nil
...@@ -78,5 +85,8 @@ func (o *FetchingL2Oracle) BlockByHash(blockHash common.Hash) *types.Block { ...@@ -78,5 +85,8 @@ func (o *FetchingL2Oracle) BlockByHash(blockHash common.Hash) *types.Block {
if err != nil { if err != nil {
panic(fmt.Errorf("fetch block %s: %w", blockHash.Hex(), err)) panic(fmt.Errorf("fetch block %s: %w", blockHash.Hex(), err))
} }
if block.NumberU64() > o.head.NumberU64() {
panic(fmt.Errorf("fetched block %v number %d above head block number %d", blockHash, block.NumberU64(), o.head.NumberU64()))
}
return block return block
} }
...@@ -5,12 +5,14 @@ import ( ...@@ -5,12 +5,14 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/big"
"math/rand" "math/rand"
"reflect" "reflect"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-node/testutils" "github.com/ethereum-optimism/optimism/op-node/testutils"
cll2 "github.com/ethereum-optimism/optimism/op-program/client/l2" cll2 "github.com/ethereum-optimism/optimism/op-program/client/l2"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -23,35 +25,7 @@ import ( ...@@ -23,35 +25,7 @@ import (
// Require the fetching oracle to implement StateOracle // Require the fetching oracle to implement StateOracle
var _ cll2.StateOracle = (*FetchingL2Oracle)(nil) var _ cll2.StateOracle = (*FetchingL2Oracle)(nil)
type callContextRequest struct { const headBlockNumber = 1000
ctx context.Context
method string
args []interface{}
}
type stubCallContext struct {
nextResult any
nextErr error
requests []callContextRequest
}
func (c *stubCallContext) CallContext(ctx context.Context, result any, method string, args ...interface{}) error {
if result != nil && reflect.TypeOf(result).Kind() != reflect.Ptr {
return fmt.Errorf("call result parameter must be pointer or nil interface: %v", result)
}
c.requests = append(c.requests, callContextRequest{ctx: ctx, method: method, args: args})
if c.nextErr != nil {
return c.nextErr
}
res, err := json.Marshal(c.nextResult)
if err != nil {
return fmt.Errorf("json marshal: %w", err)
}
err = json.Unmarshal(res, result)
if err != nil {
return fmt.Errorf("json unmarshal: %w", err)
}
return nil
}
func TestNodeByHash(t *testing.T) { func TestNodeByHash(t *testing.T) {
rng := rand.New(rand.NewSource(1234)) rng := rand.New(rand.NewSource(1234))
...@@ -152,31 +126,12 @@ func TestCodeByHash(t *testing.T) { ...@@ -152,31 +126,12 @@ func TestCodeByHash(t *testing.T) {
}) })
} }
type blockRequest struct {
ctx context.Context
blockHash common.Hash
}
type stubBlockSource struct {
requests []blockRequest
nextErr error
nextResult *types.Block
}
func (s *stubBlockSource) BlockByHash(ctx context.Context, blockHash common.Hash) (*types.Block, error) {
s.requests = append(s.requests, blockRequest{
ctx: ctx,
blockHash: blockHash,
})
return s.nextResult, s.nextErr
}
func TestBlockByHash(t *testing.T) { func TestBlockByHash(t *testing.T) {
rng := rand.New(rand.NewSource(1234)) rng := rand.New(rand.NewSource(1234))
hash := testutils.RandomHash(rng) hash := testutils.RandomHash(rng)
t.Run("Success", func(t *testing.T) { t.Run("Success", func(t *testing.T) {
block, _ := testutils.RandomBlock(rng, 1) block := blockWithNumber(rng, headBlockNumber-1)
stub := &stubBlockSource{nextResult: block} stub := &stubBlockSource{nextResult: block}
fetcher := newFetcher(stub, nil) fetcher := newFetcher(stub, nil)
...@@ -194,7 +149,7 @@ func TestBlockByHash(t *testing.T) { ...@@ -194,7 +149,7 @@ func TestBlockByHash(t *testing.T) {
}) })
t.Run("RequestArgs", func(t *testing.T) { t.Run("RequestArgs", func(t *testing.T) {
stub := &stubBlockSource{} stub := &stubBlockSource{nextResult: blockWithNumber(rng, 1)}
fetcher := newFetcher(stub, nil) fetcher := newFetcher(stub, nil)
fetcher.BlockByHash(hash) fetcher.BlockByHash(hash)
...@@ -203,11 +158,86 @@ func TestBlockByHash(t *testing.T) { ...@@ -203,11 +158,86 @@ func TestBlockByHash(t *testing.T) {
req := stub.requests[0] req := stub.requests[0]
require.Equal(t, hash, req.blockHash) require.Equal(t, hash, req.blockHash)
}) })
t.Run("PanicWhenBlockAboveHeadRequested", func(t *testing.T) {
// Block that the source can provide but is above the head block number
block := blockWithNumber(rng, headBlockNumber+1)
stub := &stubBlockSource{nextResult: block}
fetcher := newFetcher(stub, nil)
require.Panics(t, func() {
fetcher.BlockByHash(block.Hash())
})
})
}
func blockWithNumber(rng *rand.Rand, num int64) *types.Block {
header := testutils.RandomHeader(rng)
header.Number = big.NewInt(num)
return types.NewBlock(header, nil, nil, nil, trie.NewStackTrie(nil))
}
type blockRequest struct {
ctx context.Context
blockHash common.Hash
}
type stubBlockSource struct {
requests []blockRequest
nextErr error
nextResult *types.Block
}
func (s *stubBlockSource) BlockByHash(ctx context.Context, blockHash common.Hash) (*types.Block, error) {
s.requests = append(s.requests, blockRequest{
ctx: ctx,
blockHash: blockHash,
})
return s.nextResult, s.nextErr
}
type callContextRequest struct {
ctx context.Context
method string
args []interface{}
}
type stubCallContext struct {
nextResult any
nextErr error
requests []callContextRequest
}
func (c *stubCallContext) CallContext(ctx context.Context, result any, method string, args ...interface{}) error {
if result != nil && reflect.TypeOf(result).Kind() != reflect.Ptr {
return fmt.Errorf("call result parameter must be pointer or nil interface: %v", result)
}
c.requests = append(c.requests, callContextRequest{ctx: ctx, method: method, args: args})
if c.nextErr != nil {
return c.nextErr
}
res, err := json.Marshal(c.nextResult)
if err != nil {
return fmt.Errorf("json marshal: %w", err)
}
err = json.Unmarshal(res, result)
if err != nil {
return fmt.Errorf("json unmarshal: %w", err)
}
return nil
} }
func newFetcher(blockSource BlockSource, callContext CallContext) *FetchingL2Oracle { func newFetcher(blockSource BlockSource, callContext CallContext) *FetchingL2Oracle {
rng := rand.New(rand.NewSource(int64(1)))
head := testutils.MakeBlockInfo(func(i *testutils.MockBlockInfo) {
i.InfoNum = headBlockNumber
})(rng)
return &FetchingL2Oracle{ return &FetchingL2Oracle{
ctx: context.Background(),
logger: log.New(), logger: log.New(),
head: head,
blockSource: blockSource, blockSource: blockSource,
callContext: callContext, callContext: callContext,
} }
......
...@@ -19,7 +19,7 @@ func NewFetchingEngine(ctx context.Context, logger log.Logger, cfg *config.Confi ...@@ -19,7 +19,7 @@ func NewFetchingEngine(ctx context.Context, logger log.Logger, cfg *config.Confi
if err != nil { if err != nil {
return nil, err return nil, err
} }
oracle, err := NewFetchingL2Oracle(ctx, logger, cfg.L2URL) oracle, err := NewFetchingL2Oracle(ctx, logger, cfg.L2URL, cfg.L2Head)
if err != nil { if err != nil {
return nil, fmt.Errorf("connect l2 oracle: %w", err) return nil, fmt.Errorf("connect l2 oracle: %w", err)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment