Skip to content
This repository has been archived by the owner on May 11, 2024. It is now read-only.

Commit

Permalink
feat(rpc): improve reorg checks (#510)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidtaikocha committed Jan 18, 2024
1 parent fdcb4bc commit d375ee0
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 7 deletions.
5 changes: 5 additions & 0 deletions driver/anchor_tx_constructor/anchor_tx_constructor.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,8 @@ func (c *AnchorTxConstructor) signTxPayload(hash []byte) ([]byte, error) {
func (c *AnchorTxConstructor) GasLimit() uint64 {
return anchorGasLimit
}

// SignalServiceAddress returns protocol's L1 singalService constant address.
func (c *AnchorTxConstructor) SignalServiceAddress() common.Address {
return c.signalServiceAddress
}
1 change: 1 addition & 0 deletions driver/chain_syncer/calldata/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ func (s *Syncer) onBlockProposed(
reorged, l1CurrentToReset, lastInsertedBlockIDToReset, err = s.rpc.CheckL1ReorgFromL2EE(
ctx,
new(big.Int).Sub(event.BlockId, common.Big1),
s.anchorConstructor.SignalServiceAddress(),
)
if err != nil {
return fmt.Errorf("failed to check whether L1 chain has been reorged: %w", err)
Expand Down
18 changes: 15 additions & 3 deletions driver/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,11 @@ func (s *DriverTestSuite) TestCheckL1ReorgToHigherFork() {
s.Greater(l2Head2.Number.Uint64(), l2Head1.Number.Uint64())
s.Greater(l1Head2.Number.Uint64(), l1Head1.Number.Uint64())

reorged, _, _, err := s.RPCClient.CheckL1ReorgFromL2EE(context.Background(), l2Head2.Number)
reorged, _, _, err := s.RPCClient.CheckL1ReorgFromL2EE(
context.Background(),
l2Head2.Number,
common.HexToAddress(os.Getenv("L1_SIGNAL_SERVICE_CONTRACT_ADDRESS")),
)
s.Nil(err)
s.False(reorged)

Expand Down Expand Up @@ -202,7 +206,11 @@ func (s *DriverTestSuite) TestCheckL1ReorgToLowerFork() {
s.Greater(l2Head2.Number.Uint64(), l2Head1.Number.Uint64())
s.Greater(l1Head2.Number.Uint64(), l1Head1.Number.Uint64())

reorged, _, _, err := s.RPCClient.CheckL1ReorgFromL2EE(context.Background(), l2Head2.Number)
reorged, _, _, err := s.RPCClient.CheckL1ReorgFromL2EE(
context.Background(),
l2Head2.Number,
common.HexToAddress(os.Getenv("L1_SIGNAL_SERVICE_CONTRACT_ADDRESS")),
)
s.Nil(err)
s.False(reorged)

Expand Down Expand Up @@ -258,7 +266,11 @@ func (s *DriverTestSuite) TestCheckL1ReorgToSameHeightFork() {
s.Greater(l2Head2.Number.Uint64(), l2Head1.Number.Uint64())
s.Greater(l1Head2.Number.Uint64(), l1Head1.Number.Uint64())

reorged, _, _, err := s.RPCClient.CheckL1ReorgFromL2EE(context.Background(), l2Head2.Number)
reorged, _, _, err := s.RPCClient.CheckL1ReorgFromL2EE(
context.Background(),
l2Head2.Number,
common.HexToAddress(os.Getenv("L1_SIGNAL_SERVICE_CONTRACT_ADDRESS")),
)
s.Nil(err)
s.False(reorged)

Expand Down
136 changes: 135 additions & 1 deletion pkg/rpc/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"golang.org/x/sync/errgroup"

"github.com/taikoxyz/taiko-client/bindings"
"github.com/taikoxyz/taiko-client/bindings/encoding"
)

var (
Expand Down Expand Up @@ -386,7 +387,11 @@ func (c *Client) GetStorageRoot(

// 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) {
func (c *Client) CheckL1ReorgFromL2EE(
ctx context.Context,
blockID *big.Int,
l1SignalService common.Address,
) (bool, *types.Header, *big.Int, error) {
var (
reorged bool
l1CurrentToReset *types.Header
Expand Down Expand Up @@ -467,6 +472,20 @@ func (c *Client) CheckL1ReorgFromL2EE(ctx context.Context, blockID *big.Int) (bo
continue
}

isSyncedL1SnippetValid, err := c.checkSyncedL1SnippetFromAnchor(
ctx, blockID, l1Origin.L1BlockHeight.Uint64(), l1SignalService,
)
if err != nil {
return false, nil, nil, fmt.Errorf("failed to check L1 reorg from anchor transaction: %w", err)
}

if !isSyncedL1SnippetValid {
log.Info("Reorg detected due to invalid L1 snippet", "blockID", blockID)
reorged = true
blockID = new(big.Int).Sub(blockID, common.Big1)
continue
}

l1CurrentToReset = l1Header
blockIDToReset = l1Origin.BlockID
break
Expand All @@ -483,6 +502,121 @@ func (c *Client) CheckL1ReorgFromL2EE(ctx context.Context, blockID *big.Int) (bo
return reorged, l1CurrentToReset, blockIDToReset, nil
}

// checkSyncedL1SnippetFromAnchor checks whether the L1 snippet synced from the anchor transaction is valid.
func (c *Client) checkSyncedL1SnippetFromAnchor(
ctx context.Context,
blockID *big.Int,
l1Height uint64,
l1SignalService common.Address,
) (bool, error) {
log.Info("Check synced L1 snippet from anchor", "blockID", blockID)
block, err := c.L2.BlockByNumber(ctx, blockID)
if err != nil {
return false, err
}
parent, err := c.L2.BlockByHash(ctx, block.ParentHash())
if err != nil {
return false, err
}

l1BlockHash, l1SignalRoot, l1HeightInAnchor, parentGasUsed, err := c.getSyncedL1SnippetFromAnchor(
ctx,
block.Transactions()[0],
)
if err != nil {
return false, err
}

if l1HeightInAnchor != l1Height {
log.Info("Reorg detected due to L1 height mismatch", "blockID", blockID)
return true, nil
}

if parentGasUsed != uint32(parent.GasUsed()) {
log.Info("Reorg detected due to parent gas used mismatch", "blockID", blockID)
return true, nil
}

l1Header, err := c.L1.HeaderByHash(ctx, l1BlockHash)
if err != nil {
return false, err
}

currentRoot, err := c.GetStorageRoot(ctx, c.L1GethClient, l1SignalService, l1Header.Number)
if err != nil {
return false, err
}

if currentRoot != l1SignalRoot {
log.Info("Reorg detected due to L1 signal root mismatch", "blockID", blockID)
return true, nil
}

return false, nil
}

// getSyncedL1SnippetFromAnchor parses the anchor transaction calldata, and returns the synced L1 snippet,
func (c *Client) getSyncedL1SnippetFromAnchor(
ctx context.Context,
tx *types.Transaction,
) (
l1BlockHash common.Hash,
l1SignalRoot common.Hash,
l1Height uint64,
parentGasUsed uint32,
err error,
) {
method, err := encoding.TaikoL2ABI.MethodById(tx.Data())
if err != nil {
return common.Hash{}, common.Hash{}, 0, 0, err
}

if method.Name != "anchor" {
return common.Hash{}, common.Hash{}, 0, 0, fmt.Errorf("invalid method name for anchor transaction: %s", method.Name)
}

args := map[string]interface{}{}

if err := method.Inputs.UnpackIntoMap(args, tx.Data()[4:]); err != nil {
return common.Hash{}, common.Hash{}, 0, 0, err
}

l1BlockHash, ok := args["l1BlockHash"].([32]byte)
if !ok {
return common.Hash{},
common.Hash{},
0,
0,
fmt.Errorf("failed to parse l1BlockHash from anchor transaction calldata")
}
l1SignalRoot, ok = args["l1SignalRoot"].([32]byte)
if !ok {
return common.Hash{},
common.Hash{},
0,
0,
fmt.Errorf("failed to parse l1SignalRoot from anchor transaction calldata")
}
l1Height, ok = args["l1Height"].(uint64)
if !ok {
return common.Hash{},
common.Hash{},
0,
0,
fmt.Errorf("failed to parse l1Height from anchor transaction calldata")
}
parentGasUsed, ok = args["parentGasUsed"].(uint32)
if !ok {
return common.Hash{},
common.Hash{},
0,
0,
fmt.Errorf("failed to parse parentGasUsed from anchor transaction calldata")
}

return l1BlockHash, l1SignalRoot, l1Height, parentGasUsed, 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(
Expand Down
45 changes: 45 additions & 0 deletions pkg/rpc/methods_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package rpc

import (
"context"
"crypto/rand"
"math/big"
"os"
"testing"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -109,6 +111,40 @@ func TestWaitTillL2ExecutionEngineSyncedNewClient(t *testing.T) {
require.Nil(t, err)
}

func TestGetSyncedL1SnippetFromAnchor(t *testing.T) {
client := newTestClient(t)

l1BlockHash := randomHash()
l1SignalRoot := randomHash()
l1Height := randomHash().Big().Uint64()
parentGasUsed := uint32(randomHash().Big().Uint64())

key, err := client.TaikoL2.GOLDENTOUCHPRIVATEKEY(nil)
require.Nil(t, err)
testAddrPrivKey, err := crypto.ToECDSA(key.Bytes())
require.Nil(t, err)

opts, err := bind.NewKeyedTransactorWithChainID(testAddrPrivKey, client.L2ChainID)
require.Nil(t, err)

opts.NoSend = true
opts.GasLimit = 1_000_000

tx, err := client.TaikoL2.Anchor(opts, l1BlockHash, l1SignalRoot, l1Height, parentGasUsed)
require.Nil(t, err)

syncedL1BlockHash,
syncedL1SignalRoot,
syncedL1Height,
syncedParentGasUsed,
err := client.getSyncedL1SnippetFromAnchor(context.Background(), tx)
require.Nil(t, err)
require.Equal(t, l1BlockHash, syncedL1BlockHash)
require.Equal(t, l1SignalRoot, syncedL1SignalRoot)
require.Equal(t, l1Height, syncedL1Height)
require.Equal(t, parentGasUsed, syncedParentGasUsed)
}

func TestWaitTillL2ExecutionEngineSyncedContextErr(t *testing.T) {
client := newTestClient(t)
ctx, cancel := context.WithCancel(context.Background())
Expand Down Expand Up @@ -154,3 +190,12 @@ func TestGetStorageRootNewestBlock(t *testing.T) {
nil)
require.Nil(t, err)
}

// randomHash generates a random blob of data and returns it as a hash.
func randomHash() common.Hash {
var hash common.Hash
if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil {
panic(err)
}
return hash
}
18 changes: 15 additions & 3 deletions pkg/rpc/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,18 @@ func GetBlockProofStatus(
}

if proverAddress == transition.Prover {
log.Info("📬 Block's proof has already been submitted by current prover", "blockID", id)
log.Info(
"📬 Block's proof has already been submitted by current prover",
"blockID", id,
"parent", parent.Hash().Hex(),
"hash", common.Bytes2Hex(transition.BlockHash[:]),
"signalRoot", common.Bytes2Hex(transition.SignalRoot[:]),
"timestamp", transition.Timestamp,
"contester", transition.Contester,
)
return &BlockProofStatus{
IsSubmitted: true,
Invalid: false,
Invalid: transition.Contester != ZeroAddress,
ParentHeader: parent,
CurrentTransitionState: &transition,
}, nil
Expand All @@ -236,12 +244,16 @@ func GetBlockProofStatus(
"📬 Block's proof has already been submitted by another prover",
"blockID", id,
"prover", transition.Prover,
"parent", parent.Hash().Hex(),
"hash", common.Bytes2Hex(transition.BlockHash[:]),
"signalRoot", common.Bytes2Hex(transition.SignalRoot[:]),
"timestamp", transition.Timestamp,
"contester", transition.Contester,
)

return &BlockProofStatus{
IsSubmitted: true,
Invalid: false,
Invalid: transition.Contester != ZeroAddress,
ParentHeader: parent,
CurrentTransitionState: &transition,
}, nil
Expand Down
10 changes: 10 additions & 0 deletions prover/prover.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type Prover struct {
l1Current *types.Header
reorgDetectedFlag bool
tiers []*rpc.TierProviderTierWithID
l1SignalService common.Address

// Proof submitters
proofSubmitters []proofSubmitter.Submitter
Expand Down Expand Up @@ -132,6 +133,14 @@ func InitFromConfig(ctx context.Context, p *Prover, cfg *Config) (err error) {

log.Info("Protocol configs", "configs", p.protocolConfigs)

if p.l1SignalService, err = p.rpc.TaikoL1.Resolve0(
&bind.CallOpts{Context: ctx},
rpc.StringToBytes32("signal_service"),
false,
); err != nil {
return fmt.Errorf("failed to resolve L1 signal service address: %w", err)
}

p.proverAddress = crypto.PubkeyToAddress(p.cfg.L1ProverPrivKey.PublicKey)

chBufferSize := p.protocolConfigs.BlockMaxProposals
Expand Down Expand Up @@ -551,6 +560,7 @@ func (p *Prover) onBlockProposed(
reorged, l1CurrentToReset, lastHandledBlockIDToReset, err := p.rpc.CheckL1ReorgFromL2EE(
ctx,
new(big.Int).Sub(event.BlockId, common.Big1),
p.l1SignalService,
)
if err != nil {
return fmt.Errorf("failed to check whether L1 chain was reorged from L2EE (eventID %d): %w", event.BlockId, err)
Expand Down

0 comments on commit d375ee0

Please sign in to comment.