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
3 changes: 3 additions & 0 deletions op-program/client/interop/oracle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,6 @@ func (o *OracleHinterStub) HintBlockExecution(parentBlockHash common.Hash, attr

func (o *OracleHinterStub) HintWithdrawalsRoot(blockHash common.Hash, chainID eth.ChainID) {
}

func (o *OracleHinterStub) HintBlockHashLookup(blockNumber uint64, headBlockHash common.Hash, l2ChainID eth.ChainID) {
}
6 changes: 6 additions & 0 deletions op-program/client/l2/fast_canon.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"math"

l2Types "github.com/ethereum-optimism/optimism/op-program/client/l2/types"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
Expand Down Expand Up @@ -33,6 +34,7 @@ type FastCanonicalBlockHeaderOracle struct {
ctx *chainContext
db ethdb.KeyValueStore
cache *simplelru.LRU[uint64, *types.Header]
hinter l2Types.OracleHinter
}

func NewFastCanonicalBlockHeaderOracle(
Expand All @@ -54,6 +56,7 @@ func NewFastCanonicalBlockHeaderOracle(
fallback: fallback,
ctx: ctx,
db: db,
hinter: stateOracle.Hinter(),
cache: cache,
}
}
Expand Down Expand Up @@ -109,6 +112,9 @@ func (o *FastCanonicalBlockHeaderOracle) GetHeaderByNumber(n uint64) *types.Head
}

func (o *FastCanonicalBlockHeaderOracle) getHistoricalBlockHash(head *types.Header, n uint64) *types.Block {
if o.hinter != nil {
o.hinter.HintBlockHashLookup(n, head.Hash(), eth.ChainIDFromBig(o.config.ChainID))
}
statedb, err := state.New(head.Root, state.NewDatabase(triedb.NewDatabase(rawdb.NewDatabase(o.db), nil), nil))
if err != nil {
panic(fmt.Errorf("failed to get state at %v: %w", head.Hash(), err))
Expand Down
38 changes: 36 additions & 2 deletions op-program/client/l2/fast_canon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/ethereum-optimism/optimism/op-program/client/l2/test"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/testutils"
"github.com/ethereum/go-ethereum/common"
Expand All @@ -20,7 +21,13 @@ func TestFastCanonBlockHeaderOracle_GetHeaderByNumber(t *testing.T) {

logger, _ := testlog.CaptureLogger(t, log.LvlInfo)
miner, backend := test.NewMiner(t, logger, 0)
stateOracle := &test.KvStateOracle{T: t, Source: backend.TrieDB().Disk()}
chainID := eth.ChainIDFromBig(backend.Config().ChainID)
capturingHinter := &test.CapturingHinter{}
stateOracle := &test.KvStateOracle{
T: t,
Source: backend.TrieDB().Disk(),
StubHinter: NewPreimageHinter(capturingHinter),
}
miner.Mine(t, nil)
miner.Mine(t, nil)
miner.Mine(t, nil)
Expand Down Expand Up @@ -50,12 +57,31 @@ func TestFastCanonBlockHeaderOracle_GetHeaderByNumber(t *testing.T) {

h := canon.GetHeaderByNumber(3)
require.Equal(t, backend.GetBlockByNumber(3).Hash(), h.Hash())
require.Len(t, capturingHinter.Hints, 0) // No lookups required
h = canon.GetHeaderByNumber(2)
require.Equal(t, backend.GetBlockByNumber(2).Hash(), h.Hash())
require.Len(t, capturingHinter.Hints, 1)
require.Equal(t, capturingHinter.Hints[0], BlockHashLookupHint{
BlockNumber: 2,
HeadBlockHash: head.Hash(),
ChainID: chainID,
})
h = canon.GetHeaderByNumber(1)
require.Equal(t, backend.GetBlockByNumber(1).Hash(), h.Hash())
require.Len(t, capturingHinter.Hints, 2)
require.Equal(t, capturingHinter.Hints[1], BlockHashLookupHint{
BlockNumber: 1,
HeadBlockHash: head.Hash(),
ChainID: chainID,
})
h = canon.GetHeaderByNumber(0)
require.Equal(t, backend.GetBlockByNumber(0).Hash(), h.Hash())
require.Len(t, capturingHinter.Hints, 3)
require.Equal(t, capturingHinter.Hints[2], BlockHashLookupHint{
BlockNumber: 0,
HeadBlockHash: head.Hash(),
ChainID: chainID,
})
}

func TestFastCanonBlockHeaderOracle_LargeWindow(t *testing.T) {
Expand Down Expand Up @@ -247,7 +273,12 @@ func TestFastCanonBlockHeaderOracle_SetCanonical(t *testing.T) {
func runCanonicalCacheTest(t *testing.T, backend *core.BlockChain, blockNum uint64, expectedNumRequests int) {
head := backend.CurrentHeader()
tracker := newTrackingBlockByHash(backend.GetBlockByHash)
stateOracle := &test.KvStateOracle{T: t, Source: backend.TrieDB().Disk()}
capturingHinter := &test.CapturingHinter{}
stateOracle := &test.KvStateOracle{
T: t,
Source: backend.TrieDB().Disk(),
StubHinter: NewPreimageHinter(capturingHinter),
}
// Create invalid fallback to assert that it's never used.
fatalBlockByHash := func(hash common.Hash) *types.Block {
t.Fatalf("Unexpected fallback for block: %v", hash)
Expand All @@ -261,12 +292,15 @@ func runCanonicalCacheTest(t *testing.T, backend *core.BlockChain, blockNum uint
h := canon.GetHeaderByNumber(blockNum)
require.Equal(t, expect, h.Hash())
require.Equalf(t, expectedNumRequests, tracker.numRequests, "Unexpected number of requests for block: %v (%d)", expect, blockNum)
require.Len(t, capturingHinter.Hints, expectedNumRequests)

// query again and assert that it's cached
tracker.numRequests = 0
capturingHinter.Hints = nil
h = canon.GetHeaderByNumber(blockNum)
require.Equal(t, expect, h.Hash())
require.Equalf(t, 1, tracker.numRequests, "Unexpected number of requests for block: %v (%d)", expect, blockNum)
require.Len(t, capturingHinter.Hints, 1)
}

type trackingBlockByHash struct {
Expand Down
43 changes: 33 additions & 10 deletions op-program/client/l2/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ import (
)

const (
HintL2BlockHeader = "l2-block-header"
HintL2Transactions = "l2-transactions"
HintL2Receipts = "l2-receipts"
HintL2Code = "l2-code"
HintL2StateNode = "l2-state-node"
HintL2Output = "l2-output"
HintL2BlockData = "l2-block-data"
HintAgreedPrestate = "agreed-pre-state"
HintL2AccountProof = "l2-account-proof"
HintL2PayloadWitness = "l2-payload-witness"
HintL2BlockHeader = "l2-block-header"
HintL2Transactions = "l2-transactions"
HintL2Receipts = "l2-receipts"
HintL2Code = "l2-code"
HintL2StateNode = "l2-state-node"
HintL2Output = "l2-output"
HintL2BlockData = "l2-block-data"
HintAgreedPrestate = "agreed-pre-state"
HintL2AccountProof = "l2-account-proof"
HintL2PayloadWitness = "l2-payload-witness"
HintL2BlockHashLookup = "l2-block-hash-lookup"
)

type LegacyBlockHeaderHint common.Hash
Expand Down Expand Up @@ -185,3 +186,25 @@ func (l PayloadWitnessHint) Hint() string {

return HintL2PayloadWitness + " " + hexutil.Encode(marshaled)
}

type BlockHashLookupHint struct {
BlockNumber uint64
HeadBlockHash common.Hash
ChainID eth.ChainID
}

func (b BlockHashLookupHint) Hint() string {
hintBytes := make([]byte, 8+32+8)

binary.BigEndian.PutUint64(hintBytes[0:8], b.BlockNumber)
copy(hintBytes[8:40], b.HeadBlockHash.Bytes())
binary.BigEndian.PutUint64(hintBytes[40:], eth.EvilChainIDToUInt64(b.ChainID))

return HintL2BlockHashLookup + " " + hexutil.Encode(hintBytes)
}

func (b BlockHashLookupHint) String() string {
return fmt.Sprintf("%v(%v, %v, %v)", HintL2BlockHashLookup, b.BlockNumber, b.HeadBlockHash, b.ChainID)
}

var _ preimage.Hint = BlockHashLookupHint{}
10 changes: 7 additions & 3 deletions op-program/client/l2/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ type StateOracle interface {
// CodeByHash retrieves the contract code pre-image for a given hash.
// codeHash should be retrieved from the world state account for a contract.
CodeByHash(codeHash common.Hash, chainID eth.ChainID) []byte

// Hinter provides an optional interface to provide proactive hints.
Hinter() l2Types.OracleHinter
}

// Oracle defines the high-level API used to retrieve L2 data.
Expand All @@ -44,9 +47,6 @@ type Oracle interface {
TransitionStateByRoot(root common.Hash) *interopTypes.TransitionState

ReceiptsByBlockHash(blockHash common.Hash, chainID eth.ChainID) (*types.Block, types.Receipts)

// Optional interface to provide proactive hints.
Hinter() l2Types.OracleHinter
}

type PreimageOracleHinter struct {
Expand All @@ -70,6 +70,10 @@ func (p *PreimageOracleHinter) HintWithdrawalsRoot(blockHash common.Hash, chainI
p.hint.Hint(AccountProofHint{BlockHash: blockHash, Address: predeploys.L2ToL1MessagePasserAddr, ChainID: chainID})
}

func (p *PreimageOracleHinter) HintBlockHashLookup(blockNumber uint64, headBlockHash common.Hash, l2ChainID eth.ChainID) {
p.hint.Hint(BlockHashLookupHint{BlockNumber: blockNumber, HeadBlockHash: headBlockHash, ChainID: l2ChainID})
}

// PreimageOracle implements Oracle using by interfacing with the pure preimage.Oracle
// to fetch pre-images to decode into the requested data.
type PreimageOracle struct {
Expand Down
24 changes: 22 additions & 2 deletions op-program/client/l2/test/stub_oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/binary"
"testing"

preimage "github.com/ethereum-optimism/optimism/op-preimage"
interopTypes "github.com/ethereum-optimism/optimism/op-program/client/interop/types"
l2Types "github.com/ethereum-optimism/optimism/op-program/client/l2/types"
"github.com/ethereum-optimism/optimism/op-service/eth"
Expand Down Expand Up @@ -104,8 +105,9 @@ func (o StubBlockOracle) ReceiptsByBlockHash(blockHash common.Hash, chainID eth.

// KvStateOracle loads data from a source ethdb.KeyValueStore
type KvStateOracle struct {
T *testing.T
Source ethdb.KeyValueStore
T *testing.T
Source ethdb.KeyValueStore
StubHinter l2Types.OracleHinter
}

func NewKvStateOracle(t *testing.T, db ethdb.KeyValueStore) *KvStateOracle {
Expand All @@ -127,6 +129,10 @@ func (o *KvStateOracle) CodeByHash(hash common.Hash, chainID eth.ChainID) []byte
return rawdb.ReadCode(o.Source, hash)
}

func (o *KvStateOracle) Hinter() l2Types.OracleHinter {
return o.StubHinter
}

func NewStubStateOracle(t *testing.T) *StubStateOracle {
return &StubStateOracle{
t: t,
Expand Down Expand Up @@ -158,6 +164,10 @@ func (o *StubStateOracle) CodeByHash(hash common.Hash, chainID eth.ChainID) []by
return data
}

func (o *StubStateOracle) Hinter() l2Types.OracleHinter {
return nil
}

type StubPrecompileOracle struct {
t *testing.T
Results map[common.Hash]PrecompileResult
Expand All @@ -183,3 +193,13 @@ func (o *StubPrecompileOracle) Precompile(address common.Address, input []byte,
o.Calls++
return result.Result, result.Ok
}

type CapturingHinter struct {
Hints []preimage.Hint
}

func (c *CapturingHinter) Hint(v preimage.Hint) {
c.Hints = append(c.Hints, v)
}

var _ preimage.Hinter = (*CapturingHinter)(nil)
1 change: 1 addition & 0 deletions op-program/client/l2/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ import (
type OracleHinter interface {
HintBlockExecution(parentBlockHash common.Hash, attr eth.PayloadAttributes, chainID eth.ChainID)
HintWithdrawalsRoot(blockHash common.Hash, chainID eth.ChainID)
HintBlockHashLookup(blockNumber uint64, headBlockHash common.Hash, l2ChainID eth.ChainID)
}