From c9c72e1466189a638ac62f3229f260619379ab74 Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Thu, 14 Dec 2023 12:31:53 +0100 Subject: [PATCH] etrog: batch timestamp is set when is virtualized (#2904) * + add field virtual.timestamp_batch_etrog into DB, rpc returns this timestamp of the batch of null if not defined yet --- db/migrations/state/0013.sql | 6 ++ jsonrpc/endpoints_zkevm.go | 10 +++ jsonrpc/endpoints_zkevm_test.go | 16 ++++ jsonrpc/mocks/mock_state.go | 26 ++++++ jsonrpc/types/interfaces.go | 1 + jsonrpc/types/types.go | 1 + sequencer/batch.go | 3 +- sequencer/interfaces.go | 2 +- sequencer/mock_state.go | 18 ++-- state/batch.go | 38 ++++++-- state/batchV2.go | 7 +- state/interfaces.go | 3 +- state/pgstatestorage/batch.go | 38 ++++++-- state/pgstatestorage/pgstatestorage_test.go | 88 +++++++++++++++++++ .../etrog/processor_l1_sequence_batches.go | 59 ++++++++----- synchronizer/default_l1processors.go | 3 +- synchronizer/interfaces.go | 3 +- .../sync_trusted_state_process_batch.go | 13 ++- .../l2_sync_etrog/sync_trusted_state.go | 12 +-- synchronizer/mock_state.go | 20 +++-- synchronizer/synchronizer.go | 5 +- 21 files changed, 299 insertions(+), 73 deletions(-) diff --git a/db/migrations/state/0013.sql b/db/migrations/state/0013.sql index b52e1f667c..45177d785c 100644 --- a/db/migrations/state/0013.sql +++ b/db/migrations/state/0013.sql @@ -14,6 +14,9 @@ CREATE INDEX IF NOT EXISTS idx_transaction_l2_hash ON state.transaction (l2_hash ALTER TABLE state.batch ADD COLUMN wip BOOLEAN NOT NULL; +ALTER TABLE state.virtual_batch + ADD COLUMN timestamp_batch_etrog TIMESTAMP WITH TIME ZONE NULL; + -- +migrate Down ALTER TABLE state.exit_root DROP COLUMN prev_block_hash, @@ -30,3 +33,6 @@ DROP INDEX IF EXISTS state.idx_transaction_l2_hash; ALTER TABLE state.batch DROP COLUMN wip; +ALTER TABLE state.virtual_batch + DROP COLUMN timestamp_batch_etrog; + diff --git a/jsonrpc/endpoints_zkevm.go b/jsonrpc/endpoints_zkevm.go index d832a1b47d..9260f9c766 100644 --- a/jsonrpc/endpoints_zkevm.go +++ b/jsonrpc/endpoints_zkevm.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "math/big" + "time" "github.com/0xPolygonHermez/zkevm-node/hex" "github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" @@ -139,6 +140,15 @@ func (z *ZKEVMEndpoints) GetBatchByNumber(batchNumber types.BatchNumber, fullTx } else if err != nil { return RPCErrorResponse(types.DefaultErrorCode, fmt.Sprintf("couldn't load batch from state by number %v", batchNumber), err, true) } + batchTimestamp, err := z.state.GetBatchTimestamp(ctx, batchNumber, nil, dbTx) + if err != nil { + return RPCErrorResponse(types.DefaultErrorCode, fmt.Sprintf("couldn't load batch timestamp from state by number %v", batchNumber), err, true) + } + if batchTimestamp == nil { + batch.Timestamp = time.Time{} + } else { + batch.Timestamp = *batchTimestamp + } txs, _, err := z.state.GetTransactionsByBatchNumber(ctx, batchNumber, dbTx) if !errors.Is(err, state.ErrNotFound) && err != nil { diff --git a/jsonrpc/endpoints_zkevm_test.go b/jsonrpc/endpoints_zkevm_test.go index e942305f1a..4544e5fdef 100644 --- a/jsonrpc/endpoints_zkevm_test.go +++ b/jsonrpc/endpoints_zkevm_test.go @@ -18,6 +18,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/trie" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -732,6 +733,11 @@ func TestGetBatchByNumber(t *testing.T) { Return(batch, nil). Once() + m.State. + On("GetBatchTimestamp", mock.Anything, mock.Anything, (*uint64)(nil), m.DbTx). + Return(&batch.Timestamp, nil). + Once() + virtualBatch := &state.VirtualBatch{ TxHash: common.HexToHash("0x10"), } @@ -851,6 +857,11 @@ func TestGetBatchByNumber(t *testing.T) { Return(batch, nil). Once() + m.State. + On("GetBatchTimestamp", mock.Anything, mock.Anything, (*uint64)(nil), m.DbTx). + Return(&batch.Timestamp, nil). + Once() + virtualBatch := &state.VirtualBatch{ TxHash: common.HexToHash("0x10"), } @@ -1001,6 +1012,11 @@ func TestGetBatchByNumber(t *testing.T) { Return(batch, nil). Once() + m.State. + On("GetBatchTimestamp", mock.Anything, mock.Anything, (*uint64)(nil), m.DbTx). + Return(&batch.Timestamp, nil). + Once() + virtualBatch := &state.VirtualBatch{ TxHash: common.HexToHash("0x10"), } diff --git a/jsonrpc/mocks/mock_state.go b/jsonrpc/mocks/mock_state.go index d57b64d95e..cb6dde0b01 100644 --- a/jsonrpc/mocks/mock_state.go +++ b/jsonrpc/mocks/mock_state.go @@ -187,6 +187,32 @@ func (_m *StateMock) GetBatchByNumber(ctx context.Context, batchNumber uint64, d return r0, r1 } +// GetBatchTimestamp provides a mock function with given fields: ctx, batchNumber, forcedForkId, dbTx +func (_m *StateMock) GetBatchTimestamp(ctx context.Context, batchNumber uint64, forcedForkId *uint64, dbTx pgx.Tx) (*time.Time, error) { + ret := _m.Called(ctx, batchNumber, forcedForkId, dbTx) + + var r0 *time.Time + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, *uint64, pgx.Tx) (*time.Time, error)); ok { + return rf(ctx, batchNumber, forcedForkId, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, *uint64, pgx.Tx) *time.Time); ok { + r0 = rf(ctx, batchNumber, forcedForkId, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*time.Time) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, *uint64, pgx.Tx) error); ok { + r1 = rf(ctx, batchNumber, forcedForkId, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetCode provides a mock function with given fields: ctx, address, root func (_m *StateMock) GetCode(ctx context.Context, address common.Address, root common.Hash) ([]byte, error) { ret := _m.Called(ctx, address, root) diff --git a/jsonrpc/types/interfaces.go b/jsonrpc/types/interfaces.go index b562407326..8613476612 100644 --- a/jsonrpc/types/interfaces.go +++ b/jsonrpc/types/interfaces.go @@ -68,6 +68,7 @@ type StateInterface interface { GetLastClosedBatchNumber(ctx context.Context, dbTx pgx.Tx) (uint64, error) GetLastVerifiedL2BlockNumberUntilL1Block(ctx context.Context, l1FinalizedBlockNumber uint64, dbTx pgx.Tx) (uint64, error) GetLastVerifiedBatchNumberUntilL1Block(ctx context.Context, l1BlockNumber uint64, dbTx pgx.Tx) (uint64, error) + GetBatchTimestamp(ctx context.Context, batchNumber uint64, forcedForkId *uint64, dbTx pgx.Tx) (*time.Time, error) } // EthermanInterface provides integration with L1 diff --git a/jsonrpc/types/types.go b/jsonrpc/types/types.go index 094ac60f49..eba81c2288 100644 --- a/jsonrpc/types/types.go +++ b/jsonrpc/types/types.go @@ -371,6 +371,7 @@ func NewBatch(batch *state.Batch, virtualBatch *state.VirtualBatch, verifiedBatc BatchL2Data: ArgBytes(batchL2Data), Closed: closed, } + if batch.ForcedBatchNum != nil { fb := ArgUint64(*batch.ForcedBatchNum) res.ForcedBatchNumber = &fb diff --git a/sequencer/batch.go b/sequencer/batch.go index 966ec1c8ed..c61fd626d0 100644 --- a/sequencer/batch.go +++ b/sequencer/batch.go @@ -139,7 +139,8 @@ func (f *finalizer) setWIPBatch(ctx context.Context, wipStateBatch *state.Batch) //TODO: We execute the batch to get the used counter. To avoid this We can update the counters in the state.batch table for the wip batch if wipStateBatchCountOfTxs > 0 { - batchResponse, err := f.state.ExecuteBatchV2(ctx, *wipStateBatch, false, dbTx) + //TODO: Change wipStateBatch.GlobalExitRoot for L1InfoRoot and wipStateBatch.Timestamp for the TimeLimit + batchResponse, err := f.state.ExecuteBatchV2(ctx, *wipStateBatch, wipStateBatch.GlobalExitRoot, wipStateBatch.Timestamp, false, dbTx) if err != nil { return nil, err } diff --git a/sequencer/interfaces.go b/sequencer/interfaces.go index 2ff65ab81b..602c154113 100644 --- a/sequencer/interfaces.go +++ b/sequencer/interfaces.go @@ -61,7 +61,7 @@ type stateInterface interface { CloseBatch(ctx context.Context, receipt state.ProcessingReceipt, dbTx pgx.Tx) error CloseWIPBatch(ctx context.Context, receipt state.ProcessingReceipt, dbTx pgx.Tx) error ExecuteBatch(ctx context.Context, batch state.Batch, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponse, error) - ExecuteBatchV2(ctx context.Context, batch state.Batch, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponseV2, error) + ExecuteBatchV2(ctx context.Context, batch state.Batch, l1InfoRoot common.Hash, timestampLimit time.Time, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponseV2, error) GetForcedBatch(ctx context.Context, forcedBatchNumber uint64, dbTx pgx.Tx) (*state.ForcedBatch, error) GetLastBatch(ctx context.Context, dbTx pgx.Tx) (*state.Batch, error) GetLastBatchNumber(ctx context.Context, dbTx pgx.Tx) (uint64, error) diff --git a/sequencer/mock_state.go b/sequencer/mock_state.go index f0f55c3d86..3530a9958c 100644 --- a/sequencer/mock_state.go +++ b/sequencer/mock_state.go @@ -188,25 +188,25 @@ func (_m *StateMock) ExecuteBatch(ctx context.Context, batch state.Batch, update return r0, r1 } -// ExecuteBatchV2 provides a mock function with given fields: ctx, batch, updateMerkleTree, dbTx -func (_m *StateMock) ExecuteBatchV2(ctx context.Context, batch state.Batch, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponseV2, error) { - ret := _m.Called(ctx, batch, updateMerkleTree, dbTx) +// ExecuteBatchV2 provides a mock function with given fields: ctx, batch, l1InfoRoot, timestampLimit, updateMerkleTree, dbTx +func (_m *StateMock) ExecuteBatchV2(ctx context.Context, batch state.Batch, l1InfoRoot common.Hash, timestampLimit time.Time, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponseV2, error) { + ret := _m.Called(ctx, batch, l1InfoRoot, timestampLimit, updateMerkleTree, dbTx) var r0 *executor.ProcessBatchResponseV2 var r1 error - if rf, ok := ret.Get(0).(func(context.Context, state.Batch, bool, pgx.Tx) (*executor.ProcessBatchResponseV2, error)); ok { - return rf(ctx, batch, updateMerkleTree, dbTx) + if rf, ok := ret.Get(0).(func(context.Context, state.Batch, common.Hash, time.Time, bool, pgx.Tx) (*executor.ProcessBatchResponseV2, error)); ok { + return rf(ctx, batch, l1InfoRoot, timestampLimit, updateMerkleTree, dbTx) } - if rf, ok := ret.Get(0).(func(context.Context, state.Batch, bool, pgx.Tx) *executor.ProcessBatchResponseV2); ok { - r0 = rf(ctx, batch, updateMerkleTree, dbTx) + if rf, ok := ret.Get(0).(func(context.Context, state.Batch, common.Hash, time.Time, bool, pgx.Tx) *executor.ProcessBatchResponseV2); ok { + r0 = rf(ctx, batch, l1InfoRoot, timestampLimit, updateMerkleTree, dbTx) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*executor.ProcessBatchResponseV2) } } - if rf, ok := ret.Get(1).(func(context.Context, state.Batch, bool, pgx.Tx) error); ok { - r1 = rf(ctx, batch, updateMerkleTree, dbTx) + if rf, ok := ret.Get(1).(func(context.Context, state.Batch, common.Hash, time.Time, bool, pgx.Tx) error); ok { + r1 = rf(ctx, batch, l1InfoRoot, timestampLimit, updateMerkleTree, dbTx) } else { r1 = ret.Error(1) } diff --git a/state/batch.go b/state/batch.go index 50be017402..fbad56996c 100644 --- a/state/batch.go +++ b/state/batch.go @@ -25,12 +25,14 @@ const ( // Batch struct type Batch struct { - BatchNumber uint64 - Coinbase common.Address - BatchL2Data []byte - StateRoot common.Hash - LocalExitRoot common.Hash - AccInputHash common.Hash + BatchNumber uint64 + Coinbase common.Address + BatchL2Data []byte + StateRoot common.Hash + LocalExitRoot common.Hash + AccInputHash common.Hash + // Timestamp (<=incaberry) -> batch time + // (>incaberry) -> minTimestamp used in batch creation, real timestamp is in virtual_batch.batch_timestamp Timestamp time.Time Transactions []types.Transaction GlobalExitRoot common.Hash @@ -97,6 +99,9 @@ type VirtualBatch struct { Coinbase common.Address SequencerAddr common.Address BlockNumber uint64 + // TimestampBatchEtrog etrog: Batch timestamp comes from L1 block timestamp + // for previous batches is NULL because the batch timestamp is in batch table + TimestampBatchEtrog *time.Time } // Sequence represents the sequence interval @@ -525,3 +530,24 @@ func (s *State) GetLastBatch(ctx context.Context, dbTx pgx.Tx) (*Batch, error) { } return batches[0], nil } + +// GetBatchTimestamp returns the batch timestamp. +// +// for >= etrog is stored on virtual_batch.batch_timestamp +// previous batches is stored on batch.timestamp +func (s *State) GetBatchTimestamp(ctx context.Context, batchNumber uint64, forcedForkId *uint64, dbTx pgx.Tx) (*time.Time, error) { + var forkid uint64 + if forcedForkId != nil { + forkid = *forcedForkId + } else { + forkid = s.GetForkIDByBatchNumber(batchNumber) + } + batchTimestamp, virtualTimestamp, err := s.GetRawBatchTimestamps(ctx, batchNumber, dbTx) + if err != nil { + return nil, err + } + if forkid >= FORKID_ETROG { + return virtualTimestamp, nil + } + return batchTimestamp, nil +} diff --git a/state/batchV2.go b/state/batchV2.go index 49ba36d91a..7013544d3c 100644 --- a/state/batchV2.go +++ b/state/batchV2.go @@ -99,7 +99,7 @@ func (s *State) ProcessBatchV2(ctx context.Context, request ProcessRequest, upda // ExecuteBatchV2 is used by the synchronizer to reprocess batches to compare generated state root vs stored one // It is also used by the sequencer in order to calculate used zkCounter of a WIPBatch -func (s *State) ExecuteBatchV2(ctx context.Context, batch Batch, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponseV2, error) { +func (s *State) ExecuteBatchV2(ctx context.Context, batch Batch, l1InfoRoot common.Hash, timestampLimit time.Time, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponseV2, error) { if dbTx == nil { return nil, ErrDBTxNil } @@ -124,10 +124,10 @@ func (s *State) ExecuteBatchV2(ctx context.Context, batch Batch, updateMerkleTre BatchL2Data: batch.BatchL2Data, OldStateRoot: previousBatch.StateRoot.Bytes(), // TODO: Change this to L1InfoRoot - L1InfoRoot: batch.GlobalExitRoot.Bytes(), + L1InfoRoot: l1InfoRoot.Bytes(), OldAccInputHash: previousBatch.AccInputHash.Bytes(), // TODO: Change this to TimestampLimit - TimestampLimit: uint64(batch.Timestamp.Unix()), + TimestampLimit: uint64(timestampLimit.Unix()), // Changed for new sequencer strategy UpdateMerkleTree: updateMT, ChainId: s.cfg.ChainID, @@ -264,6 +264,7 @@ func (s *State) sendBatchRequestToExecutorV2(ctx context.Context, processBatchRe err = executor.ExecutorErr(res.Error) s.eventLog.LogExecutorErrorV2(ctx, res.Error, processBatchRequest) } + //workarroundDuplicatedBlock(res) elapsed := time.Since(now) if caller != metrics.DiscardCallerLabel { metrics.ExecutorProcessingTime(string(caller), elapsed) diff --git a/state/interfaces.go b/state/interfaces.go index bdc43db4ca..b3b9de51ab 100644 --- a/state/interfaces.go +++ b/state/interfaces.go @@ -39,6 +39,7 @@ type storage interface { GetLastBatchTime(ctx context.Context, dbTx pgx.Tx) (time.Time, error) GetLastVirtualBatchNum(ctx context.Context, dbTx pgx.Tx) (uint64, error) GetLatestVirtualBatchTimestamp(ctx context.Context, dbTx pgx.Tx) (time.Time, error) + GetVirtualBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*VirtualBatch, error) SetLastBatchInfoSeenOnEthereum(ctx context.Context, lastBatchNumberSeen, lastBatchNumberVerified uint64, dbTx pgx.Tx) error SetInitSyncBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) error GetBatchByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*Batch, error) @@ -53,7 +54,6 @@ type storage interface { GetTransactionsByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (txs []types.Transaction, effectivePercentages []uint8, err error) GetTxsHashesByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (encoded []common.Hash, err error) AddVirtualBatch(ctx context.Context, virtualBatch *VirtualBatch, dbTx pgx.Tx) error - GetVirtualBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*VirtualBatch, error) UpdateGERInOpenBatch(ctx context.Context, ger common.Hash, dbTx pgx.Tx) error IsBatchClosed(ctx context.Context, batchNum uint64, dbTx pgx.Tx) (bool, error) GetNextForcedBatches(ctx context.Context, nextForcedBatches int, dbTx pgx.Tx) ([]ForcedBatch, error) @@ -140,4 +140,5 @@ type storage interface { GetForkIDByBatchNumber(batchNumber uint64) uint64 GetLatestIndex(ctx context.Context, dbTx pgx.Tx) (uint32, error) BuildChangeL2Block(deltaTimestamp uint32, l1InfoTreeIndex uint32) []byte + GetRawBatchTimestamps(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*time.Time, *time.Time, error) } diff --git a/state/pgstatestorage/batch.go b/state/pgstatestorage/batch.go index 414ad1f265..2590ac5008 100644 --- a/state/pgstatestorage/batch.go +++ b/state/pgstatestorage/batch.go @@ -483,10 +483,18 @@ func scanForcedBatch(row pgx.Row) (state.ForcedBatch, error) { // AddVirtualBatch adds a new virtual batch to the storage. func (p *PostgresStorage) AddVirtualBatch(ctx context.Context, virtualBatch *state.VirtualBatch, dbTx pgx.Tx) error { - const addVirtualBatchSQL = "INSERT INTO state.virtual_batch (batch_num, tx_hash, coinbase, block_num, sequencer_addr) VALUES ($1, $2, $3, $4, $5)" - e := p.getExecQuerier(dbTx) - _, err := e.Exec(ctx, addVirtualBatchSQL, virtualBatch.BatchNumber, virtualBatch.TxHash.String(), virtualBatch.Coinbase.String(), virtualBatch.BlockNumber, virtualBatch.SequencerAddr.String()) - return err + if virtualBatch.TimestampBatchEtrog == nil { + const addVirtualBatchSQL = "INSERT INTO state.virtual_batch (batch_num, tx_hash, coinbase, block_num, sequencer_addr) VALUES ($1, $2, $3, $4, $5)" + e := p.getExecQuerier(dbTx) + _, err := e.Exec(ctx, addVirtualBatchSQL, virtualBatch.BatchNumber, virtualBatch.TxHash.String(), virtualBatch.Coinbase.String(), virtualBatch.BlockNumber, virtualBatch.SequencerAddr.String()) + return err + } else { + const addVirtualBatchSQL = "INSERT INTO state.virtual_batch (batch_num, tx_hash, coinbase, block_num, sequencer_addr, timestamp_batch_etrog) VALUES ($1, $2, $3, $4, $5, $6)" + e := p.getExecQuerier(dbTx) + _, err := e.Exec(ctx, addVirtualBatchSQL, virtualBatch.BatchNumber, virtualBatch.TxHash.String(), virtualBatch.Coinbase.String(), virtualBatch.BlockNumber, virtualBatch.SequencerAddr.String(), + virtualBatch.TimestampBatchEtrog.UTC()) + return err + } } // GetVirtualBatch get an L1 virtualBatch. @@ -499,12 +507,12 @@ func (p *PostgresStorage) GetVirtualBatch(ctx context.Context, batchNumber uint6 ) const getVirtualBatchSQL = ` - SELECT block_num, batch_num, tx_hash, coinbase, sequencer_addr + SELECT block_num, batch_num, tx_hash, coinbase, sequencer_addr, timestamp_batch_etrog FROM state.virtual_batch WHERE batch_num = $1` e := p.getExecQuerier(dbTx) - err := e.QueryRow(ctx, getVirtualBatchSQL, batchNumber).Scan(&virtualBatch.BlockNumber, &virtualBatch.BatchNumber, &txHash, &coinbase, &sequencerAddr) + err := e.QueryRow(ctx, getVirtualBatchSQL, batchNumber).Scan(&virtualBatch.BlockNumber, &virtualBatch.BatchNumber, &txHash, &coinbase, &sequencerAddr, &virtualBatch.TimestampBatchEtrog) if errors.Is(err, pgx.ErrNoRows) { return nil, state.ErrNotFound } else if err != nil { @@ -899,3 +907,21 @@ func (p *PostgresStorage) BuildChangeL2Block(deltaTimestamp uint32, l1InfoTreeIn return changeL2Block } + +// GetRawBatchTimestamps returns the timestamp of the batch with the given number. +// it returns batch_num.tstamp and virtual_batch.batch_timestamp +func (p *PostgresStorage) GetRawBatchTimestamps(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*time.Time, *time.Time, error) { + const sql = ` + SELECT b.timestamp AS batch_timestamp, v.timestamp_batch_etrog AS virtual_batch_timestamp + FROM state.batch AS b + LEFT JOIN state.virtual_batch AS v ON b.batch_num = v.batch_num + WHERE b.batch_num = $1; + ` + var batchTimestamp, virtualBatchTimestamp *time.Time + e := p.getExecQuerier(dbTx) + err := e.QueryRow(ctx, sql, batchNumber).Scan(&batchTimestamp, &virtualBatchTimestamp) + if errors.Is(err, pgx.ErrNoRows) { + return nil, nil, nil + } + return batchTimestamp, virtualBatchTimestamp, err +} diff --git a/state/pgstatestorage/pgstatestorage_test.go b/state/pgstatestorage/pgstatestorage_test.go index bf97ee9b9d..539b92cfb3 100644 --- a/state/pgstatestorage/pgstatestorage_test.go +++ b/state/pgstatestorage/pgstatestorage_test.go @@ -1156,3 +1156,91 @@ func TestGetLatestIndex(t *testing.T) { t.Log("Initial index retrieved: ", idx) require.Equal(t, state.ErrNotFound, err) } + +func TestGetVirtualBatchWithTstamp(t *testing.T) { + initOrResetDB() + ctx := context.Background() + dbTx, err := testState.BeginStateTransaction(ctx) + require.NoError(t, err) + defer func() { require.NoError(t, dbTx.Commit(ctx)) }() + + // prepare data + addr := common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") + hash := common.HexToHash("0x29e885edaf8e4b51e1d2e05f9da28161d2fb4f6b1d53827d9b80a23cf2d7d9f1") + + blockNumber := uint64(123) + + // add l1 block + err = testState.AddBlock(ctx, state.NewBlock(blockNumber), dbTx) + require.NoError(t, err) + + batchNumber := uint64(1234) + + timestampBatch := time.Date(2023, 12, 14, 14, 30, 45, 0, time.Local) + virtualTimestampBatch := time.Date(2023, 12, 14, 12, 00, 45, 0, time.Local) + + // add batch + _, err = testState.Exec(ctx, "INSERT INTO state.batch (batch_num, timestamp, wip) VALUES ($1,$2, false)", batchNumber, timestampBatch) + require.NoError(t, err) + + virtualBatch := state.VirtualBatch{BlockNumber: blockNumber, + BatchNumber: batchNumber, + Coinbase: addr, + SequencerAddr: addr, + TxHash: hash, + TimestampBatchEtrog: &virtualTimestampBatch} + err = testState.AddVirtualBatch(ctx, &virtualBatch, dbTx) + require.NoError(t, err) + + read, err := testState.GetVirtualBatch(ctx, batchNumber, dbTx) + require.NoError(t, err) + require.Equal(t, virtualBatch, *read) + forcedForkId := uint64(state.FORKID_ETROG) + timeData, err := testState.GetBatchTimestamp(ctx, batchNumber, &forcedForkId, dbTx) + require.NoError(t, err) + require.Equal(t, virtualTimestampBatch, *timeData) + + forcedForkId = uint64(state.FORKID_INCABERRY) + timeData, err = testState.GetBatchTimestamp(ctx, batchNumber, &forcedForkId, dbTx) + require.NoError(t, err) + require.Equal(t, timestampBatch, *timeData) + +} + +func TestGetVirtualBatchWithNoTstamp(t *testing.T) { + initOrResetDB() + ctx := context.Background() + dbTx, err := testState.BeginStateTransaction(ctx) + require.NoError(t, err) + defer func() { require.NoError(t, dbTx.Commit(ctx)) }() + + // prepare data + addr := common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") + hash := common.HexToHash("0x29e885edaf8e4b51e1d2e05f9da28161d2fb4f6b1d53827d9b80a23cf2d7d9f1") + + blockNumber := uint64(123) + + // add l1 block + err = testState.AddBlock(ctx, state.NewBlock(blockNumber), dbTx) + require.NoError(t, err) + + batchNumber := uint64(1234) + + // add batch + _, err = testState.Exec(ctx, "INSERT INTO state.batch (batch_num, wip) VALUES ($1, false)", batchNumber) + require.NoError(t, err) + + virtualBatch := state.VirtualBatch{BlockNumber: blockNumber, + BatchNumber: batchNumber, + Coinbase: addr, + SequencerAddr: addr, + TxHash: hash, + } + err = testState.AddVirtualBatch(ctx, &virtualBatch, dbTx) + require.NoError(t, err) + + read, err := testState.GetVirtualBatch(ctx, batchNumber, dbTx) + require.NoError(t, err) + require.Equal(t, (*time.Time)(nil), read.TimestampBatchEtrog) + +} diff --git a/synchronizer/actions/etrog/processor_l1_sequence_batches.go b/synchronizer/actions/etrog/processor_l1_sequence_batches.go index bfe01ea4ab..f1384a0b8a 100644 --- a/synchronizer/actions/etrog/processor_l1_sequence_batches.go +++ b/synchronizer/actions/etrog/processor_l1_sequence_batches.go @@ -16,6 +16,7 @@ import ( stateMetrics "github.com/0xPolygonHermez/zkevm-node/state/metrics" "github.com/0xPolygonHermez/zkevm-node/state/runtime/executor" "github.com/0xPolygonHermez/zkevm-node/synchronizer/actions" + syncCommon "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/jackc/pgx/v4" @@ -26,8 +27,7 @@ type stateProcessSequenceBatches interface { GetBatchByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*state.Batch, error) ProcessAndStoreClosedBatch(ctx context.Context, processingCtx state.ProcessingContext, encodedTxs []byte, dbTx pgx.Tx, caller metrics.CallerLabel) (common.Hash, uint64, string, error) ProcessAndStoreClosedBatchV2(ctx context.Context, processingCtx state.ProcessingContextV2, dbTx pgx.Tx, caller metrics.CallerLabel) (common.Hash, uint64, string, error) - //ExecuteBatch(ctx context.Context, batch state.Batch, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponse, error) - ExecuteBatchV2(ctx context.Context, batch state.Batch, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponseV2, error) + ExecuteBatchV2(ctx context.Context, batch state.Batch, l1InfoRoot common.Hash, timestampLimit time.Time, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponseV2, error) AddAccumulatedInputHash(ctx context.Context, batchNum uint64, accInputHash common.Hash, dbTx pgx.Tx) error ResetTrustedState(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) error AddSequence(ctx context.Context, sequence state.Sequence, dbTx pgx.Tx) error @@ -55,25 +55,31 @@ type syncProcessSequenceBatchesInterface interface { // ProcessorL1SequenceBatchesEtrog implements L1EventProcessor type ProcessorL1SequenceBatchesEtrog struct { actions.ProcessorBase[ProcessorL1SequenceBatchesEtrog] - state stateProcessSequenceBatches - etherMan ethermanProcessSequenceBatches - pool poolProcessSequenceBatchesInterface - eventLog *event.EventLog - sync syncProcessSequenceBatchesInterface + state stateProcessSequenceBatches + etherMan ethermanProcessSequenceBatches + pool poolProcessSequenceBatchesInterface + eventLog *event.EventLog + sync syncProcessSequenceBatchesInterface + timeProvider syncCommon.TimeProvider } // NewProcessorL1SequenceBatches returns instance of a processor for SequenceBatchesOrder func NewProcessorL1SequenceBatches(state stateProcessSequenceBatches, - etherMan ethermanProcessSequenceBatches, pool poolProcessSequenceBatchesInterface, eventLog *event.EventLog, sync syncProcessSequenceBatchesInterface) *ProcessorL1SequenceBatchesEtrog { + etherMan ethermanProcessSequenceBatches, + pool poolProcessSequenceBatchesInterface, + eventLog *event.EventLog, + sync syncProcessSequenceBatchesInterface, + timeProvider syncCommon.TimeProvider) *ProcessorL1SequenceBatchesEtrog { return &ProcessorL1SequenceBatchesEtrog{ ProcessorBase: actions.ProcessorBase[ProcessorL1SequenceBatchesEtrog]{ SupportedEvent: []etherman.EventOrder{etherman.SequenceBatchesOrder}, SupportedForkdIds: &ForksIdOnlyEtrog}, - state: state, - etherMan: etherMan, - pool: pool, - eventLog: eventLog, - sync: sync, + state: state, + etherMan: etherMan, + pool: pool, + eventLog: eventLog, + sync: sync, + timeProvider: timeProvider, } } @@ -86,25 +92,30 @@ func (g *ProcessorL1SequenceBatchesEtrog) Process(ctx context.Context, order eth return err } -func (g *ProcessorL1SequenceBatchesEtrog) processSequenceBatches(ctx context.Context, sequencedBatches []etherman.SequencedBatch, blockNumber uint64, timestamp time.Time, dbTx pgx.Tx) error { +func (g *ProcessorL1SequenceBatchesEtrog) processSequenceBatches(ctx context.Context, sequencedBatches []etherman.SequencedBatch, blockNumber uint64, l1BlockTimestamp time.Time, dbTx pgx.Tx) error { if len(sequencedBatches) == 0 { log.Warn("Empty sequencedBatches array detected, ignoring...") return nil } + now := g.timeProvider.Now() for _, sbatch := range sequencedBatches { virtualBatch := state.VirtualBatch{ - BatchNumber: sbatch.BatchNumber, - TxHash: sbatch.TxHash, - Coinbase: sbatch.Coinbase, - BlockNumber: blockNumber, - SequencerAddr: sbatch.SequencerAddr, + BatchNumber: sbatch.BatchNumber, + TxHash: sbatch.TxHash, + Coinbase: sbatch.Coinbase, + BlockNumber: blockNumber, + SequencerAddr: sbatch.SequencerAddr, + TimestampBatchEtrog: &l1BlockTimestamp, } batch := state.Batch{ BatchNumber: sbatch.BatchNumber, GlobalExitRoot: sbatch.PolygonRollupBaseEtrogBatchData.ForcedGlobalExitRoot, - Timestamp: timestamp, - Coinbase: sbatch.Coinbase, - BatchL2Data: sbatch.PolygonRollupBaseEtrogBatchData.Transactions, + // This timestamp now is the timeLimit. It can't be the one virtual.BatchTimestamp + // because when sync from trusted we don't now the real BatchTimestamp and + // will fails the comparation of batch time >= than previous one. + Timestamp: now, + Coinbase: sbatch.Coinbase, + BatchL2Data: sbatch.PolygonRollupBaseEtrogBatchData.Transactions, } // ForcedBatch must be processed if sbatch.PolygonRollupBaseEtrogBatchData.ForcedTimestamp > 0 { // If this is true means that the batch is forced @@ -194,8 +205,8 @@ func (g *ProcessorL1SequenceBatchesEtrog) processSequenceBatches(ctx context.Con } } else { // Reprocess batch to compare the stateRoot with tBatch.StateRoot and get accInputHash - //p, err := g.state.ExecuteBatch(ctx, batch, false, dbTx) - p, err := g.state.ExecuteBatchV2(ctx, batch, false, dbTx) + //TODO: Pass L1InfoRoot from the event of etherman + p, err := g.state.ExecuteBatchV2(ctx, batch, currentL1InfoRoot, now, false, dbTx) if err != nil { log.Errorf("error executing L1 batch: %+v, error: %v", batch, err) rollbackErr := dbTx.Rollback(ctx) diff --git a/synchronizer/default_l1processors.go b/synchronizer/default_l1processors.go index b5c87e50ce..87f511ea60 100644 --- a/synchronizer/default_l1processors.go +++ b/synchronizer/default_l1processors.go @@ -4,6 +4,7 @@ import ( "github.com/0xPolygonHermez/zkevm-node/synchronizer/actions/etrog" "github.com/0xPolygonHermez/zkevm-node/synchronizer/actions/incaberry" "github.com/0xPolygonHermez/zkevm-node/synchronizer/actions/processor_manager" + "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" ) func defaultsL1EventProcessors(sync *ClientSynchronizer) *processor_manager.L1EventProcessors { @@ -14,7 +15,7 @@ func defaultsL1EventProcessors(sync *ClientSynchronizer) *processor_manager.L1Ev p.Register(incaberry.NewProcessL1SequenceForcedBatches(sync.state, sync)) p.Register(incaberry.NewProcessorForkId(sync.state, sync)) p.Register(etrog.NewProcessorL1InfoTreeUpdate(sync.state)) - p.Register(etrog.NewProcessorL1SequenceBatches(sync.state, sync.etherMan, sync.pool, sync.eventLog, sync)) + p.Register(etrog.NewProcessorL1SequenceBatches(sync.state, sync.etherMan, sync.pool, sync.eventLog, sync, common.DefaultTimeProvider{})) p.Register(incaberry.NewProcessorL1VerifyBatch(sync.state)) return p.Build() } diff --git a/synchronizer/interfaces.go b/synchronizer/interfaces.go index c83c016737..bb5986004d 100644 --- a/synchronizer/interfaces.go +++ b/synchronizer/interfaces.go @@ -3,6 +3,7 @@ package synchronizer import ( "context" "math/big" + "time" "github.com/0xPolygonHermez/zkevm-node/etherman" "github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" @@ -51,7 +52,7 @@ type stateInterface interface { StoreTransaction(ctx context.Context, batchNumber uint64, processedTx *state.ProcessTransactionResponse, coinbase common.Address, timestamp uint64, egpLog *state.EffectiveGasPriceLog, dbTx pgx.Tx) (*state.L2Header, error) GetStateRootByBatchNumber(ctx context.Context, batchNum uint64, dbTx pgx.Tx) (common.Hash, error) ExecuteBatch(ctx context.Context, batch state.Batch, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponse, error) - ExecuteBatchV2(ctx context.Context, batch state.Batch, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponseV2, error) + ExecuteBatchV2(ctx context.Context, batch state.Batch, l1InfoRoot common.Hash, timestampLimit time.Time, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponseV2, error) GetLastVerifiedBatch(ctx context.Context, dbTx pgx.Tx) (*state.VerifiedBatch, error) GetLastVirtualBatchNum(ctx context.Context, dbTx pgx.Tx) (uint64, error) AddSequence(ctx context.Context, sequence state.Sequence, dbTx pgx.Tx) error diff --git a/synchronizer/l2_sync/l2_shared/sync_trusted_state_process_batch.go b/synchronizer/l2_sync/l2_shared/sync_trusted_state_process_batch.go index 2d78108d83..be51b71218 100644 --- a/synchronizer/l2_sync/l2_shared/sync_trusted_state_process_batch.go +++ b/synchronizer/l2_sync/l2_shared/sync_trusted_state_process_batch.go @@ -4,10 +4,12 @@ import ( "context" "encoding/hex" "fmt" + "time" "github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" "github.com/0xPolygonHermez/zkevm-node/log" "github.com/0xPolygonHermez/zkevm-node/state" + syncCommon "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" "github.com/ethereum/go-ethereum/common" "github.com/jackc/pgx/v4" ) @@ -36,8 +38,8 @@ type ProcessData struct { // The batch in trusted node, it NEVER will be nil TrustedBatch *types.Batch // Current batch in state DB, it could be nil - StateBatch *state.Batch - + StateBatch *state.Batch + Now time.Time Description string } @@ -62,13 +64,17 @@ type SyncTrustedStateBatchExecutorTemplate struct { // this is because in the permissionless the timestamp of a batch is equal to the timestamp of the l1block where is reported // but trusted doesn't known this block and use now() instead. But for sure now() musbe <= l1block.tstamp CheckBatchTimestampGreaterInsteadOfEqual bool + timeProvider syncCommon.TimeProvider } // NewSyncTrustedStateBatchExecutorTemplate creates a new SyncTrustedStateBatchExecutorTemplate -func NewSyncTrustedStateBatchExecutorTemplate(steps SyncTrustedStateBatchExecutorSteps, checkBatchTimestampGreaterInsteadOfEqual bool) *SyncTrustedStateBatchExecutorTemplate { +func NewSyncTrustedStateBatchExecutorTemplate(steps SyncTrustedStateBatchExecutorSteps, + checkBatchTimestampGreaterInsteadOfEqual bool, + timeProvider syncCommon.TimeProvider) *SyncTrustedStateBatchExecutorTemplate { return &SyncTrustedStateBatchExecutorTemplate{ Steps: steps, CheckBatchTimestampGreaterInsteadOfEqual: checkBatchTimestampGreaterInsteadOfEqual, + timeProvider: timeProvider, } } @@ -174,6 +180,7 @@ func (s *SyncTrustedStateBatchExecutorTemplate) getModeForProcessBatch(trustedNo result.StateBatch = stateBatch result.TrustedBatch = trustedNodeBatch result.OldAccInputHash = statePreviousBatch.AccInputHash + result.Now = s.timeProvider.Now() return result, nil } diff --git a/synchronizer/l2_sync/l2_sync_etrog/sync_trusted_state.go b/synchronizer/l2_sync/l2_sync_etrog/sync_trusted_state.go index 7a5e0884ea..f7bc164ac2 100644 --- a/synchronizer/l2_sync/l2_sync_etrog/sync_trusted_state.go +++ b/synchronizer/l2_sync/l2_sync_etrog/sync_trusted_state.go @@ -8,6 +8,7 @@ import ( "github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" "github.com/0xPolygonHermez/zkevm-node/log" "github.com/0xPolygonHermez/zkevm-node/state" + syncCommon "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" "github.com/0xPolygonHermez/zkevm-node/synchronizer/l2_sync/l2_shared" "github.com/ethereum/go-ethereum/common" "github.com/jackc/pgx/v4" @@ -55,14 +56,13 @@ type BatchStepsExecutorEtrog struct { // NewSyncTrustedStateEtrogExecutor creates a new prcessor for sync with L2 batches func NewSyncTrustedStateEtrogExecutor(zkEVMClient l2_shared.ZkEVMClientInterface, state l2_shared.StateInterface, stateBatchExecutor BatchStepsExecutorEtrogStateInterface, - sync l2_shared.SyncInterface) *l2_shared.SyncTrustedStateTemplate { + sync l2_shared.SyncInterface, timeProvider syncCommon.TimeProvider) *l2_shared.SyncTrustedStateTemplate { executorSteps := &BatchStepsExecutorEtrog{ state: stateBatchExecutor, - sync: sync} - //executor := &l2_shared.SyncTrustedStateBatchExecutorTemplate{ - // Steps: executorSteps, - //} - executor := l2_shared.NewSyncTrustedStateBatchExecutorTemplate(executorSteps, true) + sync: sync, + } + + executor := l2_shared.NewSyncTrustedStateBatchExecutorTemplate(executorSteps, true, timeProvider) a := l2_shared.NewSyncTrustedStateTemplate(executor, zkEVMClient, state, sync) return a } diff --git a/synchronizer/mock_state.go b/synchronizer/mock_state.go index 92614cee00..82e3cad074 100644 --- a/synchronizer/mock_state.go +++ b/synchronizer/mock_state.go @@ -17,6 +17,8 @@ import ( state "github.com/0xPolygonHermez/zkevm-node/state" + time "time" + types "github.com/ethereum/go-ethereum/core/types" ) @@ -243,25 +245,25 @@ func (_m *stateMock) ExecuteBatch(ctx context.Context, batch state.Batch, update return r0, r1 } -// ExecuteBatchV2 provides a mock function with given fields: ctx, batch, updateMerkleTree, dbTx -func (_m *stateMock) ExecuteBatchV2(ctx context.Context, batch state.Batch, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponseV2, error) { - ret := _m.Called(ctx, batch, updateMerkleTree, dbTx) +// ExecuteBatchV2 provides a mock function with given fields: ctx, batch, l1InfoRoot, timestampLimit, updateMerkleTree, dbTx +func (_m *stateMock) ExecuteBatchV2(ctx context.Context, batch state.Batch, l1InfoRoot common.Hash, timestampLimit time.Time, updateMerkleTree bool, dbTx pgx.Tx) (*executor.ProcessBatchResponseV2, error) { + ret := _m.Called(ctx, batch, l1InfoRoot, timestampLimit, updateMerkleTree, dbTx) var r0 *executor.ProcessBatchResponseV2 var r1 error - if rf, ok := ret.Get(0).(func(context.Context, state.Batch, bool, pgx.Tx) (*executor.ProcessBatchResponseV2, error)); ok { - return rf(ctx, batch, updateMerkleTree, dbTx) + if rf, ok := ret.Get(0).(func(context.Context, state.Batch, common.Hash, time.Time, bool, pgx.Tx) (*executor.ProcessBatchResponseV2, error)); ok { + return rf(ctx, batch, l1InfoRoot, timestampLimit, updateMerkleTree, dbTx) } - if rf, ok := ret.Get(0).(func(context.Context, state.Batch, bool, pgx.Tx) *executor.ProcessBatchResponseV2); ok { - r0 = rf(ctx, batch, updateMerkleTree, dbTx) + if rf, ok := ret.Get(0).(func(context.Context, state.Batch, common.Hash, time.Time, bool, pgx.Tx) *executor.ProcessBatchResponseV2); ok { + r0 = rf(ctx, batch, l1InfoRoot, timestampLimit, updateMerkleTree, dbTx) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*executor.ProcessBatchResponseV2) } } - if rf, ok := ret.Get(1).(func(context.Context, state.Batch, bool, pgx.Tx) error); ok { - r1 = rf(ctx, batch, updateMerkleTree, dbTx) + if rf, ok := ret.Get(1).(func(context.Context, state.Batch, common.Hash, time.Time, bool, pgx.Tx) error); ok { + r1 = rf(ctx, batch, l1InfoRoot, timestampLimit, updateMerkleTree, dbTx) } else { r1 = ret.Error(1) } diff --git a/synchronizer/synchronizer.go b/synchronizer/synchronizer.go index 3ce7ad0a63..54554809ce 100644 --- a/synchronizer/synchronizer.go +++ b/synchronizer/synchronizer.go @@ -23,6 +23,7 @@ import ( stateMetrics "github.com/0xPolygonHermez/zkevm-node/state/metrics" "github.com/0xPolygonHermez/zkevm-node/synchronizer/actions" "github.com/0xPolygonHermez/zkevm-node/synchronizer/actions/processor_manager" + syncCommon "github.com/0xPolygonHermez/zkevm-node/synchronizer/common" "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1_parallel_sync" "github.com/0xPolygonHermez/zkevm-node/synchronizer/l1event_orders" "github.com/0xPolygonHermez/zkevm-node/synchronizer/l2_sync/l2_sync_etrog" @@ -114,7 +115,7 @@ func NewSynchronizer( l1EventProcessors: nil, } //res.syncTrustedStateExecutor = l2_sync_incaberry.NewSyncTrustedStateExecutor(res.zkEVMClient, res.state, res) - res.syncTrustedStateExecutor = l2_sync_etrog.NewSyncTrustedStateEtrogExecutor(res.zkEVMClient, res.state, res.state, res) + res.syncTrustedStateExecutor = l2_sync_etrog.NewSyncTrustedStateEtrogExecutor(res.zkEVMClient, res.state, res.state, res, syncCommon.DefaultTimeProvider{}) res.l1EventProcessors = defaultsL1EventProcessors(res) switch cfg.L1SynchronizationMode { case ParallelMode: @@ -286,7 +287,7 @@ func (s *ClientSynchronizer) Sync() error { log.Errorf("error rolling back state. RollbackErr: %v", rollbackErr) return rollbackErr } - return fmt.Errorf("Calculated newRoot should be %s instead of %s", s.genesis.Root.String(), genesisRoot.String()) + return fmt.Errorf("calculated newRoot should be %s instead of %s", s.genesis.Root.String(), genesisRoot.String()) } // Waiting for the flushID to be stored err = s.checkFlushID(dbTx)