From 4a5adb523374008a37831da5febff9a3501a4e81 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 24 Jul 2023 00:27:16 +0800 Subject: [PATCH] feat(prover): update `l1Current` cursor to record L1 hash (#327) --- driver/chain_syncer/calldata/syncer.go | 2 +- driver/driver_test.go | 2 +- pkg/rpc/methods.go | 69 +++++++++++++++++-- pkg/rpc/methods_test.go | 27 ++++++++ prover/proof_producer/proof_producer.go | 1 + .../proof_producer/special_proof_producer.go | 41 ++++++++--- prover/proof_submitter/util.go | 12 ++-- prover/proof_submitter/util_test.go | 6 +- .../proof_submitter/valid_proof_submitter.go | 2 + prover/prover.go | 66 +++++++++++++----- 10 files changed, 188 insertions(+), 40 deletions(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index d621170a2..a0d542cb0 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -157,7 +157,7 @@ func (s *Syncer) onBlockProposed( l1CurrentToReset = genesisL1Header lastInsertedBlockIDToReset = common.Big0 } else { - reorged, l1CurrentToReset, lastInsertedBlockIDToReset, err = s.rpc.CheckL1Reorg( + reorged, l1CurrentToReset, lastInsertedBlockIDToReset, err = s.rpc.CheckL1ReorgFromL2EE( ctx, new(big.Int).Sub(event.BlockId, common.Big1), ) diff --git a/driver/driver_test.go b/driver/driver_test.go index ad5c53852..8a78bbc68 100644 --- a/driver/driver_test.go +++ b/driver/driver_test.go @@ -132,7 +132,7 @@ func (s *DriverTestSuite) TestCheckL1Reorg() { s.Greater(l2Head2.Number.Uint64(), l2Head1.Number.Uint64()) s.Greater(l1Head2.Number.Uint64(), l1Head1.Number.Uint64()) - reorged, _, _, err := s.RpcClient.CheckL1Reorg(context.Background(), l2Head2.Number) + reorged, _, _, err := s.RpcClient.CheckL1ReorgFromL2EE(context.Background(), l2Head2.Number) s.Nil(err) s.False(reorged) diff --git a/pkg/rpc/methods.go b/pkg/rpc/methods.go index 16451d5c0..84b21bd87 100644 --- a/pkg/rpc/methods.go +++ b/pkg/rpc/methods.go @@ -194,7 +194,7 @@ func (c *Client) WaitL1Origin(ctx context.Context, blockID *big.Int) (*rawdb.L1O l1Origin, err = c.L2.L1OriginByID(ctxWithTimeout, blockID) if err != nil { - log.Warn("L1Origin from L2 execution engine not found, keep retrying", "blockID", blockID, "error", err) + log.Debug("L1Origin from L2 execution engine not found, keep retrying", "blockID", blockID, "error", err) continue } @@ -328,9 +328,9 @@ func (c *Client) GetStorageRoot( return proof.StorageHash, nil } -// CheckL1Reorg checks whether the L1 chain has been reorged, if so, returns the l1Current cursor and L2 blockID -// that need to reset to. -func (c *Client) CheckL1Reorg(ctx context.Context, blockID *big.Int) (bool, *types.Header, *big.Int, error) { +// CheckL1ReorgFromL2EE checks whether the L1 chain has been reorged from the L1Origin records in L2 EE, +// if so, returns the l1Current cursor and L2 blockID that need to reset to. +func (c *Client) CheckL1ReorgFromL2EE(ctx context.Context, blockID *big.Int) (bool, *types.Header, *big.Int, error) { var ( reorged bool l1CurrentToReset *types.Header @@ -414,7 +414,7 @@ func (c *Client) CheckL1Reorg(ctx context.Context, blockID *big.Int) (bool, *typ } log.Debug( - "Check L1 reorg", + "Check L1 reorg from L2 EE", "reorged", reorged, "l1CurrentToResetNumber", l1CurrentToReset.Number, "l1CurrentToResetHash", l1CurrentToReset.Hash(), @@ -424,6 +424,65 @@ func (c *Client) CheckL1Reorg(ctx context.Context, blockID *big.Int) (bool, *typ return reorged, l1CurrentToReset, blockIDToReset, nil } +// CheckL1ReorgFromL1Cursor checks whether the L1 chain has been reorged from the given l1Current cursor, +// if so, returns the l1Current cursor that need to reset to. +func (c *Client) CheckL1ReorgFromL1Cursor( + ctx context.Context, + l1Current *types.Header, + genesisHeightL1 uint64, +) (bool, *types.Header, error) { + var ( + reorged bool + l1CurrentToReset *types.Header + ) + for { + if l1Current.Number.Uint64() <= genesisHeightL1 { + newL1Current, err := c.L1.HeaderByNumber(ctx, new(big.Int).SetUint64(genesisHeightL1)) + if err != nil { + return false, nil, err + } + + l1CurrentToReset = newL1Current + break + } + + l1Header, err := c.L1.BlockByNumber(ctx, l1Current.Number) + if err != nil { + if err.Error() == ethereum.NotFound.Error() { + continue + } + + return false, nil, err + } + + if l1Header.Hash() != l1Current.Hash() { + log.Info( + "Reorg detected", + "l1Height", l1Current.Number, + "l1HashOld", l1Current.Hash(), + "l1HashNew", l1Header.Hash(), + ) + reorged = true + if l1Current, err = c.L1.HeaderByHash(ctx, l1Current.ParentHash); err != nil { + return false, nil, err + } + continue + } + + l1CurrentToReset = l1Current + break + } + + log.Debug( + "Check L1 reorg from l1Current cursor", + "reorged", reorged, + "l1CurrentToResetNumber", l1CurrentToReset.Number, + "l1CurrentToResetHash", l1CurrentToReset.Hash(), + ) + + return reorged, l1CurrentToReset, nil +} + // IsJustSyncedByP2P checks whether the given L2 execution engine has just finished a P2P // sync. func (c *Client) IsJustSyncedByP2P(ctx context.Context) (bool, error) { diff --git a/pkg/rpc/methods_test.go b/pkg/rpc/methods_test.go index 506e2eb8a..93432e861 100644 --- a/pkg/rpc/methods_test.go +++ b/pkg/rpc/methods_test.go @@ -2,6 +2,7 @@ package rpc import ( "context" + "math/big" "testing" "github.com/ethereum/go-ethereum/common" @@ -63,3 +64,29 @@ func TestGetProtocolStateVariables(t *testing.T) { _, err := client.GetProtocolStateVariables(nil) require.Nil(t, err) } + +func TestCheckL1ReorgFromL1Cursor(t *testing.T) { + client := newTestClient(t) + + l1Head, err := client.L1.HeaderByNumber(context.Background(), nil) + require.Nil(t, err) + + _, newL1Current, err := client.CheckL1ReorgFromL1Cursor(context.Background(), l1Head, l1Head.Number.Uint64()) + require.Nil(t, err) + + require.Equal(t, l1Head.Number.Uint64(), newL1Current.Number.Uint64()) + + stateVar, err := client.TaikoL1.GetStateVariables(nil) + require.Nil(t, err) + + reorged, _, err := client.CheckL1ReorgFromL1Cursor(context.Background(), l1Head, stateVar.GenesisHeight) + require.Nil(t, err) + require.False(t, reorged) + + l1Head.BaseFee = new(big.Int).Add(l1Head.BaseFee, common.Big1) + + reorged, newL1Current, err = client.CheckL1ReorgFromL1Cursor(context.Background(), l1Head, stateVar.GenesisHeight) + require.Nil(t, err) + require.True(t, reorged) + require.Equal(t, l1Head.ParentHash, newL1Current.Hash()) +} diff --git a/prover/proof_producer/proof_producer.go b/prover/proof_producer/proof_producer.go index eadea1997..dd842864c 100644 --- a/prover/proof_producer/proof_producer.go +++ b/prover/proof_producer/proof_producer.go @@ -25,6 +25,7 @@ type ProofRequestOptions struct { BlockHash common.Hash ParentHash common.Hash SignalRoot common.Hash + EventL1Hash common.Hash Graffiti string GasUsed uint64 ParentGasUsed uint64 diff --git a/prover/proof_producer/special_proof_producer.go b/prover/proof_producer/special_proof_producer.go index d71743f51..e19336a49 100644 --- a/prover/proof_producer/special_proof_producer.go +++ b/prover/proof_producer/special_proof_producer.go @@ -8,6 +8,7 @@ import ( "math/big" "time" + "github.com/cenkalti/backoff/v4" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -76,9 +77,31 @@ func (p *SpecialProofProducer) RequestProof( "beneficiary", meta.Beneficiary, "height", header.Number, "hash", header.Hash(), + "delay", p.delay, ) - block, err := p.rpc.L2.BlockByHash(ctx, header.Hash()) + time.AfterFunc(p.delay, func() { + if err := backoff.Retry( + func() error { return p.requestSpecialProof(ctx, opts, blockID, meta, header, resultCh) }, + backoff.NewExponentialBackOff(), + ); err != nil { + log.Error("Failed to request special proof", "blockID", blockID) + } + }) + + return nil +} + +// requestSpecialProof tries to generate a special proof for protocol. +func (p *SpecialProofProducer) requestSpecialProof( + ctx context.Context, + opts *ProofRequestOptions, + blockID *big.Int, + meta *bindings.TaikoDataBlockMetadata, + header *types.Header, + resultCh chan *ProofWithHeader, +) error { + block, err := p.rpc.L2.BlockByNumber(ctx, header.Number) if err != nil { return fmt.Errorf("failed to get L2 block with given hash %s: %w", header.Hash(), err) } @@ -127,15 +150,13 @@ func (p *SpecialProofProducer) RequestProof( return fmt.Errorf("failed to sign evidence: %w", err) } - time.AfterFunc(p.delay, func() { - resultCh <- &ProofWithHeader{ - BlockID: blockID, - Header: header, - Meta: meta, - ZkProof: proof, - Opts: opts, - } - }) + resultCh <- &ProofWithHeader{ + BlockID: blockID, + Header: header, + Meta: meta, + ZkProof: proof, + Opts: opts, + } return nil } diff --git a/prover/proof_submitter/util.go b/prover/proof_submitter/util.go index c5f21a191..f2b86c3b3 100644 --- a/prover/proof_submitter/util.go +++ b/prover/proof_submitter/util.go @@ -66,6 +66,7 @@ func sendTxWithBackoff( ctx context.Context, cli *rpc.Client, blockID *big.Int, + eventL1Hash common.Hash, proposedAt uint64, meta *bindings.TaikoDataBlockMetadata, sendTxFunc func() (*types.Transaction, error), @@ -81,23 +82,22 @@ func sendTxWithBackoff( } // Check if the corresponding L1 block is still in the canonical chain. - l1Header, err := cli.L1.HeaderByNumber(ctx, new(big.Int).SetUint64(meta.L1Height)) + l1Header, err := cli.L1.HeaderByNumber(ctx, new(big.Int).SetUint64(meta.L1Height+1)) if err != nil { log.Warn( "Failed to fetch L1 block", "blockID", blockID, - "l1Height", meta.L1Height, - "l1Hash", common.BytesToHash(meta.L1Hash[:]), + "l1Height", meta.L1Height+1, "error", err, ) return err } - if l1Header.Hash() != meta.L1Hash { + if l1Header.Hash() != eventL1Hash { log.Warn( "Reorg detected, skip the current proof submission", "blockID", blockID, - "l1Height", meta.L1Height, - "l1HashOld", common.BytesToHash(meta.L1Hash[:]), + "l1Height", meta.L1Height+1, + "l1HashOld", eventL1Hash, "l1HashNew", l1Header.Hash(), ) return nil diff --git a/prover/proof_submitter/util_test.go b/prover/proof_submitter/util_test.go index e57f0039c..c4545d501 100644 --- a/prover/proof_submitter/util_test.go +++ b/prover/proof_submitter/util_test.go @@ -31,11 +31,14 @@ func (s *ProofSubmitterTestSuite) TestGetProveBlocksTxOpts() { func (s *ProofSubmitterTestSuite) TestSendTxWithBackoff() { l1Head, err := s.RpcClient.L1.HeaderByNumber(context.Background(), nil) s.Nil(err) - meta := &bindings.TaikoDataBlockMetadata{L1Height: l1Head.Number.Uint64(), L1Hash: l1Head.Hash()} + l1HeadChild, err := s.RpcClient.L1.HeaderByNumber(context.Background(), new(big.Int).Sub(l1Head.Number, common.Big1)) + s.Nil(err) + meta := &bindings.TaikoDataBlockMetadata{L1Height: l1HeadChild.Number.Uint64(), L1Hash: l1HeadChild.Hash()} s.NotNil(sendTxWithBackoff( context.Background(), s.RpcClient, common.Big1, + l1Head.Hash(), 0, meta, func() (*types.Transaction, error) { return nil, errors.New("L1_TEST") }, @@ -46,6 +49,7 @@ func (s *ProofSubmitterTestSuite) TestSendTxWithBackoff() { context.Background(), s.RpcClient, common.Big1, + l1Head.Hash(), 0, meta, func() (*types.Transaction, error) { diff --git a/prover/proof_submitter/valid_proof_submitter.go b/prover/proof_submitter/valid_proof_submitter.go index c3a4fc2d4..5ef6c9a6d 100644 --- a/prover/proof_submitter/valid_proof_submitter.go +++ b/prover/proof_submitter/valid_proof_submitter.go @@ -129,6 +129,7 @@ func (s *ValidProofSubmitter) RequestProof(ctx context.Context, event *bindings. BlockHash: block.Hash(), ParentHash: block.ParentHash(), SignalRoot: signalRoot, + EventL1Hash: event.Raw.BlockHash, Graffiti: common.Bytes2Hex(s.graffiti[:]), GasUsed: block.GasUsed(), ParentGasUsed: parent.GasUsed(), @@ -255,6 +256,7 @@ func (s *ValidProofSubmitter) SubmitProof( ctx, s.rpc, blockID, + proofWithHeader.Opts.EventL1Hash, block.Header().Time, proofWithHeader.Meta, sendTx, diff --git a/prover/prover.go b/prover/prover.go index f5751fad4..c607232c6 100644 --- a/prover/prover.go +++ b/prover/prover.go @@ -13,6 +13,7 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -50,7 +51,8 @@ type Prover struct { // States latestVerifiedL1Height uint64 lastHandledBlockID uint64 - l1Current uint64 + genesisHeightL1 uint64 + l1Current *types.Header reorgDetectedFlag bool // Proof submitters @@ -325,7 +327,7 @@ func (p *Prover) proveOp() error { iter, err := eventIterator.NewBlockProposedIterator(p.ctx, &eventIterator.BlockProposedIteratorConfig{ Client: p.rpc.L1, TaikoL1: p.rpc.TaikoL1, - StartHeight: new(big.Int).SetUint64(p.l1Current), + StartHeight: new(big.Int).SetUint64(p.l1Current.Number.Uint64()), OnBlockProposedEvent: p.onBlockProposed, }) if err != nil { @@ -356,13 +358,28 @@ func (p *Prover) onBlockProposed( return fmt.Errorf("failed to wait L1Origin (eventID %d): %w", event.BlockId, err) } - // Check whteher the L1 chain has been reorged. - reorged, l1CurrentToReset, lastHandledBlockIDToReset, err := p.rpc.CheckL1Reorg( + // Check whteher the L2 EE's recorded L1 info, to see if the L1 chain has been reorged. + reorged, l1CurrentToReset, lastHandledBlockIDToReset, err := p.rpc.CheckL1ReorgFromL2EE( ctx, new(big.Int).Sub(event.BlockId, common.Big1), ) if err != nil { - return fmt.Errorf("failed to check whether L1 chain was reorged (eventID %d): %w", event.BlockId, err) + return fmt.Errorf("failed to check whether L1 chain was reorged from L2EE (eventID %d): %w", event.BlockId, err) + } + + // then check the l1Current cursor at first, to see if the L1 chain has been reorged. + if !reorged { + if reorged, l1CurrentToReset, err = p.rpc.CheckL1ReorgFromL1Cursor( + ctx, + p.l1Current, + p.genesisHeightL1, + ); err != nil { + return fmt.Errorf( + "failed to check whether L1 chain was reorged from l1Current (eventID %d): %w", + event.BlockId, + err, + ) + } } if reorged { @@ -373,8 +390,12 @@ func (p *Prover) onBlockProposed( "lastHandledBlockIDOld", p.lastHandledBlockID, "lastHandledBlockIDNew", lastHandledBlockIDToReset, ) - p.l1Current = l1CurrentToReset.Number.Uint64() - p.lastHandledBlockID = lastHandledBlockIDToReset.Uint64() + p.l1Current = l1CurrentToReset + if lastHandledBlockIDToReset == nil { + p.lastHandledBlockID = 0 + } else { + p.lastHandledBlockID = lastHandledBlockIDToReset.Uint64() + } p.reorgDetectedFlag = true return nil @@ -581,7 +602,11 @@ func (p *Prover) onBlockProposed( p.proposeConcurrencyGuard <- struct{}{} - p.l1Current = event.Raw.BlockNumber + newL1Current, err := p.rpc.L1.HeaderByHash(ctx, event.Raw.BlockHash) + if err != nil { + return err + } + p.l1Current = newL1Current p.lastHandledBlockID = event.BlockId.Uint64() go func() { @@ -704,14 +729,20 @@ func (p *Prover) initL1Current(startingBlockID *big.Int) error { return err } - if startingBlockID == nil { - stateVars, err := p.rpc.GetProtocolStateVariables(nil) - if err != nil { - return err - } + stateVars, err := p.rpc.GetProtocolStateVariables(nil) + if err != nil { + return err + } + p.genesisHeightL1 = stateVars.GenesisHeight + if startingBlockID == nil { if stateVars.LastVerifiedBlockId == 0 { - p.l1Current = stateVars.GenesisHeight + genesisL1Header, err := p.rpc.L1.HeaderByNumber(p.ctx, new(big.Int).SetUint64(stateVars.GenesisHeight)) + if err != nil { + return err + } + + p.l1Current = genesisL1Header return nil } @@ -724,7 +755,7 @@ func (p *Prover) initL1Current(startingBlockID *big.Int) error { if err != nil { if err.Error() == ethereum.NotFound.Error() { log.Warn("Failed to find L1Origin for blockID, use latest L1 head instead", "blockID", startingBlockID) - l1Head, err := p.rpc.L1.BlockNumber(p.ctx) + l1Head, err := p.rpc.L1.HeaderByNumber(p.ctx, nil) if err != nil { return err } @@ -735,7 +766,10 @@ func (p *Prover) initL1Current(startingBlockID *big.Int) error { return err } - p.l1Current = latestVerifiedHeaderL1Origin.L1BlockHeight.Uint64() + if p.l1Current, err = p.rpc.L1.HeaderByHash(p.ctx, latestVerifiedHeaderL1Origin.L1BlockHash); err != nil { + return err + } + return nil }