Commit 9c858501 authored by inphi's avatar inphi

op-program: Boot program using output root

The output root replaces the L2 head hash at boot as the agreed upon point to
run the fault proof program. The L2 head is still necessary for
derivation but is redundant as it's contained in the l2 output.
parent bbd72147
...@@ -86,7 +86,10 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) { ...@@ -86,7 +86,10 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) {
require.NoError(t, waitForSafeHead(ctx, receipt.BlockNumber.Uint64(), rollupClient)) require.NoError(t, waitForSafeHead(ctx, receipt.BlockNumber.Uint64(), rollupClient))
t.Logf("Capture current L2 head as agreed starting point. l2Head=%x l2BlockNumber=%v", receipt.BlockHash, receipt.BlockNumber) t.Logf("Capture current L2 head as agreed starting point. l2Head=%x l2BlockNumber=%v", receipt.BlockHash, receipt.BlockNumber)
l2Head := receipt.BlockHash agreedL2Output, err := rollupClient.OutputAtBlock(ctx, receipt.BlockNumber.Uint64())
require.NoError(t, err, "could not retrieve l2 agreed block")
l2Head := agreedL2Output.BlockRef.Hash
l2OutputRoot := agreedL2Output.OutputRoot
t.Log("=====Stopping batch submitter=====") t.Log("=====Stopping batch submitter=====")
err = sys.BatchSubmitter.Stop(ctx) err = sys.BatchSubmitter.Stop(ctx)
...@@ -136,6 +139,7 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) { ...@@ -136,6 +139,7 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) {
testFaultProofProgramScenario(t, ctx, sys, &FaultProofProgramTestScenario{ testFaultProofProgramScenario(t, ctx, sys, &FaultProofProgramTestScenario{
L1Head: l1Head, L1Head: l1Head,
L2Head: l2Head, L2Head: l2Head,
L2OutputRoot: common.Hash(l2OutputRoot),
L2Claim: common.Hash(l2Claim), L2Claim: common.Hash(l2Claim),
L2ClaimBlockNumber: l2ClaimBlockNumber, L2ClaimBlockNumber: l2ClaimBlockNumber,
Detached: detached, Detached: detached,
...@@ -181,9 +185,12 @@ func testVerifyL2OutputRoot(t *testing.T, detached bool) { ...@@ -181,9 +185,12 @@ func testVerifyL2OutputRoot(t *testing.T, detached bool) {
}) })
t.Log("Capture current L2 head as agreed starting point") t.Log("Capture current L2 head as agreed starting point")
l2AgreedBlock, err := l2Seq.BlockByNumber(ctx, nil) latestBlock, err := l2Seq.BlockByNumber(ctx, nil)
require.NoError(t, err)
agreedL2Output, err := rollupClient.OutputAtBlock(ctx, latestBlock.NumberU64())
require.NoError(t, err, "could not retrieve l2 agreed block") require.NoError(t, err, "could not retrieve l2 agreed block")
l2Head := l2AgreedBlock.Hash() l2Head := agreedL2Output.BlockRef.Hash
l2OutputRoot := agreedL2Output.OutputRoot
t.Log("Sending transactions to modify existing state, within challenged period") t.Log("Sending transactions to modify existing state, within challenged period")
SendDepositTx(t, cfg, l1Client, l2Seq, opts, func(l2Opts *DepositTxOpts) { SendDepositTx(t, cfg, l1Client, l2Seq, opts, func(l2Opts *DepositTxOpts) {
...@@ -214,6 +221,7 @@ func testVerifyL2OutputRoot(t *testing.T, detached bool) { ...@@ -214,6 +221,7 @@ func testVerifyL2OutputRoot(t *testing.T, detached bool) {
testFaultProofProgramScenario(t, ctx, sys, &FaultProofProgramTestScenario{ testFaultProofProgramScenario(t, ctx, sys, &FaultProofProgramTestScenario{
L1Head: l1Head, L1Head: l1Head,
L2Head: l2Head, L2Head: l2Head,
L2OutputRoot: common.Hash(l2OutputRoot),
L2Claim: common.Hash(l2Claim), L2Claim: common.Hash(l2Claim),
L2ClaimBlockNumber: l2ClaimBlockNumber, L2ClaimBlockNumber: l2ClaimBlockNumber,
Detached: detached, Detached: detached,
...@@ -223,6 +231,7 @@ func testVerifyL2OutputRoot(t *testing.T, detached bool) { ...@@ -223,6 +231,7 @@ func testVerifyL2OutputRoot(t *testing.T, detached bool) {
type FaultProofProgramTestScenario struct { type FaultProofProgramTestScenario struct {
L1Head common.Hash L1Head common.Hash
L2Head common.Hash L2Head common.Hash
L2OutputRoot common.Hash
L2Claim common.Hash L2Claim common.Hash
L2ClaimBlockNumber uint64 L2ClaimBlockNumber uint64
Detached bool Detached bool
...@@ -231,7 +240,7 @@ type FaultProofProgramTestScenario struct { ...@@ -231,7 +240,7 @@ type FaultProofProgramTestScenario struct {
// testFaultProofProgramScenario runs the fault proof program in several contexts, given a test scenario. // testFaultProofProgramScenario runs the fault proof program in several contexts, given a test scenario.
func testFaultProofProgramScenario(t *testing.T, ctx context.Context, sys *System, s *FaultProofProgramTestScenario) { func testFaultProofProgramScenario(t *testing.T, ctx context.Context, sys *System, s *FaultProofProgramTestScenario) {
preimageDir := t.TempDir() preimageDir := t.TempDir()
fppConfig := oppconf.NewConfig(sys.RollupConfig, sys.L2GenesisCfg.Config, s.L1Head, s.L2Head, common.Hash(s.L2Claim), s.L2ClaimBlockNumber) fppConfig := oppconf.NewConfig(sys.RollupConfig, sys.L2GenesisCfg.Config, s.L1Head, s.L2Head, s.L2OutputRoot, common.Hash(s.L2Claim), s.L2ClaimBlockNumber)
fppConfig.L1URL = sys.NodeEndpoint("l1") fppConfig.L1URL = sys.NodeEndpoint("l1")
fppConfig.L2URL = sys.NodeEndpoint("sequencer") fppConfig.L2URL = sys.NodeEndpoint("sequencer")
fppConfig.DataDir = preimageDir fppConfig.DataDir = preimageDir
......
package eth package eth
import ( import (
"errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
) )
type OutputResponse struct { type OutputResponse struct {
...@@ -12,3 +15,70 @@ type OutputResponse struct { ...@@ -12,3 +15,70 @@ type OutputResponse struct {
StateRoot common.Hash `json:"stateRoot"` StateRoot common.Hash `json:"stateRoot"`
Status *SyncStatus `json:"syncStatus"` Status *SyncStatus `json:"syncStatus"`
} }
var (
ErrInvalidOutput = errors.New("invalid output")
ErrInvalidOutputVersion = errors.New("invalid output version")
OutputVersionV0 = Bytes32{}
)
type Output interface {
// Version returns the version of the L2 output
Version() Bytes32
// Marshal a L2 output into a byte slice for hashing
Marshal() []byte
}
type OutputV0 struct {
StateRoot Bytes32
MessagePasserStorageRoot Bytes32
BlockHash common.Hash
}
func (o *OutputV0) Version() Bytes32 {
return OutputVersionV0
}
func (o *OutputV0) Marshal() []byte {
var buf [128]byte
version := o.Version()
copy(buf[:32], version[:])
copy(buf[32:], o.StateRoot[:])
copy(buf[64:], o.MessagePasserStorageRoot[:])
copy(buf[96:], o.BlockHash[:])
return buf[:]
}
// OutputRoot returns the keccak256 hash of the marshaled L2 output
func OutputRoot(output Output) Bytes32 {
marshaled := output.Marshal()
return Bytes32(crypto.Keccak256Hash(marshaled))
}
func UnmarshalOutput(data []byte) (Output, error) {
if len(data) < 32 {
return nil, ErrInvalidOutput
}
var ver Bytes32
copy(ver[:], data[:32])
switch ver {
case OutputVersionV0:
return unmarshalOutputV0(data)
default:
return nil, ErrInvalidOutputVersion
}
}
func unmarshalOutputV0(data []byte) (*OutputV0, error) {
if len(data) < 128 {
return nil, ErrInvalidOutput
}
var output OutputV0
// data[:32] is the version
copy(output.StateRoot[:], data[32:64])
copy(output.MessagePasserStorageRoot[:], data[64:96])
copy(output.BlockHash[:], data[96:128])
return &output, nil
}
package eth
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestOutputV0Codec(t *testing.T) {
output := OutputV0{
StateRoot: Bytes32{1, 2, 3},
MessagePasserStorageRoot: Bytes32{4, 5, 6},
BlockHash: common.Hash{7, 8, 9},
}
marshaled := output.Marshal()
unmarshaled, err := UnmarshalOutput(marshaled)
require.NoError(t, err)
unmarshaledV0 := unmarshaled.(*OutputV0)
require.Equal(t, output, *unmarshaledV0)
_, err = UnmarshalOutput([]byte{0: 0xA, 32: 0xA})
require.ErrorIs(t, err, ErrInvalidOutputVersion)
_, err = UnmarshalOutput([]byte{64: 0xA})
require.ErrorIs(t, err, ErrInvalidOutput)
}
...@@ -5,32 +5,33 @@ import ( ...@@ -5,32 +5,33 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/crypto"
) )
var NilProof = errors.New("Output root proof is nil") var ErrNilProof = errors.New("output root proof is nil")
// ComputeL2OutputRoot computes the L2 output root by hashing an output root proof. // ComputeL2OutputRoot computes the L2 output root by hashing an output root proof.
func ComputeL2OutputRoot(proofElements *bindings.TypesOutputRootProof) (eth.Bytes32, error) { func ComputeL2OutputRoot(proofElements *bindings.TypesOutputRootProof) (eth.Bytes32, error) {
if proofElements == nil { if proofElements == nil {
return eth.Bytes32{}, NilProof return eth.Bytes32{}, ErrNilProof
} }
digest := crypto.Keccak256Hash( if proofElements.Version != [32]byte{} {
proofElements.Version[:], return eth.Bytes32{}, errors.New("unsupported output root version")
proofElements.StateRoot[:], }
proofElements.MessagePasserStorageRoot[:], l2Output := eth.OutputV0{
proofElements.LatestBlockhash[:], StateRoot: eth.Bytes32(proofElements.StateRoot),
) MessagePasserStorageRoot: proofElements.MessagePasserStorageRoot,
return eth.Bytes32(digest), nil BlockHash: proofElements.LatestBlockhash,
}
return eth.OutputRoot(&l2Output), nil
} }
func ComputeL2OutputRootV0(block eth.BlockInfo, storageRoot [32]byte) (eth.Bytes32, error) { func ComputeL2OutputRootV0(block eth.BlockInfo, storageRoot [32]byte) (eth.Bytes32, error) {
var l2OutputRootVersion eth.Bytes32 // it's zero for now stateRoot := block.Root()
return ComputeL2OutputRoot(&bindings.TypesOutputRootProof{ l2Output := eth.OutputV0{
Version: l2OutputRootVersion, StateRoot: eth.Bytes32(stateRoot),
StateRoot: block.Root(),
MessagePasserStorageRoot: storageRoot, MessagePasserStorageRoot: storageRoot,
LatestBlockhash: block.Hash(), BlockHash: block.Hash(),
}) }
return eth.OutputRoot(&l2Output), nil
} }
...@@ -115,3 +115,10 @@ func (s *L1Client) L1BlockRefByHash(ctx context.Context, hash common.Hash) (eth. ...@@ -115,3 +115,10 @@ func (s *L1Client) L1BlockRefByHash(ctx context.Context, hash common.Hash) (eth.
s.l1BlockRefsCache.Add(ref.Hash, ref) s.l1BlockRefsCache.Add(ref.Hash, ref)
return ref, nil return ref, nil
} }
func (s *L1Client) L2OutputByRoot(ctx context.Context, l2OutputRoot common.Hash) (eth.Output, error) {
// TODO(inphi): Fetch Output from preset. Or directly from the oracle
//return s.OutputByRoot(ctx, l2OutputRoot)
var output eth.Output
return output, nil
}
...@@ -37,3 +37,12 @@ func (m *MockL1Source) L1BlockRefByHash(ctx context.Context, hash common.Hash) ( ...@@ -37,3 +37,12 @@ func (m *MockL1Source) L1BlockRefByHash(ctx context.Context, hash common.Hash) (
func (m *MockL1Source) ExpectL1BlockRefByHash(hash common.Hash, ref eth.L1BlockRef, err error) { func (m *MockL1Source) ExpectL1BlockRefByHash(hash common.Hash, ref eth.L1BlockRef, err error) {
m.Mock.On("L1BlockRefByHash", hash).Once().Return(ref, &err) m.Mock.On("L1BlockRefByHash", hash).Once().Return(ref, &err)
} }
func (m *MockL1Source) L2OutputByRoot(ctx context.Context, root common.Hash) (eth.Output, error) {
out := m.Mock.MethodCalled("L2OutputByRoot", root)
return out[0].(eth.Output), *out[1].(*error)
}
func (m *MockL1Source) ExpectL2OutputByRoot(root common.Hash, output eth.Output, err error) {
m.Mock.On("L2OutputByRoot", root).Once().Return(output, &err)
}
...@@ -12,7 +12,7 @@ import ( ...@@ -12,7 +12,7 @@ import (
const ( const (
L1HeadLocalIndex preimage.LocalIndexKey = iota + 1 L1HeadLocalIndex preimage.LocalIndexKey = iota + 1
L2HeadLocalIndex L2OutputRootLocalIndex
L2ClaimLocalIndex L2ClaimLocalIndex
L2ClaimBlockNumberLocalIndex L2ClaimBlockNumberLocalIndex
L2ChainConfigLocalIndex L2ChainConfigLocalIndex
...@@ -21,7 +21,7 @@ const ( ...@@ -21,7 +21,7 @@ const (
type BootInfo struct { type BootInfo struct {
L1Head common.Hash L1Head common.Hash
L2Head common.Hash L2OutputRoot common.Hash
L2Claim common.Hash L2Claim common.Hash
L2ClaimBlockNumber uint64 L2ClaimBlockNumber uint64
L2ChainConfig *params.ChainConfig L2ChainConfig *params.ChainConfig
...@@ -42,7 +42,7 @@ func NewBootstrapClient(r oracleClient) *BootstrapClient { ...@@ -42,7 +42,7 @@ func NewBootstrapClient(r oracleClient) *BootstrapClient {
func (br *BootstrapClient) BootInfo() *BootInfo { func (br *BootstrapClient) BootInfo() *BootInfo {
l1Head := common.BytesToHash(br.r.Get(L1HeadLocalIndex)) l1Head := common.BytesToHash(br.r.Get(L1HeadLocalIndex))
l2Head := common.BytesToHash(br.r.Get(L2HeadLocalIndex)) l2OutputRoot := common.BytesToHash(br.r.Get(L2OutputRootLocalIndex))
l2Claim := common.BytesToHash(br.r.Get(L2ClaimLocalIndex)) l2Claim := common.BytesToHash(br.r.Get(L2ClaimLocalIndex))
l2ClaimBlockNumber := binary.BigEndian.Uint64(br.r.Get(L2ClaimBlockNumberLocalIndex)) l2ClaimBlockNumber := binary.BigEndian.Uint64(br.r.Get(L2ClaimBlockNumberLocalIndex))
l2ChainConfig := new(params.ChainConfig) l2ChainConfig := new(params.ChainConfig)
...@@ -58,7 +58,7 @@ func (br *BootstrapClient) BootInfo() *BootInfo { ...@@ -58,7 +58,7 @@ func (br *BootstrapClient) BootInfo() *BootInfo {
return &BootInfo{ return &BootInfo{
L1Head: l1Head, L1Head: l1Head,
L2Head: l2Head, L2OutputRoot: l2OutputRoot,
L2Claim: l2Claim, L2Claim: l2Claim,
L2ClaimBlockNumber: l2ClaimBlockNumber, L2ClaimBlockNumber: l2ClaimBlockNumber,
L2ChainConfig: l2ChainConfig, L2ChainConfig: l2ChainConfig,
......
...@@ -15,7 +15,7 @@ import ( ...@@ -15,7 +15,7 @@ import (
func TestBootstrapClient(t *testing.T) { func TestBootstrapClient(t *testing.T) {
bootInfo := &BootInfo{ bootInfo := &BootInfo{
L1Head: common.HexToHash("0x1111"), L1Head: common.HexToHash("0x1111"),
L2Head: common.HexToHash("0x2222"), L2OutputRoot: common.HexToHash("0x2222"),
L2Claim: common.HexToHash("0x3333"), L2Claim: common.HexToHash("0x3333"),
L2ClaimBlockNumber: 1, L2ClaimBlockNumber: 1,
L2ChainConfig: params.GoerliChainConfig, L2ChainConfig: params.GoerliChainConfig,
...@@ -34,8 +34,8 @@ func (o *mockBoostrapOracle) Get(key preimage.Key) []byte { ...@@ -34,8 +34,8 @@ func (o *mockBoostrapOracle) Get(key preimage.Key) []byte {
switch key.PreimageKey() { switch key.PreimageKey() {
case L1HeadLocalIndex.PreimageKey(): case L1HeadLocalIndex.PreimageKey():
return o.b.L1Head[:] return o.b.L1Head[:]
case L2HeadLocalIndex.PreimageKey(): case L2OutputRootLocalIndex.PreimageKey():
return o.b.L2Head[:] return o.b.L2OutputRoot[:]
case L2ClaimLocalIndex.PreimageKey(): case L2ClaimLocalIndex.PreimageKey():
return o.b.L2Claim[:] return o.b.L2Claim[:]
case L2ClaimBlockNumberLocalIndex.PreimageKey(): case L2ClaimBlockNumberLocalIndex.PreimageKey():
......
...@@ -12,21 +12,24 @@ const cacheSize = 2000 ...@@ -12,21 +12,24 @@ const cacheSize = 2000
// CachingOracle is an implementation of Oracle that delegates to another implementation, adding caching of all results // CachingOracle is an implementation of Oracle that delegates to another implementation, adding caching of all results
type CachingOracle struct { type CachingOracle struct {
oracle Oracle oracle Oracle
blocks *simplelru.LRU[common.Hash, eth.BlockInfo] blocks *simplelru.LRU[common.Hash, eth.BlockInfo]
txs *simplelru.LRU[common.Hash, types.Transactions] txs *simplelru.LRU[common.Hash, types.Transactions]
rcpts *simplelru.LRU[common.Hash, types.Receipts] rcpts *simplelru.LRU[common.Hash, types.Receipts]
outputs *simplelru.LRU[common.Hash, eth.Output]
} }
func NewCachingOracle(oracle Oracle) *CachingOracle { func NewCachingOracle(oracle Oracle) *CachingOracle {
blockLRU, _ := simplelru.NewLRU[common.Hash, eth.BlockInfo](cacheSize, nil) blockLRU, _ := simplelru.NewLRU[common.Hash, eth.BlockInfo](cacheSize, nil)
txsLRU, _ := simplelru.NewLRU[common.Hash, types.Transactions](cacheSize, nil) txsLRU, _ := simplelru.NewLRU[common.Hash, types.Transactions](cacheSize, nil)
rcptsLRU, _ := simplelru.NewLRU[common.Hash, types.Receipts](cacheSize, nil) rcptsLRU, _ := simplelru.NewLRU[common.Hash, types.Receipts](cacheSize, nil)
outputsLRU, _ := simplelru.NewLRU[common.Hash, eth.Output](cacheSize, nil)
return &CachingOracle{ return &CachingOracle{
oracle: oracle, oracle: oracle,
blocks: blockLRU, blocks: blockLRU,
txs: txsLRU, txs: txsLRU,
rcpts: rcptsLRU, rcpts: rcptsLRU,
outputs: outputsLRU,
} }
} }
...@@ -61,3 +64,13 @@ func (o *CachingOracle) ReceiptsByBlockHash(blockHash common.Hash) (eth.BlockInf ...@@ -61,3 +64,13 @@ func (o *CachingOracle) ReceiptsByBlockHash(blockHash common.Hash) (eth.BlockInf
o.rcpts.Add(blockHash, rcpts) o.rcpts.Add(blockHash, rcpts)
return block, rcpts return block, rcpts
} }
func (o *CachingOracle) L2OutputByRoot(l2OutputRoot common.Hash) eth.Output {
output, ok := o.outputs.Get(l2OutputRoot)
if ok {
return output
}
output = o.oracle.L2OutputByRoot(l2OutputRoot)
o.outputs.Add(l2OutputRoot, output)
return output
}
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/testutils" "github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum-optimism/optimism/op-program/client/l1/test" "github.com/ethereum-optimism/optimism/op-program/client/l1/test"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -71,3 +72,29 @@ func TestCachingOracle_ReceiptsByBlockHash(t *testing.T) { ...@@ -71,3 +72,29 @@ func TestCachingOracle_ReceiptsByBlockHash(t *testing.T) {
require.Equal(t, eth.BlockToInfo(block), actualBlock) require.Equal(t, eth.BlockToInfo(block), actualBlock)
require.EqualValues(t, rcpts, actualRcpts) require.EqualValues(t, rcpts, actualRcpts)
} }
func TestCachingOracle_L2OutputByRoot(t *testing.T) {
rng := rand.New(rand.NewSource(1))
stub := test.NewStubOracle(t)
oracle := NewCachingOracle(stub)
block, _ := testutils.RandomBlock(rng, 3)
var storageRoot [32]byte
rng.Read(storageRoot[:])
l2Output := &eth.OutputV0{
StateRoot: eth.Bytes32(block.Root()),
MessagePasserStorageRoot: storageRoot,
BlockHash: block.Hash(),
}
l2OutputRoot := common.Hash(eth.OutputRoot(l2Output))
// Initial call retrieves from the stub
stub.L2Outputs[l2OutputRoot] = l2Output
result := oracle.L2OutputByRoot(l2OutputRoot)
require.Equal(t, l2Output.Marshal(), result)
// Later calls should retrieve from cache
delete(stub.L2Outputs, l2OutputRoot)
result = oracle.L2OutputByRoot(l2OutputRoot)
require.Equal(t, l2Output.Marshal(), result)
}
...@@ -80,3 +80,7 @@ func (o *OracleL1Client) InfoAndTxsByHash(ctx context.Context, hash common.Hash) ...@@ -80,3 +80,7 @@ func (o *OracleL1Client) InfoAndTxsByHash(ctx context.Context, hash common.Hash)
info, txs := o.oracle.TransactionsByBlockHash(hash) info, txs := o.oracle.TransactionsByBlockHash(hash)
return info, txs, nil return info, txs, nil
} }
func (o *OracleL1Client) L2OutputByRoot(ctx context.Context, root common.Hash) (eth.Output, error) {
return o.oracle.L2OutputByRoot(root), nil
}
...@@ -10,6 +10,7 @@ const ( ...@@ -10,6 +10,7 @@ const (
HintL1BlockHeader = "l1-block-header" HintL1BlockHeader = "l1-block-header"
HintL1Transactions = "l1-transactions" HintL1Transactions = "l1-transactions"
HintL1Receipts = "l1-receipts" HintL1Receipts = "l1-receipts"
HintL2Output = "l1-l2-output"
) )
type BlockHeaderHint common.Hash type BlockHeaderHint common.Hash
...@@ -35,3 +36,11 @@ var _ preimage.Hint = ReceiptsHint{} ...@@ -35,3 +36,11 @@ var _ preimage.Hint = ReceiptsHint{}
func (l ReceiptsHint) Hint() string { func (l ReceiptsHint) Hint() string {
return HintL1Receipts + " " + (common.Hash)(l).String() return HintL1Receipts + " " + (common.Hash)(l).String()
} }
type L2OutputHint common.Hash
var _ preimage.Hint = L2OutputHint{}
func (l L2OutputHint) Hint() string {
return HintL2Output + " " + (common.Hash)(l).String()
}
...@@ -21,6 +21,9 @@ type Oracle interface { ...@@ -21,6 +21,9 @@ type Oracle interface {
// ReceiptsByBlockHash retrieves the receipts from the block with the given hash. // ReceiptsByBlockHash retrieves the receipts from the block with the given hash.
ReceiptsByBlockHash(blockHash common.Hash) (eth.BlockInfo, types.Receipts) ReceiptsByBlockHash(blockHash common.Hash) (eth.BlockInfo, types.Receipts)
// L2OutputByRoot retrieves the L2 output for the given L2 output root.
L2OutputByRoot(l2OutputRoot common.Hash) eth.Output
} }
// PreimageOracle implements Oracle using by interfacing with the pure preimage.Oracle // PreimageOracle implements Oracle using by interfacing with the pure preimage.Oracle
...@@ -86,3 +89,13 @@ func (p *PreimageOracle) ReceiptsByBlockHash(blockHash common.Hash) (eth.BlockIn ...@@ -86,3 +89,13 @@ func (p *PreimageOracle) ReceiptsByBlockHash(blockHash common.Hash) (eth.BlockIn
return info, receipts return info, receipts
} }
func (p *PreimageOracle) L2OutputByRoot(l2OutputRoot common.Hash) eth.Output {
p.hint.Hint(L2OutputHint(l2OutputRoot))
data := p.oracle.Get(preimage.Keccak256Key(l2OutputRoot))
output, err := eth.UnmarshalOutput(data)
if err != nil {
panic(fmt.Errorf("invalidd L2 output data for root %s: %w", l2OutputRoot, err))
}
return output
}
...@@ -19,14 +19,18 @@ type StubOracle struct { ...@@ -19,14 +19,18 @@ type StubOracle struct {
// Rcpts maps Block hash to receipts // Rcpts maps Block hash to receipts
Rcpts map[common.Hash]types.Receipts Rcpts map[common.Hash]types.Receipts
// L2Outputs maps L2 output roots to L2 outputs
L2Outputs map[common.Hash]eth.Output
} }
func NewStubOracle(t *testing.T) *StubOracle { func NewStubOracle(t *testing.T) *StubOracle {
return &StubOracle{ return &StubOracle{
t: t, t: t,
Blocks: make(map[common.Hash]eth.BlockInfo), Blocks: make(map[common.Hash]eth.BlockInfo),
Txs: make(map[common.Hash]types.Transactions), Txs: make(map[common.Hash]types.Transactions),
Rcpts: make(map[common.Hash]types.Receipts), Rcpts: make(map[common.Hash]types.Receipts),
L2Outputs: make(map[common.Hash]eth.Output),
} }
} }
func (o StubOracle) HeaderByBlockHash(blockHash common.Hash) eth.BlockInfo { func (o StubOracle) HeaderByBlockHash(blockHash common.Hash) eth.BlockInfo {
...@@ -52,3 +56,11 @@ func (o StubOracle) ReceiptsByBlockHash(blockHash common.Hash) (eth.BlockInfo, t ...@@ -52,3 +56,11 @@ func (o StubOracle) ReceiptsByBlockHash(blockHash common.Hash) (eth.BlockInfo, t
} }
return o.HeaderByBlockHash(blockHash), rcpts return o.HeaderByBlockHash(blockHash), rcpts
} }
func (o StubOracle) L2OutputByRoot(l2OutputRoot common.Hash) eth.Output {
output, ok := o.L2Outputs[l2OutputRoot]
if !ok {
o.t.Fatalf("unknown output %s", l2OutputRoot)
}
return output
}
...@@ -53,7 +53,7 @@ func RunProgram(logger log.Logger, preimageOracle io.ReadWriter, preimageHinter ...@@ -53,7 +53,7 @@ func RunProgram(logger log.Logger, preimageOracle io.ReadWriter, preimageHinter
bootInfo.RollupConfig, bootInfo.RollupConfig,
bootInfo.L2ChainConfig, bootInfo.L2ChainConfig,
bootInfo.L1Head, bootInfo.L1Head,
bootInfo.L2Head, bootInfo.L2OutputRoot,
bootInfo.L2Claim, bootInfo.L2Claim,
bootInfo.L2ClaimBlockNumber, bootInfo.L2ClaimBlockNumber,
l1PreimageOracle, l1PreimageOracle,
...@@ -62,8 +62,17 @@ func RunProgram(logger log.Logger, preimageOracle io.ReadWriter, preimageHinter ...@@ -62,8 +62,17 @@ func RunProgram(logger log.Logger, preimageOracle io.ReadWriter, preimageHinter
} }
// runDerivation executes the L2 state transition, given a minimal interface to retrieve data. // runDerivation executes the L2 state transition, given a minimal interface to retrieve data.
func runDerivation(logger log.Logger, cfg *rollup.Config, l2Cfg *params.ChainConfig, l1Head common.Hash, l2Head common.Hash, l2Claim common.Hash, l2ClaimBlockNum uint64, l1Oracle l1.Oracle, l2Oracle l2.Oracle) error { func runDerivation(logger log.Logger, cfg *rollup.Config, l2Cfg *params.ChainConfig, l1Head common.Hash, l2OutputRoot common.Hash, l2Claim common.Hash, l2ClaimBlockNum uint64, l1Oracle l1.Oracle, l2Oracle l2.Oracle) error {
l1Source := l1.NewOracleL1Client(logger, l1Oracle, l1Head) l1Source := l1.NewOracleL1Client(logger, l1Oracle, l1Head)
output, err := l1Source.L2OutputByRoot(context.Background(), l2OutputRoot)
if err != nil {
return fmt.Errorf("failed to find L2 output for %s: %w", l2OutputRoot, err)
}
outputV0, ok := output.(*eth.OutputV0)
if !ok {
return fmt.Errorf("unsupported L2 output version: %d", output.Version())
}
l2Head := outputV0.BlockHash
engineBackend, err := l2.NewOracleBackedL2Chain(logger, l2Oracle, l2Cfg, l2Head) engineBackend, err := l2.NewOracleBackedL2Chain(logger, l2Oracle, l2Cfg, l2Head)
if err != nil { if err != nil {
return fmt.Errorf("failed to create oracle-backed L2 chain: %w", err) return fmt.Errorf("failed to create oracle-backed L2 chain: %w", err)
......
...@@ -20,10 +20,12 @@ var ( ...@@ -20,10 +20,12 @@ var (
l1HeadValue = common.HexToHash("0x111111").Hex() l1HeadValue = common.HexToHash("0x111111").Hex()
l2HeadValue = common.HexToHash("0x222222").Hex() l2HeadValue = common.HexToHash("0x222222").Hex()
l2ClaimValue = common.HexToHash("0x333333").Hex() l2ClaimValue = common.HexToHash("0x333333").Hex()
l2OutputRoot = common.HexToHash("0x444444").Hex()
l2ClaimBlockNumber = uint64(1203) l2ClaimBlockNumber = uint64(1203)
// Note: This is actually the L1 goerli genesis config. Just using it as an arbitrary, valid genesis config // Note: This is actually the L1 goerli genesis config. Just using it as an arbitrary, valid genesis config
l2Genesis = core.DefaultGoerliGenesisBlock() l2Genesis = core.DefaultGoerliGenesisBlock()
l2GenesisConfig = l2Genesis.Config l2GenesisConfig = l2Genesis.Config
l2OutputOracleAddress = common.HexToAddress("0x1234567890123456789012345678901234567890").Hex()
) )
func TestLogLevel(t *testing.T) { func TestLogLevel(t *testing.T) {
...@@ -48,6 +50,7 @@ func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { ...@@ -48,6 +50,7 @@ func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
config.OPGoerliChainConfig, config.OPGoerliChainConfig,
common.HexToHash(l1HeadValue), common.HexToHash(l1HeadValue),
common.HexToHash(l2HeadValue), common.HexToHash(l2HeadValue),
common.HexToHash(l2OutputRoot),
common.HexToHash(l2ClaimValue), common.HexToHash(l2ClaimValue),
l2ClaimBlockNumber) l2ClaimBlockNumber)
require.Equal(t, defaultCfg, cfg) require.Equal(t, defaultCfg, cfg)
...@@ -121,14 +124,14 @@ func TestL2Genesis(t *testing.T) { ...@@ -121,14 +124,14 @@ func TestL2Genesis(t *testing.T) {
}) })
} }
func TestL2Head(t *testing.T) { func TestL2OutputRoot(t *testing.T) {
t.Run("Required", func(t *testing.T) { t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l2.head is required", addRequiredArgsExcept("--l2.head")) verifyArgsInvalid(t, "flag l2.outputroot is required", addRequiredArgsExcept("--l2.head"))
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, replaceRequiredArg("--l2.head", l2HeadValue)) cfg := configForArgs(t, replaceRequiredArg("--l2.outputroot", l2HeadValue))
require.Equal(t, common.HexToHash(l2HeadValue), cfg.L2Head) require.Equal(t, common.HexToHash(l2HeadValue), cfg.L2OutputRoot)
}) })
t.Run("Invalid", func(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
......
...@@ -22,6 +22,7 @@ var ( ...@@ -22,6 +22,7 @@ var (
ErrMissingL2Genesis = errors.New("missing l2 genesis") ErrMissingL2Genesis = errors.New("missing l2 genesis")
ErrInvalidL1Head = errors.New("invalid l1 head") ErrInvalidL1Head = errors.New("invalid l1 head")
ErrInvalidL2Head = errors.New("invalid l2 head") ErrInvalidL2Head = errors.New("invalid l2 head")
ErrInvalidL2OutputRoot = errors.New("invalid l2 output root")
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")
ErrInvalidL2Claim = errors.New("invalid l2 claim") ErrInvalidL2Claim = errors.New("invalid l2 claim")
ErrInvalidL2ClaimBlock = errors.New("invalid l2 claim block number") ErrInvalidL2ClaimBlock = errors.New("invalid l2 claim block number")
...@@ -41,9 +42,12 @@ type Config struct { ...@@ -41,9 +42,12 @@ type Config struct {
L1TrustRPC bool L1TrustRPC bool
L1RPCKind sources.RPCProviderKind L1RPCKind sources.RPCProviderKind
// L2Head is the agreed L2 block to start derivation from // L2Head is the l2 block hash contained in the L2 Output referenced by the L2OutputRoot
// TODO(inphi): This can be made optional with hardcoded rollup configs and output oracle addresses
L2Head common.Hash L2Head common.Hash
L2URL string // L2OutputRoot is the agreed L2 output root to start derivation from
L2OutputRoot common.Hash
L2URL string
// L2Claim is the claimed L2 output root to verify // L2Claim is the claimed L2 output root to verify
L2Claim common.Hash L2Claim common.Hash
// L2ClaimBlockNumber is the block number the claimed L2 output root is from // L2ClaimBlockNumber is the block number the claimed L2 output root is from
...@@ -70,8 +74,8 @@ func (c *Config) Check() error { ...@@ -70,8 +74,8 @@ func (c *Config) Check() error {
if c.L1Head == (common.Hash{}) { if c.L1Head == (common.Hash{}) {
return ErrInvalidL1Head return ErrInvalidL1Head
} }
if c.L2Head == (common.Hash{}) { if c.L2OutputRoot == (common.Hash{}) {
return ErrInvalidL2Head return ErrInvalidL2OutputRoot
} }
if c.L2Claim == (common.Hash{}) { if c.L2Claim == (common.Hash{}) {
return ErrInvalidL2Claim return ErrInvalidL2Claim
...@@ -99,12 +103,21 @@ func (c *Config) FetchingEnabled() bool { ...@@ -99,12 +103,21 @@ 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, l2Genesis *params.ChainConfig, l1Head common.Hash, l2Head common.Hash, l2Claim common.Hash, l2ClaimBlockNum uint64) *Config { func NewConfig(
rollupCfg *rollup.Config,
l2Genesis *params.ChainConfig,
l1Head common.Hash,
l2Head common.Hash,
l2OutputRoot common.Hash,
l2Claim common.Hash,
l2ClaimBlockNum uint64,
) *Config {
return &Config{ return &Config{
Rollup: rollupCfg, Rollup: rollupCfg,
L2ChainConfig: l2Genesis, L2ChainConfig: l2Genesis,
L1Head: l1Head, L1Head: l1Head,
L2Head: l2Head, L2Head: l2Head,
L2OutputRoot: l2OutputRoot,
L2Claim: l2Claim, L2Claim: l2Claim,
L2ClaimBlockNumber: l2ClaimBlockNum, L2ClaimBlockNumber: l2ClaimBlockNum,
L1RPCKind: sources.RPCKindBasic, L1RPCKind: sources.RPCKindBasic,
...@@ -123,6 +136,10 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) { ...@@ -123,6 +136,10 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) {
if l2Head == (common.Hash{}) { if l2Head == (common.Hash{}) {
return nil, ErrInvalidL2Head return nil, ErrInvalidL2Head
} }
l2OutputRoot := common.HexToHash(ctx.String(flags.L2Head.Name))
if l2OutputRoot == (common.Hash{}) {
return nil, ErrInvalidL2OutputRoot
}
l2Claim := common.HexToHash(ctx.String(flags.L2Claim.Name)) l2Claim := common.HexToHash(ctx.String(flags.L2Claim.Name))
if l2Claim == (common.Hash{}) { if l2Claim == (common.Hash{}) {
return nil, ErrInvalidL2Claim return nil, ErrInvalidL2Claim
...@@ -152,6 +169,7 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) { ...@@ -152,6 +169,7 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) {
L2URL: ctx.String(flags.L2NodeAddr.Name), L2URL: ctx.String(flags.L2NodeAddr.Name),
L2ChainConfig: l2ChainConfig, L2ChainConfig: l2ChainConfig,
L2Head: l2Head, L2Head: l2Head,
L2OutputRoot: l2OutputRoot,
L2Claim: l2Claim, L2Claim: l2Claim,
L2ClaimBlockNumber: l2ClaimBlockNum, L2ClaimBlockNumber: l2ClaimBlockNum,
L1Head: l1Head, L1Head: l1Head,
......
...@@ -16,6 +16,7 @@ var ( ...@@ -16,6 +16,7 @@ var (
validL1Head = common.Hash{0xaa} validL1Head = common.Hash{0xaa}
validL2Head = common.Hash{0xbb} validL2Head = common.Hash{0xbb}
validL2Claim = common.Hash{0xcc} validL2Claim = common.Hash{0xcc}
validL2OutputRoot = common.Hash{0xdd}
validL2ClaimBlockNum = uint64(15) validL2ClaimBlockNum = uint64(15)
) )
...@@ -151,7 +152,7 @@ func TestRejectExecAndServerMode(t *testing.T) { ...@@ -151,7 +152,7 @@ func TestRejectExecAndServerMode(t *testing.T) {
} }
func validConfig() *Config { func validConfig() *Config {
cfg := NewConfig(validRollupConfig, validL2Genesis, validL1Head, validL2Head, validL2Claim, validL2ClaimBlockNum) cfg := NewConfig(validRollupConfig, validL2Genesis, validL1Head, validL2Head, validL2OutputRoot, validL2Claim, validL2ClaimBlockNum)
cfg.DataDir = "/tmp/configTest" cfg.DataDir = "/tmp/configTest"
return cfg return cfg
} }
...@@ -50,6 +50,11 @@ var ( ...@@ -50,6 +50,11 @@ var (
Usage: "Hash of the agreed L2 block to start derivation from", Usage: "Hash of the agreed L2 block to start derivation from",
EnvVars: prefixEnvVars("L2_HEAD"), EnvVars: prefixEnvVars("L2_HEAD"),
} }
L2OutputRoot = &cli.StringFlag{
Name: "l2.outputroot",
Usage: "L2 Output Root at l2.head",
EnvVars: prefixEnvVars("L2_OUTPUT_ROOT"),
}
L2Claim = &cli.StringFlag{ L2Claim = &cli.StringFlag{
Name: "l2.claim", Name: "l2.claim",
Usage: "Claimed L2 output root to validate", Usage: "Claimed L2 output root to validate",
...@@ -103,6 +108,7 @@ var Flags []cli.Flag ...@@ -103,6 +108,7 @@ var Flags []cli.Flag
var requiredFlags = []cli.Flag{ var requiredFlags = []cli.Flag{
L1Head, L1Head,
L2Head, L2Head,
L2OutputRoot,
L2Claim, L2Claim,
L2BlockNumber, L2BlockNumber,
} }
......
...@@ -23,7 +23,8 @@ func TestServerMode(t *testing.T) { ...@@ -23,7 +23,8 @@ func TestServerMode(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
l1Head := common.Hash{0x11} l1Head := common.Hash{0x11}
cfg := config.NewConfig(&chaincfg.Goerli, config.OPGoerliChainConfig, l1Head, common.Hash{0x22}, common.Hash{0x33}, 1000) l2OutputRoot := common.Hash{0x33}
cfg := config.NewConfig(&chaincfg.Goerli, config.OPGoerliChainConfig, l1Head, common.Hash{0x22}, l2OutputRoot, common.Hash{0x44}, 1000)
cfg.DataDir = dir cfg.DataDir = dir
cfg.ServerMode = true cfg.ServerMode = true
...@@ -43,7 +44,8 @@ func TestServerMode(t *testing.T) { ...@@ -43,7 +44,8 @@ func TestServerMode(t *testing.T) {
hClient := preimage.NewHintWriter(hintClient) hClient := preimage.NewHintWriter(hintClient)
l1PreimageOracle := l1.NewPreimageOracle(pClient, hClient) l1PreimageOracle := l1.NewPreimageOracle(pClient, hClient)
require.Equal(t, l1Head.Bytes(), pClient.Get(client.L1HeadLocalIndex), "Should get preimages") require.Equal(t, l1Head.Bytes(), pClient.Get(client.L1HeadLocalIndex), "Should get l1 head preimages")
require.Equal(t, l2OutputRoot.Bytes(), pClient.Get(client.L2OutputRootLocalIndex), "Should get l2 output root preimages")
// Should exit when a preimage is unavailable // Should exit when a preimage is unavailable
require.Panics(t, func() { require.Panics(t, func() {
......
...@@ -19,7 +19,7 @@ func NewLocalPreimageSource(config *config.Config) *LocalPreimageSource { ...@@ -19,7 +19,7 @@ func NewLocalPreimageSource(config *config.Config) *LocalPreimageSource {
var ( var (
l1HeadKey = client.L1HeadLocalIndex.PreimageKey() l1HeadKey = client.L1HeadLocalIndex.PreimageKey()
l2HeadKey = client.L2HeadLocalIndex.PreimageKey() l2OutputRootKey = client.L2OutputRootLocalIndex.PreimageKey()
l2ClaimKey = client.L2ClaimLocalIndex.PreimageKey() l2ClaimKey = client.L2ClaimLocalIndex.PreimageKey()
l2ClaimBlockNumberKey = client.L2ClaimBlockNumberLocalIndex.PreimageKey() l2ClaimBlockNumberKey = client.L2ClaimBlockNumberLocalIndex.PreimageKey()
l2ChainConfigKey = client.L2ChainConfigLocalIndex.PreimageKey() l2ChainConfigKey = client.L2ChainConfigLocalIndex.PreimageKey()
...@@ -30,8 +30,8 @@ func (s *LocalPreimageSource) Get(key common.Hash) ([]byte, error) { ...@@ -30,8 +30,8 @@ func (s *LocalPreimageSource) Get(key common.Hash) ([]byte, error) {
switch [32]byte(key) { switch [32]byte(key) {
case l1HeadKey: case l1HeadKey:
return s.config.L1Head.Bytes(), nil return s.config.L1Head.Bytes(), nil
case l2HeadKey: case l2OutputRootKey:
return s.config.L2Head.Bytes(), nil return s.config.L2OutputRoot.Bytes(), nil
case l2ClaimKey: case l2ClaimKey:
return s.config.L2Claim.Bytes(), nil return s.config.L2Claim.Bytes(), nil
case l2ClaimBlockNumberKey: case l2ClaimBlockNumberKey:
......
...@@ -17,7 +17,7 @@ func TestLocalPreimageSource(t *testing.T) { ...@@ -17,7 +17,7 @@ func TestLocalPreimageSource(t *testing.T) {
cfg := &config.Config{ cfg := &config.Config{
Rollup: &chaincfg.Goerli, Rollup: &chaincfg.Goerli,
L1Head: common.HexToHash("0x1111"), L1Head: common.HexToHash("0x1111"),
L2Head: common.HexToHash("0x2222"), L2OutputRoot: common.HexToHash("0x2222"),
L2Claim: common.HexToHash("0x3333"), L2Claim: common.HexToHash("0x3333"),
L2ClaimBlockNumber: 1234, L2ClaimBlockNumber: 1234,
L2ChainConfig: params.GoerliChainConfig, L2ChainConfig: params.GoerliChainConfig,
...@@ -29,7 +29,7 @@ func TestLocalPreimageSource(t *testing.T) { ...@@ -29,7 +29,7 @@ func TestLocalPreimageSource(t *testing.T) {
expected []byte expected []byte
}{ }{
{"L1Head", l1HeadKey, cfg.L1Head.Bytes()}, {"L1Head", l1HeadKey, cfg.L1Head.Bytes()},
{"L2Head", l2HeadKey, cfg.L2Head.Bytes()}, {"L2OutputRoot", l2OutputRootKey, cfg.L2OutputRoot.Bytes()},
{"L2Claim", l2ClaimKey, cfg.L2Claim.Bytes()}, {"L2Claim", l2ClaimKey, cfg.L2Claim.Bytes()},
{"L2ClaimBlockNumber", l2ClaimBlockNumberKey, binary.BigEndian.AppendUint64(nil, cfg.L2ClaimBlockNumber)}, {"L2ClaimBlockNumber", l2ClaimBlockNumberKey, binary.BigEndian.AppendUint64(nil, cfg.L2ClaimBlockNumber)},
{"Rollup", rollupKey, asJson(t, cfg.Rollup)}, {"Rollup", rollupKey, asJson(t, cfg.Rollup)},
......
...@@ -23,6 +23,7 @@ type L1Source interface { ...@@ -23,6 +23,7 @@ type L1Source interface {
InfoByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, error) InfoByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, error)
InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, 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) FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error)
L2OutputByRoot(ctx context.Context, l2OutputRoot common.Hash) (eth.Output, error)
} }
type L2Source interface { type L2Source interface {
...@@ -98,6 +99,12 @@ func (p *Prefetcher) prefetch(ctx context.Context, hint string) error { ...@@ -98,6 +99,12 @@ func (p *Prefetcher) prefetch(ctx context.Context, hint string) error {
return fmt.Errorf("failed to fetch L1 block %s receipts: %w", hash, err) return fmt.Errorf("failed to fetch L1 block %s receipts: %w", hash, err)
} }
return p.storeReceipts(receipts) return p.storeReceipts(receipts)
case l1.HintL2Output:
output, err := p.l1Fetcher.L2OutputByRoot(ctx, hash)
if err != nil {
return fmt.Errorf("failed to fetch L2 output root %s: %w", hash, err)
}
return p.kvStore.Put(preimage.Keccak256Key(hash).PreimageKey(), output.Marshal())
case l2.HintL2BlockHeader: case l2.HintL2BlockHeader:
header, txs, err := p.l2Fetcher.InfoAndTxsByHash(ctx, hash) header, txs, err := p.l2Fetcher.InfoAndTxsByHash(ctx, hash)
if err != nil { if err != nil {
......
...@@ -73,6 +73,20 @@ func (s *RetryingL1Source) FetchReceipts(ctx context.Context, blockHash common.H ...@@ -73,6 +73,20 @@ func (s *RetryingL1Source) FetchReceipts(ctx context.Context, blockHash common.H
return info, rcpts, err return info, rcpts, err
} }
func (s *RetryingL1Source) L2OutputByRoot(ctx context.Context, root common.Hash) (eth.Output, error) {
var output eth.Output
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
o, err := s.source.L2OutputByRoot(ctx, root)
if err != nil {
s.logger.Warn("Failed to fetch l2 output", "root", root, "err", err)
return err
}
output = o
return nil
})
return output, err
}
var _ L1Source = (*RetryingL1Source)(nil) var _ L1Source = (*RetryingL1Source)(nil)
type RetryingL2Source struct { type RetryingL2Source struct {
......
...@@ -101,6 +101,18 @@ func Run(l1RpcUrl string, l2RpcUrl string, l2OracleAddr common.Address) error { ...@@ -101,6 +101,18 @@ func Run(l1RpcUrl string, l2RpcUrl string, l2OracleAddr common.Address) error {
return fmt.Errorf("retrieve agreed l2 block: %w", err) return fmt.Errorf("retrieve agreed l2 block: %w", err)
} }
l2Head := l2AgreedBlock.Hash() l2Head := l2AgreedBlock.Hash()
agreedOutputIndex, err := outputOracle.GetL2OutputIndexAfter(callOpts, l2AgreedBlock.Number())
if err != nil {
return fmt.Errorf("failed to output index after agreed block")
}
agreedOutput, err := outputOracle.GetL2Output(callOpts, agreedOutputIndex)
if err != nil {
return fmt.Errorf("retrieve agreed output: %w", err)
}
if agreedOutput.OutputRoot == output.OutputRoot {
// TODO(inphi): Don't return here but keep searching preceeding blocks for a different output
return fmt.Errorf("agreed output is the same as the output claim")
}
temp, err := os.MkdirTemp("", "oracledata") temp, err := os.MkdirTemp("", "oracledata")
if err != nil { if err != nil {
...@@ -120,6 +132,7 @@ func Run(l1RpcUrl string, l2RpcUrl string, l2OracleAddr common.Address) error { ...@@ -120,6 +132,7 @@ func Run(l1RpcUrl string, l2RpcUrl string, l2OracleAddr common.Address) error {
"--datadir", temp, "--datadir", temp,
"--l1.head", l1Head.Hex(), "--l1.head", l1Head.Hex(),
"--l2.head", l2Head.Hex(), "--l2.head", l2Head.Hex(),
"--l2.outputroot", common.Bytes2Hex(agreedOutput.OutputRoot[:]),
"--l2.claim", l2Claim.Hex(), "--l2.claim", l2Claim.Hex(),
"--l2.blocknumber", l2BlockNumber.String(), "--l2.blocknumber", l2BlockNumber.String(),
} }
......
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