Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions op-program/client/l2/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ func (o *OracleKeyValueStore) Get(key []byte) ([]byte, error) {

if len(key) == codePrefixedKeyLength && bytes.HasPrefix(key, 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 {
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 {
Expand Down
53 changes: 26 additions & 27 deletions op-program/client/l2/db_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package l2

import (
"fmt"
"math/big"
"testing"

Expand All @@ -27,24 +26,16 @@ var (
var _ ethdb.KeyValueStore = (*OracleKeyValueStore)(nil)

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) {
oracle := newStubStateOracle()
oracle := newStubStateOracle(t)
db := NewOracleBackedDB(oracle)
val, err := db.Get([]byte{1, 2, 3})
require.ErrorIs(t, err, ErrInvalidKeyLength)
require.Nil(t, val)
})

t.Run("KeyWithCodePrefix", func(t *testing.T) {
oracle := newStubStateOracle()
oracle := newStubStateOracle(t)
db := NewOracleBackedDB(oracle)
key := common.HexToHash("0x12345678")
prefixedKey := append(rawdb.CodePrefix, key.Bytes()...)
Expand All @@ -58,7 +49,7 @@ func TestGet(t *testing.T) {
})

t.Run("NormalKeyThatHappensToStartWithCodePrefix", func(t *testing.T) {
oracle := newStubStateOracle()
oracle := newStubStateOracle(t)
db := NewOracleBackedDB(oracle)
key := make([]byte, common.HashLength)
copy(rawdb.CodePrefix, key)
Expand All @@ -74,7 +65,7 @@ func TestGet(t *testing.T) {
t.Run("KnownKey", func(t *testing.T) {
key := common.HexToHash("0xAA4488")
expected := []byte{2, 6, 3, 8}
oracle := newStubStateOracle()
oracle := newStubStateOracle(t)
oracle.data[key] = expected
db := NewOracleBackedDB(oracle)
val, err := db.Get(key.Bytes())
Expand All @@ -85,7 +76,7 @@ func TestGet(t *testing.T) {

func TestPut(t *testing.T) {
t.Run("NewKey", func(t *testing.T) {
oracle := newStubStateOracle()
oracle := newStubStateOracle(t)
db := NewOracleBackedDB(oracle)
key := common.HexToHash("0xAA4488")
value := []byte{2, 6, 3, 8}
Expand All @@ -97,7 +88,7 @@ func TestPut(t *testing.T) {
require.Equal(t, value, actual)
})
t.Run("ReplaceKey", func(t *testing.T) {
oracle := newStubStateOracle()
oracle := newStubStateOracle(t)
db := NewOracleBackedDB(oracle)
key := common.HexToHash("0xAA4488")
value1 := []byte{2, 6, 3, 8}
Expand All @@ -119,14 +110,15 @@ func TestSupportsStateDBOperations(t *testing.T) {
genesisBlock := l2Genesis.MustCommit(realDb)

loader := &kvStateOracle{
t: t,
source: realDb,
}
assertStateDataAvailable(t, NewOracleBackedDB(loader), l2Genesis, genesisBlock)
}

func TestUpdateState(t *testing.T) {
l2Genesis := createGenesis()
oracle := newStubStateOracle()
oracle := newStubStateOracle(t)
db := rawdb.NewDatabase(NewOracleBackedDB(oracle))

genesisBlock := l2Genesis.MustCommit(db)
Expand Down Expand Up @@ -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")
}

func newStubStateOracle() *stubStateOracle {
func newStubStateOracle(t *testing.T) *stubStateOracle {
return &stubStateOracle{
t: t,
data: make(map[common.Hash][]byte),
code: make(map[common.Hash][]byte),
}
}

type stubStateOracle struct {
t *testing.T
data 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]
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]
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
type kvStateOracle struct {
t *testing.T
source ethdb.KeyValueStore
}

func (o *kvStateOracle) NodeByHash(nodeHash common.Hash) ([]byte, error) {
return o.source.Get(nodeHash.Bytes())
func (o *kvStateOracle) NodeByHash(nodeHash common.Hash) []byte {
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) {
return rawdb.ReadCode(o.source, hash), nil
func (o *kvStateOracle) CodeByHash(hash common.Hash) []byte {
return rawdb.ReadCode(o.source, hash)
}
14 changes: 2 additions & 12 deletions op-program/client/l2/engine_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ type OracleBackedL2Chain struct {
var _ engineapi.EngineBackend = (*OracleBackedL2Chain)(nil)

func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, chainCfg *params.ChainConfig, l2Head common.Hash) (*OracleBackedL2Chain, error) {
head, err := oracle.BlockByHash(l2Head)
if err != nil {
return nil, fmt.Errorf("loading l2 head: %w", err)
}
head := oracle.BlockByHash(l2Head)
logger.Info("Loaded L2 head", "hash", head.Hash(), "number", head.Number())
return &OracleBackedL2Chain{
log: logger,
Expand Down Expand Up @@ -99,10 +96,7 @@ func (o *OracleBackedL2Chain) GetBlockByHash(hash common.Hash) *types.Block {
return block
}
// Retrieve from the oracle
block, err := o.oracle.BlockByHash(hash)
if err != nil {
handleError(err)
}
block = o.oracle.BlockByHash(hash)
if block == nil {
return nil
}
Expand Down Expand Up @@ -195,7 +189,3 @@ func (o *OracleBackedL2Chain) SetFinalized(header *types.Header) {
func (o *OracleBackedL2Chain) SetSafe(header *types.Header) {
o.safe = header
}

func handleError(err error) {
panic(err)
}
9 changes: 5 additions & 4 deletions op-program/client/l2/engine_backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,9 @@ func TestUpdateStateDatabaseWhenImportingBlock(t *testing.T) {

require.NotEqual(t, blocks[1].Root(), newBlock.Root(), "block should have modified world state")

_, err = chain.StateAt(newBlock.Root())
require.Error(t, err, "state from non-imported block should not be available")
require.Panics(t, func() {
_, _ = chain.StateAt(newBlock.Root())
}, "state from non-imported block should not be available")

err = chain.InsertBlockWithoutSetHead(newBlock)
require.NoError(t, err)
Expand Down Expand Up @@ -223,8 +224,8 @@ func newStubBlockOracle(chain []*types.Block, db ethdb.Database) *stubBlockOracl
}
}

func (o stubBlockOracle) BlockByHash(blockHash common.Hash) (*types.Block, error) {
return o.blocks[blockHash], nil
func (o stubBlockOracle) BlockByHash(blockHash common.Hash) *types.Block {
return o.blocks[blockHash]
}

func TestEngineAPITests(t *testing.T) {
Expand Down
9 changes: 3 additions & 6 deletions op-program/client/l2/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ type StateOracle interface {
// 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.
// 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, error)
NodeByHash(nodeHash common.Hash) []byte

// CodeByHash retrieves the contract code pre-image for a given hash.
// 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, error)
CodeByHash(codeHash common.Hash) []byte
}

// Oracle defines the high-level API used to retrieve L2 data.
Expand All @@ -26,6 +24,5 @@ type Oracle interface {
StateOracle

// BlockByHash retrieves the block with the given hash.
// Returns an error if the block is not available.
BlockByHash(blockHash common.Hash) (*types.Block, error)
BlockByHash(blockHash common.Hash) *types.Block
}
23 changes: 15 additions & 8 deletions op-program/host/l2/fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,26 @@ func NewFetchingL2Oracle(ctx context.Context, logger log.Logger, l2Url string) (
}, 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)
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
code, err := o.dbGet(append(rawdb.CodePrefix, hash.Bytes()...))
if err != nil {
// 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) {
Expand All @@ -66,10 +73,10 @@ func (o *FetchingL2Oracle) dbGet(key []byte) ([]byte, error) {
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)
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
}
34 changes: 15 additions & 19 deletions op-program/host/l2/fetcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ func TestNodeByHash(t *testing.T) {
}
fetcher := newFetcher(nil, stub)

node, err := fetcher.NodeByHash(hash)
require.ErrorIs(t, err, stub.nextErr)
require.Nil(t, node)
require.Panics(t, func() {
fetcher.NodeByHash(hash)
})
})

t.Run("Success", func(t *testing.T) {
Expand All @@ -75,8 +75,7 @@ func TestNodeByHash(t *testing.T) {
}
fetcher := newFetcher(nil, stub)

node, err := fetcher.NodeByHash(hash)
require.NoError(t, err)
node := fetcher.NodeByHash(hash)
require.EqualValues(t, expected, node)
})

Expand All @@ -86,7 +85,7 @@ func TestNodeByHash(t *testing.T) {
}
fetcher := newFetcher(nil, stub)

_, _ = fetcher.NodeByHash(hash)
fetcher.NodeByHash(hash)
require.Len(t, stub.requests, 1, "should make single request")
req := stub.requests[0]
require.Equal(t, "debug_dbGet", req.method)
Expand All @@ -104,9 +103,7 @@ func TestCodeByHash(t *testing.T) {
}
fetcher := newFetcher(nil, stub)

node, err := fetcher.CodeByHash(hash)
require.ErrorIs(t, err, stub.nextErr)
require.Nil(t, node)
require.Panics(t, func() { fetcher.CodeByHash(hash) })
})

t.Run("Success", func(t *testing.T) {
Expand All @@ -116,8 +113,7 @@ func TestCodeByHash(t *testing.T) {
}
fetcher := newFetcher(nil, stub)

node, err := fetcher.CodeByHash(hash)
require.NoError(t, err)
node := fetcher.CodeByHash(hash)
require.EqualValues(t, expected, node)
})

Expand All @@ -127,7 +123,7 @@ func TestCodeByHash(t *testing.T) {
}
fetcher := newFetcher(nil, stub)

_, _ = fetcher.CodeByHash(hash)
fetcher.CodeByHash(hash)
require.Len(t, stub.requests, 1, "should make single request")
req := stub.requests[0]
require.Equal(t, "debug_dbGet", req.method)
Expand All @@ -141,7 +137,8 @@ func TestCodeByHash(t *testing.T) {
}
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")
req := stub.requests[0]
require.Equal(t, "debug_dbGet", req.method)
Expand Down Expand Up @@ -183,25 +180,24 @@ func TestBlockByHash(t *testing.T) {
stub := &stubBlockSource{nextResult: block}
fetcher := newFetcher(stub, nil)

res, err := fetcher.BlockByHash(hash)
require.NoError(t, err)
res := fetcher.BlockByHash(hash)
require.Same(t, block, res)
})

t.Run("Error", func(t *testing.T) {
stub := &stubBlockSource{nextErr: errors.New("boom")}
fetcher := newFetcher(stub, nil)

res, err := fetcher.BlockByHash(hash)
require.ErrorIs(t, err, stub.nextErr)
require.Nil(t, res)
require.Panics(t, func() {
fetcher.BlockByHash(hash)
})
})

t.Run("RequestArgs", func(t *testing.T) {
stub := &stubBlockSource{}
fetcher := newFetcher(stub, nil)

_, _ = fetcher.BlockByHash(hash)
fetcher.BlockByHash(hash)

require.Len(t, stub.requests, 1, "should make single request")
req := stub.requests[0]
Expand Down