From 5994a08dae0a298cb483088a3be922fc4d98155f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Ram=C3=ADrez?= <58293609+ToniRamirezM@users.noreply.github.com> Date: Thu, 23 Feb 2023 15:14:49 +0100 Subject: [PATCH] Detect L2 reorgs by counting number of reorgs (#1688) --- sequencer/dbmanager.go | 59 ++++++++++++++---------------------- sequencer/interfaces.go | 3 ++ sequencer/mock_db_manager.go | 21 +++++++++++++ sequencer/mock_state.go | 21 +++++++++++++ state/pgstatestorage.go | 13 ++++++++ 5 files changed, 81 insertions(+), 36 deletions(-) diff --git a/sequencer/dbmanager.go b/sequencer/dbmanager.go index e6b5ccd879..086890bfcf 100644 --- a/sequencer/dbmanager.go +++ b/sequencer/dbmanager.go @@ -29,6 +29,7 @@ type dbManager struct { l2ReorgCh chan L2ReorgEvent ctx context.Context batchConstraints batchConstraints + numberOfReorgs uint64 } func (d *dbManager) GetBatchByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*state.Batch, error) { @@ -45,7 +46,12 @@ type ClosingBatchParameters struct { } func newDBManager(ctx context.Context, txPool txPool, state dbManagerStateInterface, worker *Worker, closingSignalCh ClosingSignalCh, txsStore TxsStore, batchConstraints batchConstraints) *dbManager { - return &dbManager{ctx: ctx, txPool: txPool, state: state, worker: worker, txsStore: txsStore, l2ReorgCh: closingSignalCh.L2ReorgCh, batchConstraints: batchConstraints} + numberOfReorgs, err := state.CountReorgs(ctx, nil) + if err != nil { + log.Error("failed to get number of reorgs: %v", err) + } + + return &dbManager{ctx: ctx, txPool: txPool, state: state, worker: worker, txsStore: txsStore, l2ReorgCh: closingSignalCh.L2ReorgCh, batchConstraints: batchConstraints, numberOfReorgs: numberOfReorgs} } // Start stars the dbManager routines @@ -162,41 +168,17 @@ func (d *dbManager) storeProcessedTxAndDeleteFromPool() { log.Errorf("StoreProcessedTxAndDeleteFromPool: %v", err) } - // // Check if the Tx is still valid in the state to detect reorgs - // lastStateRoot, err := d.state.GetLastStateRoot(d.ctx, dbTx) - // if err != nil { - // err = dbTx.Rollback(d.ctx) - // if err != nil { - // log.Errorf("StoreProcessedTxAndDeleteFromPool: %v", err) - // } - // d.txsStore.Wg.Done() - // continue - // } - - // if txToStore.previousL2BlockStateRoot != state.ZeroHash && lastStateRoot != txToStore.previousL2BlockStateRoot { - // // We may have closed the batch because of a new GER without transactions - // // If there are no transactions in the batch when closing it, we can not base our check in l2 blocks - // // so we will check against the latest closed batch - - // lastBatch, err := d.state.GetLastClosedBatch(d.ctx, dbTx) - // if err != nil { - // err = dbTx.Rollback(d.ctx) - // if err != nil { - // log.Errorf("StoreProcessedTxAndDeleteFromPool: %v", err) - // } - // d.txsStore.Wg.Done() - // continue - // } - - // lastStateRoot = lastBatch.StateRoot - - // if lastStateRoot != txToStore.previousL2BlockStateRoot { - // log.Warnf("L2 reorg detected. Expected OldStateRoot: %v actual OldStateRoot: %v", lastStateRoot, txToStore.previousL2BlockStateRoot) - // d.l2ReorgCh <- L2ReorgEvent{} - // d.txsStore.Wg.Done() - // continue - // } - // } + numberOfReorgs, err := d.state.CountReorgs(d.ctx, nil) + if err != nil { + log.Error("failed to get number of reorgs: %v", err) + } + + if numberOfReorgs != d.numberOfReorgs { + log.Warnf("New L2 reorg detected") + d.l2ReorgCh <- L2ReorgEvent{} + d.txsStore.Wg.Done() + continue + } err = d.StoreProcessedTransaction(d.ctx, txToStore.batchNumber, txToStore.txResponse, txToStore.coinbase, txToStore.timestamp, dbTx) if err != nil { @@ -587,3 +569,8 @@ func (d *dbManager) UpdateTxStatus(ctx context.Context, hash common.Hash, newSta func (d *dbManager) GetLatestVirtualBatchTimestamp(ctx context.Context, dbTx pgx.Tx) (time.Time, error) { return d.state.GetLatestVirtualBatchTimestamp(ctx, dbTx) } + +// CountReorgs returns the number of reorgs +func (d *dbManager) CountReorgs(ctx context.Context, dbTx pgx.Tx) (uint64, error) { + return d.state.CountReorgs(ctx, dbTx) +} diff --git a/sequencer/interfaces.go b/sequencer/interfaces.go index 0aece8c3d7..e9f22af086 100644 --- a/sequencer/interfaces.go +++ b/sequencer/interfaces.go @@ -70,6 +70,7 @@ type stateInterface interface { GetForcedBatchesSince(ctx context.Context, forcedBatchNumber, maxBlockNumber uint64, dbTx pgx.Tx) ([]*state.ForcedBatch, error) GetLastTrustedForcedBatchNumber(ctx context.Context, dbTx pgx.Tx) (uint64, error) GetLatestVirtualBatchTimestamp(ctx context.Context, dbTx pgx.Tx) (time.Time, error) + CountReorgs(ctx context.Context, dbTx pgx.Tx) (uint64, error) } type workerInterface interface { @@ -109,6 +110,7 @@ type dbManagerInterface interface { GetBalanceByStateRoot(ctx context.Context, address common.Address, root common.Hash) (*big.Int, error) UpdateTxStatus(ctx context.Context, hash common.Hash, newStatus pool.TxStatus) error GetLatestVirtualBatchTimestamp(ctx context.Context, dbTx pgx.Tx) (time.Time, error) + CountReorgs(ctx context.Context, dbTx pgx.Tx) (uint64, error) } type dbManagerStateInterface interface { @@ -136,6 +138,7 @@ type dbManagerStateInterface interface { GetLastTrustedForcedBatchNumber(ctx context.Context, dbTx pgx.Tx) (uint64, error) GetBalanceByStateRoot(ctx context.Context, address common.Address, root common.Hash) (*big.Int, error) GetLatestVirtualBatchTimestamp(ctx context.Context, dbTx pgx.Tx) (time.Time, error) + CountReorgs(ctx context.Context, dbTx pgx.Tx) (uint64, error) } type ethTxManager interface { diff --git a/sequencer/mock_db_manager.go b/sequencer/mock_db_manager.go index 12475d1219..a23f59bec0 100644 --- a/sequencer/mock_db_manager.go +++ b/sequencer/mock_db_manager.go @@ -63,6 +63,27 @@ func (_m *DbManagerMock) CloseBatch(ctx context.Context, params ClosingBatchPara return r0 } +// CountReorgs provides a mock function with given fields: ctx, dbTx +func (_m *DbManagerMock) CountReorgs(ctx context.Context, dbTx pgx.Tx) (uint64, error) { + ret := _m.Called(ctx, dbTx) + + var r0 uint64 + if rf, ok := ret.Get(0).(func(context.Context, pgx.Tx) uint64); ok { + r0 = rf(ctx, dbTx) + } else { + r0 = ret.Get(0).(uint64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, pgx.Tx) error); ok { + r1 = rf(ctx, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // CreateFirstBatch provides a mock function with given fields: ctx, sequencerAddress func (_m *DbManagerMock) CreateFirstBatch(ctx context.Context, sequencerAddress common.Address) state.ProcessingContext { ret := _m.Called(ctx, sequencerAddress) diff --git a/sequencer/mock_state.go b/sequencer/mock_state.go index 6ef1107095..b1fa04456e 100644 --- a/sequencer/mock_state.go +++ b/sequencer/mock_state.go @@ -86,6 +86,27 @@ func (_m *StateMock) CloseBatch(ctx context.Context, receipt state.ProcessingRec return r0 } +// CountReorgs provides a mock function with given fields: ctx, dbTx +func (_m *StateMock) CountReorgs(ctx context.Context, dbTx pgx.Tx) (uint64, error) { + ret := _m.Called(ctx, dbTx) + + var r0 uint64 + if rf, ok := ret.Get(0).(func(context.Context, pgx.Tx) uint64); ok { + r0 = rf(ctx, dbTx) + } else { + r0 = ret.Get(0).(uint64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, pgx.Tx) error); ok { + r1 = rf(ctx, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ExecuteBatch provides a mock function with given fields: ctx, batch, dbTx func (_m *StateMock) ExecuteBatch(ctx context.Context, batch state.Batch, dbTx pgx.Tx) (*pb.ProcessBatchResponse, error) { ret := _m.Called(ctx, batch, dbTx) diff --git a/state/pgstatestorage.go b/state/pgstatestorage.go index 7097fc1e49..f9e5dfbf85 100644 --- a/state/pgstatestorage.go +++ b/state/pgstatestorage.go @@ -2210,6 +2210,19 @@ func (p *PostgresStorage) AddTrustedReorg(ctx context.Context, reorg *TrustedReo return err } +// CountReorgs returns the number of reorgs +func (p *PostgresStorage) CountReorgs(ctx context.Context, dbTx pgx.Tx) (uint64, error) { + const countReorgsSQL = "SELECT COUNT(*) FROM state.trusted_reorg" + + var count uint64 + q := p.getExecQuerier(dbTx) + err := q.QueryRow(ctx, countReorgsSQL).Scan(&count) + if err != nil { + return 0, err + } + return count, nil +} + // GetReorgedTransactions returns the transactions that were reorged func (p *PostgresStorage) GetReorgedTransactions(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (txs []*types.Transaction, err error) { const getReorgedTransactionsSql = "SELECT encoded FROM state.transaction t INNER JOIN state.l2block b ON t.l2_block_num = b.block_num WHERE b.batch_num >= $1 ORDER BY l2_block_num ASC"