Commit 63f0753f authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into jg/ci2

parents d5be220c 50154173
...@@ -100,7 +100,7 @@ func NewMetrics(procName string) *Metrics { ...@@ -100,7 +100,7 @@ func NewMetrics(procName string) *Metrics {
Help: "1 if the op-batcher has finished starting up", Help: "1 if the op-batcher has finished starting up",
}), }),
ChannelEvs: opmetrics.NewEventVec(factory, ns, "channel", "Channel", []string{"stage"}), ChannelEvs: opmetrics.NewEventVec(factory, ns, "", "channel", "Channel", []string{"stage"}),
PendingBlocksCount: *factory.NewGaugeVec(prometheus.GaugeOpts{ PendingBlocksCount: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Namespace: ns,
...@@ -145,7 +145,7 @@ func NewMetrics(procName string) *Metrics { ...@@ -145,7 +145,7 @@ func NewMetrics(procName string) *Metrics {
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)...),
}), }),
BatcherTxEvs: opmetrics.NewEventVec(factory, ns, "batcher_tx", "BatcherTx", []string{"stage"}), BatcherTxEvs: opmetrics.NewEventVec(factory, ns, "", "batcher_tx", "BatcherTx", []string{"stage"}),
} }
} }
......
...@@ -640,11 +640,11 @@ func TestSystemMockP2P(t *testing.T) { ...@@ -640,11 +640,11 @@ func TestSystemMockP2P(t *testing.T) {
require.Nil(t, err, "Sending L2 tx to sequencer") require.Nil(t, err, "Sending L2 tx to sequencer")
// Wait for tx to be mined on the L2 sequencer chain // Wait for tx to be mined on the L2 sequencer chain
receiptSeq, err := waitForTransaction(tx.Hash(), l2Seq, 10*time.Duration(sys.RollupConfig.BlockTime)*time.Second) receiptSeq, err := waitForTransaction(tx.Hash(), l2Seq, 5*time.Minute)
require.Nil(t, err, "Waiting for L2 tx on sequencer") require.Nil(t, err, "Waiting for L2 tx on sequencer")
// Wait until the block it was first included in shows up in the safe chain on the verifier // Wait until the block it was first included in shows up in the safe chain on the verifier
receiptVerif, err := waitForTransaction(tx.Hash(), l2Verif, 10*time.Duration(sys.RollupConfig.BlockTime)*time.Second) receiptVerif, err := waitForTransaction(tx.Hash(), l2Verif, 5*time.Minute)
require.Nil(t, err, "Waiting for L2 tx on verifier") require.Nil(t, err, "Waiting for L2 tx on verifier")
require.Equal(t, receiptSeq, receiptVerif) require.Equal(t, receiptSeq, receiptVerif)
......
package l1
import (
"context"
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
var (
ErrNotFound = ethereum.NotFound
ErrUnknownLabel = errors.New("unknown label")
)
type OracleL1Client struct {
oracle Oracle
head eth.L1BlockRef
}
func NewOracleL1Client(logger log.Logger, oracle Oracle, l1Head common.Hash) *OracleL1Client {
head := eth.InfoToL1BlockRef(oracle.HeaderByBlockHash(l1Head))
logger.Info("L1 head loaded", "hash", head.Hash, "number", head.Number)
return &OracleL1Client{
oracle: oracle,
head: head,
}
}
func (o OracleL1Client) L1BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L1BlockRef, error) {
if label != eth.Unsafe && label != eth.Safe && label != eth.Finalized {
return eth.L1BlockRef{}, fmt.Errorf("%w: %s", ErrUnknownLabel, label)
}
// The L1 head is pre-agreed and unchanging so it can be used for all of unsafe, safe and finalized
return o.head, nil
}
func (o OracleL1Client) L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error) {
if number > o.head.Number {
return eth.L1BlockRef{}, fmt.Errorf("%w: block number %d", ErrNotFound, number)
}
block := o.head
for block.Number > number {
block = eth.InfoToL1BlockRef(o.oracle.HeaderByBlockHash(block.ParentHash))
}
return block, nil
}
func (o OracleL1Client) L1BlockRefByHash(ctx context.Context, hash common.Hash) (eth.L1BlockRef, error) {
return eth.InfoToL1BlockRef(o.oracle.HeaderByBlockHash(hash)), nil
}
func (o OracleL1Client) InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error) {
return o.oracle.HeaderByBlockHash(hash), nil
}
func (o OracleL1Client) FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error) {
info, rcpts := o.oracle.ReceiptsByBlockHash(blockHash)
return info, rcpts, nil
}
func (o OracleL1Client) InfoAndTxsByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, types.Transactions, error) {
info, txs := o.oracle.TransactionsByBlockHash(hash)
return info, txs, nil
}
package l1
import (
"context"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
var _ derive.L1Fetcher = (*OracleL1Client)(nil)
var head = blockNum(1000)
func TestInfoByHash(t *testing.T) {
client, oracle := newClient(t)
hash := common.HexToHash("0xAABBCC")
expected := &sources.HeaderInfo{}
oracle.blocks[hash] = expected
info, err := client.InfoByHash(context.Background(), hash)
require.NoError(t, err)
require.Equal(t, expected, info)
}
func TestL1BlockRefByHash(t *testing.T) {
client, oracle := newClient(t)
hash := common.HexToHash("0xAABBCC")
header := &sources.HeaderInfo{}
oracle.blocks[hash] = header
expected := eth.InfoToL1BlockRef(header)
ref, err := client.L1BlockRefByHash(context.Background(), hash)
require.NoError(t, err)
require.Equal(t, expected, ref)
}
func TestFetchReceipts(t *testing.T) {
client, oracle := newClient(t)
hash := common.HexToHash("0xAABBCC")
expectedInfo := &sources.HeaderInfo{}
expectedReceipts := types.Receipts{
&types.Receipt{},
}
oracle.blocks[hash] = expectedInfo
oracle.rcpts[hash] = expectedReceipts
info, rcpts, err := client.FetchReceipts(context.Background(), hash)
require.NoError(t, err)
require.Equal(t, expectedInfo, info)
require.Equal(t, expectedReceipts, rcpts)
}
func TestInfoAndTxsByHash(t *testing.T) {
client, oracle := newClient(t)
hash := common.HexToHash("0xAABBCC")
expectedInfo := &sources.HeaderInfo{}
expectedTxs := types.Transactions{
&types.Transaction{},
}
oracle.blocks[hash] = expectedInfo
oracle.txs[hash] = expectedTxs
info, txs, err := client.InfoAndTxsByHash(context.Background(), hash)
require.NoError(t, err)
require.Equal(t, expectedInfo, info)
require.Equal(t, expectedTxs, txs)
}
func TestL1BlockRefByLabel(t *testing.T) {
t.Run("Unsafe", func(t *testing.T) {
client, _ := newClient(t)
ref, err := client.L1BlockRefByLabel(context.Background(), eth.Unsafe)
require.NoError(t, err)
require.Equal(t, eth.InfoToL1BlockRef(head), ref)
})
t.Run("Safe", func(t *testing.T) {
client, _ := newClient(t)
ref, err := client.L1BlockRefByLabel(context.Background(), eth.Safe)
require.NoError(t, err)
require.Equal(t, eth.InfoToL1BlockRef(head), ref)
})
t.Run("Finalized", func(t *testing.T) {
client, _ := newClient(t)
ref, err := client.L1BlockRefByLabel(context.Background(), eth.Finalized)
require.NoError(t, err)
require.Equal(t, eth.InfoToL1BlockRef(head), ref)
})
t.Run("UnknownLabel", func(t *testing.T) {
client, _ := newClient(t)
ref, err := client.L1BlockRefByLabel(context.Background(), eth.BlockLabel("unknown"))
require.ErrorIs(t, err, ErrUnknownLabel)
require.Equal(t, eth.L1BlockRef{}, ref)
})
}
func TestL1BlockRefByNumber(t *testing.T) {
t.Run("Head", func(t *testing.T) {
client, _ := newClient(t)
ref, err := client.L1BlockRefByNumber(context.Background(), head.NumberU64())
require.NoError(t, err)
require.Equal(t, eth.InfoToL1BlockRef(head), ref)
})
t.Run("AfterHead", func(t *testing.T) {
client, _ := newClient(t)
ref, err := client.L1BlockRefByNumber(context.Background(), head.NumberU64()+1)
// Must be ethereum.NotFound error so the derivation pipeline knows it has gone past the chain head
require.ErrorIs(t, err, ethereum.NotFound)
require.Equal(t, eth.L1BlockRef{}, ref)
})
t.Run("ParentOfHead", func(t *testing.T) {
client, oracle := newClient(t)
parent := blockNum(head.NumberU64() - 1)
oracle.blocks[parent.Hash()] = parent
ref, err := client.L1BlockRefByNumber(context.Background(), parent.NumberU64())
require.NoError(t, err)
require.Equal(t, eth.InfoToL1BlockRef(parent), ref)
})
t.Run("AncestorOfHead", func(t *testing.T) {
client, oracle := newClient(t)
block := head
blocks := []eth.BlockInfo{block}
for i := 0; i < 10; i++ {
block = blockNum(block.NumberU64() - 1)
oracle.blocks[block.Hash()] = block
blocks = append(blocks, block)
}
for _, block := range blocks {
ref, err := client.L1BlockRefByNumber(context.Background(), block.NumberU64())
require.NoError(t, err)
require.Equal(t, eth.InfoToL1BlockRef(block), ref)
}
})
}
func newClient(t *testing.T) (*OracleL1Client, *stubOracle) {
stub := &stubOracle{
t: t,
blocks: make(map[common.Hash]eth.BlockInfo),
txs: make(map[common.Hash]types.Transactions),
rcpts: make(map[common.Hash]types.Receipts),
}
stub.blocks[head.Hash()] = head
client := NewOracleL1Client(testlog.Logger(t, log.LvlDebug), stub, head.Hash())
return client, stub
}
type stubOracle struct {
t *testing.T
// blocks maps block hash to eth.BlockInfo
blocks map[common.Hash]eth.BlockInfo
// txs maps block hash to transactions
txs map[common.Hash]types.Transactions
// rcpts maps Block hash to receipts
rcpts map[common.Hash]types.Receipts
}
func (o stubOracle) HeaderByBlockHash(blockHash common.Hash) eth.BlockInfo {
info, ok := o.blocks[blockHash]
if !ok {
o.t.Fatalf("unknown block %s", blockHash)
}
return info
}
func (o stubOracle) TransactionsByBlockHash(blockHash common.Hash) (eth.BlockInfo, types.Transactions) {
txs, ok := o.txs[blockHash]
if !ok {
o.t.Fatalf("unknown txs %s", blockHash)
}
return o.HeaderByBlockHash(blockHash), txs
}
func (o stubOracle) ReceiptsByBlockHash(blockHash common.Hash) (eth.BlockInfo, types.Receipts) {
rcpts, ok := o.rcpts[blockHash]
if !ok {
o.t.Fatalf("unknown rcpts %s", blockHash)
}
return o.HeaderByBlockHash(blockHash), rcpts
}
func blockNum(num uint64) eth.BlockInfo {
parentNum := num - 1
return &testutils.MockBlockInfo{
InfoHash: common.BytesToHash(big.NewInt(int64(num)).Bytes()),
InfoParentHash: common.BytesToHash(big.NewInt(int64(parentNum)).Bytes()),
InfoCoinbase: common.Address{},
InfoRoot: common.Hash{},
InfoNum: num,
InfoTime: num * 2,
InfoMixDigest: [32]byte{},
InfoBaseFee: nil,
InfoReceiptRoot: common.Hash{},
InfoGasUsed: 0,
}
}
package l1
import (
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
type Oracle interface {
// HeaderByBlockHash retrieves the block header with the given hash.
HeaderByBlockHash(blockHash common.Hash) eth.BlockInfo
// TransactionsByBlockHash retrieves the transactions from the block with the given hash.
TransactionsByBlockHash(blockHash common.Hash) (eth.BlockInfo, types.Transactions)
// ReceiptsByBlockHash retrieves the receipts from the block with the given hash.
ReceiptsByBlockHash(blockHash common.Hash) (eth.BlockInfo, types.Receipts)
}
...@@ -40,12 +40,12 @@ func (o *OracleKeyValueStore) Get(key []byte) ([]byte, error) { ...@@ -40,12 +40,12 @@ func (o *OracleKeyValueStore) Get(key []byte) ([]byte, error) {
if len(key) == codePrefixedKeyLength && bytes.HasPrefix(key, rawdb.CodePrefix) { if len(key) == codePrefixedKeyLength && bytes.HasPrefix(key, rawdb.CodePrefix) {
key = key[len(rawdb.CodePrefix):] key = key[len(rawdb.CodePrefix):]
return o.oracle.CodeByHash(*(*[common.HashLength]byte)(key)) return o.oracle.CodeByHash(*(*[common.HashLength]byte)(key)), nil
} }
if len(key) != common.HashLength { if len(key) != common.HashLength {
return nil, ErrInvalidKeyLength return nil, ErrInvalidKeyLength
} }
return o.oracle.NodeByHash(*(*[common.HashLength]byte)(key)) return o.oracle.NodeByHash(*(*[common.HashLength]byte)(key)), nil
} }
func (o *OracleKeyValueStore) NewBatch() ethdb.Batch { func (o *OracleKeyValueStore) NewBatch() ethdb.Batch {
......
package l2 package l2
import ( import (
"fmt"
"math/big" "math/big"
"testing" "testing"
...@@ -27,16 +26,8 @@ var ( ...@@ -27,16 +26,8 @@ var (
var _ ethdb.KeyValueStore = (*OracleKeyValueStore)(nil) var _ ethdb.KeyValueStore = (*OracleKeyValueStore)(nil)
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
t.Run("UnknownKey", func(t *testing.T) {
oracle := newStubStateOracle()
db := NewOracleBackedDB(oracle)
val, err := db.Get(common.Hash{}.Bytes())
require.Error(t, err)
require.Nil(t, val)
})
t.Run("IncorrectLengthKey", func(t *testing.T) { t.Run("IncorrectLengthKey", func(t *testing.T) {
oracle := newStubStateOracle() oracle := newStubStateOracle(t)
db := NewOracleBackedDB(oracle) db := NewOracleBackedDB(oracle)
val, err := db.Get([]byte{1, 2, 3}) val, err := db.Get([]byte{1, 2, 3})
require.ErrorIs(t, err, ErrInvalidKeyLength) require.ErrorIs(t, err, ErrInvalidKeyLength)
...@@ -44,7 +35,7 @@ func TestGet(t *testing.T) { ...@@ -44,7 +35,7 @@ func TestGet(t *testing.T) {
}) })
t.Run("KeyWithCodePrefix", func(t *testing.T) { t.Run("KeyWithCodePrefix", func(t *testing.T) {
oracle := newStubStateOracle() oracle := newStubStateOracle(t)
db := NewOracleBackedDB(oracle) db := NewOracleBackedDB(oracle)
key := common.HexToHash("0x12345678") key := common.HexToHash("0x12345678")
prefixedKey := append(rawdb.CodePrefix, key.Bytes()...) prefixedKey := append(rawdb.CodePrefix, key.Bytes()...)
...@@ -58,7 +49,7 @@ func TestGet(t *testing.T) { ...@@ -58,7 +49,7 @@ func TestGet(t *testing.T) {
}) })
t.Run("NormalKeyThatHappensToStartWithCodePrefix", func(t *testing.T) { t.Run("NormalKeyThatHappensToStartWithCodePrefix", func(t *testing.T) {
oracle := newStubStateOracle() oracle := newStubStateOracle(t)
db := NewOracleBackedDB(oracle) db := NewOracleBackedDB(oracle)
key := make([]byte, common.HashLength) key := make([]byte, common.HashLength)
copy(rawdb.CodePrefix, key) copy(rawdb.CodePrefix, key)
...@@ -74,7 +65,7 @@ func TestGet(t *testing.T) { ...@@ -74,7 +65,7 @@ func TestGet(t *testing.T) {
t.Run("KnownKey", func(t *testing.T) { t.Run("KnownKey", func(t *testing.T) {
key := common.HexToHash("0xAA4488") key := common.HexToHash("0xAA4488")
expected := []byte{2, 6, 3, 8} expected := []byte{2, 6, 3, 8}
oracle := newStubStateOracle() oracle := newStubStateOracle(t)
oracle.data[key] = expected oracle.data[key] = expected
db := NewOracleBackedDB(oracle) db := NewOracleBackedDB(oracle)
val, err := db.Get(key.Bytes()) val, err := db.Get(key.Bytes())
...@@ -85,7 +76,7 @@ func TestGet(t *testing.T) { ...@@ -85,7 +76,7 @@ func TestGet(t *testing.T) {
func TestPut(t *testing.T) { func TestPut(t *testing.T) {
t.Run("NewKey", func(t *testing.T) { t.Run("NewKey", func(t *testing.T) {
oracle := newStubStateOracle() oracle := newStubStateOracle(t)
db := NewOracleBackedDB(oracle) db := NewOracleBackedDB(oracle)
key := common.HexToHash("0xAA4488") key := common.HexToHash("0xAA4488")
value := []byte{2, 6, 3, 8} value := []byte{2, 6, 3, 8}
...@@ -97,7 +88,7 @@ func TestPut(t *testing.T) { ...@@ -97,7 +88,7 @@ func TestPut(t *testing.T) {
require.Equal(t, value, actual) require.Equal(t, value, actual)
}) })
t.Run("ReplaceKey", func(t *testing.T) { t.Run("ReplaceKey", func(t *testing.T) {
oracle := newStubStateOracle() oracle := newStubStateOracle(t)
db := NewOracleBackedDB(oracle) db := NewOracleBackedDB(oracle)
key := common.HexToHash("0xAA4488") key := common.HexToHash("0xAA4488")
value1 := []byte{2, 6, 3, 8} value1 := []byte{2, 6, 3, 8}
...@@ -119,6 +110,7 @@ func TestSupportsStateDBOperations(t *testing.T) { ...@@ -119,6 +110,7 @@ func TestSupportsStateDBOperations(t *testing.T) {
genesisBlock := l2Genesis.MustCommit(realDb) genesisBlock := l2Genesis.MustCommit(realDb)
loader := &kvStateOracle{ loader := &kvStateOracle{
t: t,
source: realDb, source: realDb,
} }
assertStateDataAvailable(t, NewOracleBackedDB(loader), l2Genesis, genesisBlock) assertStateDataAvailable(t, NewOracleBackedDB(loader), l2Genesis, genesisBlock)
...@@ -126,7 +118,7 @@ func TestSupportsStateDBOperations(t *testing.T) { ...@@ -126,7 +118,7 @@ func TestSupportsStateDBOperations(t *testing.T) {
func TestUpdateState(t *testing.T) { func TestUpdateState(t *testing.T) {
l2Genesis := createGenesis() l2Genesis := createGenesis()
oracle := newStubStateOracle() oracle := newStubStateOracle(t)
db := rawdb.NewDatabase(NewOracleBackedDB(oracle)) db := rawdb.NewDatabase(NewOracleBackedDB(oracle))
genesisBlock := l2Genesis.MustCommit(db) genesisBlock := l2Genesis.MustCommit(db)
...@@ -203,43 +195,50 @@ func assertStateDataAvailable(t *testing.T, db ethdb.KeyValueStore, l2Genesis *c ...@@ -203,43 +195,50 @@ func assertStateDataAvailable(t *testing.T, db ethdb.KeyValueStore, l2Genesis *c
require.Equal(t, common.Hash{}, statedb.GetCodeHash(unknownAccount), "unset account code hash") require.Equal(t, common.Hash{}, statedb.GetCodeHash(unknownAccount), "unset account code hash")
} }
func newStubStateOracle() *stubStateOracle { func newStubStateOracle(t *testing.T) *stubStateOracle {
return &stubStateOracle{ return &stubStateOracle{
t: t,
data: make(map[common.Hash][]byte), data: make(map[common.Hash][]byte),
code: make(map[common.Hash][]byte), code: make(map[common.Hash][]byte),
} }
} }
type stubStateOracle struct { type stubStateOracle struct {
t *testing.T
data map[common.Hash][]byte data map[common.Hash][]byte
code map[common.Hash][]byte code map[common.Hash][]byte
} }
func (o *stubStateOracle) NodeByHash(nodeHash common.Hash) ([]byte, error) { func (o *stubStateOracle) NodeByHash(nodeHash common.Hash) []byte {
data, ok := o.data[nodeHash] data, ok := o.data[nodeHash]
if !ok { if !ok {
return nil, fmt.Errorf("no value for node %v", nodeHash) o.t.Fatalf("no value for node %v", nodeHash)
} }
return data, nil return data
} }
func (o *stubStateOracle) CodeByHash(hash common.Hash) ([]byte, error) { func (o *stubStateOracle) CodeByHash(hash common.Hash) []byte {
data, ok := o.code[hash] data, ok := o.code[hash]
if !ok { if !ok {
return nil, fmt.Errorf("no value for code %v", hash) o.t.Fatalf("no value for code %v", hash)
} }
return data, nil return data
} }
// kvStateOracle loads data from a source ethdb.KeyValueStore // kvStateOracle loads data from a source ethdb.KeyValueStore
type kvStateOracle struct { type kvStateOracle struct {
t *testing.T
source ethdb.KeyValueStore source ethdb.KeyValueStore
} }
func (o *kvStateOracle) NodeByHash(nodeHash common.Hash) ([]byte, error) { func (o *kvStateOracle) NodeByHash(nodeHash common.Hash) []byte {
return o.source.Get(nodeHash.Bytes()) val, err := o.source.Get(nodeHash.Bytes())
if err != nil {
o.t.Fatalf("error retrieving node %v: %v", nodeHash, err)
}
return val
} }
func (o *kvStateOracle) CodeByHash(hash common.Hash) ([]byte, error) { func (o *kvStateOracle) CodeByHash(hash common.Hash) []byte {
return rawdb.ReadCode(o.source, hash), nil return rawdb.ReadCode(o.source, hash)
} }
...@@ -35,10 +35,8 @@ type OracleBackedL2Chain struct { ...@@ -35,10 +35,8 @@ type OracleBackedL2Chain struct {
var _ engineapi.EngineBackend = (*OracleBackedL2Chain)(nil) var _ engineapi.EngineBackend = (*OracleBackedL2Chain)(nil)
func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, chainCfg *params.ChainConfig, l2Head common.Hash) (*OracleBackedL2Chain, error) { func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, chainCfg *params.ChainConfig, l2Head common.Hash) (*OracleBackedL2Chain, error) {
head, err := oracle.BlockByHash(l2Head) head := oracle.BlockByHash(l2Head)
if err != nil { logger.Info("Loaded L2 head", "hash", head.Hash(), "number", head.Number())
return nil, fmt.Errorf("loading l2 head: %w", err)
}
return &OracleBackedL2Chain{ return &OracleBackedL2Chain{
log: logger, log: logger,
oracle: oracle, oracle: oracle,
...@@ -98,10 +96,7 @@ func (o *OracleBackedL2Chain) GetBlockByHash(hash common.Hash) *types.Block { ...@@ -98,10 +96,7 @@ func (o *OracleBackedL2Chain) GetBlockByHash(hash common.Hash) *types.Block {
return block return block
} }
// Retrieve from the oracle // Retrieve from the oracle
block, err := o.oracle.BlockByHash(hash) block = o.oracle.BlockByHash(hash)
if err != nil {
handleError(err)
}
if block == nil { if block == nil {
return nil return nil
} }
...@@ -194,7 +189,3 @@ func (o *OracleBackedL2Chain) SetFinalized(header *types.Header) { ...@@ -194,7 +189,3 @@ func (o *OracleBackedL2Chain) SetFinalized(header *types.Header) {
func (o *OracleBackedL2Chain) SetSafe(header *types.Header) { func (o *OracleBackedL2Chain) SetSafe(header *types.Header) {
o.safe = header o.safe = header
} }
func handleError(err error) {
panic(err)
}
...@@ -113,8 +113,9 @@ func TestUpdateStateDatabaseWhenImportingBlock(t *testing.T) { ...@@ -113,8 +113,9 @@ 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")
_, err = chain.StateAt(newBlock.Root()) require.Panics(t, func() {
require.Error(t, err, "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)
...@@ -223,8 +224,8 @@ func newStubBlockOracle(chain []*types.Block, db ethdb.Database) *stubBlockOracl ...@@ -223,8 +224,8 @@ func newStubBlockOracle(chain []*types.Block, db ethdb.Database) *stubBlockOracl
} }
} }
func (o stubBlockOracle) BlockByHash(blockHash common.Hash) (*types.Block, error) { func (o stubBlockOracle) BlockByHash(blockHash common.Hash) *types.Block {
return o.blocks[blockHash], nil return o.blocks[blockHash]
} }
func TestEngineAPITests(t *testing.T) { func TestEngineAPITests(t *testing.T) {
......
...@@ -11,13 +11,11 @@ type StateOracle interface { ...@@ -11,13 +11,11 @@ type StateOracle interface {
// NodeByHash retrieves the merkle-patricia trie node pre-image for a given hash. // NodeByHash retrieves the merkle-patricia trie node pre-image for a given hash.
// Trie nodes may be from the world state trie or any account storage trie. // Trie nodes may be from the world state trie or any account storage trie.
// Contract code is not stored as part of the trie and must be retrieved via CodeByHash // Contract code is not stored as part of the trie and must be retrieved via CodeByHash
// Returns an error if the pre-image is unavailable. NodeByHash(nodeHash common.Hash) []byte
NodeByHash(nodeHash common.Hash) ([]byte, error)
// CodeByHash retrieves the contract code pre-image for a given hash. // CodeByHash retrieves the contract code pre-image for a given hash.
// codeHash should be retrieved from the world state account for a contract. // codeHash should be retrieved from the world state account for a contract.
// Returns an error if the pre-image is unavailable. CodeByHash(codeHash common.Hash) []byte
CodeByHash(codeHash common.Hash) ([]byte, error)
} }
// Oracle defines the high-level API used to retrieve L2 data. // Oracle defines the high-level API used to retrieve L2 data.
...@@ -26,6 +24,5 @@ type Oracle interface { ...@@ -26,6 +24,5 @@ type Oracle interface {
StateOracle StateOracle
// BlockByHash retrieves the block with the given hash. // BlockByHash retrieves the block with the given hash.
// Returns an error if the block is not available. BlockByHash(blockHash common.Hash) *types.Block
BlockByHash(blockHash common.Hash) (*types.Block, error)
} }
...@@ -13,7 +13,9 @@ import ( ...@@ -13,7 +13,9 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var l2HeadValue = "0x6303578b1fa9480389c51bbcef6fe045bb877da39740819e9eb5f36f94949bd0" // Use HexToHash(...).Hex() to ensure the strings are the correct length for a hash
var l1HeadValue = common.HexToHash("0x111111").Hex()
var l2HeadValue = common.HexToHash("0x222222").Hex()
func TestLogLevel(t *testing.T) { func TestLogLevel(t *testing.T) {
t.Run("RejectInvalid", func(t *testing.T) { t.Run("RejectInvalid", func(t *testing.T) {
...@@ -32,7 +34,8 @@ func TestLogLevel(t *testing.T) { ...@@ -32,7 +34,8 @@ func TestLogLevel(t *testing.T) {
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs()) cfg := configForArgs(t, addRequiredArgs())
require.Equal(t, config.NewConfig(&chaincfg.Goerli, "genesis.json", common.HexToHash(l2HeadValue)), cfg) defaultCfg := config.NewConfig(&chaincfg.Goerli, "genesis.json", common.HexToHash(l1HeadValue), common.HexToHash(l2HeadValue))
require.Equal(t, defaultCfg, cfg)
} }
func TestNetwork(t *testing.T) { func TestNetwork(t *testing.T) {
...@@ -102,6 +105,21 @@ func TestL2Head(t *testing.T) { ...@@ -102,6 +105,21 @@ func TestL2Head(t *testing.T) {
}) })
} }
func TestL1Head(t *testing.T) {
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l1.head is required", addRequiredArgsExcept("--l1.head"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, replaceRequiredArg("--l1.head", l1HeadValue))
require.Equal(t, common.HexToHash(l1HeadValue), cfg.L1Head)
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t, config.ErrInvalidL1Head.Error(), replaceRequiredArg("--l1.head", "something"))
})
}
func TestL1(t *testing.T) { func TestL1(t *testing.T) {
expected := "https://example.com:8545" expected := "https://example.com:8545"
cfg := configForArgs(t, addRequiredArgs("--l1", expected)) cfg := configForArgs(t, addRequiredArgs("--l1", expected))
...@@ -149,7 +167,8 @@ func TestL1RPCKind(t *testing.T) { ...@@ -149,7 +167,8 @@ func TestL1RPCKind(t *testing.T) {
// Offline support will be added later, but for now it just bails out with an error // Offline support will be added later, but for now it just bails out with an error
func TestOfflineModeNotSupported(t *testing.T) { func TestOfflineModeNotSupported(t *testing.T) {
logger := log.New() logger := log.New()
err := FaultProofProgram(logger, config.NewConfig(&chaincfg.Goerli, "genesis.json", common.HexToHash(l2HeadValue))) cfg := config.NewConfig(&chaincfg.Goerli, "genesis.json", common.HexToHash(l1HeadValue), common.HexToHash(l2HeadValue))
err := FaultProofProgram(logger, cfg)
require.ErrorContains(t, err, "offline mode not supported") require.ErrorContains(t, err, "offline mode not supported")
} }
...@@ -199,8 +218,9 @@ func replaceRequiredArg(name string, value string) []string { ...@@ -199,8 +218,9 @@ func replaceRequiredArg(name string, value string) []string {
func requiredArgs() map[string]string { func requiredArgs() map[string]string {
return map[string]string{ return map[string]string{
"--network": "goerli", "--network": "goerli",
"--l2.genesis": "genesis.json", "--l1.head": l1HeadValue,
"--l2.head": l2HeadValue, "--l2.head": l2HeadValue,
"--l2.genesis": "genesis.json",
} }
} }
......
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
var ( var (
ErrMissingRollupConfig = errors.New("missing rollup config") ErrMissingRollupConfig = errors.New("missing rollup config")
ErrMissingL2Genesis = errors.New("missing l2 genesis") ErrMissingL2Genesis = errors.New("missing l2 genesis")
ErrInvalidL1Head = errors.New("invalid l1 head")
ErrInvalidL2Head = errors.New("invalid l2 head") ErrInvalidL2Head = errors.New("invalid l2 head")
ErrL1AndL2Inconsistent = errors.New("l1 and l2 options must be specified together or both omitted") ErrL1AndL2Inconsistent = errors.New("l1 and l2 options must be specified together or both omitted")
) )
...@@ -22,6 +23,7 @@ type Config struct { ...@@ -22,6 +23,7 @@ type Config struct {
Rollup *rollup.Config Rollup *rollup.Config
L2URL string L2URL string
L2GenesisPath string L2GenesisPath string
L1Head common.Hash
L2Head common.Hash L2Head common.Hash
L1URL string L1URL string
L1TrustRPC bool L1TrustRPC bool
...@@ -35,12 +37,15 @@ func (c *Config) Check() error { ...@@ -35,12 +37,15 @@ func (c *Config) Check() error {
if err := c.Rollup.Check(); err != nil { if err := c.Rollup.Check(); err != nil {
return err return err
} }
if c.L2GenesisPath == "" { if c.L1Head == (common.Hash{}) {
return ErrMissingL2Genesis return ErrInvalidL1Head
} }
if c.L2Head == (common.Hash{}) { if c.L2Head == (common.Hash{}) {
return ErrInvalidL2Head return ErrInvalidL2Head
} }
if c.L2GenesisPath == "" {
return ErrMissingL2Genesis
}
if (c.L1URL != "") != (c.L2URL != "") { if (c.L1URL != "") != (c.L2URL != "") {
return ErrL1AndL2Inconsistent return ErrL1AndL2Inconsistent
} }
...@@ -52,10 +57,11 @@ func (c *Config) FetchingEnabled() bool { ...@@ -52,10 +57,11 @@ func (c *Config) FetchingEnabled() bool {
} }
// NewConfig creates a Config with all optional values set to the CLI default value // NewConfig creates a Config with all optional values set to the CLI default value
func NewConfig(rollupCfg *rollup.Config, l2GenesisPath string, l2Head common.Hash) *Config { func NewConfig(rollupCfg *rollup.Config, l2GenesisPath string, l1Head common.Hash, l2Head common.Hash) *Config {
return &Config{ return &Config{
Rollup: rollupCfg, Rollup: rollupCfg,
L2GenesisPath: l2GenesisPath, L2GenesisPath: l2GenesisPath,
L1Head: l1Head,
L2Head: l2Head, L2Head: l2Head,
L1RPCKind: sources.RPCKindBasic, L1RPCKind: sources.RPCKindBasic,
} }
...@@ -73,11 +79,16 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) { ...@@ -73,11 +79,16 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
if l2Head == (common.Hash{}) { if l2Head == (common.Hash{}) {
return nil, ErrInvalidL2Head return nil, ErrInvalidL2Head
} }
l1Head := common.HexToHash(ctx.GlobalString(flags.L1Head.Name))
if l1Head == (common.Hash{}) {
return nil, ErrInvalidL1Head
}
return &Config{ return &Config{
Rollup: rollupCfg, Rollup: rollupCfg,
L2URL: ctx.GlobalString(flags.L2NodeAddr.Name), L2URL: ctx.GlobalString(flags.L2NodeAddr.Name),
L2GenesisPath: ctx.GlobalString(flags.L2GenesisPath.Name), L2GenesisPath: ctx.GlobalString(flags.L2GenesisPath.Name),
L2Head: l2Head, L2Head: l2Head,
L1Head: l1Head,
L1URL: ctx.GlobalString(flags.L1NodeAddr.Name), L1URL: ctx.GlobalString(flags.L1NodeAddr.Name),
L1TrustRPC: ctx.GlobalBool(flags.L1TrustRPC.Name), L1TrustRPC: ctx.GlobalBool(flags.L1TrustRPC.Name),
L1RPCKind: sources.RPCProviderKind(ctx.GlobalString(flags.L1RPCProviderKind.Name)), L1RPCKind: sources.RPCProviderKind(ctx.GlobalString(flags.L1RPCProviderKind.Name)),
......
...@@ -11,66 +11,58 @@ import ( ...@@ -11,66 +11,58 @@ import (
var validRollupConfig = &chaincfg.Goerli var validRollupConfig = &chaincfg.Goerli
var validL2GenesisPath = "genesis.json" var validL2GenesisPath = "genesis.json"
var validL1Head = common.HexToHash("0x112233889988FF")
var validL2Head = common.HexToHash("0x6303578b1fa9480389c51bbcef6fe045bb877da39740819e9eb5f36f94949bd0") var validL2Head = common.HexToHash("0x6303578b1fa9480389c51bbcef6fe045bb877da39740819e9eb5f36f94949bd0")
func TestDefaultConfigIsValid(t *testing.T) { func TestDefaultConfigIsValid(t *testing.T) {
err := NewConfig(validRollupConfig, validL2GenesisPath, validL2Head).Check() err := NewConfig(validRollupConfig, validL2GenesisPath, validL1Head, validL2Head).Check()
require.NoError(t, err) require.NoError(t, err)
} }
func TestRollupConfig(t *testing.T) { func TestRollupConfig(t *testing.T) {
t.Run("Required", func(t *testing.T) { t.Run("Required", func(t *testing.T) {
err := NewConfig(nil, validL2GenesisPath, validL2Head).Check() err := NewConfig(nil, validL2GenesisPath, validL1Head, validL2Head).Check()
require.ErrorIs(t, err, ErrMissingRollupConfig) require.ErrorIs(t, err, ErrMissingRollupConfig)
}) })
t.Run("Invalid", func(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
err := NewConfig(&rollup.Config{}, validL2GenesisPath, validL2Head).Check() err := NewConfig(&rollup.Config{}, validL2GenesisPath, validL1Head, validL2Head).Check()
require.ErrorIs(t, err, rollup.ErrBlockTimeZero) require.ErrorIs(t, err, rollup.ErrBlockTimeZero)
}) })
} }
func TestL2Genesis(t *testing.T) { func TestL1HeadRequired(t *testing.T) {
t.Run("Required", func(t *testing.T) { err := NewConfig(validRollupConfig, validL2GenesisPath, common.Hash{}, validL2Head).Check()
err := NewConfig(validRollupConfig, "", validL2Head).Check() require.ErrorIs(t, err, ErrInvalidL1Head)
require.ErrorIs(t, err, ErrMissingL2Genesis)
})
t.Run("Valid", func(t *testing.T) {
err := NewConfig(validRollupConfig, validL2GenesisPath, validL2Head).Check()
require.NoError(t, err)
})
} }
func TestL2Head(t *testing.T) { func TestL2HeadRequired(t *testing.T) {
t.Run("Required", func(t *testing.T) { err := NewConfig(validRollupConfig, validL2GenesisPath, validL1Head, common.Hash{}).Check()
err := NewConfig(validRollupConfig, validL2GenesisPath, common.Hash{}).Check() require.ErrorIs(t, err, ErrInvalidL2Head)
require.ErrorIs(t, err, ErrInvalidL2Head) }
})
t.Run("Valid", func(t *testing.T) { func TestL2GenesisRequired(t *testing.T) {
err := NewConfig(validRollupConfig, validL2GenesisPath, validL2Head).Check() err := NewConfig(validRollupConfig, "", validL1Head, validL2Head).Check()
require.NoError(t, err) require.ErrorIs(t, err, ErrMissingL2Genesis)
})
} }
func TestFetchingArgConsistency(t *testing.T) { func TestFetchingArgConsistency(t *testing.T) {
t.Run("RequireL2WhenL1Set", func(t *testing.T) { t.Run("RequireL2WhenL1Set", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L1URL = "https://example.com:1234" cfg.L1URL = "https://example.com:1234"
require.ErrorIs(t, cfg.Check(), ErrL1AndL2Inconsistent) require.ErrorIs(t, cfg.Check(), ErrL1AndL2Inconsistent)
}) })
t.Run("RequireL1WhenL2Set", func(t *testing.T) { t.Run("RequireL1WhenL2Set", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L2URL = "https://example.com:1234" cfg.L2URL = "https://example.com:1234"
require.ErrorIs(t, cfg.Check(), ErrL1AndL2Inconsistent) require.ErrorIs(t, cfg.Check(), ErrL1AndL2Inconsistent)
}) })
t.Run("AllowNeitherSet", func(t *testing.T) { t.Run("AllowNeitherSet", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
require.NoError(t, cfg.Check()) require.NoError(t, cfg.Check())
}) })
t.Run("AllowBothSet", func(t *testing.T) { t.Run("AllowBothSet", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L1URL = "https://example.com:1234" cfg.L1URL = "https://example.com:1234"
cfg.L2URL = "https://example.com:4678" cfg.L2URL = "https://example.com:4678"
require.NoError(t, cfg.Check()) require.NoError(t, cfg.Check())
...@@ -79,30 +71,30 @@ func TestFetchingArgConsistency(t *testing.T) { ...@@ -79,30 +71,30 @@ func TestFetchingArgConsistency(t *testing.T) {
func TestFetchingEnabled(t *testing.T) { func TestFetchingEnabled(t *testing.T) {
t.Run("FetchingNotEnabledWhenNoFetcherUrlsSpecified", func(t *testing.T) { t.Run("FetchingNotEnabledWhenNoFetcherUrlsSpecified", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
require.False(t, cfg.FetchingEnabled(), "Should not enable fetching when node URL not supplied") require.False(t, cfg.FetchingEnabled(), "Should not enable fetching when node URL not supplied")
}) })
t.Run("FetchingEnabledWhenFetcherUrlsSpecified", func(t *testing.T) { t.Run("FetchingEnabledWhenFetcherUrlsSpecified", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L2URL = "https://example.com:1234" cfg.L2URL = "https://example.com:1234"
require.False(t, cfg.FetchingEnabled(), "Should not enable fetching when node URL not supplied") require.False(t, cfg.FetchingEnabled(), "Should not enable fetching when node URL not supplied")
}) })
t.Run("FetchingNotEnabledWhenNoL1UrlSpecified", func(t *testing.T) { t.Run("FetchingNotEnabledWhenNoL1UrlSpecified", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L2URL = "https://example.com:1234" cfg.L2URL = "https://example.com:1234"
require.False(t, cfg.FetchingEnabled(), "Should not enable L1 fetching when L1 node URL not supplied") require.False(t, cfg.FetchingEnabled(), "Should not enable L1 fetching when L1 node URL not supplied")
}) })
t.Run("FetchingNotEnabledWhenNoL2UrlSpecified", func(t *testing.T) { t.Run("FetchingNotEnabledWhenNoL2UrlSpecified", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L1URL = "https://example.com:1234" cfg.L1URL = "https://example.com:1234"
require.False(t, cfg.FetchingEnabled(), "Should not enable L2 fetching when L2 node URL not supplied") require.False(t, cfg.FetchingEnabled(), "Should not enable L2 fetching when L2 node URL not supplied")
}) })
t.Run("FetchingEnabledWhenBothFetcherUrlsSpecified", func(t *testing.T) { t.Run("FetchingEnabledWhenBothFetcherUrlsSpecified", func(t *testing.T) {
cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL2Head) cfg := NewConfig(&chaincfg.Beta1, validL2GenesisPath, validL1Head, validL2Head)
cfg.L1URL = "https://example.com:1234" cfg.L1URL = "https://example.com:1234"
cfg.L2URL = "https://example.com:5678" cfg.L2URL = "https://example.com:5678"
require.True(t, cfg.FetchingEnabled(), "Should enable fetching when node URL supplied") require.True(t, cfg.FetchingEnabled(), "Should enable fetching when node URL supplied")
......
...@@ -31,16 +31,21 @@ var ( ...@@ -31,16 +31,21 @@ var (
Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)", Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_RPC"), EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_RPC"),
} }
L2GenesisPath = cli.StringFlag{ L1Head = cli.StringFlag{
Name: "l2.genesis", Name: "l1.head",
Usage: "Path to the op-geth genesis file", Usage: "Hash of the L1 head block. Derivation stops after this block is processed.",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_GENESIS"), EnvVar: service.PrefixEnvVar(envVarPrefix, "L1_HEAD"),
} }
L2Head = cli.StringFlag{ L2Head = cli.StringFlag{
Name: "l2.head", Name: "l2.head",
Usage: "Hash of the agreed L2 block to start derivation from", Usage: "Hash of the agreed L2 block to start derivation from",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_HEAD"), EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_HEAD"),
} }
L2GenesisPath = cli.StringFlag{
Name: "l2.genesis",
Usage: "Path to the op-geth genesis file",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_GENESIS"),
}
L1NodeAddr = cli.StringFlag{ L1NodeAddr = cli.StringFlag{
Name: "l1", Name: "l1",
Usage: "Address of L1 JSON-RPC endpoint to use (eth namespace required)", Usage: "Address of L1 JSON-RPC endpoint to use (eth namespace required)",
...@@ -70,8 +75,9 @@ var programFlags = []cli.Flag{ ...@@ -70,8 +75,9 @@ var programFlags = []cli.Flag{
RollupConfig, RollupConfig,
Network, Network,
L2NodeAddr, L2NodeAddr,
L2GenesisPath, L1Head,
L2Head, L2Head,
L2GenesisPath,
L1NodeAddr, L1NodeAddr,
L1TrustRPC, L1TrustRPC,
L1RPCProviderKind, L1RPCProviderKind,
...@@ -97,5 +103,8 @@ func CheckRequired(ctx *cli.Context) error { ...@@ -97,5 +103,8 @@ func CheckRequired(ctx *cli.Context) error {
if ctx.GlobalString(L2Head.Name) == "" { if ctx.GlobalString(L2Head.Name) == "" {
return fmt.Errorf("flag %s is required", L2Head.Name) return fmt.Errorf("flag %s is required", L2Head.Name)
} }
if ctx.GlobalString(L1Head.Name) == "" {
return fmt.Errorf("flag %s is required", L1Head.Name)
}
return nil return nil
} }
package l1
import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
type Source interface {
InfoByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, error)
InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error)
FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error)
}
type FetchingL1Oracle struct {
ctx context.Context
logger log.Logger
source Source
}
func NewFetchingL1Oracle(ctx context.Context, logger log.Logger, source Source) *FetchingL1Oracle {
return &FetchingL1Oracle{
ctx: ctx,
logger: logger,
source: source,
}
}
func (o FetchingL1Oracle) HeaderByBlockHash(blockHash common.Hash) eth.BlockInfo {
o.logger.Trace("HeaderByBlockHash", "hash", blockHash)
info, err := o.source.InfoByHash(o.ctx, blockHash)
if err != nil {
panic(fmt.Errorf("retrieve block %s: %w", blockHash, err))
}
if info == nil {
panic(fmt.Errorf("unknown block: %s", blockHash))
}
return info
}
func (o FetchingL1Oracle) TransactionsByBlockHash(blockHash common.Hash) (eth.BlockInfo, types.Transactions) {
o.logger.Trace("TransactionsByBlockHash", "hash", blockHash)
info, txs, err := o.source.InfoAndTxsByHash(o.ctx, blockHash)
if err != nil {
panic(fmt.Errorf("retrieve transactions for block %s: %w", blockHash, err))
}
if info == nil || txs == nil {
panic(fmt.Errorf("unknown block: %s", blockHash))
}
return info, txs
}
func (o FetchingL1Oracle) ReceiptsByBlockHash(blockHash common.Hash) (eth.BlockInfo, types.Receipts) {
o.logger.Trace("ReceiptsByBlockHash", "hash", blockHash)
info, rcpts, err := o.source.FetchReceipts(o.ctx, blockHash)
if err != nil {
panic(fmt.Errorf("retrieve receipts for block %s: %w", blockHash, err))
}
if info == nil || rcpts == nil {
panic(fmt.Errorf("unknown block: %s", blockHash))
}
return info, rcpts
}
package l1
import (
"context"
"errors"
"fmt"
"testing"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-node/testlog"
cll1 "github.com/ethereum-optimism/optimism/op-program/client/l1"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
// Needs to implement the Oracle interface
var _ cll1.Oracle = (*FetchingL1Oracle)(nil)
// Want to be able to use an L1Client as the data source
var _ Source = (*sources.L1Client)(nil)
func TestHeaderByHash(t *testing.T) {
t.Run("Success", func(t *testing.T) {
expected := &sources.HeaderInfo{}
source := &stubSource{nextInfo: expected}
oracle := newFetchingOracle(t, source)
actual := oracle.HeaderByBlockHash(expected.Hash())
require.Equal(t, expected, actual)
})
t.Run("UnknownBlock", func(t *testing.T) {
oracle := newFetchingOracle(t, &stubSource{})
hash := common.HexToHash("0x4455")
require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() {
oracle.HeaderByBlockHash(hash)
})
})
t.Run("Error", func(t *testing.T) {
err := errors.New("kaboom")
source := &stubSource{nextErr: err}
oracle := newFetchingOracle(t, source)
hash := common.HexToHash("0x8888")
require.PanicsWithError(t, fmt.Errorf("retrieve block %s: %w", hash, err).Error(), func() {
oracle.HeaderByBlockHash(hash)
})
})
}
func TestTransactionsByHash(t *testing.T) {
t.Run("Success", func(t *testing.T) {
expectedInfo := &sources.HeaderInfo{}
expectedTxs := types.Transactions{
&types.Transaction{},
}
source := &stubSource{nextInfo: expectedInfo, nextTxs: expectedTxs}
oracle := newFetchingOracle(t, source)
info, txs := oracle.TransactionsByBlockHash(expectedInfo.Hash())
require.Equal(t, expectedInfo, info)
require.Equal(t, expectedTxs, txs)
})
t.Run("UnknownBlock_NoInfo", func(t *testing.T) {
oracle := newFetchingOracle(t, &stubSource{})
hash := common.HexToHash("0x4455")
require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() {
oracle.TransactionsByBlockHash(hash)
})
})
t.Run("UnknownBlock_NoTxs", func(t *testing.T) {
oracle := newFetchingOracle(t, &stubSource{nextInfo: &sources.HeaderInfo{}})
hash := common.HexToHash("0x4455")
require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() {
oracle.TransactionsByBlockHash(hash)
})
})
t.Run("Error", func(t *testing.T) {
err := errors.New("kaboom")
source := &stubSource{nextErr: err}
oracle := newFetchingOracle(t, source)
hash := common.HexToHash("0x8888")
require.PanicsWithError(t, fmt.Errorf("retrieve transactions for block %s: %w", hash, err).Error(), func() {
oracle.TransactionsByBlockHash(hash)
})
})
}
func TestReceiptsByHash(t *testing.T) {
t.Run("Success", func(t *testing.T) {
expectedInfo := &sources.HeaderInfo{}
expectedRcpts := types.Receipts{
&types.Receipt{},
}
source := &stubSource{nextInfo: expectedInfo, nextRcpts: expectedRcpts}
oracle := newFetchingOracle(t, source)
info, rcpts := oracle.ReceiptsByBlockHash(expectedInfo.Hash())
require.Equal(t, expectedInfo, info)
require.Equal(t, expectedRcpts, rcpts)
})
t.Run("UnknownBlock_NoInfo", func(t *testing.T) {
oracle := newFetchingOracle(t, &stubSource{})
hash := common.HexToHash("0x4455")
require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() {
oracle.ReceiptsByBlockHash(hash)
})
})
t.Run("UnknownBlock_NoTxs", func(t *testing.T) {
oracle := newFetchingOracle(t, &stubSource{nextInfo: &sources.HeaderInfo{}})
hash := common.HexToHash("0x4455")
require.PanicsWithError(t, fmt.Errorf("unknown block: %s", hash).Error(), func() {
oracle.ReceiptsByBlockHash(hash)
})
})
t.Run("Error", func(t *testing.T) {
err := errors.New("kaboom")
source := &stubSource{nextErr: err}
oracle := newFetchingOracle(t, source)
hash := common.HexToHash("0x8888")
require.PanicsWithError(t, fmt.Errorf("retrieve receipts for block %s: %w", hash, err).Error(), func() {
oracle.ReceiptsByBlockHash(hash)
})
})
}
func newFetchingOracle(t *testing.T, source Source) *FetchingL1Oracle {
return NewFetchingL1Oracle(context.Background(), testlog.Logger(t, log.LvlDebug), source)
}
type stubSource struct {
nextInfo eth.BlockInfo
nextTxs types.Transactions
nextRcpts types.Receipts
nextErr error
}
func (s stubSource) InfoByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, error) {
return s.nextInfo, s.nextErr
}
func (s stubSource) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) {
return s.nextInfo, s.nextTxs, s.nextErr
}
func (s stubSource) FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error) {
return s.nextInfo, s.nextRcpts, s.nextErr
}
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/client" "github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
cll1 "github.com/ethereum-optimism/optimism/op-program/client/l1"
"github.com/ethereum-optimism/optimism/op-program/host/config" "github.com/ethereum-optimism/optimism/op-program/host/config"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -16,5 +17,10 @@ func NewFetchingL1(ctx context.Context, logger log.Logger, cfg *config.Config) ( ...@@ -16,5 +17,10 @@ func NewFetchingL1(ctx context.Context, logger log.Logger, cfg *config.Config) (
return nil, err return nil, err
} }
return sources.NewL1Client(rpc, logger, nil, sources.L1ClientDefaultConfig(cfg.Rollup, cfg.L1TrustRPC, cfg.L1RPCKind)) source, err := sources.NewL1Client(rpc, logger, nil, sources.L1ClientDefaultConfig(cfg.Rollup, cfg.L1TrustRPC, cfg.L1RPCKind))
if err != nil {
return nil, err
}
oracle := NewFetchingL1Oracle(ctx, logger, source)
return cll1.NewOracleL1Client(logger, oracle, cfg.L1Head), err
} }
...@@ -42,19 +42,26 @@ func NewFetchingL2Oracle(ctx context.Context, logger log.Logger, l2Url string) ( ...@@ -42,19 +42,26 @@ func NewFetchingL2Oracle(ctx context.Context, logger log.Logger, l2Url string) (
}, nil }, nil
} }
func (o *FetchingL2Oracle) NodeByHash(hash common.Hash) ([]byte, error) { func (o *FetchingL2Oracle) NodeByHash(hash common.Hash) []byte {
// MPT nodes are stored as the hash of the node (with no prefix) // MPT nodes are stored as the hash of the node (with no prefix)
return o.dbGet(hash.Bytes()) node, err := o.dbGet(hash.Bytes())
if err != nil {
panic(err)
}
return node
} }
func (o *FetchingL2Oracle) CodeByHash(hash common.Hash) ([]byte, error) { func (o *FetchingL2Oracle) CodeByHash(hash common.Hash) []byte {
// First try retrieving with the new code prefix // First try retrieving with the new code prefix
code, err := o.dbGet(append(rawdb.CodePrefix, hash.Bytes()...)) code, err := o.dbGet(append(rawdb.CodePrefix, hash.Bytes()...))
if err != nil { if err != nil {
// Fallback to the legacy un-prefixed version // Fallback to the legacy un-prefixed version
return o.dbGet(hash.Bytes()) code, err = o.dbGet(hash.Bytes())
if err != nil {
panic(err)
}
} }
return code, nil return code
} }
func (o *FetchingL2Oracle) dbGet(key []byte) ([]byte, error) { func (o *FetchingL2Oracle) dbGet(key []byte) ([]byte, error) {
...@@ -66,10 +73,10 @@ func (o *FetchingL2Oracle) dbGet(key []byte) ([]byte, error) { ...@@ -66,10 +73,10 @@ func (o *FetchingL2Oracle) dbGet(key []byte) ([]byte, error) {
return node, nil return node, nil
} }
func (o *FetchingL2Oracle) BlockByHash(blockHash common.Hash) (*types.Block, error) { func (o *FetchingL2Oracle) BlockByHash(blockHash common.Hash) *types.Block {
block, err := o.blockSource.BlockByHash(o.ctx, blockHash) block, err := o.blockSource.BlockByHash(o.ctx, blockHash)
if err != nil { if err != nil {
return nil, fmt.Errorf("fetch block %s: %w", blockHash.Hex(), err) panic(fmt.Errorf("fetch block %s: %w", blockHash.Hex(), err))
} }
return block, nil return block
} }
...@@ -63,9 +63,9 @@ func TestNodeByHash(t *testing.T) { ...@@ -63,9 +63,9 @@ func TestNodeByHash(t *testing.T) {
} }
fetcher := newFetcher(nil, stub) fetcher := newFetcher(nil, stub)
node, err := fetcher.NodeByHash(hash) require.Panics(t, func() {
require.ErrorIs(t, err, stub.nextErr) fetcher.NodeByHash(hash)
require.Nil(t, node) })
}) })
t.Run("Success", func(t *testing.T) { t.Run("Success", func(t *testing.T) {
...@@ -75,8 +75,7 @@ func TestNodeByHash(t *testing.T) { ...@@ -75,8 +75,7 @@ func TestNodeByHash(t *testing.T) {
} }
fetcher := newFetcher(nil, stub) fetcher := newFetcher(nil, stub)
node, err := fetcher.NodeByHash(hash) node := fetcher.NodeByHash(hash)
require.NoError(t, err)
require.EqualValues(t, expected, node) require.EqualValues(t, expected, node)
}) })
...@@ -86,7 +85,7 @@ func TestNodeByHash(t *testing.T) { ...@@ -86,7 +85,7 @@ func TestNodeByHash(t *testing.T) {
} }
fetcher := newFetcher(nil, stub) fetcher := newFetcher(nil, stub)
_, _ = fetcher.NodeByHash(hash) fetcher.NodeByHash(hash)
require.Len(t, stub.requests, 1, "should make single request") require.Len(t, stub.requests, 1, "should make single request")
req := stub.requests[0] req := stub.requests[0]
require.Equal(t, "debug_dbGet", req.method) require.Equal(t, "debug_dbGet", req.method)
...@@ -104,9 +103,7 @@ func TestCodeByHash(t *testing.T) { ...@@ -104,9 +103,7 @@ func TestCodeByHash(t *testing.T) {
} }
fetcher := newFetcher(nil, stub) fetcher := newFetcher(nil, stub)
node, err := fetcher.CodeByHash(hash) require.Panics(t, func() { fetcher.CodeByHash(hash) })
require.ErrorIs(t, err, stub.nextErr)
require.Nil(t, node)
}) })
t.Run("Success", func(t *testing.T) { t.Run("Success", func(t *testing.T) {
...@@ -116,8 +113,7 @@ func TestCodeByHash(t *testing.T) { ...@@ -116,8 +113,7 @@ func TestCodeByHash(t *testing.T) {
} }
fetcher := newFetcher(nil, stub) fetcher := newFetcher(nil, stub)
node, err := fetcher.CodeByHash(hash) node := fetcher.CodeByHash(hash)
require.NoError(t, err)
require.EqualValues(t, expected, node) require.EqualValues(t, expected, node)
}) })
...@@ -127,7 +123,7 @@ func TestCodeByHash(t *testing.T) { ...@@ -127,7 +123,7 @@ func TestCodeByHash(t *testing.T) {
} }
fetcher := newFetcher(nil, stub) fetcher := newFetcher(nil, stub)
_, _ = fetcher.CodeByHash(hash) fetcher.CodeByHash(hash)
require.Len(t, stub.requests, 1, "should make single request") require.Len(t, stub.requests, 1, "should make single request")
req := stub.requests[0] req := stub.requests[0]
require.Equal(t, "debug_dbGet", req.method) require.Equal(t, "debug_dbGet", req.method)
...@@ -141,7 +137,8 @@ func TestCodeByHash(t *testing.T) { ...@@ -141,7 +137,8 @@ func TestCodeByHash(t *testing.T) {
} }
fetcher := newFetcher(nil, stub) fetcher := newFetcher(nil, stub)
_, _ = fetcher.CodeByHash(hash) // Panics because the code can't be found with or without the prefix
require.Panics(t, func() { fetcher.CodeByHash(hash) })
require.Len(t, stub.requests, 2, "should request with and without prefix") require.Len(t, stub.requests, 2, "should request with and without prefix")
req := stub.requests[0] req := stub.requests[0]
require.Equal(t, "debug_dbGet", req.method) require.Equal(t, "debug_dbGet", req.method)
...@@ -183,8 +180,7 @@ func TestBlockByHash(t *testing.T) { ...@@ -183,8 +180,7 @@ func TestBlockByHash(t *testing.T) {
stub := &stubBlockSource{nextResult: block} stub := &stubBlockSource{nextResult: block}
fetcher := newFetcher(stub, nil) fetcher := newFetcher(stub, nil)
res, err := fetcher.BlockByHash(hash) res := fetcher.BlockByHash(hash)
require.NoError(t, err)
require.Same(t, block, res) require.Same(t, block, res)
}) })
...@@ -192,16 +188,16 @@ func TestBlockByHash(t *testing.T) { ...@@ -192,16 +188,16 @@ func TestBlockByHash(t *testing.T) {
stub := &stubBlockSource{nextErr: errors.New("boom")} stub := &stubBlockSource{nextErr: errors.New("boom")}
fetcher := newFetcher(stub, nil) fetcher := newFetcher(stub, nil)
res, err := fetcher.BlockByHash(hash) require.Panics(t, func() {
require.ErrorIs(t, err, stub.nextErr) fetcher.BlockByHash(hash)
require.Nil(t, res) })
}) })
t.Run("RequestArgs", func(t *testing.T) { t.Run("RequestArgs", func(t *testing.T) {
stub := &stubBlockSource{} stub := &stubBlockSource{}
fetcher := newFetcher(stub, nil) fetcher := newFetcher(stub, nil)
_, _ = fetcher.BlockByHash(hash) fetcher.BlockByHash(hash)
require.Len(t, stub.requests, 1, "should make single request") require.Len(t, stub.requests, 1, "should make single request")
req := stub.requests[0] req := stub.requests[0]
......
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -334,7 +335,11 @@ func (l *L2OutputSubmitter) sendTransaction(ctx context.Context, output *eth.Out ...@@ -334,7 +335,11 @@ func (l *L2OutputSubmitter) sendTransaction(ctx context.Context, output *eth.Out
if err != nil { if err != nil {
return err return err
} }
l.log.Info("proposer tx successfully published", "tx_hash", receipt.TxHash) if receipt.Status == types.ReceiptStatusFailed {
l.log.Error("proposer tx successfully published but reverted", "tx_hash", receipt.TxHash)
} else {
l.log.Info("proposer tx successfully published", "tx_hash", receipt.TxHash)
}
return nil return nil
} }
......
...@@ -16,17 +16,19 @@ func (e *Event) Record() { ...@@ -16,17 +16,19 @@ func (e *Event) Record() {
e.LastTime.SetToCurrentTime() e.LastTime.SetToCurrentTime()
} }
func NewEvent(factory Factory, ns string, name string, displayName string) Event { func NewEvent(factory Factory, ns string, subsystem string, name string, displayName string) Event {
return Event{ return Event{
Total: factory.NewCounter(prometheus.CounterOpts{ Total: factory.NewCounter(prometheus.CounterOpts{
Namespace: ns, Namespace: ns,
Name: fmt.Sprintf("%s_total", name), Name: fmt.Sprintf("%s_total", name),
Help: fmt.Sprintf("Count of %s events", displayName), Help: fmt.Sprintf("Count of %s events", displayName),
Subsystem: subsystem,
}), }),
LastTime: factory.NewGauge(prometheus.GaugeOpts{ LastTime: factory.NewGauge(prometheus.GaugeOpts{
Namespace: ns, Namespace: ns,
Name: fmt.Sprintf("last_%s_unix", name), Name: fmt.Sprintf("last_%s_unix", name),
Help: fmt.Sprintf("Timestamp of last %s event", displayName), Help: fmt.Sprintf("Timestamp of last %s event", displayName),
Subsystem: subsystem,
}), }),
} }
} }
...@@ -41,17 +43,19 @@ func (e *EventVec) Record(lvs ...string) { ...@@ -41,17 +43,19 @@ func (e *EventVec) Record(lvs ...string) {
e.LastTime.WithLabelValues(lvs...).SetToCurrentTime() e.LastTime.WithLabelValues(lvs...).SetToCurrentTime()
} }
func NewEventVec(factory Factory, ns string, name string, displayName string, labelNames []string) EventVec { func NewEventVec(factory Factory, ns string, subsystem string, name string, displayName string, labelNames []string) EventVec {
return EventVec{ return EventVec{
Total: *factory.NewCounterVec(prometheus.CounterOpts{ Total: *factory.NewCounterVec(prometheus.CounterOpts{
Namespace: ns, Namespace: ns,
Name: fmt.Sprintf("%s_total", name), Name: fmt.Sprintf("%s_total", name),
Help: fmt.Sprintf("Count of %s events", displayName), Help: fmt.Sprintf("Count of %s events", displayName),
Subsystem: subsystem,
}, labelNames), }, labelNames),
LastTime: *factory.NewGaugeVec(prometheus.GaugeOpts{ LastTime: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Namespace: ns,
Name: fmt.Sprintf("last_%s_unix", name), Name: fmt.Sprintf("last_%s_unix", name),
Help: fmt.Sprintf("Timestamp of last %s event", displayName), Help: fmt.Sprintf("Timestamp of last %s event", displayName),
Subsystem: subsystem,
}, labelNames), }, labelNames),
} }
} }
...@@ -88,8 +88,8 @@ func MakeTxMetrics(ns string, factory metrics.Factory) TxMetrics { ...@@ -88,8 +88,8 @@ func MakeTxMetrics(ns string, factory metrics.Factory) TxMetrics {
Help: "Count of publish errors. Labells are sanitized error strings", Help: "Count of publish errors. Labells are sanitized error strings",
Subsystem: "txmgr", Subsystem: "txmgr",
}, []string{"error"}), }, []string{"error"}),
confirmEvent: metrics.NewEventVec(factory, ns, "confirm", "tx confirm", []string{"status"}), confirmEvent: metrics.NewEventVec(factory, ns, "txmgr", "confirm", "tx confirm", []string{"status"}),
publishEvent: metrics.NewEvent(factory, ns, "publish", "tx publish"), publishEvent: metrics.NewEvent(factory, ns, "txmgr", "publish", "tx publish"),
rpcError: factory.NewCounter(prometheus.CounterOpts{ rpcError: factory.NewCounter(prometheus.CounterOpts{
Namespace: ns, Namespace: ns,
Name: "rpc_error_count", Name: "rpc_error_count",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
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