diff --git a/etherman/etherman.go b/etherman/etherman.go index 47ccb49aec..258d276d34 100644 --- a/etherman/etherman.go +++ b/etherman/etherman.go @@ -44,6 +44,11 @@ import ( "golang.org/x/crypto/sha3" ) +const ( + // ETrogUpgradeVersion is the version of the LxLy upgrade + ETrogUpgradeVersion = 2 +) + var ( // Events RollupManager setBatchFeeSignatureHash = crypto.Keccak256Hash([]byte("SetBatchFee(uint256)")) @@ -361,6 +366,26 @@ func (etherMan *Client) VerifyGenBlockNumber(ctx context.Context, genBlockNumber return true, nil } +// GetL1BlockUpgradeLxLy It returns the block genesis for LxLy before genesisBlock or error +func (etherMan *Client) GetL1BlockUpgradeLxLy(ctx context.Context, genesisBlock uint64) (uint64, error) { + it, err := etherMan.RollupManager.FilterInitialized(&bind.FilterOpts{ + Start: 1, + End: &genesisBlock, + Context: ctx, + }) + if err != nil { + return uint64(0), err + } + for it.Next() { + log.Debugf("BlockNumber: %d Topics:Initialized(%d)", it.Event.Raw.BlockNumber, it.Event.Version) + if it.Event.Version == ETrogUpgradeVersion { // 2 is ETROG (LxLy upgrade) + log.Infof("LxLy upgrade found at blockNumber: %d", it.Event.Raw.BlockNumber) + return it.Event.Raw.BlockNumber, nil + } + } + return uint64(0), ErrNotFound +} + // GetForks returns fork information func (etherMan *Client) GetForks(ctx context.Context, genBlockNumber uint64, lastL1BlockSynced uint64) ([]state.ForkIDInterval, error) { log.Debug("Getting forkIDs from blockNumber: ", genBlockNumber) @@ -497,6 +522,25 @@ func (etherMan *Client) GetRollupInfoByBlockRange(ctx context.Context, fromBlock return blocks, blocksOrder, nil } +// GetRollupInfoByBlockRangePreviousRollupGenesis function retrieves the Rollup information that are included in all this ethereum blocks +// but it only retrieves the information from the previous rollup genesis block to the current block. +func (etherMan *Client) GetRollupInfoByBlockRangePreviousRollupGenesis(ctx context.Context, fromBlock uint64, toBlock *uint64) ([]Block, map[common.Hash][]Order, error) { + // Filter query + query := ethereum.FilterQuery{ + FromBlock: new(big.Int).SetUint64(fromBlock), + Addresses: []common.Address{etherMan.l1Cfg.GlobalExitRootManagerAddr}, + Topics: [][]common.Hash{{updateL1InfoTreeSignatureHash}}, + } + if toBlock != nil { + query.ToBlock = new(big.Int).SetUint64(*toBlock) + } + blocks, blocksOrder, err := etherMan.readEvents(ctx, query) + if err != nil { + return nil, nil, err + } + return blocks, blocksOrder, nil +} + // Order contains the event order to let the synchronizer store the information following this order. type Order struct { Name EventOrder diff --git a/synchronizer/common/syncinterfaces/etherman.go b/synchronizer/common/syncinterfaces/etherman.go index 24e5dbda69..0bfafc081d 100644 --- a/synchronizer/common/syncinterfaces/etherman.go +++ b/synchronizer/common/syncinterfaces/etherman.go @@ -14,12 +14,18 @@ type EthermanFullInterface interface { HeaderByNumber(ctx context.Context, number *big.Int) (*ethTypes.Header, error) GetRollupInfoByBlockRange(ctx context.Context, fromBlock uint64, toBlock *uint64) ([]etherman.Block, map[common.Hash][]etherman.Order, error) EthBlockByNumber(ctx context.Context, blockNumber uint64) (*ethTypes.Block, error) - GetLatestBatchNumber() (uint64, error) GetTrustedSequencerURL() (string, error) VerifyGenBlockNumber(ctx context.Context, genBlockNumber uint64) (bool, error) GetLatestVerifiedBatchNum() (uint64, error) + EthermanGetLatestBatchNumber + EthermanPreRollup } type EthermanGetLatestBatchNumber interface { GetLatestBatchNumber() (uint64, error) } + +type EthermanPreRollup interface { + GetL1BlockUpgradeLxLy(ctx context.Context, genesisBlock uint64) (uint64, error) + GetRollupInfoByBlockRangePreviousRollupGenesis(ctx context.Context, fromBlock uint64, toBlock *uint64) ([]etherman.Block, map[common.Hash][]etherman.Order, error) +} diff --git a/synchronizer/common/syncinterfaces/mocks/etherman_full_interface.go b/synchronizer/common/syncinterfaces/mocks/etherman_full_interface.go index fe6e6c3df6..17197d1ad4 100644 --- a/synchronizer/common/syncinterfaces/mocks/etherman_full_interface.go +++ b/synchronizer/common/syncinterfaces/mocks/etherman_full_interface.go @@ -87,6 +87,63 @@ func (_c *EthermanFullInterface_EthBlockByNumber_Call) RunAndReturn(run func(con return _c } +// GetL1BlockUpgradeLxLy provides a mock function with given fields: ctx, genesisBlock +func (_m *EthermanFullInterface) GetL1BlockUpgradeLxLy(ctx context.Context, genesisBlock uint64) (uint64, error) { + ret := _m.Called(ctx, genesisBlock) + + if len(ret) == 0 { + panic("no return value specified for GetL1BlockUpgradeLxLy") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64) (uint64, error)); ok { + return rf(ctx, genesisBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64) uint64); ok { + r0 = rf(ctx, genesisBlock) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64) error); ok { + r1 = rf(ctx, genesisBlock) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthermanFullInterface_GetL1BlockUpgradeLxLy_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetL1BlockUpgradeLxLy' +type EthermanFullInterface_GetL1BlockUpgradeLxLy_Call struct { + *mock.Call +} + +// GetL1BlockUpgradeLxLy is a helper method to define mock.On call +// - ctx context.Context +// - genesisBlock uint64 +func (_e *EthermanFullInterface_Expecter) GetL1BlockUpgradeLxLy(ctx interface{}, genesisBlock interface{}) *EthermanFullInterface_GetL1BlockUpgradeLxLy_Call { + return &EthermanFullInterface_GetL1BlockUpgradeLxLy_Call{Call: _e.mock.On("GetL1BlockUpgradeLxLy", ctx, genesisBlock)} +} + +func (_c *EthermanFullInterface_GetL1BlockUpgradeLxLy_Call) Run(run func(ctx context.Context, genesisBlock uint64)) *EthermanFullInterface_GetL1BlockUpgradeLxLy_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64)) + }) + return _c +} + +func (_c *EthermanFullInterface_GetL1BlockUpgradeLxLy_Call) Return(_a0 uint64, _a1 error) *EthermanFullInterface_GetL1BlockUpgradeLxLy_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthermanFullInterface_GetL1BlockUpgradeLxLy_Call) RunAndReturn(run func(context.Context, uint64) (uint64, error)) *EthermanFullInterface_GetL1BlockUpgradeLxLy_Call { + _c.Call.Return(run) + return _c +} + // GetLatestBatchNumber provides a mock function with given fields: func (_m *EthermanFullInterface) GetLatestBatchNumber() (uint64, error) { ret := _m.Called() @@ -266,6 +323,75 @@ func (_c *EthermanFullInterface_GetRollupInfoByBlockRange_Call) RunAndReturn(run return _c } +// GetRollupInfoByBlockRangePreviousRollupGenesis provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *EthermanFullInterface) GetRollupInfoByBlockRangePreviousRollupGenesis(ctx context.Context, fromBlock uint64, toBlock *uint64) ([]etherman.Block, map[common.Hash][]etherman.Order, error) { + ret := _m.Called(ctx, fromBlock, toBlock) + + if len(ret) == 0 { + panic("no return value specified for GetRollupInfoByBlockRangePreviousRollupGenesis") + } + + var r0 []etherman.Block + var r1 map[common.Hash][]etherman.Order + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, *uint64) ([]etherman.Block, map[common.Hash][]etherman.Order, error)); ok { + return rf(ctx, fromBlock, toBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, *uint64) []etherman.Block); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]etherman.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, *uint64) map[common.Hash][]etherman.Order); ok { + r1 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(map[common.Hash][]etherman.Order) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, uint64, *uint64) error); ok { + r2 = rf(ctx, fromBlock, toBlock) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// EthermanFullInterface_GetRollupInfoByBlockRangePreviousRollupGenesis_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRollupInfoByBlockRangePreviousRollupGenesis' +type EthermanFullInterface_GetRollupInfoByBlockRangePreviousRollupGenesis_Call struct { + *mock.Call +} + +// GetRollupInfoByBlockRangePreviousRollupGenesis is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - toBlock *uint64 +func (_e *EthermanFullInterface_Expecter) GetRollupInfoByBlockRangePreviousRollupGenesis(ctx interface{}, fromBlock interface{}, toBlock interface{}) *EthermanFullInterface_GetRollupInfoByBlockRangePreviousRollupGenesis_Call { + return &EthermanFullInterface_GetRollupInfoByBlockRangePreviousRollupGenesis_Call{Call: _e.mock.On("GetRollupInfoByBlockRangePreviousRollupGenesis", ctx, fromBlock, toBlock)} +} + +func (_c *EthermanFullInterface_GetRollupInfoByBlockRangePreviousRollupGenesis_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock *uint64)) *EthermanFullInterface_GetRollupInfoByBlockRangePreviousRollupGenesis_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(*uint64)) + }) + return _c +} + +func (_c *EthermanFullInterface_GetRollupInfoByBlockRangePreviousRollupGenesis_Call) Return(_a0 []etherman.Block, _a1 map[common.Hash][]etherman.Order, _a2 error) *EthermanFullInterface_GetRollupInfoByBlockRangePreviousRollupGenesis_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *EthermanFullInterface_GetRollupInfoByBlockRangePreviousRollupGenesis_Call) RunAndReturn(run func(context.Context, uint64, *uint64) ([]etherman.Block, map[common.Hash][]etherman.Order, error)) *EthermanFullInterface_GetRollupInfoByBlockRangePreviousRollupGenesis_Call { + _c.Call.Return(run) + return _c +} + // GetTrustedSequencerURL provides a mock function with given fields: func (_m *EthermanFullInterface) GetTrustedSequencerURL() (string, error) { ret := _m.Called() diff --git a/synchronizer/mock_etherman.go b/synchronizer/mock_etherman.go index 4c0b9c1763..062c346809 100644 --- a/synchronizer/mock_etherman.go +++ b/synchronizer/mock_etherman.go @@ -87,6 +87,63 @@ func (_c *ethermanMock_EthBlockByNumber_Call) RunAndReturn(run func(context.Cont return _c } +// GetL1BlockUpgradeLxLy provides a mock function with given fields: ctx, genesisBlock +func (_m *ethermanMock) GetL1BlockUpgradeLxLy(ctx context.Context, genesisBlock uint64) (uint64, error) { + ret := _m.Called(ctx, genesisBlock) + + if len(ret) == 0 { + panic("no return value specified for GetL1BlockUpgradeLxLy") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64) (uint64, error)); ok { + return rf(ctx, genesisBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64) uint64); ok { + r0 = rf(ctx, genesisBlock) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64) error); ok { + r1 = rf(ctx, genesisBlock) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ethermanMock_GetL1BlockUpgradeLxLy_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetL1BlockUpgradeLxLy' +type ethermanMock_GetL1BlockUpgradeLxLy_Call struct { + *mock.Call +} + +// GetL1BlockUpgradeLxLy is a helper method to define mock.On call +// - ctx context.Context +// - genesisBlock uint64 +func (_e *ethermanMock_Expecter) GetL1BlockUpgradeLxLy(ctx interface{}, genesisBlock interface{}) *ethermanMock_GetL1BlockUpgradeLxLy_Call { + return ðermanMock_GetL1BlockUpgradeLxLy_Call{Call: _e.mock.On("GetL1BlockUpgradeLxLy", ctx, genesisBlock)} +} + +func (_c *ethermanMock_GetL1BlockUpgradeLxLy_Call) Run(run func(ctx context.Context, genesisBlock uint64)) *ethermanMock_GetL1BlockUpgradeLxLy_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64)) + }) + return _c +} + +func (_c *ethermanMock_GetL1BlockUpgradeLxLy_Call) Return(_a0 uint64, _a1 error) *ethermanMock_GetL1BlockUpgradeLxLy_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ethermanMock_GetL1BlockUpgradeLxLy_Call) RunAndReturn(run func(context.Context, uint64) (uint64, error)) *ethermanMock_GetL1BlockUpgradeLxLy_Call { + _c.Call.Return(run) + return _c +} + // GetLatestBatchNumber provides a mock function with given fields: func (_m *ethermanMock) GetLatestBatchNumber() (uint64, error) { ret := _m.Called() @@ -266,6 +323,75 @@ func (_c *ethermanMock_GetRollupInfoByBlockRange_Call) RunAndReturn(run func(con return _c } +// GetRollupInfoByBlockRangePreviousRollupGenesis provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *ethermanMock) GetRollupInfoByBlockRangePreviousRollupGenesis(ctx context.Context, fromBlock uint64, toBlock *uint64) ([]etherman.Block, map[common.Hash][]etherman.Order, error) { + ret := _m.Called(ctx, fromBlock, toBlock) + + if len(ret) == 0 { + panic("no return value specified for GetRollupInfoByBlockRangePreviousRollupGenesis") + } + + var r0 []etherman.Block + var r1 map[common.Hash][]etherman.Order + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, *uint64) ([]etherman.Block, map[common.Hash][]etherman.Order, error)); ok { + return rf(ctx, fromBlock, toBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, *uint64) []etherman.Block); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]etherman.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, *uint64) map[common.Hash][]etherman.Order); ok { + r1 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(map[common.Hash][]etherman.Order) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, uint64, *uint64) error); ok { + r2 = rf(ctx, fromBlock, toBlock) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// ethermanMock_GetRollupInfoByBlockRangePreviousRollupGenesis_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRollupInfoByBlockRangePreviousRollupGenesis' +type ethermanMock_GetRollupInfoByBlockRangePreviousRollupGenesis_Call struct { + *mock.Call +} + +// GetRollupInfoByBlockRangePreviousRollupGenesis is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - toBlock *uint64 +func (_e *ethermanMock_Expecter) GetRollupInfoByBlockRangePreviousRollupGenesis(ctx interface{}, fromBlock interface{}, toBlock interface{}) *ethermanMock_GetRollupInfoByBlockRangePreviousRollupGenesis_Call { + return ðermanMock_GetRollupInfoByBlockRangePreviousRollupGenesis_Call{Call: _e.mock.On("GetRollupInfoByBlockRangePreviousRollupGenesis", ctx, fromBlock, toBlock)} +} + +func (_c *ethermanMock_GetRollupInfoByBlockRangePreviousRollupGenesis_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock *uint64)) *ethermanMock_GetRollupInfoByBlockRangePreviousRollupGenesis_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(*uint64)) + }) + return _c +} + +func (_c *ethermanMock_GetRollupInfoByBlockRangePreviousRollupGenesis_Call) Return(_a0 []etherman.Block, _a1 map[common.Hash][]etherman.Order, _a2 error) *ethermanMock_GetRollupInfoByBlockRangePreviousRollupGenesis_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *ethermanMock_GetRollupInfoByBlockRangePreviousRollupGenesis_Call) RunAndReturn(run func(context.Context, uint64, *uint64) ([]etherman.Block, map[common.Hash][]etherman.Order, error)) *ethermanMock_GetRollupInfoByBlockRangePreviousRollupGenesis_Call { + _c.Call.Return(run) + return _c +} + // GetTrustedSequencerURL provides a mock function with given fields: func (_m *ethermanMock) GetTrustedSequencerURL() (string, error) { ret := _m.Called() diff --git a/synchronizer/mock_state.go b/synchronizer/mock_state.go index 29de3d7500..ad1a3eeaaa 100644 --- a/synchronizer/mock_state.go +++ b/synchronizer/mock_state.go @@ -1216,6 +1216,66 @@ func (_c *StateMock_GetL1InfoTreeDataFromBatchL2Data_Call) RunAndReturn(run func return _c } +// GetL2BlockByNumber provides a mock function with given fields: ctx, blockNumber, dbTx +func (_m *StateMock) GetL2BlockByNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.L2Block, error) { + ret := _m.Called(ctx, blockNumber, dbTx) + + if len(ret) == 0 { + panic("no return value specified for GetL2BlockByNumber") + } + + var r0 *state.L2Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) (*state.L2Block, error)); ok { + return rf(ctx, blockNumber, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) *state.L2Block); ok { + r0 = rf(ctx, blockNumber, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*state.L2Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { + r1 = rf(ctx, blockNumber, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// StateMock_GetL2BlockByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetL2BlockByNumber' +type StateMock_GetL2BlockByNumber_Call struct { + *mock.Call +} + +// GetL2BlockByNumber is a helper method to define mock.On call +// - ctx context.Context +// - blockNumber uint64 +// - dbTx pgx.Tx +func (_e *StateMock_Expecter) GetL2BlockByNumber(ctx interface{}, blockNumber interface{}, dbTx interface{}) *StateMock_GetL2BlockByNumber_Call { + return &StateMock_GetL2BlockByNumber_Call{Call: _e.mock.On("GetL2BlockByNumber", ctx, blockNumber, dbTx)} +} + +func (_c *StateMock_GetL2BlockByNumber_Call) Run(run func(ctx context.Context, blockNumber uint64, dbTx pgx.Tx)) *StateMock_GetL2BlockByNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(pgx.Tx)) + }) + return _c +} + +func (_c *StateMock_GetL2BlockByNumber_Call) Return(_a0 *state.L2Block, _a1 error) *StateMock_GetL2BlockByNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *StateMock_GetL2BlockByNumber_Call) RunAndReturn(run func(context.Context, uint64, pgx.Tx) (*state.L2Block, error)) *StateMock_GetL2BlockByNumber_Call { + _c.Call.Return(run) + return _c +} + // GetLastBatchNumber provides a mock function with given fields: ctx, dbTx func (_m *StateMock) GetLastBatchNumber(ctx context.Context, dbTx pgx.Tx) (uint64, error) { ret := _m.Called(ctx, dbTx) @@ -1392,6 +1452,63 @@ func (_c *StateMock_GetLastL2BlockByBatchNumber_Call) RunAndReturn(run func(cont return _c } +// GetLastL2BlockNumber provides a mock function with given fields: ctx, dbTx +func (_m *StateMock) GetLastL2BlockNumber(ctx context.Context, dbTx pgx.Tx) (uint64, error) { + ret := _m.Called(ctx, dbTx) + + if len(ret) == 0 { + panic("no return value specified for GetLastL2BlockNumber") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, pgx.Tx) (uint64, error)); ok { + return rf(ctx, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, pgx.Tx) uint64); ok { + r0 = rf(ctx, dbTx) + } else { + r0 = ret.Get(0).(uint64) + } + + 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 +} + +// StateMock_GetLastL2BlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLastL2BlockNumber' +type StateMock_GetLastL2BlockNumber_Call struct { + *mock.Call +} + +// GetLastL2BlockNumber is a helper method to define mock.On call +// - ctx context.Context +// - dbTx pgx.Tx +func (_e *StateMock_Expecter) GetLastL2BlockNumber(ctx interface{}, dbTx interface{}) *StateMock_GetLastL2BlockNumber_Call { + return &StateMock_GetLastL2BlockNumber_Call{Call: _e.mock.On("GetLastL2BlockNumber", ctx, dbTx)} +} + +func (_c *StateMock_GetLastL2BlockNumber_Call) Run(run func(ctx context.Context, dbTx pgx.Tx)) *StateMock_GetLastL2BlockNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(pgx.Tx)) + }) + return _c +} + +func (_c *StateMock_GetLastL2BlockNumber_Call) Return(_a0 uint64, _a1 error) *StateMock_GetLastL2BlockNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *StateMock_GetLastL2BlockNumber_Call) RunAndReturn(run func(context.Context, pgx.Tx) (uint64, error)) *StateMock_GetLastL2BlockNumber_Call { + _c.Call.Return(run) + return _c +} + // GetLastVerifiedBatch provides a mock function with given fields: ctx, dbTx func (_m *StateMock) GetLastVerifiedBatch(ctx context.Context, dbTx pgx.Tx) (*state.VerifiedBatch, error) { ret := _m.Called(ctx, dbTx) @@ -2598,6 +2715,56 @@ func (_c *StateMock_UpdateBatchL2Data_Call) RunAndReturn(run func(context.Contex return _c } +// UpdateForkIDBlockNumber provides a mock function with given fields: ctx, forkdID, newBlockNumber, updateMemCache, dbTx +func (_m *StateMock) UpdateForkIDBlockNumber(ctx context.Context, forkdID uint64, newBlockNumber uint64, updateMemCache bool, dbTx pgx.Tx) error { + ret := _m.Called(ctx, forkdID, newBlockNumber, updateMemCache, dbTx) + + if len(ret) == 0 { + panic("no return value specified for UpdateForkIDBlockNumber") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, bool, pgx.Tx) error); ok { + r0 = rf(ctx, forkdID, newBlockNumber, updateMemCache, dbTx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// StateMock_UpdateForkIDBlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateForkIDBlockNumber' +type StateMock_UpdateForkIDBlockNumber_Call struct { + *mock.Call +} + +// UpdateForkIDBlockNumber is a helper method to define mock.On call +// - ctx context.Context +// - forkdID uint64 +// - newBlockNumber uint64 +// - updateMemCache bool +// - dbTx pgx.Tx +func (_e *StateMock_Expecter) UpdateForkIDBlockNumber(ctx interface{}, forkdID interface{}, newBlockNumber interface{}, updateMemCache interface{}, dbTx interface{}) *StateMock_UpdateForkIDBlockNumber_Call { + return &StateMock_UpdateForkIDBlockNumber_Call{Call: _e.mock.On("UpdateForkIDBlockNumber", ctx, forkdID, newBlockNumber, updateMemCache, dbTx)} +} + +func (_c *StateMock_UpdateForkIDBlockNumber_Call) Run(run func(ctx context.Context, forkdID uint64, newBlockNumber uint64, updateMemCache bool, dbTx pgx.Tx)) *StateMock_UpdateForkIDBlockNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64), args[3].(bool), args[4].(pgx.Tx)) + }) + return _c +} + +func (_c *StateMock_UpdateForkIDBlockNumber_Call) Return(_a0 error) *StateMock_UpdateForkIDBlockNumber_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *StateMock_UpdateForkIDBlockNumber_Call) RunAndReturn(run func(context.Context, uint64, uint64, bool, pgx.Tx) error) *StateMock_UpdateForkIDBlockNumber_Call { + _c.Call.Return(run) + return _c +} + // UpdateWIPBatch provides a mock function with given fields: ctx, receipt, dbTx func (_m *StateMock) UpdateWIPBatch(ctx context.Context, receipt state.ProcessingReceipt, dbTx pgx.Tx) error { ret := _m.Called(ctx, receipt, dbTx) diff --git a/synchronizer/synchronizer.go b/synchronizer/synchronizer.go index 0985b3dbf7..8caa59bf37 100644 --- a/synchronizer/synchronizer.go +++ b/synchronizer/synchronizer.go @@ -241,6 +241,160 @@ func rollback(ctx context.Context, dbTx pgx.Tx, err error) error { return err } +func (s *ClientSynchronizer) isGenesisProcessed(ctx context.Context, dbTx pgx.Tx) (bool, *state.Block, error) { + lastEthBlockSynced, err := s.state.GetLastBlock(ctx, dbTx) + if err != nil && errors.Is(err, state.ErrStateNotSynchronized) { + return false, lastEthBlockSynced, nil + } + + if lastEthBlockSynced.BlockNumber >= s.genesis.RollupBlockNumber { + log.Infof("Genesis block processed. Last block synced: %d >= genesis %d", lastEthBlockSynced.BlockNumber, s.genesis.RollupBlockNumber) + return true, lastEthBlockSynced, nil + } + log.Warnf("Genesis block not processed yet. Last block synced: %d < genesis %d", lastEthBlockSynced.BlockNumber, s.genesis.RollupBlockNumber) + return false, lastEthBlockSynced, nil +} + +// getStartingL1Block find if need to update and if yes the starting point: +// bool -> need to process blocks +// uint64 -> first block to synchronize +// error -> error +// 1. First try to get last block on DB, if there are could be fully synced or pending blocks +// 2. If DB is empty the LxLy upgrade block as starting point +func (s *ClientSynchronizer) getStartingL1Block(ctx context.Context, genesisBlockNumber, rollupManagerBlockNumber uint64, dbTx pgx.Tx) (bool, uint64, error) { + lastBlock, err := s.state.GetLastBlock(ctx, dbTx) + if err != nil && errors.Is(err, state.ErrStateNotSynchronized) { + // No block on DB + upgradeLxLyBlockNumber := rollupManagerBlockNumber + if upgradeLxLyBlockNumber == 0 { + upgradeLxLyBlockNumber, err = s.etherMan.GetL1BlockUpgradeLxLy(ctx, genesisBlockNumber) + if err != nil && errors.Is(err, etherman.ErrNotFound) { + log.Infof("sync pregenesis: LxLy upgrade not detected before genesis block %d, it'll be sync as usual. Nothing to do yet", genesisBlockNumber) + return false, 0, nil + } else if err != nil { + log.Errorf("sync pregenesis: error getting LxLy upgrade block. Error: %v", err) + return false, 0, err + } + } + if rollupManagerBlockNumber >= genesisBlockNumber { + log.Infof("sync pregenesis: rollupManagerBlockNumber>=genesisBlockNumber (%d>=%d). Nothing in pregenesis", rollupManagerBlockNumber, genesisBlockNumber) + return false, 0, nil + } + log.Infof("sync pregenesis: No block on DB, starting from LxLy upgrade block (rollupManagerBlockNumber) %d", upgradeLxLyBlockNumber) + return true, upgradeLxLyBlockNumber, nil + } else if err != nil { + log.Errorf("Error getting last Block on DB err:%v", err) + return false, 0, err + } + if lastBlock.BlockNumber >= genesisBlockNumber-1 { + log.Warnf("sync pregenesis: Last block processed is %d, which is greater or equal than the previous genesis block %d", lastBlock, genesisBlockNumber) + return false, 0, nil + } + log.Infof("sync pregenesis: Continue processing pre-genesis blocks, last block processed on DB is %d", lastBlock.BlockNumber+1) + return true, lastBlock.BlockNumber + 1, nil +} + +func (s *ClientSynchronizer) synchronizePreGenesisRollupEvents(syncChunkSize uint64, ctx context.Context) error { + // Sync events from RollupManager that happen before rollup creation + startTime := time.Now() + log.Info("synchronizing events from RollupManager that happen before rollup creation") + needToUpdate, fromBlock, err := s.getStartingL1Block(ctx, s.genesis.RollupBlockNumber, s.genesis.RollupManagerBlockNumber, nil) + if err != nil { + log.Errorf("sync pregenesis: error getting starting L1 block. Error: %v", err) + return err + } + if !needToUpdate { + log.Infof("sync pregenesis: No need to process blocks before the genesis block %d", s.genesis.RollupBlockNumber) + return nil + } + toBlockFinal := s.genesis.RollupBlockNumber - 1 + log.Infof("sync pregenesis: starting syncing pre genesis LxLy events from block %d to block %d (total %d blocks) chunk size %d", + fromBlock, toBlockFinal, toBlockFinal-fromBlock+1, syncChunkSize) + for i := fromBlock; true; i += syncChunkSize { + toBlock := min(i+syncChunkSize-1, toBlockFinal) + log.Debugf("sync pregenesis: syncing L1InfoTree from blocks [%d - %d] remains: %d", i, toBlock, toBlockFinal-toBlock) + blocks, order, err := s.etherMan.GetRollupInfoByBlockRangePreviousRollupGenesis(s.ctx, i, &toBlock) + if err != nil { + log.Error("sync pregenesis: error getting rollupInfoByBlockRange before rollup genesis: ", err) + return err + } + log.Debugf("sync pregenesis: syncing L1InfoTree from blocks [%d - %d] -> num_block:%d num_order:%d", i, toBlock, len(blocks), len(order)) + err = s.ProcessBlockRange(blocks, order) + if err != nil { + log.Error("sync pregenesis: error processing blocks before the genesis: ", err) + return err + } + if toBlock == toBlockFinal { + break + } + } + elapsedTime := time.Since(startTime) + log.Infof("sync pregenesis: sync L1InfoTree finish: from %d to %d total_block %d done in %s", fromBlock, toBlockFinal, toBlockFinal-fromBlock+1, &elapsedTime) + return nil +} + +func (s *ClientSynchronizer) processGenesis() (*state.Block, error) { + log.Info("State is empty, verifying genesis block") + valid, err := s.etherMan.VerifyGenBlockNumber(s.ctx, s.genesis.RollupBlockNumber) + if err != nil { + log.Error("error checking genesis block number. Error: ", err) + return nil, err + } else if !valid { + log.Error("genesis Block number configured is not valid. It is required the block number where the PolygonZkEVM smc was deployed") + return nil, fmt.Errorf("genesis Block number configured is not valid. It is required the block number where the PolygonZkEVM smc was deployed") + } + err = s.synchronizePreGenesisRollupEvents(s.cfg.SyncChunkSize, s.ctx) + if err != nil { + log.Error("error synchronizing pre genesis events: ", err) + return nil, err + } + + header, err := s.etherMan.HeaderByNumber(s.ctx, big.NewInt(0).SetUint64(s.genesis.RollupBlockNumber)) + if err != nil { + log.Errorf("error getting l1 block header for block %d. Error: %v", s.genesis.RollupBlockNumber, err) + return nil, err + } + log.Info("synchronizing rollup creation block") + lastEthBlockSynced := &state.Block{ + BlockNumber: header.Number.Uint64(), + BlockHash: header.Hash(), + ParentHash: header.ParentHash, + ReceivedAt: time.Unix(int64(header.Time), 0), + } + dbTx, err := s.state.BeginStateTransaction(s.ctx) + if err != nil { + log.Errorf("error creating db transaction to get latest block. Error: %v", err) + return nil, err + } + genesisRoot, err := s.state.SetGenesis(s.ctx, *lastEthBlockSynced, s.genesis, stateMetrics.SynchronizerCallerLabel, dbTx) + if err != nil { + log.Error("error setting genesis: ", err) + return nil, rollback(s.ctx, dbTx, err) + } + err = s.RequestAndProcessRollupGenesisBlock(dbTx, lastEthBlockSynced) + if err != nil { + log.Error("error processing Rollup genesis block: ", err) + return nil, rollback(s.ctx, dbTx, err) + } + + if genesisRoot != s.genesis.Root { + log.Errorf("Calculated newRoot should be %s instead of %s", s.genesis.Root.String(), genesisRoot.String()) + return nil, rollback(s.ctx, dbTx, 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) + if err != nil { + log.Error("error checking genesis flushID: ", err) + return nil, rollback(s.ctx, dbTx, err) + } + if err := dbTx.Commit(s.ctx); err != nil { + log.Errorf("error genesis committing dbTx, err: %v", err) + return nil, rollback(s.ctx, dbTx, err) + } + log.Info("Genesis root matches! Stored genesis blocks.") + return lastEthBlockSynced, nil +} + // Sync function will read the last state synced and will continue from that point. // Sync() will read blockchain events to detect rollup updates func (s *ClientSynchronizer) Sync() error { @@ -248,97 +402,26 @@ func (s *ClientSynchronizer) Sync() error { // If there is no lastEthereumBlock means that sync from the beginning is necessary. If not, it continues from the retrieved ethereum block // Get the latest synced block. If there is no block on db, use genesis block log.Info("Sync started") - dbTx, err := s.state.BeginStateTransaction(s.ctx) + + genesisDone, lastEthBlockSynced, err := s.isGenesisProcessed(s.ctx, nil) if err != nil { - log.Errorf("error creating db transaction to get latest block. Error: %v", err) + log.Errorf("error checking if genesis is processed. Error: %v", err) return err } - lastEthBlockSynced, err := s.state.GetLastBlock(s.ctx, dbTx) - if err != nil { - if errors.Is(err, state.ErrStateNotSynchronized) { - log.Info("State is empty, verifying genesis block") - valid, err := s.etherMan.VerifyGenBlockNumber(s.ctx, s.genesis.RollupBlockNumber) - if err != nil { - log.Error("error checking genesis block number. Error: ", err) - return rollback(s.ctx, dbTx, err) - } else if !valid { - log.Error("genesis Block number configured is not valid. It is required the block number where the PolygonZkEVM smc was deployed") - return rollback(s.ctx, dbTx, fmt.Errorf("genesis Block number configured is not valid. It is required the block number where the PolygonZkEVM smc was deployed")) - } - - // Sync events from RollupManager that happen before rollup creation - log.Info("synchronizing events from RollupManager that happen before rollup creation") - for i := s.genesis.RollupManagerBlockNumber; true; i += s.cfg.SyncChunkSize { - toBlock := min(i+s.cfg.SyncChunkSize-1, s.genesis.RollupBlockNumber-1) - blocks, order, err := s.etherMan.GetRollupInfoByBlockRange(s.ctx, i, &toBlock) - if err != nil { - log.Error("error getting rollupInfoByBlockRange before rollup genesis: ", err) - rollbackErr := dbTx.Rollback(s.ctx) - if rollbackErr != nil { - log.Errorf("error rolling back state. RollbackErr: %v, err: %s", rollbackErr, err.Error()) - return rollbackErr - } - return err - } - err = s.ProcessBlockRange(blocks, order) - if err != nil { - log.Error("error processing blocks before the genesis: ", err) - rollbackErr := dbTx.Rollback(s.ctx) - if rollbackErr != nil { - log.Errorf("error rolling back state. RollbackErr: %v, err: %s", rollbackErr, err.Error()) - return rollbackErr - } - return err - } - if toBlock == s.genesis.RollupBlockNumber-1 { - break - } - } - - header, err := s.etherMan.HeaderByNumber(s.ctx, big.NewInt(0).SetUint64(s.genesis.RollupBlockNumber)) - if err != nil { - log.Errorf("error getting l1 block header for block %d. Error: %v", s.genesis.RollupBlockNumber, err) - return rollback(s.ctx, dbTx, err) - } - log.Info("synchronizing rollup creation block") - lastEthBlockSynced = &state.Block{ - BlockNumber: header.Number.Uint64(), - BlockHash: header.Hash(), - ParentHash: header.ParentHash, - ReceivedAt: time.Unix(int64(header.Time), 0), - } - genesisRoot, err := s.state.SetGenesis(s.ctx, *lastEthBlockSynced, s.genesis, stateMetrics.SynchronizerCallerLabel, dbTx) - if err != nil { - log.Error("error setting genesis: ", err) - return rollback(s.ctx, dbTx, err) - } - err = s.RequestAndProcessRollupGenesisBlock(dbTx, lastEthBlockSynced) - if err != nil { - log.Error("error processing Rollup genesis block: ", err) - return rollback(s.ctx, dbTx, err) - } - - if genesisRoot != s.genesis.Root { - log.Errorf("Calculated newRoot should be %s instead of %s", s.genesis.Root.String(), genesisRoot.String()) - return rollback(s.ctx, dbTx, 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) - if err != nil { - log.Error("error checking genesis flushID: ", err) - return rollback(s.ctx, dbTx, err) - } - log.Debug("Genesis root matches!") - } else { - log.Error("unexpected error getting the latest ethereum block. Error: ", err) - rollbackErr := dbTx.Rollback(s.ctx) - if rollbackErr != nil { - log.Errorf("error rolling back state. RollbackErr: %v, err: %s", rollbackErr, err.Error()) - return rollbackErr - } + if !genesisDone { + lastEthBlockSynced, err = s.processGenesis() + if err != nil { + log.Errorf("error processing genesis. Error: %v", err) return err } } + + dbTx, err := s.state.BeginStateTransaction(s.ctx) + if err != nil { + log.Errorf("error creating db transaction to get latest block. Error: %v", err) + return err + } + initBatchNumber, err := s.state.GetLastBatchNumber(s.ctx, dbTx) if err != nil { log.Error("error getting latest batchNumber synced. Error: ", err) diff --git a/synchronizer/synchronizer_test.go b/synchronizer/synchronizer_test.go index d5c40b2abf..1c1a9fdc82 100644 --- a/synchronizer/synchronizer_test.go +++ b/synchronizer/synchronizer_test.go @@ -120,7 +120,7 @@ func TestGivenPermissionlessNodeWhenSyncronizeFirstTimeABatchThenStoreItInALocal // but it used a feature that is not implemented in new one that is asking beyond the last block on L1 func TestForcedBatchEtrog(t *testing.T) { genesis := state.Genesis{ - RollupBlockNumber: uint64(123456), + RollupBlockNumber: uint64(0), } cfg := Config{ SyncInterval: cfgTypes.Duration{Duration: 1 * time.Second}, @@ -147,7 +147,15 @@ func TestForcedBatchEtrog(t *testing.T) { ToBatchNumber: ^uint64(0), } m.State.EXPECT().GetForkIDInMemory(uint64(7)).Return(&forkIdInterval) + parentHash := common.HexToHash("0x111") + ethHeader := ðTypes.Header{Number: big.NewInt(1), ParentHash: parentHash} + ethBlock := ethTypes.NewBlockWithHeader(ethHeader) + lastBlock := &state.Block{BlockHash: ethBlock.Hash(), BlockNumber: ethBlock.Number().Uint64(), ParentHash: ethBlock.ParentHash()} + m.State. + On("GetLastBlock", mock.Anything, nil). + Return(lastBlock, nil). + Once() m.State. On("BeginStateTransaction", ctxMatchBy). Run(func(args mock.Arguments) { @@ -155,16 +163,17 @@ func TestForcedBatchEtrog(t *testing.T) { parentHash := common.HexToHash("0x111") ethHeader := ðTypes.Header{Number: big.NewInt(1), ParentHash: parentHash} ethBlock := ethTypes.NewBlockWithHeader(ethHeader) - lastBlock := &state.Block{BlockHash: ethBlock.Hash(), BlockNumber: ethBlock.Number().Uint64()} + lastBlock := &state.Block{BlockHash: ethBlock.Hash(), BlockNumber: ethBlock.Number().Uint64(), ParentHash: ethBlock.ParentHash()} m.State. On("GetForkIDByBatchNumber", mock.Anything). Return(uint64(7), nil). Maybe() + m.State. On("GetLastBlock", ctx, m.DbTx). Return(lastBlock, nil). - Once() + Maybe() m.State. On("GetLastBatchNumber", ctx, m.DbTx). @@ -377,7 +386,7 @@ func TestForcedBatchEtrog(t *testing.T) { // but it used a feature that is not implemented in new one that is asking beyond the last block on L1 func TestSequenceForcedBatchIncaberry(t *testing.T) { genesis := state.Genesis{ - RollupBlockNumber: uint64(123456), + RollupBlockNumber: uint64(0), } cfg := Config{ SyncInterval: cfgTypes.Duration{Duration: 1 * time.Second}, @@ -396,7 +405,14 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { ethermanForL1 := []syncinterfaces.EthermanFullInterface{m.Etherman} sync, err := NewSynchronizer(true, m.Etherman, ethermanForL1, m.State, m.Pool, m.EthTxManager, m.ZKEVMClient, nil, genesis, cfg, false) require.NoError(t, err) - + parentHash := common.HexToHash("0x111") + ethHeader := ðTypes.Header{Number: big.NewInt(1), ParentHash: parentHash} + ethBlock := ethTypes.NewBlockWithHeader(ethHeader) + lastBlock := &state.Block{BlockHash: ethBlock.Hash(), BlockNumber: ethBlock.Number().Uint64(), ParentHash: ethBlock.ParentHash()} + m.State. + On("GetLastBlock", mock.Anything, nil). + Return(lastBlock, nil). + Once() // state preparation ctxMatchBy := mock.MatchedBy(func(ctx context.Context) bool { return ctx != nil }) m.State. @@ -406,7 +422,7 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { parentHash := common.HexToHash("0x111") ethHeader := ðTypes.Header{Number: big.NewInt(1), ParentHash: parentHash} ethBlock := ethTypes.NewBlockWithHeader(ethHeader) - lastBlock := &state.Block{BlockHash: ethBlock.Hash(), BlockNumber: ethBlock.Number().Uint64()} + lastBlock := &state.Block{BlockHash: ethBlock.Hash(), BlockNumber: ethBlock.Number().Uint64(), ParentHash: ethBlock.ParentHash()} m.State. On("GetForkIDByBatchNumber", mock.Anything). Return(uint64(1), nil). @@ -416,11 +432,6 @@ func TestSequenceForcedBatchIncaberry(t *testing.T) { Return(uint64(1), nil). Maybe() - m.State. - On("GetLastBlock", ctx, m.DbTx). - Return(lastBlock, nil). - Once() - m.State. On("GetLastBatchNumber", ctx, m.DbTx). Return(uint64(10), nil). diff --git a/test/Makefile b/test/Makefile index ebe52591e2..fbbef14a4b 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,4 +1,4 @@ -DOCKERCOMPOSE := docker-compose -f docker-compose.yml +DOCKERCOMPOSE := docker compose -f docker-compose.yml DOCKERCOMPOSEAPPSEQ := zkevm-sequencer DOCKERCOMPOSEAPPSEQV1TOV2 := zkevm-sequencer-v1tov2 DOCKERCOMPOSEAPPSEQSENDER := zkevm-sequence-sender @@ -113,8 +113,8 @@ STOPMETRICS := $(DOCKERCOMPOSE) stop $(DOCKERCOMPOSEMETRICS) && $(DOCKERCOMPOSE) STOP := $(DOCKERCOMPOSE) down --remove-orphans -RUNDACDB := docker-compose up -d zkevm-data-node-db -STOPDACDB := docker-compose stop zkevm-data-node-db && docker-compose rm -f zkevm-data-node-db +RUNDACDB := docker compose up -d zkevm-data-node-db +STOPDACDB := docker compose stop zkevm-data-node-db && docker compose rm -f zkevm-data-node-db .PHONY: test-full-non-e2e test-full-non-e2e: stop ## Runs non-e2e tests checking race conditions