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

fix(driver): Handle reorg #216

Merged
merged 34 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ba90549
ref
cyberhorsey May 5, 2023
f6d2596
Merge branch 'main' of github.com:taikochain/taiko-client into main
cyberhorsey May 9, 2023
1a0ed15
Merge branch 'main' of github.com:taikochain/taiko-client into main
cyberhorsey May 10, 2023
e6124e6
handle reorg on events being removed
cyberhorsey May 10, 2023
fca8bfb
comment
cyberhorsey May 10, 2023
a7bbfe2
block batch iterator test
cyberhorsey May 10, 2023
2b6b00a
test
cyberhorsey May 10, 2023
5a9fbc6
reorg
cyberhorsey May 10, 2023
cac9316
reorg test
cyberhorsey May 11, 2023
d530db8
abort handling blocks when reorg is detected
cyberhorsey May 11, 2023
42baf91
tests
cyberhorsey May 11, 2023
4865f1f
comment
cyberhorsey May 11, 2023
75d4919
tests
cyberhorsey May 11, 2023
882f29f
comments
cyberhorsey May 12, 2023
16711a3
detect if block id is 0
cyberhorsey May 12, 2023
0068bb8
Merge branch 'main' into handle_reorg
cyberhorsey May 12, 2023
6e76e34
reset l1 current + get event
cyberhorsey May 12, 2023
2d4f88c
tests
cyberhorsey May 12, 2023
d367828
Merge branch 'handle_reorg' of github.com:taikochain/taiko-client int…
cyberhorsey May 12, 2023
bd8b1c9
lint
cyberhorsey May 12, 2023
5910819
comment was incorrect, i belive ResetL1Current is for blockProposed, …
cyberhorsey May 12, 2023
5b774f4
Merge branch 'main' into handle_reorg
davidtaikocha May 12, 2023
9116c80
Merge branch 'main' into handle_reorg
davidtaikocha May 14, 2023
4d92da6
Merge branch 'main' into handle_reorg
davidtaikocha May 15, 2023
d9db02d
Update driver/chain_syncer/calldata/syncer.go
cyberhorsey May 15, 2023
e634644
Update driver/chain_syncer/calldata/syncer.go
cyberhorsey May 15, 2023
b00ac39
Update driver/chain_syncer/calldata/syncer.go
cyberhorsey May 15, 2023
092c717
compare to state vars num blocks, and handle 0 block for rewinding
cyberhorsey May 15, 2023
dc0ebb6
typo
cyberhorsey May 15, 2023
94072ca
Merge branch 'main' into handle_reorg
RogerLamTd May 16, 2023
a9151c7
Merge branch 'main' into handle_reorg
davidtaikocha May 16, 2023
83fb81a
feat: update some logic and add more tests based on #216 (#228)
davidtaikocha May 16, 2023
2d3c747
remove parentblockinfo
cyberhorsey May 16, 2023
77baff8
feat: fix tests and rename Id to ID
davidtaikocha May 16, 2023
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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ lint:
test:
@TAIKO_MONO_DIR=${TAIKO_MONO_DIR} \
COMPILE_PROTOCOL=${COMPILE_PROTOCOL} \
PACKAGE=${PACKAGE} \
RUN_TESTS=true \
./integration_test/entrypoint.sh

Expand Down
137 changes: 137 additions & 0 deletions driver/chain_syncer/calldata/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math/big"
"time"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -114,8 +115,14 @@ func (s *Syncer) onBlockProposed(
"L1Height", event.Raw.BlockNumber,
"L1Hash", event.Raw.BlockHash,
"BlockID", event.Id,
"Removed", event.Raw.Removed,
)

// handle reorg
if event.Raw.Removed {
return s.handleReorg(ctx, event)
}

// Fetch the L2 parent block.
var (
parent *types.Header
Expand Down Expand Up @@ -223,6 +230,136 @@ func (s *Syncer) onBlockProposed(
return nil
}

// handleReorg detects reorg and rewinds the chain by 1 until we find a block that is still in the chain,
// then inserts that chain as the new head.
cyberhorsey marked this conversation as resolved.
Show resolved Hide resolved
func (s *Syncer) handleReorg(ctx context.Context, event *bindings.TaikoL1ClientBlockProposed) error {
log.Info(
"Reorg detected",
"L1Height", event.Raw.BlockNumber,
"L1Hash", event.Raw.BlockHash,
"BlockID", event.Id,
"Removed", event.Raw.Removed,
)

// rewind chain by 1 until we find a block that is still in the chain
var lastKnownGoodBlockId *big.Int
var blockId *big.Int = s.lastInsertedBlockID
var block *types.Block
var err error

for {
if blockId.Cmp(big.NewInt(0)) == 0 {
lastKnownGoodBlockId = new(big.Int).SetUint64(0)
break
}

block, err = s.rpc.L2.BlockByNumber(ctx, blockId)
if err != nil && !errors.Is(err, ethereum.NotFound) {
return err
}

if block != nil {
// block exists, we can rewind to this block
davidtaikocha marked this conversation as resolved.
Show resolved Hide resolved
lastKnownGoodBlockId = blockId
break
} else {
// otherwise, sub 1 from blockId and try again
blockId = new(big.Int).Sub(s.lastInsertedBlockID, big.NewInt(1))
}
}

// shouuldnt be able to reach this error because of the 0 check above
cyberhorsey marked this conversation as resolved.
Show resolved Hide resolved
// but just in case
if lastKnownGoodBlockId == nil {
return fmt.Errorf("failed to find last known good block ID after reorg")
}

log.Info(
"🔗 Last known good block ID before reorg found",
"blockID", event.Id,
)

parent, err := s.rpc.L2ParentByBlockId(ctx, lastKnownGoodBlockId)
davidtaikocha marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return fmt.Errorf("error getting l2 parent by block id: %w", err)
}

// reset l1 current to when the last known good block was inserted, and return the event.
blockProposedEvent, _, err := s.state.ResetL1Current(ctx, &state.HeightOrID{Height: block.Number()})
if err != nil {
return fmt.Errorf("faile to reset l1 current: %w", err)
cyberhorsey marked this conversation as resolved.
Show resolved Hide resolved
}

tx, err := s.rpc.L1.TransactionInBlock(
ctx,
block.Hash(),
blockProposedEvent.Raw.TxIndex,
)
if err != nil {
return fmt.Errorf("failed to fetch original TaikoL1.proposeBlock transaction: %w", err)
}

txListBytes, hint, _, err := s.txListValidator.ValidateTxList(block.Number(), tx.Data())
if err != nil {
return fmt.Errorf("failed to validate transactions list: %w", err)
}

if hint != txListValidator.HintOK {
log.Info("Invalid transactions list, insert an empty L2 block instead", "blockID", block.NumberU64())
txListBytes = []byte{}
}

l1Origin := &rawdb.L1Origin{
BlockID: block.Number(),
L2BlockHash: common.Hash{}, // Will be set by taiko-geth.
L1BlockHeight: new(big.Int).SetUint64(blockProposedEvent.Raw.BlockNumber),
L1BlockHash: blockProposedEvent.Raw.BlockHash,
}

payloadData, rpcError, payloadError := s.insertNewHead(
ctx,
blockProposedEvent,
parent,
s.state.GetHeadBlockID(),
txListBytes,
l1Origin,
)

if rpcError != nil {
return fmt.Errorf("failed to insert new head to L2 execution engine: %w", rpcError)
}

if payloadError != nil {
log.Warn(
"Ignore invalid block context", "blockID", event.Id, "payloadError", payloadError, "payloadData", payloadData,
)
return nil
}

log.Debug("Payload data", "hash", payloadData.BlockHash, "txs", len(payloadData.Transactions))

log.Info(
"🔗 Rewound chain and inserted last known good block as new head",
"blockID", event.Id,
"height", payloadData.Number,
"hash", payloadData.BlockHash,
"latestVerifiedBlockHeight", s.state.GetLatestVerifiedBlock().Height,
"latestVerifiedBlockHash", s.state.GetLatestVerifiedBlock().Hash,
"transactions", len(payloadData.Transactions),
"baseFee", payloadData.BaseFeePerGas,
"withdrawals", len(payloadData.Withdrawals),
)

metrics.DriverL1CurrentHeightGauge.Update(int64(event.Raw.BlockNumber))
s.lastInsertedBlockID = block.Number()

if s.progressTracker.Triggered() {
s.progressTracker.ClearMeta()
}

return nil
}

// insertNewHead tries to insert a new head block to the L2 execution engine's local
// block chain through Engine APIs.
func (s *Syncer) insertNewHead(
Expand Down
2 changes: 1 addition & 1 deletion driver/chain_syncer/chain_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (s *L2ChainSyncer) Sync(l1End *types.Header) error {
}

// Reset the L1Current cursor.
blockID, err := s.state.ResetL1Current(s.ctx, heightOrID)
_, blockID, err := s.state.ResetL1Current(s.ctx, heightOrID)
if err != nil {
return err
}
Expand Down
41 changes: 22 additions & 19 deletions driver/state/l1_current.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,36 @@ func (s *State) SetL1Current(h *types.Header) {
}

// ResetL1Current resets the l1Current cursor to the L1 height which emitted a
// BlockProven event with given blockID / blockHash.
func (s *State) ResetL1Current(ctx context.Context, heightOrID *HeightOrID) (*big.Int, error) {
// BlockProposed event with given blockID / blockHash.
func (s *State) ResetL1Current(
ctx context.Context,
heightOrID *HeightOrID,
) (*bindings.TaikoL1ClientBlockProposed, *big.Int, error) {
if !heightOrID.NotEmpty() {
return nil, fmt.Errorf("empty input %v", heightOrID)
return nil, nil, fmt.Errorf("empty input %v", heightOrID)
}

log.Info("Reset L1 current cursor", "heightOrID", heightOrID)

var (
l1CurrentHeight *big.Int
err error
err error
)

if (heightOrID.ID != nil && heightOrID.ID.Cmp(common.Big0) == 0) ||
(heightOrID.Height != nil && heightOrID.Height.Cmp(common.Big0) == 0) {
l1Current, err := s.rpc.L1.HeaderByNumber(ctx, s.GenesisL1Height)
if err != nil {
return nil, err
return nil, nil, err
}
s.SetL1Current(l1Current)
return common.Big0, nil
return nil, common.Big0, nil
}

// Need to find the block ID at first, before filtering the BlockProposed events.
if heightOrID.ID == nil {
header, err := s.rpc.L2.HeaderByNumber(context.Background(), heightOrID.Height)
if err != nil {
return nil, err
return nil, nil, err
}
targetHash := header.Hash()

Expand Down Expand Up @@ -85,18 +87,19 @@ func (s *State) ResetL1Current(ctx context.Context, heightOrID *HeightOrID) (*bi
)

if err != nil {
return nil, err
return nil, nil, err
}

if err := iter.Iter(); err != nil {
return nil, err
return nil, nil, err
}

if heightOrID.ID == nil {
return nil, fmt.Errorf("BlockProven event not found, hash: %s", targetHash)
return nil, nil, fmt.Errorf("BlockProven event not found, hash: %s", targetHash)
}
}

var event *bindings.TaikoL1ClientBlockProposed
iter, err := eventIterator.NewBlockProposedIterator(
ctx,
&eventIterator.BlockProposedIteratorConfig{
Expand All @@ -111,32 +114,32 @@ func (s *State) ResetL1Current(ctx context.Context, heightOrID *HeightOrID) (*bi
e *bindings.TaikoL1ClientBlockProposed,
end eventIterator.EndBlockProposedEventIterFunc,
) error {
l1CurrentHeight = new(big.Int).SetUint64(e.Raw.BlockNumber)
event = e
end()
return nil
},
},
)

if err != nil {
return nil, err
return nil, nil, err
}

if err := iter.Iter(); err != nil {
return nil, err
return nil, nil, err
}

if l1CurrentHeight == nil {
return nil, fmt.Errorf("BlockProposed event not found, blockID: %s", heightOrID.ID)
if event == nil {
return nil, nil, fmt.Errorf("BlockProposed event not found, blockID: %s", heightOrID.ID)
}

l1Current, err := s.rpc.L1.HeaderByNumber(ctx, l1CurrentHeight)
l1Current, err := s.rpc.L1.HeaderByNumber(ctx, new(big.Int).SetUint64(event.Raw.BlockNumber))
if err != nil {
return nil, err
return nil, nil, err
}
s.SetL1Current(l1Current)

log.Info("Reset L1 current cursor", "height", s.GetL1Current().Number)

return heightOrID.ID, nil
return event, heightOrID.ID, nil
}
6 changes: 3 additions & 3 deletions driver/state/l1_current_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ func (s *DriverStateTestSuite) TestSetL1Current() {
}

func (s *DriverStateTestSuite) TestResetL1CurrentEmptyHeight() {
l1Current, err := s.s.ResetL1Current(context.Background(), &HeightOrID{ID: common.Big0})
_, l1Current, err := s.s.ResetL1Current(context.Background(), &HeightOrID{ID: common.Big0})
s.Nil(err)
s.Zero(l1Current.Uint64())

_, err = s.s.ResetL1Current(context.Background(), &HeightOrID{Height: common.Big0})
_, _, err = s.s.ResetL1Current(context.Background(), &HeightOrID{Height: common.Big0})
s.Nil(err)
}

func (s *DriverStateTestSuite) TestResetL1CurrentEmptyID() {
_, err := s.s.ResetL1Current(context.Background(), &HeightOrID{Height: common.Big1})
_, _, err := s.s.ResetL1Current(context.Background(), &HeightOrID{Height: common.Big1})
s.NotNil(err)
}
Loading