From c4c790f5f6cf1e3d9e288aefcdd8c96b26f5cff3 Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:37:09 +0200 Subject: [PATCH] feat: Use InitL1InfoRootMap (#87) - Fix l1infotreesync. Error UNIQUE constraint failed: rollup_exit_root.hash on VerifyBatches Event - l1infotreesync: Add verification for contract address. The problem is that if you set bad address cdk run normally but you don't get any information about L1InfoTree. - l1infotreesync: Add support to `InitL1InfoRootMap` - Allow to use `InitL1InfoRootMap` if there are no leaves on L1InfoTree Internal: - Fix local config file generation for debug on vscode (`./scripts/local_config`) - Add support to `contractVersions` - Remove param `-custom-network-file` that is no longer used - Refactor `l1infotreesync/processor.go` in multiples files - Change some tree functions to use a `tx db.Querier` instead of `ctx context.Context`: context was not used to do DB query was using `db` directly. In some test I need to query over current tx --- .github/workflows/test-e2e.yml | 1 + bridgesync/mock_l2_test.go | 24 +- cmd/run.go | 3 +- config/default.go | 22 +- l1infotreesync/downloader.go | 62 +- l1infotreesync/downloader_test.go | 55 + l1infotreesync/e2e_test.go | 11 +- l1infotreesync/l1infotreesync.go | 37 +- .../migrations/l1infotreesync0002.sql | 14 + l1infotreesync/migrations/migrations.go | 7 + l1infotreesync/mock_reorgdetector_test.go | 2 +- l1infotreesync/mocks/eth_clienter.go | 1086 +++++++++++++++++ l1infotreesync/processor.go | 116 +- l1infotreesync/processor_initl1inforootmap.go | 37 + .../processor_initl1inforootmap_test.go | 67 + l1infotreesync/processor_test.go | 79 +- l1infotreesync/processor_verifybatches.go | 104 ++ .../processor_verifybatches_test.go | 127 ++ reorgdetector/mock_eth_client.go | 2 +- scripts/local_config | 45 +- sequencesender/txbuilder/banana_base.go | 93 +- sequencesender/txbuilder/banana_base_test.go | 40 + .../txbuilder/banana_validium_test.go | 4 + sequencesender/txbuilder/banana_zkevm_test.go | 4 + .../mocks_txbuilder/l1_info_syncer.go | 58 + sonar-project.properties | 4 +- sync/mock_l2_test.go | 2 +- sync/mock_processor_test.go | 2 +- sync/mock_reorgdetector_test.go | 2 +- test/Makefile | 35 +- test/config/test.kurtosis_template.toml | 29 +- test/helpers/aggoracle_e2e.go | 3 +- test/helpers/mock_ethtxmanager.go | 2 +- tree/tree.go | 11 +- tree/tree_test.go | 20 +- tree/updatabletree_test.go | 49 + 36 files changed, 2050 insertions(+), 209 deletions(-) create mode 100644 l1infotreesync/downloader_test.go create mode 100644 l1infotreesync/migrations/l1infotreesync0002.sql create mode 100644 l1infotreesync/mocks/eth_clienter.go create mode 100644 l1infotreesync/processor_initl1inforootmap.go create mode 100644 l1infotreesync/processor_initl1inforootmap_test.go create mode 100644 l1infotreesync/processor_verifybatches.go create mode 100644 l1infotreesync/processor_verifybatches_test.go create mode 100644 tree/updatabletree_test.go diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 721cbf09..c89275c7 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -65,6 +65,7 @@ jobs: uses: actions/checkout@v4 with: repository: 0xPolygon/kurtosis-cdk + ref: fix/missing_cdk_config_rollupmanager path: "kurtosis-cdk" ref: "v0.2.11" diff --git a/bridgesync/mock_l2_test.go b/bridgesync/mock_l2_test.go index a8f33ef8..adbff51f 100644 --- a/bridgesync/mock_l2_test.go +++ b/bridgesync/mock_l2_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.39.0. DO NOT EDIT. package bridgesync @@ -12,6 +12,8 @@ import ( mock "github.com/stretchr/testify/mock" + rpc "github.com/ethereum/go-ethereum/rpc" + types "github.com/ethereum/go-ethereum/core/types" ) @@ -138,6 +140,26 @@ func (_m *L2Mock) CallContract(ctx context.Context, call ethereum.CallMsg, block return r0, r1 } +// Client provides a mock function with given fields: +func (_m *L2Mock) Client() *rpc.Client { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Client") + } + + var r0 *rpc.Client + if rf, ok := ret.Get(0).(func() *rpc.Client); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*rpc.Client) + } + } + + return r0 +} + // CodeAt provides a mock function with given fields: ctx, contract, blockNumber func (_m *L2Mock) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { ret := _m.Called(ctx, contract, blockNumber) diff --git a/cmd/run.go b/cmd/run.go index 0b744243..68f4acdd 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -492,6 +492,7 @@ func runL1InfoTreeSyncerIfNeeded( cfg.L1InfoTreeSync.InitialBlock, cfg.L1InfoTreeSync.RetryAfterErrorPeriod.Duration, cfg.L1InfoTreeSync.MaxRetryAttemptsAfterError, + l1infotreesync.FlagNone, ) if err != nil { log.Fatal(err) @@ -511,7 +512,7 @@ func runL1ClientIfNeeded(components []string, urlRPCL1 string) *ethclient.Client log.Debugf("dialing L1 client at: %s", urlRPCL1) l1CLient, err := ethclient.Dial(urlRPCL1) if err != nil { - log.Fatal(err) + log.Fatalf("failed to create client for L1 using URL: %s. Err:%v", urlRPCL1, err) } return l1CLient diff --git a/config/default.go b/config/default.go index 377e9033..e02a37ac 100644 --- a/config/default.go +++ b/config/default.go @@ -5,6 +5,18 @@ const DefaultValues = ` ForkUpgradeBatchNumber = 0 ForkUpgradeNewForkId = 0 +[Etherman] + URL="http://localhost:8545" + ForkIDChunkSize=100 + [Etherman.EthermanConfig] + URL="http://localhost:8545" + MultiGasProvider=false + L1ChainID=1337 + HTTPHeaders=[] + [Etherman.EthermanConfig.Etherscan] + ApiKey="" + Url="https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=" + [Common] NetworkID = 1 IsValidiumMode = false @@ -141,7 +153,7 @@ DBPath = "/tmp/reorgdetectorl1" DBPath = "/tmp/reorgdetectorl2" [L1InfoTreeSync] -DBPath = "/tmp/L1InfoTreeSync" +DBPath = "/tmp/L1InfoTreeSync.sqlite" GlobalExitRootAddr="0x8464135c8F25Da09e49BC8782676a84730C318bC" SyncBlockChunkSize=10 BlockFinality="LatestBlock" @@ -250,4 +262,12 @@ RetryAfterErrorPeriod = "1s" MaxRetryAttemptsAfterError = -1 WaitForNewBlocksPeriod = "1s" DownloadBufferSize = 100 + +[NetworkConfig.L1] +L1ChainID = 0 +PolAddr = "0x0000000000000000000000000000000000000000" +ZkEVMAddr = "0x0000000000000000000000000000000000000000" +RollupManagerAddr = "0x0000000000000000000000000000000000000000" +GlobalExitRootManagerAddr = "0x0000000000000000000000000000000000000000" + ` diff --git a/l1infotreesync/downloader.go b/l1infotreesync/downloader.go index 16ccb37a..ed3c7efb 100644 --- a/l1infotreesync/downloader.go +++ b/l1infotreesync/downloader.go @@ -33,15 +33,68 @@ type EthClienter interface { bind.ContractBackend } -func buildAppender(client EthClienter, globalExitRoot, rollupManager common.Address) (sync.LogAppenderMap, error) { - ger, err := polygonzkevmglobalexitrootv2.NewPolygonzkevmglobalexitrootv2(globalExitRoot, client) +func checkSMCIsRollupManager(rollupManagerAddr common.Address, + rollupManagerContract *polygonrollupmanager.Polygonrollupmanager) error { + bridgeAddr, err := rollupManagerContract.BridgeAddress(nil) if err != nil { - return nil, err + return fmt.Errorf("fail sanity check RollupManager(%s) Contract. Err: %w", rollupManagerAddr.String(), err) + } + log.Infof("sanity check rollupManager(%s) OK. bridgeAddr: %s", rollupManagerAddr.String(), bridgeAddr.String()) + return nil +} + +func checkSMCIsGlobalExitRoot(globalExitRootAddr common.Address, + gerContract *polygonzkevmglobalexitrootv2.Polygonzkevmglobalexitrootv2) error { + depositCount, err := gerContract.DepositCount(nil) + if err != nil { + return fmt.Errorf("fail sanity check GlobalExitRoot(%s) Contract. Err: %w", globalExitRootAddr.String(), err) } - rm, err := polygonrollupmanager.NewPolygonrollupmanager(rollupManager, client) + log.Infof("sanity check GlobalExitRoot(%s) OK. DepositCount: %v", globalExitRootAddr.String(), depositCount) + return nil +} + +func sanityCheckContracts(globalExitRoot, rollupManager common.Address, + gerContract *polygonzkevmglobalexitrootv2.Polygonzkevmglobalexitrootv2, + rollupManagerContract *polygonrollupmanager.Polygonrollupmanager) error { + errGER := checkSMCIsGlobalExitRoot(globalExitRoot, gerContract) + errRollup := checkSMCIsRollupManager(rollupManager, rollupManagerContract) + if errGER != nil || errRollup != nil { + err := fmt.Errorf("sanityCheckContracts: fails sanity check contracts. ErrGER: %w, ErrRollup: %w", errGER, errRollup) + log.Error(err) + return err + } + return nil +} + +func createContracts(client EthClienter, globalExitRoot, rollupManager common.Address) ( + *polygonzkevmglobalexitrootv2.Polygonzkevmglobalexitrootv2, + *polygonrollupmanager.Polygonrollupmanager, + error) { + gerContract, err := polygonzkevmglobalexitrootv2.NewPolygonzkevmglobalexitrootv2(globalExitRoot, client) if err != nil { + return nil, nil, err + } + + rollupManagerContract, err := polygonrollupmanager.NewPolygonrollupmanager(rollupManager, client) + if err != nil { + return nil, nil, err + } + return gerContract, rollupManagerContract, nil +} + +func buildAppender(client EthClienter, globalExitRoot, + rollupManager common.Address, flags CreationFlags) (sync.LogAppenderMap, error) { + ger, rm, err := createContracts(client, globalExitRoot, rollupManager) + if err != nil { + err := fmt.Errorf("buildAppender: fails contracts creation. Err:%w", err) + log.Error(err) return nil, err } + err = sanityCheckContracts(globalExitRoot, rollupManager, ger, rm) + if err != nil && flags&FlagAllowWrongContractsAddrs == 0 { + return nil, fmt.Errorf("buildAppender: fails sanity check contracts. Err:%w", err) + } + appender := make(sync.LogAppenderMap) appender[initL1InfoRootMapSignature] = func(b *sync.EVMBlock, l types.Log) error { init, err := ger.ParseInitL1InfoRootMap(l) @@ -91,6 +144,7 @@ func buildAppender(client EthClienter, globalExitRoot, rollupManager common.Addr return nil } + // This event is coming from RollupManager appender[verifyBatchesSignature] = func(b *sync.EVMBlock, l types.Log) error { verifyBatches, err := rm.ParseVerifyBatches(l) if err != nil { diff --git a/l1infotreesync/downloader_test.go b/l1infotreesync/downloader_test.go new file mode 100644 index 00000000..6007a3d6 --- /dev/null +++ b/l1infotreesync/downloader_test.go @@ -0,0 +1,55 @@ +package l1infotreesync + +import ( + "fmt" + "math/big" + "strings" + "testing" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/banana/polygonzkevmglobalexitrootv2" + mocks_l1infotreesync "github.com/0xPolygon/cdk/l1infotreesync/mocks" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestBuildAppenderErrorOnBadContractAddr(t *testing.T) { + l1Client := mocks_l1infotreesync.NewEthClienter(t) + globalExitRoot := common.HexToAddress("0x1") + rollupManager := common.HexToAddress("0x2") + l1Client.EXPECT().CallContract(mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("test-error")) + flags := FlagNone + _, err := buildAppender(l1Client, globalExitRoot, rollupManager, flags) + require.Error(t, err) +} + +func TestBuildAppenderBypassBadContractAddr(t *testing.T) { + l1Client := mocks_l1infotreesync.NewEthClienter(t) + globalExitRoot := common.HexToAddress("0x1") + rollupManager := common.HexToAddress("0x2") + l1Client.EXPECT().CallContract(mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("test-error")) + flags := FlagAllowWrongContractsAddrs + _, err := buildAppender(l1Client, globalExitRoot, rollupManager, flags) + require.NoError(t, err) +} + +func TestBuildAppenderVerifiedContractAddr(t *testing.T) { + l1Client := mocks_l1infotreesync.NewEthClienter(t) + globalExitRoot := common.HexToAddress("0x1") + rollupManager := common.HexToAddress("0x2") + + smcAbi, err := abi.JSON(strings.NewReader(polygonzkevmglobalexitrootv2.Polygonzkevmglobalexitrootv2ABI)) + require.NoError(t, err) + bigInt := big.NewInt(1) + returnGER, err := smcAbi.Methods["depositCount"].Outputs.Pack(bigInt) + require.NoError(t, err) + l1Client.EXPECT().CallContract(mock.Anything, mock.Anything, mock.Anything).Return(returnGER, nil).Once() + v := common.HexToAddress("0x1234") + returnRM, err := smcAbi.Methods["bridgeAddress"].Outputs.Pack(v) + require.NoError(t, err) + l1Client.EXPECT().CallContract(mock.Anything, mock.Anything, mock.Anything).Return(returnRM, nil).Once() + flags := FlagNone + _, err = buildAppender(l1Client, globalExitRoot, rollupManager, flags) + require.NoError(t, err) +} diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index 21820059..c522c73a 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -80,7 +80,8 @@ func TestE2E(t *testing.T) { rdm.On("AddBlockToTrack", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) client, gerAddr, verifyAddr, gerSc, verifySC, err := newSimulatedClient(auth) require.NoError(t, err) - syncer, err := l1infotreesync.New(ctx, dbPath, gerAddr, verifyAddr, 10, etherman.LatestBlock, rdm, client.Client(), time.Millisecond, 0, 100*time.Millisecond, 3) + syncer, err := l1infotreesync.New(ctx, dbPath, gerAddr, verifyAddr, 10, etherman.LatestBlock, rdm, client.Client(), time.Millisecond, 0, 100*time.Millisecond, 3, + l1infotreesync.FlagAllowWrongContractsAddrs) require.NoError(t, err) go syncer.Start(ctx) @@ -173,7 +174,8 @@ func TestWithReorgs(t *testing.T) { rd, err := reorgdetector.New(client.Client(), reorgdetector.Config{DBPath: dbPathReorg, CheckReorgsInterval: cdktypes.NewDuration(time.Millisecond * 30)}) require.NoError(t, err) require.NoError(t, rd.Start(ctx)) - syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerAddr, verifyAddr, 10, etherman.LatestBlock, rd, client.Client(), time.Millisecond, 0, time.Second, 25) + syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerAddr, verifyAddr, 10, etherman.LatestBlock, rd, client.Client(), time.Millisecond, 0, time.Second, 25, + l1infotreesync.FlagAllowWrongContractsAddrs) require.NoError(t, err) go syncer.Start(ctx) @@ -281,7 +283,7 @@ func TestStressAndReorgs(t *testing.T) { ) ctx := context.Background() - dbPathSyncer := path.Join(t.TempDir(), "file::memory:?cache=shared") + dbPathSyncer := path.Join(t.TempDir(), "file:TestStressAndReorgs:memory:?cache=shared") dbPathReorg := t.TempDir() privateKey, err := crypto.GenerateKey() require.NoError(t, err) @@ -292,7 +294,8 @@ func TestStressAndReorgs(t *testing.T) { rd, err := reorgdetector.New(client.Client(), reorgdetector.Config{DBPath: dbPathReorg, CheckReorgsInterval: cdktypes.NewDuration(time.Millisecond * 100)}) require.NoError(t, err) require.NoError(t, rd.Start(ctx)) - syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerAddr, verifyAddr, 10, etherman.LatestBlock, rd, client.Client(), time.Millisecond, 0, time.Second, 100) + syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerAddr, verifyAddr, 10, etherman.LatestBlock, rd, client.Client(), time.Millisecond, 0, time.Second, 100, + l1infotreesync.FlagAllowWrongContractsAddrs) require.NoError(t, err) go syncer.Start(ctx) diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index 4c4b796e..a7e50128 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -5,6 +5,7 @@ import ( "errors" "time" + "github.com/0xPolygon/cdk/db" "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/sync" "github.com/0xPolygon/cdk/tree" @@ -12,9 +13,18 @@ import ( "github.com/ethereum/go-ethereum/common" ) +type CreationFlags uint64 + const ( reorgDetectorID = "l1infotreesync" downloadBufferSize = 1000 + // CreationFlags defitinion + FlagNone CreationFlags = 0 + FlagAllowWrongContractsAddrs CreationFlags = 1 << iota // Allow to set wrong contracts addresses +) + +var ( + ErrNotFound = errors.New("l1infotreesync: not found") ) type L1InfoTreeSync struct { @@ -36,6 +46,7 @@ func New( initialBlock uint64, retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, + flags CreationFlags, ) (*L1InfoTreeSync, error) { processor, err := newProcessor(dbPath) if err != nil { @@ -59,7 +70,7 @@ func New( MaxRetryAttemptsAfterError: maxRetryAttemptsAfterError, } - appender, err := buildAppender(l1Client, globalExitRoot, rollupManager) + appender, err := buildAppender(l1Client, globalExitRoot, rollupManager, flags) if err != nil { return nil, err } @@ -111,10 +122,21 @@ func (s *L1InfoTreeSync) GetRollupExitTreeMerkleProof( return s.processor.rollupExitTree.GetProof(ctx, networkID-1, root) } +func translateError(err error) error { + if errors.Is(err, db.ErrNotFound) { + return ErrNotFound + } + return err +} + // GetLatestInfoUntilBlock returns the most recent L1InfoTreeLeaf that occurred before or at blockNum. // If the blockNum has not been processed yet the error ErrBlockNotProcessed will be returned +// It can returns next errors: +// - ErrBlockNotProcessed, +// - ErrNotFound func (s *L1InfoTreeSync) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { - return s.processor.GetLatestInfoUntilBlock(ctx, blockNum) + leaf, err := s.processor.GetLatestInfoUntilBlock(ctx, blockNum) + return leaf, translateError(err) } // GetInfoByIndex returns the value of a leaf (not the hash) of the L1 info tree @@ -129,12 +151,12 @@ func (s *L1InfoTreeSync) GetL1InfoTreeRootByIndex(ctx context.Context, index uin // GetLastRollupExitRoot return the last rollup exit root processed func (s *L1InfoTreeSync) GetLastRollupExitRoot(ctx context.Context) (types.Root, error) { - return s.processor.rollupExitTree.GetLastRoot(ctx) + return s.processor.rollupExitTree.GetLastRoot(nil) } // GetLastL1InfoTreeRoot return the last root and index processed from the L1 Info tree func (s *L1InfoTreeSync) GetLastL1InfoTreeRoot(ctx context.Context) (types.Root, error) { - return s.processor.l1InfoTree.GetLastRoot(ctx) + return s.processor.l1InfoTree.GetLastRoot(nil) } // GetLastProcessedBlock return the last processed block @@ -149,7 +171,7 @@ func (s *L1InfoTreeSync) GetLocalExitRoot( return common.Hash{}, errors.New("network 0 is not a rollup, and it's not part of the rollup exit tree") } - return s.processor.rollupExitTree.GetLeaf(ctx, networkID-1, rollupExitRoot) + return s.processor.rollupExitTree.GetLeaf(nil, networkID-1, rollupExitRoot) } func (s *L1InfoTreeSync) GetLastVerifiedBatches(rollupID uint32) (*VerifyBatches, error) { @@ -190,3 +212,8 @@ func (s *L1InfoTreeSync) GetL1InfoTreeMerkleProofFromIndexToRoot( ) (types.Proof, error) { return s.processor.l1InfoTree.GetProof(ctx, index, root) } + +// GetInitL1InfoRootMap returns the initial L1 info root map, nil if no root map has been set +func (s *L1InfoTreeSync) GetInitL1InfoRootMap(ctx context.Context) (*L1InfoTreeInitial, error) { + return s.processor.GetInitL1InfoRootMap(nil) +} diff --git a/l1infotreesync/migrations/l1infotreesync0002.sql b/l1infotreesync/migrations/l1infotreesync0002.sql new file mode 100644 index 00000000..d1f09481 --- /dev/null +++ b/l1infotreesync/migrations/l1infotreesync0002.sql @@ -0,0 +1,14 @@ +-- +migrate Down +DROP TABLE IF EXISTS l1info_initial; + +-- +migrate Up + +CREATE TABLE l1info_initial ( + -- single_row_id prevent to have more than 1 row in this table + single_row_id INTEGER check(single_row_id=1) NOT NULL DEFAULT 1, + block_num INTEGER NOT NULL REFERENCES block(num) ON DELETE CASCADE, + leaf_count INTEGER NOT NULL, + l1_info_root VARCHAR NOT NULL, + PRIMARY KEY (single_row_id) +); + diff --git a/l1infotreesync/migrations/migrations.go b/l1infotreesync/migrations/migrations.go index 768dde37..47fac070 100644 --- a/l1infotreesync/migrations/migrations.go +++ b/l1infotreesync/migrations/migrations.go @@ -16,12 +16,19 @@ const ( //go:embed l1infotreesync0001.sql var mig001 string +//go:embed l1infotreesync0002.sql +var mig002 string + func RunMigrations(dbPath string) error { migrations := []types.Migration{ { ID: "l1infotreesync0001", SQL: mig001, }, + { + ID: "l1infotreesync0002", + SQL: mig002, + }, } for _, tm := range treeMigrations.Migrations { migrations = append(migrations, types.Migration{ diff --git a/l1infotreesync/mock_reorgdetector_test.go b/l1infotreesync/mock_reorgdetector_test.go index 8255443e..18ac7bc8 100644 --- a/l1infotreesync/mock_reorgdetector_test.go +++ b/l1infotreesync/mock_reorgdetector_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.39.0. DO NOT EDIT. package l1infotreesync diff --git a/l1infotreesync/mocks/eth_clienter.go b/l1infotreesync/mocks/eth_clienter.go new file mode 100644 index 00000000..270c40d9 --- /dev/null +++ b/l1infotreesync/mocks/eth_clienter.go @@ -0,0 +1,1086 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks_l1infotreesync + +import ( + context "context" + big "math/big" + + common "github.com/ethereum/go-ethereum/common" + + ethereum "github.com/ethereum/go-ethereum" + + mock "github.com/stretchr/testify/mock" + + types "github.com/ethereum/go-ethereum/core/types" +) + +// EthClienter is an autogenerated mock type for the EthClienter type +type EthClienter struct { + mock.Mock +} + +type EthClienter_Expecter struct { + mock *mock.Mock +} + +func (_m *EthClienter) EXPECT() *EthClienter_Expecter { + return &EthClienter_Expecter{mock: &_m.Mock} +} + +// BlockByHash provides a mock function with given fields: ctx, hash +func (_m *EthClienter) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + ret := _m.Called(ctx, hash) + + if len(ret) == 0 { + panic("no return value specified for BlockByHash") + } + + var r0 *types.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (*types.Block, error)); ok { + return rf(ctx, hash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) *types.Block); ok { + r0 = rf(ctx, hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_BlockByHash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockByHash' +type EthClienter_BlockByHash_Call struct { + *mock.Call +} + +// BlockByHash is a helper method to define mock.On call +// - ctx context.Context +// - hash common.Hash +func (_e *EthClienter_Expecter) BlockByHash(ctx interface{}, hash interface{}) *EthClienter_BlockByHash_Call { + return &EthClienter_BlockByHash_Call{Call: _e.mock.On("BlockByHash", ctx, hash)} +} + +func (_c *EthClienter_BlockByHash_Call) Run(run func(ctx context.Context, hash common.Hash)) *EthClienter_BlockByHash_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *EthClienter_BlockByHash_Call) Return(_a0 *types.Block, _a1 error) *EthClienter_BlockByHash_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_BlockByHash_Call) RunAndReturn(run func(context.Context, common.Hash) (*types.Block, error)) *EthClienter_BlockByHash_Call { + _c.Call.Return(run) + return _c +} + +// BlockByNumber provides a mock function with given fields: ctx, number +func (_m *EthClienter) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + ret := _m.Called(ctx, number) + + if len(ret) == 0 { + panic("no return value specified for BlockByNumber") + } + + var r0 *types.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*types.Block, error)); ok { + return rf(ctx, number) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *types.Block); ok { + r0 = rf(ctx, number) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, number) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_BlockByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockByNumber' +type EthClienter_BlockByNumber_Call struct { + *mock.Call +} + +// BlockByNumber is a helper method to define mock.On call +// - ctx context.Context +// - number *big.Int +func (_e *EthClienter_Expecter) BlockByNumber(ctx interface{}, number interface{}) *EthClienter_BlockByNumber_Call { + return &EthClienter_BlockByNumber_Call{Call: _e.mock.On("BlockByNumber", ctx, number)} +} + +func (_c *EthClienter_BlockByNumber_Call) Run(run func(ctx context.Context, number *big.Int)) *EthClienter_BlockByNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*big.Int)) + }) + return _c +} + +func (_c *EthClienter_BlockByNumber_Call) Return(_a0 *types.Block, _a1 error) *EthClienter_BlockByNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_BlockByNumber_Call) RunAndReturn(run func(context.Context, *big.Int) (*types.Block, error)) *EthClienter_BlockByNumber_Call { + _c.Call.Return(run) + return _c +} + +// BlockNumber provides a mock function with given fields: ctx +func (_m *EthClienter) BlockNumber(ctx context.Context) (uint64, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for BlockNumber") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (uint64, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) uint64); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_BlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockNumber' +type EthClienter_BlockNumber_Call struct { + *mock.Call +} + +// BlockNumber is a helper method to define mock.On call +// - ctx context.Context +func (_e *EthClienter_Expecter) BlockNumber(ctx interface{}) *EthClienter_BlockNumber_Call { + return &EthClienter_BlockNumber_Call{Call: _e.mock.On("BlockNumber", ctx)} +} + +func (_c *EthClienter_BlockNumber_Call) Run(run func(ctx context.Context)) *EthClienter_BlockNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *EthClienter_BlockNumber_Call) Return(_a0 uint64, _a1 error) *EthClienter_BlockNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_BlockNumber_Call) RunAndReturn(run func(context.Context) (uint64, error)) *EthClienter_BlockNumber_Call { + _c.Call.Return(run) + return _c +} + +// CallContract provides a mock function with given fields: ctx, call, blockNumber +func (_m *EthClienter) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + ret := _m.Called(ctx, call, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for CallContract") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg, *big.Int) ([]byte, error)); ok { + return rf(ctx, call, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg, *big.Int) []byte); ok { + r0 = rf(ctx, call, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg, *big.Int) error); ok { + r1 = rf(ctx, call, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_CallContract_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CallContract' +type EthClienter_CallContract_Call struct { + *mock.Call +} + +// CallContract is a helper method to define mock.On call +// - ctx context.Context +// - call ethereum.CallMsg +// - blockNumber *big.Int +func (_e *EthClienter_Expecter) CallContract(ctx interface{}, call interface{}, blockNumber interface{}) *EthClienter_CallContract_Call { + return &EthClienter_CallContract_Call{Call: _e.mock.On("CallContract", ctx, call, blockNumber)} +} + +func (_c *EthClienter_CallContract_Call) Run(run func(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int)) *EthClienter_CallContract_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.CallMsg), args[2].(*big.Int)) + }) + return _c +} + +func (_c *EthClienter_CallContract_Call) Return(_a0 []byte, _a1 error) *EthClienter_CallContract_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_CallContract_Call) RunAndReturn(run func(context.Context, ethereum.CallMsg, *big.Int) ([]byte, error)) *EthClienter_CallContract_Call { + _c.Call.Return(run) + return _c +} + +// CodeAt provides a mock function with given fields: ctx, contract, blockNumber +func (_m *EthClienter) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + ret := _m.Called(ctx, contract, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for CodeAt") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) ([]byte, error)); ok { + return rf(ctx, contract, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) []byte); ok { + r0 = rf(ctx, contract, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, *big.Int) error); ok { + r1 = rf(ctx, contract, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_CodeAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CodeAt' +type EthClienter_CodeAt_Call struct { + *mock.Call +} + +// CodeAt is a helper method to define mock.On call +// - ctx context.Context +// - contract common.Address +// - blockNumber *big.Int +func (_e *EthClienter_Expecter) CodeAt(ctx interface{}, contract interface{}, blockNumber interface{}) *EthClienter_CodeAt_Call { + return &EthClienter_CodeAt_Call{Call: _e.mock.On("CodeAt", ctx, contract, blockNumber)} +} + +func (_c *EthClienter_CodeAt_Call) Run(run func(ctx context.Context, contract common.Address, blockNumber *big.Int)) *EthClienter_CodeAt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address), args[2].(*big.Int)) + }) + return _c +} + +func (_c *EthClienter_CodeAt_Call) Return(_a0 []byte, _a1 error) *EthClienter_CodeAt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_CodeAt_Call) RunAndReturn(run func(context.Context, common.Address, *big.Int) ([]byte, error)) *EthClienter_CodeAt_Call { + _c.Call.Return(run) + return _c +} + +// EstimateGas provides a mock function with given fields: ctx, call +func (_m *EthClienter) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { + ret := _m.Called(ctx, call) + + if len(ret) == 0 { + panic("no return value specified for EstimateGas") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) (uint64, error)); ok { + return rf(ctx, call) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) uint64); ok { + r0 = rf(ctx, call) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg) error); ok { + r1 = rf(ctx, call) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_EstimateGas_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EstimateGas' +type EthClienter_EstimateGas_Call struct { + *mock.Call +} + +// EstimateGas is a helper method to define mock.On call +// - ctx context.Context +// - call ethereum.CallMsg +func (_e *EthClienter_Expecter) EstimateGas(ctx interface{}, call interface{}) *EthClienter_EstimateGas_Call { + return &EthClienter_EstimateGas_Call{Call: _e.mock.On("EstimateGas", ctx, call)} +} + +func (_c *EthClienter_EstimateGas_Call) Run(run func(ctx context.Context, call ethereum.CallMsg)) *EthClienter_EstimateGas_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.CallMsg)) + }) + return _c +} + +func (_c *EthClienter_EstimateGas_Call) Return(_a0 uint64, _a1 error) *EthClienter_EstimateGas_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_EstimateGas_Call) RunAndReturn(run func(context.Context, ethereum.CallMsg) (uint64, error)) *EthClienter_EstimateGas_Call { + _c.Call.Return(run) + return _c +} + +// FilterLogs provides a mock function with given fields: ctx, q +func (_m *EthClienter) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + ret := _m.Called(ctx, q) + + if len(ret) == 0 { + panic("no return value specified for FilterLogs") + } + + var r0 []types.Log + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery) ([]types.Log, error)); ok { + return rf(ctx, q) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery) []types.Log); ok { + r0 = rf(ctx, q) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Log) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.FilterQuery) error); ok { + r1 = rf(ctx, q) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_FilterLogs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FilterLogs' +type EthClienter_FilterLogs_Call struct { + *mock.Call +} + +// FilterLogs is a helper method to define mock.On call +// - ctx context.Context +// - q ethereum.FilterQuery +func (_e *EthClienter_Expecter) FilterLogs(ctx interface{}, q interface{}) *EthClienter_FilterLogs_Call { + return &EthClienter_FilterLogs_Call{Call: _e.mock.On("FilterLogs", ctx, q)} +} + +func (_c *EthClienter_FilterLogs_Call) Run(run func(ctx context.Context, q ethereum.FilterQuery)) *EthClienter_FilterLogs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.FilterQuery)) + }) + return _c +} + +func (_c *EthClienter_FilterLogs_Call) Return(_a0 []types.Log, _a1 error) *EthClienter_FilterLogs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_FilterLogs_Call) RunAndReturn(run func(context.Context, ethereum.FilterQuery) ([]types.Log, error)) *EthClienter_FilterLogs_Call { + _c.Call.Return(run) + return _c +} + +// HeaderByHash provides a mock function with given fields: ctx, hash +func (_m *EthClienter) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + ret := _m.Called(ctx, hash) + + if len(ret) == 0 { + panic("no return value specified for HeaderByHash") + } + + var r0 *types.Header + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (*types.Header, error)); ok { + return rf(ctx, hash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) *types.Header); ok { + r0 = rf(ctx, hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_HeaderByHash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HeaderByHash' +type EthClienter_HeaderByHash_Call struct { + *mock.Call +} + +// HeaderByHash is a helper method to define mock.On call +// - ctx context.Context +// - hash common.Hash +func (_e *EthClienter_Expecter) HeaderByHash(ctx interface{}, hash interface{}) *EthClienter_HeaderByHash_Call { + return &EthClienter_HeaderByHash_Call{Call: _e.mock.On("HeaderByHash", ctx, hash)} +} + +func (_c *EthClienter_HeaderByHash_Call) Run(run func(ctx context.Context, hash common.Hash)) *EthClienter_HeaderByHash_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *EthClienter_HeaderByHash_Call) Return(_a0 *types.Header, _a1 error) *EthClienter_HeaderByHash_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_HeaderByHash_Call) RunAndReturn(run func(context.Context, common.Hash) (*types.Header, error)) *EthClienter_HeaderByHash_Call { + _c.Call.Return(run) + return _c +} + +// HeaderByNumber provides a mock function with given fields: ctx, number +func (_m *EthClienter) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + ret := _m.Called(ctx, number) + + if len(ret) == 0 { + panic("no return value specified for HeaderByNumber") + } + + var r0 *types.Header + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*types.Header, error)); ok { + return rf(ctx, number) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *types.Header); ok { + r0 = rf(ctx, number) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, number) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_HeaderByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HeaderByNumber' +type EthClienter_HeaderByNumber_Call struct { + *mock.Call +} + +// HeaderByNumber is a helper method to define mock.On call +// - ctx context.Context +// - number *big.Int +func (_e *EthClienter_Expecter) HeaderByNumber(ctx interface{}, number interface{}) *EthClienter_HeaderByNumber_Call { + return &EthClienter_HeaderByNumber_Call{Call: _e.mock.On("HeaderByNumber", ctx, number)} +} + +func (_c *EthClienter_HeaderByNumber_Call) Run(run func(ctx context.Context, number *big.Int)) *EthClienter_HeaderByNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*big.Int)) + }) + return _c +} + +func (_c *EthClienter_HeaderByNumber_Call) Return(_a0 *types.Header, _a1 error) *EthClienter_HeaderByNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_HeaderByNumber_Call) RunAndReturn(run func(context.Context, *big.Int) (*types.Header, error)) *EthClienter_HeaderByNumber_Call { + _c.Call.Return(run) + return _c +} + +// PendingCodeAt provides a mock function with given fields: ctx, account +func (_m *EthClienter) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + ret := _m.Called(ctx, account) + + if len(ret) == 0 { + panic("no return value specified for PendingCodeAt") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address) ([]byte, error)); ok { + return rf(ctx, account) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address) []byte); ok { + r0 = rf(ctx, account) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address) error); ok { + r1 = rf(ctx, account) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_PendingCodeAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PendingCodeAt' +type EthClienter_PendingCodeAt_Call struct { + *mock.Call +} + +// PendingCodeAt is a helper method to define mock.On call +// - ctx context.Context +// - account common.Address +func (_e *EthClienter_Expecter) PendingCodeAt(ctx interface{}, account interface{}) *EthClienter_PendingCodeAt_Call { + return &EthClienter_PendingCodeAt_Call{Call: _e.mock.On("PendingCodeAt", ctx, account)} +} + +func (_c *EthClienter_PendingCodeAt_Call) Run(run func(ctx context.Context, account common.Address)) *EthClienter_PendingCodeAt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address)) + }) + return _c +} + +func (_c *EthClienter_PendingCodeAt_Call) Return(_a0 []byte, _a1 error) *EthClienter_PendingCodeAt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_PendingCodeAt_Call) RunAndReturn(run func(context.Context, common.Address) ([]byte, error)) *EthClienter_PendingCodeAt_Call { + _c.Call.Return(run) + return _c +} + +// PendingNonceAt provides a mock function with given fields: ctx, account +func (_m *EthClienter) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + ret := _m.Called(ctx, account) + + if len(ret) == 0 { + panic("no return value specified for PendingNonceAt") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address) (uint64, error)); ok { + return rf(ctx, account) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address) uint64); ok { + r0 = rf(ctx, account) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address) error); ok { + r1 = rf(ctx, account) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_PendingNonceAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PendingNonceAt' +type EthClienter_PendingNonceAt_Call struct { + *mock.Call +} + +// PendingNonceAt is a helper method to define mock.On call +// - ctx context.Context +// - account common.Address +func (_e *EthClienter_Expecter) PendingNonceAt(ctx interface{}, account interface{}) *EthClienter_PendingNonceAt_Call { + return &EthClienter_PendingNonceAt_Call{Call: _e.mock.On("PendingNonceAt", ctx, account)} +} + +func (_c *EthClienter_PendingNonceAt_Call) Run(run func(ctx context.Context, account common.Address)) *EthClienter_PendingNonceAt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address)) + }) + return _c +} + +func (_c *EthClienter_PendingNonceAt_Call) Return(_a0 uint64, _a1 error) *EthClienter_PendingNonceAt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_PendingNonceAt_Call) RunAndReturn(run func(context.Context, common.Address) (uint64, error)) *EthClienter_PendingNonceAt_Call { + _c.Call.Return(run) + return _c +} + +// SendTransaction provides a mock function with given fields: ctx, tx +func (_m *EthClienter) SendTransaction(ctx context.Context, tx *types.Transaction) error { + ret := _m.Called(ctx, tx) + + if len(ret) == 0 { + panic("no return value specified for SendTransaction") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction) error); ok { + r0 = rf(ctx, tx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// EthClienter_SendTransaction_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendTransaction' +type EthClienter_SendTransaction_Call struct { + *mock.Call +} + +// SendTransaction is a helper method to define mock.On call +// - ctx context.Context +// - tx *types.Transaction +func (_e *EthClienter_Expecter) SendTransaction(ctx interface{}, tx interface{}) *EthClienter_SendTransaction_Call { + return &EthClienter_SendTransaction_Call{Call: _e.mock.On("SendTransaction", ctx, tx)} +} + +func (_c *EthClienter_SendTransaction_Call) Run(run func(ctx context.Context, tx *types.Transaction)) *EthClienter_SendTransaction_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*types.Transaction)) + }) + return _c +} + +func (_c *EthClienter_SendTransaction_Call) Return(_a0 error) *EthClienter_SendTransaction_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EthClienter_SendTransaction_Call) RunAndReturn(run func(context.Context, *types.Transaction) error) *EthClienter_SendTransaction_Call { + _c.Call.Return(run) + return _c +} + +// SubscribeFilterLogs provides a mock function with given fields: ctx, q, ch +func (_m *EthClienter) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + ret := _m.Called(ctx, q, ch) + + if len(ret) == 0 { + panic("no return value specified for SubscribeFilterLogs") + } + + var r0 ethereum.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery, chan<- types.Log) (ethereum.Subscription, error)); ok { + return rf(ctx, q, ch) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery, chan<- types.Log) ethereum.Subscription); ok { + r0 = rf(ctx, q, ch) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(ethereum.Subscription) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.FilterQuery, chan<- types.Log) error); ok { + r1 = rf(ctx, q, ch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_SubscribeFilterLogs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SubscribeFilterLogs' +type EthClienter_SubscribeFilterLogs_Call struct { + *mock.Call +} + +// SubscribeFilterLogs is a helper method to define mock.On call +// - ctx context.Context +// - q ethereum.FilterQuery +// - ch chan<- types.Log +func (_e *EthClienter_Expecter) SubscribeFilterLogs(ctx interface{}, q interface{}, ch interface{}) *EthClienter_SubscribeFilterLogs_Call { + return &EthClienter_SubscribeFilterLogs_Call{Call: _e.mock.On("SubscribeFilterLogs", ctx, q, ch)} +} + +func (_c *EthClienter_SubscribeFilterLogs_Call) Run(run func(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log)) *EthClienter_SubscribeFilterLogs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.FilterQuery), args[2].(chan<- types.Log)) + }) + return _c +} + +func (_c *EthClienter_SubscribeFilterLogs_Call) Return(_a0 ethereum.Subscription, _a1 error) *EthClienter_SubscribeFilterLogs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_SubscribeFilterLogs_Call) RunAndReturn(run func(context.Context, ethereum.FilterQuery, chan<- types.Log) (ethereum.Subscription, error)) *EthClienter_SubscribeFilterLogs_Call { + _c.Call.Return(run) + return _c +} + +// SubscribeNewHead provides a mock function with given fields: ctx, ch +func (_m *EthClienter) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { + ret := _m.Called(ctx, ch) + + if len(ret) == 0 { + panic("no return value specified for SubscribeNewHead") + } + + var r0 ethereum.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, chan<- *types.Header) (ethereum.Subscription, error)); ok { + return rf(ctx, ch) + } + if rf, ok := ret.Get(0).(func(context.Context, chan<- *types.Header) ethereum.Subscription); ok { + r0 = rf(ctx, ch) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(ethereum.Subscription) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, chan<- *types.Header) error); ok { + r1 = rf(ctx, ch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_SubscribeNewHead_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SubscribeNewHead' +type EthClienter_SubscribeNewHead_Call struct { + *mock.Call +} + +// SubscribeNewHead is a helper method to define mock.On call +// - ctx context.Context +// - ch chan<- *types.Header +func (_e *EthClienter_Expecter) SubscribeNewHead(ctx interface{}, ch interface{}) *EthClienter_SubscribeNewHead_Call { + return &EthClienter_SubscribeNewHead_Call{Call: _e.mock.On("SubscribeNewHead", ctx, ch)} +} + +func (_c *EthClienter_SubscribeNewHead_Call) Run(run func(ctx context.Context, ch chan<- *types.Header)) *EthClienter_SubscribeNewHead_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(chan<- *types.Header)) + }) + return _c +} + +func (_c *EthClienter_SubscribeNewHead_Call) Return(_a0 ethereum.Subscription, _a1 error) *EthClienter_SubscribeNewHead_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_SubscribeNewHead_Call) RunAndReturn(run func(context.Context, chan<- *types.Header) (ethereum.Subscription, error)) *EthClienter_SubscribeNewHead_Call { + _c.Call.Return(run) + return _c +} + +// SuggestGasPrice provides a mock function with given fields: ctx +func (_m *EthClienter) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasPrice") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_SuggestGasPrice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasPrice' +type EthClienter_SuggestGasPrice_Call struct { + *mock.Call +} + +// SuggestGasPrice is a helper method to define mock.On call +// - ctx context.Context +func (_e *EthClienter_Expecter) SuggestGasPrice(ctx interface{}) *EthClienter_SuggestGasPrice_Call { + return &EthClienter_SuggestGasPrice_Call{Call: _e.mock.On("SuggestGasPrice", ctx)} +} + +func (_c *EthClienter_SuggestGasPrice_Call) Run(run func(ctx context.Context)) *EthClienter_SuggestGasPrice_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *EthClienter_SuggestGasPrice_Call) Return(_a0 *big.Int, _a1 error) *EthClienter_SuggestGasPrice_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_SuggestGasPrice_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *EthClienter_SuggestGasPrice_Call { + _c.Call.Return(run) + return _c +} + +// SuggestGasTipCap provides a mock function with given fields: ctx +func (_m *EthClienter) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasTipCap") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_SuggestGasTipCap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasTipCap' +type EthClienter_SuggestGasTipCap_Call struct { + *mock.Call +} + +// SuggestGasTipCap is a helper method to define mock.On call +// - ctx context.Context +func (_e *EthClienter_Expecter) SuggestGasTipCap(ctx interface{}) *EthClienter_SuggestGasTipCap_Call { + return &EthClienter_SuggestGasTipCap_Call{Call: _e.mock.On("SuggestGasTipCap", ctx)} +} + +func (_c *EthClienter_SuggestGasTipCap_Call) Run(run func(ctx context.Context)) *EthClienter_SuggestGasTipCap_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *EthClienter_SuggestGasTipCap_Call) Return(_a0 *big.Int, _a1 error) *EthClienter_SuggestGasTipCap_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_SuggestGasTipCap_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *EthClienter_SuggestGasTipCap_Call { + _c.Call.Return(run) + return _c +} + +// TransactionCount provides a mock function with given fields: ctx, blockHash +func (_m *EthClienter) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { + ret := _m.Called(ctx, blockHash) + + if len(ret) == 0 { + panic("no return value specified for TransactionCount") + } + + var r0 uint + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (uint, error)); ok { + return rf(ctx, blockHash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) uint); ok { + r0 = rf(ctx, blockHash) + } else { + r0 = ret.Get(0).(uint) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, blockHash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_TransactionCount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TransactionCount' +type EthClienter_TransactionCount_Call struct { + *mock.Call +} + +// TransactionCount is a helper method to define mock.On call +// - ctx context.Context +// - blockHash common.Hash +func (_e *EthClienter_Expecter) TransactionCount(ctx interface{}, blockHash interface{}) *EthClienter_TransactionCount_Call { + return &EthClienter_TransactionCount_Call{Call: _e.mock.On("TransactionCount", ctx, blockHash)} +} + +func (_c *EthClienter_TransactionCount_Call) Run(run func(ctx context.Context, blockHash common.Hash)) *EthClienter_TransactionCount_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *EthClienter_TransactionCount_Call) Return(_a0 uint, _a1 error) *EthClienter_TransactionCount_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_TransactionCount_Call) RunAndReturn(run func(context.Context, common.Hash) (uint, error)) *EthClienter_TransactionCount_Call { + _c.Call.Return(run) + return _c +} + +// TransactionInBlock provides a mock function with given fields: ctx, blockHash, index +func (_m *EthClienter) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { + ret := _m.Called(ctx, blockHash, index) + + if len(ret) == 0 { + panic("no return value specified for TransactionInBlock") + } + + var r0 *types.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash, uint) (*types.Transaction, error)); ok { + return rf(ctx, blockHash, index) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash, uint) *types.Transaction); ok { + r0 = rf(ctx, blockHash, index) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash, uint) error); ok { + r1 = rf(ctx, blockHash, index) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EthClienter_TransactionInBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TransactionInBlock' +type EthClienter_TransactionInBlock_Call struct { + *mock.Call +} + +// TransactionInBlock is a helper method to define mock.On call +// - ctx context.Context +// - blockHash common.Hash +// - index uint +func (_e *EthClienter_Expecter) TransactionInBlock(ctx interface{}, blockHash interface{}, index interface{}) *EthClienter_TransactionInBlock_Call { + return &EthClienter_TransactionInBlock_Call{Call: _e.mock.On("TransactionInBlock", ctx, blockHash, index)} +} + +func (_c *EthClienter_TransactionInBlock_Call) Run(run func(ctx context.Context, blockHash common.Hash, index uint)) *EthClienter_TransactionInBlock_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash), args[2].(uint)) + }) + return _c +} + +func (_c *EthClienter_TransactionInBlock_Call) Return(_a0 *types.Transaction, _a1 error) *EthClienter_TransactionInBlock_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EthClienter_TransactionInBlock_Call) RunAndReturn(run func(context.Context, common.Hash, uint) (*types.Transaction, error)) *EthClienter_TransactionInBlock_Call { + _c.Call.Return(run) + return _c +} + +// NewEthClienter creates a new instance of EthClienter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEthClienter(t interface { + mock.TestingT + Cleanup(func()) +}) *EthClienter { + mock := &EthClienter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 0bb31cc3..c6a4ef1a 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -53,11 +53,22 @@ type VerifyBatches struct { RollupExitRoot common.Hash `meddler:"rollup_exit_root,hash"` } +func (v *VerifyBatches) String() string { + return fmt.Sprintf("BlockNumber: %d, BlockPosition: %d, RollupID: %d, NumBatch: %d, StateRoot: %s, "+ + "ExitRoot: %s, Aggregator: %s, RollupExitRoot: %s", + v.BlockNumber, v.BlockPosition, v.RollupID, v.NumBatch, v.StateRoot.String(), + v.ExitRoot.String(), v.Aggregator.String(), v.RollupExitRoot.String()) +} + type InitL1InfoRootMap struct { LeafCount uint32 CurrentL1InfoRoot common.Hash } +func (i *InitL1InfoRootMap) String() string { + return fmt.Sprintf("LeafCount: %d, CurrentL1InfoRoot: %s", i.LeafCount, i.CurrentL1InfoRoot.String()) +} + type Event struct { UpdateL1InfoTree *UpdateL1InfoTree VerifyBatches *VerifyBatches @@ -77,6 +88,24 @@ type L1InfoTreeLeaf struct { Hash common.Hash `meddler:"hash,hash"` } +func (l *L1InfoTreeLeaf) String() string { + return fmt.Sprintf("BlockNumber: %d, BlockPosition: %d, L1InfoTreeIndex: %d, PreviousBlockHash: %s, "+ + "Timestamp: %d, MainnetExitRoot: %s, RollupExitRoot: %s, GlobalExitRoot: %s, Hash: %s", + l.BlockNumber, l.BlockPosition, l.L1InfoTreeIndex, l.PreviousBlockHash.String(), + l.Timestamp, l.MainnetExitRoot.String(), l.RollupExitRoot.String(), l.GlobalExitRoot.String(), l.Hash.String()) +} + +// L1InfoTreeInitial representation of the initial info of the L1 Info tree for this rollup +type L1InfoTreeInitial struct { + BlockNumber uint64 `meddler:"block_num"` + LeafCount uint32 `meddler:"leaf_count"` + L1InfoRoot common.Hash `meddler:"l1_info_root,hash"` +} + +func (l *L1InfoTreeInitial) String() string { + return fmt.Sprintf("BlockNumber: %d, LeafCount: %d, L1InfoRoot: %s", l.BlockNumber, l.LeafCount, l.L1InfoRoot.String()) +} + // Hash as expected by the tree func (l *L1InfoTreeLeaf) hash() common.Hash { var res [treeTypes.DefaultHeight]byte @@ -227,7 +256,7 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { // ProcessBlock process the events of the block to build the rollup exit tree and the l1 info tree // and updates the last processed block (can be called without events for that purpose) -func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { +func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { tx, err := db.NewTx(ctx, p.db) if err != nil { return err @@ -240,8 +269,8 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { } }() - if _, err = tx.Exec(`INSERT INTO block (num) VALUES ($1)`, b.Num); err != nil { - return fmt.Errorf("err: %w", err) + if _, err := tx.Exec(`INSERT INTO block (num) VALUES ($1)`, block.Num); err != nil { + return fmt.Errorf("insert Block. err: %w", err) } var initialL1InfoIndex uint32 @@ -253,12 +282,12 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { initialL1InfoIndex = 0 err = nil case err != nil: - return fmt.Errorf("err: %w", err) + return fmt.Errorf("getLastIndex err: %w", err) default: initialL1InfoIndex = lastIndex + 1 } - for _, e := range b.Events { + for _, e := range block.Events { event, ok := e.(Event) if !ok { return errors.New("failed to convert from sync.Block.Event into Event") @@ -266,7 +295,7 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { if event.UpdateL1InfoTree != nil { index := initialL1InfoIndex + l1InfoLeavesAdded info := &L1InfoTreeLeaf{ - BlockNumber: b.Num, + BlockNumber: block.Num, BlockPosition: event.UpdateL1InfoTree.BlockPosition, L1InfoTreeIndex: index, PreviousBlockHash: event.UpdateL1InfoTree.ParentHash, @@ -277,45 +306,44 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { info.GlobalExitRoot = info.globalExitRoot() info.Hash = info.hash() if err = meddler.Insert(tx, "l1info_leaf", info); err != nil { - return fmt.Errorf("err: %w", err) + return fmt.Errorf("insert l1info_leaf %s. err: %w", info.String(), err) } + err = p.l1InfoTree.AddLeaf(tx, info.BlockNumber, info.BlockPosition, treeTypes.Leaf{ Index: info.L1InfoTreeIndex, Hash: info.Hash, }) if err != nil { - return fmt.Errorf("err: %w", err) + return fmt.Errorf("AddLeaf(%s). err: %w", info.String(), err) } + log.Infof("inserted L1InfoTreeLeaf %s", info.String()) l1InfoLeavesAdded++ } - if event.VerifyBatches != nil { - newRoot, err := p.rollupExitTree.UpsertLeaf(tx, b.Num, event.VerifyBatches.BlockPosition, treeTypes.Leaf{ - Index: event.VerifyBatches.RollupID - 1, - Hash: event.VerifyBatches.ExitRoot, - }) + log.Debugf("handle VerifyBatches event %s", event.VerifyBatches.String()) + err = p.processVerifyBatches(tx, block.Num, event.VerifyBatches) if err != nil { - return fmt.Errorf("err: %w", err) - } - verifyBatches := event.VerifyBatches - verifyBatches.BlockNumber = b.Num - verifyBatches.RollupExitRoot = newRoot - if err = meddler.Insert(tx, "verify_batches", verifyBatches); err != nil { - return fmt.Errorf("err: %w", err) + err = fmt.Errorf("processVerifyBatches. err: %w", err) + log.Errorf("error processing VerifyBatches: %v", err) + return err } } if event.InitL1InfoRootMap != nil { - // TODO: indicate that l1 Info tree indexes before the one on this - // event are not safe to use - log.Debugf("TODO: handle InitL1InfoRootMap event") + log.Debugf("handle InitL1InfoRootMap event %s", event.InitL1InfoRootMap.String()) + err = processEventInitL1InfoRootMap(tx, block.Num, event.InitL1InfoRootMap) + if err != nil { + err = fmt.Errorf("initL1InfoRootMap. Err: %w", err) + log.Errorf("error processing InitL1InfoRootMap: %v", err) + return err + } } } if err := tx.Commit(); err != nil { return fmt.Errorf("err: %w", err) } - log.Infof("block %d processed with %d events", b.Num, len(b.Events)) + log.Infof("block %d processed with %d events", block.Num, len(block.Events)) return nil } @@ -329,39 +357,6 @@ func (p *processor) getLastIndex(tx db.Querier) (uint32, error) { return lastProcessedIndex, err } -func (p *processor) GetLastVerifiedBatches(rollupID uint32) (*VerifyBatches, error) { - verified := &VerifyBatches{} - err := meddler.QueryRow(p.db, verified, ` - SELECT * FROM verify_batches - WHERE rollup_id = $1 - ORDER BY block_num DESC, block_pos DESC - LIMIT 1; - `, rollupID) - return verified, db.ReturnErrNotFound(err) -} - -func (p *processor) GetFirstVerifiedBatches(rollupID uint32) (*VerifyBatches, error) { - verified := &VerifyBatches{} - err := meddler.QueryRow(p.db, verified, ` - SELECT * FROM verify_batches - WHERE rollup_id = $1 - ORDER BY block_num ASC, block_pos ASC - LIMIT 1; - `, rollupID) - return verified, db.ReturnErrNotFound(err) -} - -func (p *processor) GetFirstVerifiedBatchesAfterBlock(rollupID uint32, blockNum uint64) (*VerifyBatches, error) { - verified := &VerifyBatches{} - err := meddler.QueryRow(p.db, verified, ` - SELECT * FROM verify_batches - WHERE rollup_id = $1 AND block_num >= $2 - ORDER BY block_num ASC, block_pos ASC - LIMIT 1; - `, rollupID, blockNum) - return verified, db.ReturnErrNotFound(err) -} - func (p *processor) GetFirstL1InfoWithRollupExitRoot(rollupExitRoot common.Hash) (*L1InfoTreeLeaf, error) { info := &L1InfoTreeLeaf{} err := meddler.QueryRow(p.db, info, ` @@ -413,3 +408,10 @@ func (p *processor) GetInfoByGlobalExitRoot(ger common.Hash) (*L1InfoTreeLeaf, e `, ger.Hex()) return info, db.ReturnErrNotFound(err) } + +func (p *processor) getDBQuerier(tx db.Txer) db.Querier { + if tx != nil { + return tx + } + return p.db +} diff --git a/l1infotreesync/processor_initl1inforootmap.go b/l1infotreesync/processor_initl1inforootmap.go new file mode 100644 index 00000000..92732cd9 --- /dev/null +++ b/l1infotreesync/processor_initl1inforootmap.go @@ -0,0 +1,37 @@ +package l1infotreesync + +import ( + "database/sql" + "errors" + "fmt" + + "github.com/0xPolygon/cdk/db" + "github.com/0xPolygon/cdk/log" + "github.com/russross/meddler" +) + +func processEventInitL1InfoRootMap(tx db.Txer, blockNumber uint64, event *InitL1InfoRootMap) error { + if event == nil { + return nil + } + info := &L1InfoTreeInitial{ + BlockNumber: blockNumber, + LeafCount: event.LeafCount, + L1InfoRoot: event.CurrentL1InfoRoot, + } + log.Infof("insert InitL1InfoRootMap %s ", info.String()) + if err := meddler.Insert(tx, "l1info_initial", info); err != nil { + return fmt.Errorf("err: %w", err) + } + return nil +} + +// GetInitL1InfoRootMap returns the initial L1 info root map, nil if no root map has been set +func (p *processor) GetInitL1InfoRootMap(tx db.Txer) (*L1InfoTreeInitial, error) { + info := &L1InfoTreeInitial{} + err := meddler.QueryRow(p.getDBQuerier(tx), info, `SELECT block_num, leaf_count,l1_info_root FROM l1info_initial`) + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + return info, err +} diff --git a/l1infotreesync/processor_initl1inforootmap_test.go b/l1infotreesync/processor_initl1inforootmap_test.go new file mode 100644 index 00000000..753d7a25 --- /dev/null +++ b/l1infotreesync/processor_initl1inforootmap_test.go @@ -0,0 +1,67 @@ +package l1infotreesync + +import ( + "context" + "testing" + + "github.com/0xPolygon/cdk/sync" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestInitL1InfoRootMap(t *testing.T) { + dbPath := "file:TestInitL1InfoRootMap?mode=memory&cache=shared" + sut, err := newProcessor(dbPath) + require.NoError(t, err) + ctx := context.TODO() + event := InitL1InfoRootMap{ + LeafCount: 1, + CurrentL1InfoRoot: common.HexToHash("beef"), + } + block := sync.Block{ + Num: 1, + Events: []interface{}{ + Event{InitL1InfoRootMap: &event}, + }, + } + + err = sut.ProcessBlock(ctx, block) + require.NoError(t, err) + + info, err := sut.GetInitL1InfoRootMap(nil) + require.NoError(t, err) + require.NotNil(t, info) + require.Equal(t, event.LeafCount, info.LeafCount) + require.Equal(t, event.CurrentL1InfoRoot, info.L1InfoRoot) + require.Equal(t, block.Num, info.BlockNumber) +} + +func TestInitL1InfoRootMapDontAllow2Rows(t *testing.T) { + dbPath := "file:TestInitL1InfoRootMapDontAllow2Rows?mode=memory&cache=shared" + sut, err := newProcessor(dbPath) + require.NoError(t, err) + ctx := context.TODO() + block := sync.Block{ + Num: 1, + Events: []interface{}{ + Event{InitL1InfoRootMap: &InitL1InfoRootMap{ + LeafCount: 1, + CurrentL1InfoRoot: common.HexToHash("beef"), + }}, + }, + } + err = sut.ProcessBlock(ctx, block) + require.NoError(t, err) + block.Num = 2 + err = sut.ProcessBlock(ctx, block) + require.Error(t, err, "should not allow to insert a second row") +} + +func TestGetInitL1InfoRootMap(t *testing.T) { + dbPath := "file:TestGetInitL1InfoRootMap?mode=memory&cache=shared" + sut, err := newProcessor(dbPath) + require.NoError(t, err) + info, err := sut.GetInitL1InfoRootMap(nil) + require.NoError(t, err, "should return no error if no row is present, because it returns data=nil") + require.Nil(t, info, "should return nil if no row is present") +} diff --git a/l1infotreesync/processor_test.go b/l1infotreesync/processor_test.go index 3da02998..b31d2237 100644 --- a/l1infotreesync/processor_test.go +++ b/l1infotreesync/processor_test.go @@ -10,72 +10,6 @@ import ( "golang.org/x/net/context" ) -func TestGetVerifiedBatches(t *testing.T) { - dbPath := "file:TestGetVerifiedBatches?mode=memory&cache=shared" - p, err := newProcessor(dbPath) - require.NoError(t, err) - ctx := context.Background() - - // Test ErrNotFound returned correctly on all methods - _, err = p.GetLastVerifiedBatches(0) - require.Equal(t, db.ErrNotFound, err) - _, err = p.GetFirstVerifiedBatches(0) - require.Equal(t, db.ErrNotFound, err) - _, err = p.GetFirstVerifiedBatchesAfterBlock(0, 0) - require.Equal(t, db.ErrNotFound, err) - - // First insert - expected1 := &VerifyBatches{ - RollupID: 420, - NumBatch: 69, - StateRoot: common.HexToHash("5ca1e"), - ExitRoot: common.HexToHash("b455"), - Aggregator: common.HexToAddress("beef"), - } - err = p.ProcessBlock(ctx, sync.Block{ - Num: 1, - Events: []interface{}{ - Event{VerifyBatches: expected1}, - }, - }) - require.NoError(t, err) - _, err = p.GetLastVerifiedBatches(0) - require.Equal(t, db.ErrNotFound, err) - actual, err := p.GetLastVerifiedBatches(420) - require.NoError(t, err) - require.Equal(t, expected1, actual) - actual, err = p.GetFirstVerifiedBatches(420) - require.NoError(t, err) - require.Equal(t, expected1, actual) - - // Second insert - expected2 := &VerifyBatches{ - RollupID: 420, - NumBatch: 690, - StateRoot: common.HexToHash("5ca1e3"), - ExitRoot: common.HexToHash("ba55"), - Aggregator: common.HexToAddress("beef3"), - } - err = p.ProcessBlock(ctx, sync.Block{ - Num: 2, - Events: []interface{}{ - Event{VerifyBatches: expected2}, - }, - }) - require.NoError(t, err) - _, err = p.GetLastVerifiedBatches(0) - require.Equal(t, db.ErrNotFound, err) - actual, err = p.GetLastVerifiedBatches(420) - require.NoError(t, err) - require.Equal(t, expected2, actual) - actual, err = p.GetFirstVerifiedBatches(420) - require.NoError(t, err) - require.Equal(t, expected1, actual) - actual, err = p.GetFirstVerifiedBatchesAfterBlock(420, 2) - require.NoError(t, err) - require.Equal(t, expected2, actual) -} - func TestGetInfo(t *testing.T) { dbPath := "file:TestGetInfo?mode=memory&cache=shared" p, err := newProcessor(dbPath) @@ -174,3 +108,16 @@ func TestGetInfo(t *testing.T) { require.NoError(t, err) require.Equal(t, expected2, *actual) } + +func TestGetLatestInfoUntilBlockIfNotFoundReturnsErrNotFound(t *testing.T) { + dbPath := "file:TestGetLatestInfoUntilBlock?mode=memory&cache=shared" + sut, err := newProcessor(dbPath) + require.NoError(t, err) + ctx := context.Background() + // Fake block 1 + _, err = sut.db.Exec(`INSERT INTO block (num) VALUES ($1)`, 1) + require.NoError(t, err) + + _, err = sut.GetLatestInfoUntilBlock(ctx, 1) + require.Equal(t, db.ErrNotFound, err) +} diff --git a/l1infotreesync/processor_verifybatches.go b/l1infotreesync/processor_verifybatches.go new file mode 100644 index 00000000..9d1d0efb --- /dev/null +++ b/l1infotreesync/processor_verifybatches.go @@ -0,0 +1,104 @@ +package l1infotreesync + +import ( + "errors" + "fmt" + + "github.com/0xPolygon/cdk/db" + "github.com/0xPolygon/cdk/log" + treeTypes "github.com/0xPolygon/cdk/tree/types" + "github.com/ethereum/go-ethereum/common" + "github.com/russross/meddler" +) + +func (p *processor) processVerifyBatches(tx db.Txer, blockNumber uint64, event *VerifyBatches) error { + if event == nil { + return fmt.Errorf("processVerifyBatches: event is nil") + } + if tx == nil { + return fmt.Errorf("processVerifyBatches: tx is nil, is mandatory to pass a tx") + } + log.Debugf("VerifyBatches: rollupExitTree.UpsertLeaf (blockNumber=%d, event=%s)", blockNumber, event.String()) + // If ExitRoot is zero if the leaf doesnt exists doesnt change the root of tree. + // if leaf already exists doesn't make sense to 'empty' the leaf, so we keep previous value + if event.ExitRoot == (common.Hash{}) { + log.Infof("skipping VerifyBatches event with empty ExitRoot (blockNumber=%d, event=%s)", blockNumber, event.String()) + return nil + } + isNewLeaf, err := p.isNewValueForRollupExitTree(tx, event) + if err != nil { + return fmt.Errorf("isNewValueForrollupExitTree. err: %w", err) + } + if !isNewLeaf { + log.Infof("skipping VerifyBatches event with same ExitRoot (blockNumber=%d, event=%s)", blockNumber, event.String()) + return nil + } + log.Infof("UpsertLeaf VerifyBatches event (blockNumber=%d, event=%s)", blockNumber, event.String()) + newRoot, err := p.rollupExitTree.UpsertLeaf(tx, blockNumber, event.BlockPosition, treeTypes.Leaf{ + Index: event.RollupID - 1, + Hash: event.ExitRoot, + }) + if err != nil { + return fmt.Errorf("error rollupExitTree.UpsertLeaf. err: %w", err) + } + verifyBatches := event + verifyBatches.BlockNumber = blockNumber + verifyBatches.RollupExitRoot = newRoot + if err = meddler.Insert(tx, "verify_batches", verifyBatches); err != nil { + return fmt.Errorf("error inserting verify_batches. err: %w", err) + } + return nil +} + +func (p *processor) isNewValueForRollupExitTree(tx db.Querier, event *VerifyBatches) (bool, error) { + currentRoot, err := p.rollupExitTree.GetLastRoot(tx) + if err != nil && errors.Is(err, db.ErrNotFound) { + // The tree is empty, so is a new value for sure + return true, nil + } + if err != nil { + return false, fmt.Errorf("error rollupExitTree.GetLastRoot. err: %w", err) + } + leaf, err := p.rollupExitTree.GetLeaf(tx, event.RollupID-1, currentRoot.Hash) + if err != nil && errors.Is(err, db.ErrNotFound) { + // The leaf doesn't exist, so is a new value + return true, nil + } + if err != nil { + return false, fmt.Errorf("error rollupExitTree.GetLeaf. err: %w", err) + } + return leaf != event.ExitRoot, nil +} + +func (p *processor) GetLastVerifiedBatches(rollupID uint32) (*VerifyBatches, error) { + verified := &VerifyBatches{} + err := meddler.QueryRow(p.db, verified, ` + SELECT * FROM verify_batches + WHERE rollup_id = $1 + ORDER BY block_num DESC, block_pos DESC + LIMIT 1; + `, rollupID) + return verified, db.ReturnErrNotFound(err) +} + +func (p *processor) GetFirstVerifiedBatches(rollupID uint32) (*VerifyBatches, error) { + verified := &VerifyBatches{} + err := meddler.QueryRow(p.db, verified, ` + SELECT * FROM verify_batches + WHERE rollup_id = $1 + ORDER BY block_num ASC, block_pos ASC + LIMIT 1; + `, rollupID) + return verified, db.ReturnErrNotFound(err) +} + +func (p *processor) GetFirstVerifiedBatchesAfterBlock(rollupID uint32, blockNum uint64) (*VerifyBatches, error) { + verified := &VerifyBatches{} + err := meddler.QueryRow(p.db, verified, ` + SELECT * FROM verify_batches + WHERE rollup_id = $1 AND block_num >= $2 + ORDER BY block_num ASC, block_pos ASC + LIMIT 1; + `, rollupID, blockNum) + return verified, db.ReturnErrNotFound(err) +} diff --git a/l1infotreesync/processor_verifybatches_test.go b/l1infotreesync/processor_verifybatches_test.go new file mode 100644 index 00000000..d943b541 --- /dev/null +++ b/l1infotreesync/processor_verifybatches_test.go @@ -0,0 +1,127 @@ +package l1infotreesync + +import ( + "context" + "testing" + + "github.com/0xPolygon/cdk/db" + "github.com/0xPolygon/cdk/sync" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestProcessVerifyBatchesNil(t *testing.T) { + dbPath := "file:TestProcessVerifyBatchesNil?mode=memory&cache=shared" + sut, err := newProcessor(dbPath) + require.NoError(t, err) + err = sut.processVerifyBatches(nil, 1, nil) + require.Error(t, err) +} + +func TestProcessVerifyBatchesOK(t *testing.T) { + dbPath := "file:TestProcessVerifyBatchesOK?mode=memory&cache=shared" + sut, err := newProcessor(dbPath) + require.NoError(t, err) + event := VerifyBatches{ + BlockPosition: 1, + RollupID: 1, + NumBatch: 1, + StateRoot: common.HexToHash("5ca1e"), + ExitRoot: common.HexToHash("b455"), + Aggregator: common.HexToAddress("beef"), + RollupExitRoot: common.HexToHash("b455"), + } + ctx := context.TODO() + tx, err := db.NewTx(ctx, sut.db) + require.NoError(t, err) + _, err = tx.Exec(`INSERT INTO block (num) VALUES ($1)`, 1) + require.NoError(t, err) + err = sut.processVerifyBatches(tx, 1, &event) + require.NoError(t, err) +} + +func TestProcessVerifyBatchesSkip0000(t *testing.T) { + dbPath := "file:TestProcessVerifyBatchesSkip0000?mode=memory&cache=shared" + sut, err := newProcessor(dbPath) + require.NoError(t, err) + event := VerifyBatches{ + BlockPosition: 1, + RollupID: 1, + NumBatch: 1, + StateRoot: common.HexToHash("5ca1e"), + ExitRoot: common.Hash{}, + Aggregator: common.HexToAddress("beef"), + RollupExitRoot: common.HexToHash("b455"), + } + ctx := context.TODO() + tx, err := db.NewTx(ctx, sut.db) + require.NoError(t, err) + err = sut.processVerifyBatches(tx, 1, &event) + require.NoError(t, err) +} + +func TestGetVerifiedBatches(t *testing.T) { + dbPath := "file:TestGetVerifiedBatches?mode=memory&cache=shared" + p, err := newProcessor(dbPath) + require.NoError(t, err) + ctx := context.Background() + + // Test ErrNotFound returned correctly on all methods + _, err = p.GetLastVerifiedBatches(0) + require.Equal(t, db.ErrNotFound, err) + _, err = p.GetFirstVerifiedBatches(0) + require.Equal(t, db.ErrNotFound, err) + _, err = p.GetFirstVerifiedBatchesAfterBlock(0, 0) + require.Equal(t, db.ErrNotFound, err) + + // First insert + expected1 := &VerifyBatches{ + RollupID: 420, + NumBatch: 69, + StateRoot: common.HexToHash("5ca1e"), + ExitRoot: common.HexToHash("b455"), + Aggregator: common.HexToAddress("beef"), + } + err = p.ProcessBlock(ctx, sync.Block{ + Num: 1, + Events: []interface{}{ + Event{VerifyBatches: expected1}, + }, + }) + require.NoError(t, err) + _, err = p.GetLastVerifiedBatches(0) + require.Equal(t, db.ErrNotFound, err) + actual, err := p.GetLastVerifiedBatches(420) + require.NoError(t, err) + require.Equal(t, expected1, actual) + actual, err = p.GetFirstVerifiedBatches(420) + require.NoError(t, err) + require.Equal(t, expected1, actual) + + // Second insert + expected2 := &VerifyBatches{ + RollupID: 420, + NumBatch: 690, + StateRoot: common.HexToHash("5ca1e3"), + ExitRoot: common.HexToHash("ba55"), + Aggregator: common.HexToAddress("beef3"), + } + err = p.ProcessBlock(ctx, sync.Block{ + Num: 2, + Events: []interface{}{ + Event{VerifyBatches: expected2}, + }, + }) + require.NoError(t, err) + _, err = p.GetLastVerifiedBatches(0) + require.Equal(t, db.ErrNotFound, err) + actual, err = p.GetLastVerifiedBatches(420) + require.NoError(t, err) + require.Equal(t, expected2, actual) + actual, err = p.GetFirstVerifiedBatches(420) + require.NoError(t, err) + require.Equal(t, expected1, actual) + actual, err = p.GetFirstVerifiedBatchesAfterBlock(420, 2) + require.NoError(t, err) + require.Equal(t, expected2, actual) +} diff --git a/reorgdetector/mock_eth_client.go b/reorgdetector/mock_eth_client.go index a76c62f9..0c561ab3 100644 --- a/reorgdetector/mock_eth_client.go +++ b/reorgdetector/mock_eth_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.39.0. DO NOT EDIT. package reorgdetector diff --git a/scripts/local_config b/scripts/local_config index ed8aaec3..aeb008b0 100755 --- a/scripts/local_config +++ b/scripts/local_config @@ -2,6 +2,18 @@ #Include common varaibles source $(dirname $0)/../test/scripts/env.sh +function export_values_of_genesis(){ + local _GENESIS_FILE=$1 + if [ ! -f $_GENESIS_FILE ]; then + echo "Error: genesis file not found: $_GENESIS_FILE" + exit 1 + fi + export l1_chain_id=$(jq -r '.L1Config.chainId' $_GENESIS_FILE | tr -d '"') + export pol_token_address=$(jq -r '.L1Config.polTokenAddress' $_GENESIS_FILE) + export zkevm_rollup_address=$(jq -r '.L1Config.polygonZkEVMAddress' $_GENESIS_FILE) + export zkevm_rollup_manager_address=$(jq -r '.L1Config.polygonRollupManagerAddress' $_GENESIS_FILE) + export zkevm_global_exit_root_address=$(jq -r '.L1Config.polygonZkEVMGlobalExitRootAddress' $_GENESIS_FILE) +} @@ -23,16 +35,16 @@ EOF fi -if [ -z $TMP_CDK_FOLDER -o -z $ENCLAVE ]; then - echo "TMP_CDK_FOLDER or ENCLAVE is not set. Must be set on file env.sh" +if [ -z $TMP_CDK_FOLDER -o -z $KURTOSIS_ENCLAVE ]; then + echo "TMP_CDK_FOLDER or KURTOSIS_ENCLAVE is not set. Must be set on file env.sh" exit 1 fi -kurtosis enclave inspect $ENCLAVE > /dev/null +kurtosis enclave inspect $KURTOSIS_ENCLAVE > /dev/null if [ $? -ne 0 ]; then - echo "Error inspecting enclave $ENCLAVE" + echo "Error inspecting enclave $KURTOSIS_ENCLAVE" echo "You must start kurtosis environment before running this script" echo "- start kurtosis:" - echo " kurtosis clean --all; kurtosis run --enclave $ENCLAVE --args-file params.yml --image-download always ." + echo " kurtosis clean --all; kurtosis run --enclave $KURTOSIS_ENCLAVE --args-file params.yml --image-download always ." exit 1 fi @@ -40,34 +52,39 @@ DEST=${TMP_CDK_FOLDER}/local_config [ ! -d ${DEST} ] && mkdir -p ${DEST} rm $DEST/* -kurtosis files download $ENCLAVE genesis $DEST +kurtosis files download $KURTOSIS_ENCLAVE genesis $DEST [ $? -ne 0 ] && echo "Error downloading genesis" && exit 1 export genesis_file=$DEST/genesis.json -kurtosis files download $ENCLAVE sequencer-keystore $DEST +export_values_of_genesis $genesis_file +kurtosis files download $KURTOSIS_ENCLAVE sequencer-keystore $DEST [ $? -ne 0 ] && echo "Error downloading sequencer-keystore" && exit 1 export sequencer_keystore_file=$DEST/sequencer.keystore -l1_rpc_port=$(kurtosis port print $ENCLAVE el-1-geth-lighthouse rpc | cut -f 3 -d ":") +l1_rpc_port=$(kurtosis port print $KURTOSIS_ENCLAVE el-1-geth-lighthouse rpc | cut -f 3 -d ":") [ $? -ne 0 ] && echo "Error getting l1_rpc_port" && exit 1 || export l1_rpc_port && echo "l1_rpc_port=$l1_rpc_port" +l1_rpc_addr=$(kurtosis port print $KURTOSIS_ENCLAVE el-1-geth-lighthouse rpc) +[ $? -ne 0 ] && echo "Error getting l1_rpc_addr" && exit 1 || export l1_rpc_addr && echo "l1_rpc_addr=$l1_rpc_addr" +l2_rpc_addr=$(kurtosis port print $KURTOSIS_ENCLAVE cdk-erigon-node-001 http-rpc) +[ $? -ne 0 ] && echo "Error getting l2_rpc_addr" && exit 1 || export l2_rpc_addr && echo "l2_rpc_addr=$l2_rpc_addr" -zkevm_data_streamer_port=$(kurtosis port print $ENCLAVE cdk-erigon-sequencer-001 data-streamer | cut -f 3 -d ":") +zkevm_data_streamer_port=$(kurtosis port print $KURTOSIS_ENCLAVE cdk-erigon-sequencer-001 data-streamer | cut -f 3 -d ":") [ $? -ne 0 ] && echo "Error getting zkevm_data_streamer_port" && exit 1 || export zkevm_data_streamer_port && echo "zkevm_data_streamer_port=$zkevm_data_streamer_port" -kurtosis files download $ENCLAVE cdk-node-config-artifact $DEST +kurtosis files download $KURTOSIS_ENCLAVE cdk-node-config-artifact $DEST export zkevm_l2_sequencer_address=$(cat $DEST/cdk-node-config.toml |grep L2Coinbase | cut -f 2 -d "="| tr -d '"' | tr -d ' ') export zkevm_l2_keystore_password=$(cat $DEST/cdk-node-config.toml |grep -A1 L2Coinbase | tr ',' '\n' | grep Password | cut -f 2 -d '=' | tr -d '}' | tr -d '"' | tr -d ' ') export l1_chain_id=$(cat $DEST/cdk-node-config.toml | grep L1ChainID | cut -f 2 -d '=' | head -n 1) echo $l1_chain_id export zkevm_is_validium=$(cat $DEST/cdk-node-config.toml | grep IsValidiumMode | cut -f 2 -d '=') - +export zkevm_contract_versions=$(cat $DEST/cdk-node-config.toml | grep ContractVersions | cut -f 2 -d '=' | tr -d '"' | tr -d ' ') if [ "$zkevm_is_validium" == "true" ]; then echo "Validium mode detected... Retrieving the dac_port" - dac_port=$(kurtosis port print $ENCLAVE zkevm-dac-001 dac | cut -f 3 -d ":") + dac_port=$(kurtosis port print $KURTOSIS_ENCLAVE zkevm-dac-001 dac | cut -f 3 -d ":") [ $? -ne 0 ] && echo "Error getting dac_port" && exit 1 || export dac_port && echo "dac_port=$dac_port" fi envsubst < test/config/test.kurtosis_template.toml > $DEST/test.kurtosis.toml - +echo "file generated at:" $DEST/test.kurtosis.toml echo "- to restart kurtosis:" echo " kurtosis clean --all; kurtosis run --enclave cdk-v1 --args-file params.yml --image-download always ." echo " " @@ -87,7 +104,7 @@ cat << EOF "run", "-cfg", "$DEST/test.kurtosis.toml", "-components", "sequence-sender,aggregator", - "-custom-network-file", "$DEST/local_config/genesis.json" ] }, EOF + diff --git a/sequencesender/txbuilder/banana_base.go b/sequencesender/txbuilder/banana_base.go index 6d191c4a..2868bb4b 100644 --- a/sequencesender/txbuilder/banana_base.go +++ b/sequencesender/txbuilder/banana_base.go @@ -2,6 +2,7 @@ package txbuilder import ( "context" + "errors" "fmt" "math/big" @@ -27,6 +28,7 @@ type globalExitRootBananaContractor interface { type l1InfoSyncer interface { GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*l1infotreesync.L1InfoTreeLeaf, error) + GetInitL1InfoRootMap(ctx context.Context) (*l1infotreesync.L1InfoTreeInitial, error) } type l1Client interface { @@ -74,39 +76,90 @@ func (t *TxBuilderBananaBase) NewBatchFromL2Block(l2Block *datastream.L2Block) s return NewBananaBatch(batch) } -func (t *TxBuilderBananaBase) NewSequence( - ctx context.Context, batches []seqsendertypes.Batch, coinbase common.Address, -) (seqsendertypes.Sequence, error) { - ethBatches := toEthermanBatches(batches) - sequence := etherman.NewSequenceBanana(ethBatches, coinbase) - var greatestL1Index uint32 - for _, b := range sequence.Batches { - if greatestL1Index < b.L1InfoTreeIndex { - greatestL1Index = b.L1InfoTreeIndex +func getHighestL1InfoIndex(batches []etherman.Batch) uint32 { + var highestL1Index uint32 + for _, b := range batches { + if highestL1Index < b.L1InfoTreeIndex { + highestL1Index = b.L1InfoTreeIndex } } + return highestL1Index +} + +// Returns CounterL1InfoRoot to use for this batch +func (t *TxBuilderBananaBase) GetCounterL1InfoRoot(ctx context.Context, highestL1IndexInBatch uint32) (uint32, error) { header, err := t.ethClient.HeaderByNumber(ctx, t.blockFinality) if err != nil { - return nil, fmt.Errorf("error calling HeaderByNumber, with block finality %d: %w", t.blockFinality.Int64(), err) + return 0, fmt.Errorf("error calling HeaderByNumber, with block finality %d: %w", t.blockFinality.Int64(), err) } + var resL1InfoCounter uint32 + info, err := t.l1InfoTree.GetLatestInfoUntilBlock(ctx, header.Number.Uint64()) + if err == nil { + resL1InfoCounter = info.L1InfoTreeIndex + 1 + } + if errors.Is(err, l1infotreesync.ErrNotFound) { + // There are no L1 Info tree leaves yet, so we can try to use L1InfoRootMap event + l1infotreeInitial, err := t.l1InfoTree.GetInitL1InfoRootMap(ctx) + if l1infotreeInitial == nil || err != nil { + return 0, fmt.Errorf("error no leaves on L1InfoTree yet and GetInitL1InfoRootMap fails: %w", err) + } + // We use this leaf as first one + resL1InfoCounter = l1infotreeInitial.LeafCount + } else if err != nil { + return 0, fmt.Errorf("error calling GetLatestInfoUntilBlock with block num %d: %w", header.Number.Uint64(), err) + } + // special case: there are no leaves in L1InfoTree yet + if resL1InfoCounter == 0 && highestL1IndexInBatch == 0 { + log.Infof("No L1 Info tree leaves yet, batch use no leaf") + return resL1InfoCounter, nil + } + if resL1InfoCounter > highestL1IndexInBatch { + return resL1InfoCounter, nil + } + + return 0, fmt.Errorf( + "sequence contained an L1 Info tree index (%d) that is greater than the one synced with the desired finality (%d)", + highestL1IndexInBatch, resL1InfoCounter, + ) +} + +func (t *TxBuilderBananaBase) CheckL1InfoTreeLeafCounterVsInitL1InfoMap(ctx context.Context, leafCounter uint32) error { + l1infotreeInitial, err := t.l1InfoTree.GetInitL1InfoRootMap(ctx) if err != nil { - return nil, fmt.Errorf("error calling GetLatestInfoUntilBlock with block num %d: %w", header.Number.Uint64(), err) - } - if info.L1InfoTreeIndex >= greatestL1Index { - sequence.CounterL1InfoRoot = info.L1InfoTreeIndex + 1 - } else { - return nil, fmt.Errorf( - "sequence contained an L1 Info tree index (%d) that is greater than the one synced with the desired finality (%d)", - greatestL1Index, info.L1InfoTreeIndex, - ) + return fmt.Errorf("l1InfoTree.GetInitL1InfoRootMap fails: %w", err) } + if l1infotreeInitial == nil { + log.Warnf("No InitL1InfoRootMap found, skipping check") + return nil + } + if leafCounter < l1infotreeInitial.LeafCount { + return fmt.Errorf("cant use this leafCounter because is previous to first value on contract Map"+ + "leafCounter(%d) < l1infotreeInitial.LeafCount(%d)", leafCounter, l1infotreeInitial.LeafCount) + } + return nil +} +func (t *TxBuilderBananaBase) NewSequence( + ctx context.Context, batches []seqsendertypes.Batch, coinbase common.Address, +) (seqsendertypes.Sequence, error) { + ethBatches := toEthermanBatches(batches) + sequence := etherman.NewSequenceBanana(ethBatches, coinbase) + greatestL1Index := getHighestL1InfoIndex(sequence.Batches) + + counterL1InfoRoot, err := t.GetCounterL1InfoRoot(ctx, greatestL1Index) + if err != nil { + return nil, err + } + sequence.CounterL1InfoRoot = counterL1InfoRoot l1InfoRoot, err := t.getL1InfoRoot(sequence.CounterL1InfoRoot) if err != nil { return nil, err } - + err = t.CheckL1InfoTreeLeafCounterVsInitL1InfoMap(ctx, sequence.CounterL1InfoRoot) + if err != nil { + return nil, err + } sequence.L1InfoRoot = l1InfoRoot accInputHash, err := t.rollupContract.LastAccInputHash(&bind.CallOpts{Pending: false}) diff --git a/sequencesender/txbuilder/banana_base_test.go b/sequencesender/txbuilder/banana_base_test.go index 3b449084..44d7a7b1 100644 --- a/sequencesender/txbuilder/banana_base_test.go +++ b/sequencesender/txbuilder/banana_base_test.go @@ -31,6 +31,7 @@ func TestBananaBaseNewSequenceEmpty(t *testing.T) { Return(&l1infotreesync.L1InfoTreeLeaf{L1InfoTreeIndex: 69}, nil) lastAcc := common.HexToHash("0x8aca9664752dbae36135fd0956c956fc4a370feeac67485b49bcd4b99608ae41") testData.rollupContract.EXPECT().LastAccInputHash(mock.Anything).Return(lastAcc, nil) + testData.l1InfoTreeSync.EXPECT().GetInitL1InfoRootMap(mock.Anything).Return(nil, nil) seq, err := testData.sut.NewSequence(context.TODO(), nil, common.Address{}) require.NotNil(t, seq) require.NoError(t, err) @@ -74,6 +75,8 @@ func TestBananaBaseNewSequenceBatch(t *testing.T) { Coinbase: []byte{1, 2, 3}, GlobalExitRoot: []byte{4, 5, 6}, } + testData.l1InfoTreeSync.EXPECT().GetInitL1InfoRootMap(mock.Anything).Return(nil, nil).Once() + batch := testData.sut.NewBatchFromL2Block(l2Block) batches := []seqsendertypes.Batch{batch} lastAcc := common.HexToHash("0x8aca9664752dbae36135fd0956c956fc4a370feeac67485b49bcd4b99608ae41") @@ -124,6 +127,41 @@ func TestBananaSanityCheckNilSeq(t *testing.T) { require.Error(t, err, "nil sequence") } +func TestBananaEmptyL1InfoTree(t *testing.T) { + testData := newBananaBaseTestData(t) + + testData.l1Client.On("HeaderByNumber", mock.Anything, mock.Anything). + Return(&types.Header{Number: big.NewInt(69)}, nil) + testData.l1InfoTreeSync.EXPECT().GetLatestInfoUntilBlock(testData.ctx, uint64(69)).Return(nil, l1infotreesync.ErrNotFound) + testData.l1InfoTreeSync.EXPECT().GetInitL1InfoRootMap(testData.ctx).Return(&l1infotreesync.L1InfoTreeInitial{LeafCount: 10}, nil) + + leafCounter, err := testData.sut.GetCounterL1InfoRoot(testData.ctx, 0) + require.NoError(t, err) + require.Equal(t, uint32(10), leafCounter) +} + +func TestCheckL1InfoTreeLeafCounterVsInitL1InfoMap(t *testing.T) { + testData := newBananaBaseTestData(t) + + testData.l1InfoTreeSync.EXPECT().GetInitL1InfoRootMap(testData.ctx).Return(&l1infotreesync.L1InfoTreeInitial{LeafCount: 10}, nil) + err := testData.sut.CheckL1InfoTreeLeafCounterVsInitL1InfoMap(testData.ctx, 10) + require.NoError(t, err, "10 == 10 so is accepted") + + err = testData.sut.CheckL1InfoTreeLeafCounterVsInitL1InfoMap(testData.ctx, 9) + require.Error(t, err, "9 < 10 so is rejected") + + err = testData.sut.CheckL1InfoTreeLeafCounterVsInitL1InfoMap(testData.ctx, 11) + require.NoError(t, err, "11 > 10 so is accepted") +} + +func TestCheckL1InfoTreeLeafCounterVsInitL1InfoMapNotFound(t *testing.T) { + testData := newBananaBaseTestData(t) + + testData.l1InfoTreeSync.EXPECT().GetInitL1InfoRootMap(testData.ctx).Return(nil, nil) + err := testData.sut.CheckL1InfoTreeLeafCounterVsInitL1InfoMap(testData.ctx, 10) + require.NoError(t, err, "10 == 10 so is accepted") +} + type testDataBananaBase struct { rollupContract *mocks_txbuilder.RollupBananaBaseContractor getContract *mocks_txbuilder.GlobalExitRootBananaContractor @@ -131,6 +169,7 @@ type testDataBananaBase struct { sut *txbuilder.TxBuilderBananaBase l1InfoTreeSync *mocks_txbuilder.L1InfoSyncer l1Client *mocks_txbuilder.L1Client + ctx context.Context } func newBananaBaseTestData(t *testing.T) *testDataBananaBase { @@ -155,5 +194,6 @@ func newBananaBaseTestData(t *testing.T) *testDataBananaBase { sut: sut, l1InfoTreeSync: l1InfoSyncer, l1Client: l1Client, + ctx: context.TODO(), } } diff --git a/sequencesender/txbuilder/banana_validium_test.go b/sequencesender/txbuilder/banana_validium_test.go index 8f764595..71f059b9 100644 --- a/sequencesender/txbuilder/banana_validium_test.go +++ b/sequencesender/txbuilder/banana_validium_test.go @@ -34,6 +34,8 @@ func TestBananaValidiumBuildSequenceBatchesTxSequenceErrorsFromDA(t *testing.T) Return(&types.Header{Number: big.NewInt(69)}, nil) testData.l1InfoTreeSync.On("GetLatestInfoUntilBlock", mock.Anything, mock.Anything). Return(&l1infotreesync.L1InfoTreeLeaf{L1InfoTreeIndex: 7}, nil) + testData.l1InfoTreeSync.EXPECT().GetInitL1InfoRootMap(mock.Anything).Return(nil, nil) + seq, err := newSequenceBananaValidiumForTest(testData) require.NoError(t, err) ctx := context.TODO() @@ -53,6 +55,8 @@ func TestBananaValidiumBuildSequenceBatchesTxSequenceDAOk(t *testing.T) { Return(&types.Header{Number: big.NewInt(69)}, nil) testData.l1InfoTreeSync.On("GetLatestInfoUntilBlock", mock.Anything, mock.Anything). Return(&l1infotreesync.L1InfoTreeLeaf{L1InfoTreeIndex: 7}, nil) + testData.l1InfoTreeSync.EXPECT().GetInitL1InfoRootMap(mock.Anything).Return(nil, nil) + seq, err := newSequenceBananaValidiumForTest(testData) require.NoError(t, err) ctx := context.TODO() diff --git a/sequencesender/txbuilder/banana_zkevm_test.go b/sequencesender/txbuilder/banana_zkevm_test.go index a4ff4bd7..4570729e 100644 --- a/sequencesender/txbuilder/banana_zkevm_test.go +++ b/sequencesender/txbuilder/banana_zkevm_test.go @@ -40,6 +40,8 @@ func TestBananaZkevmBuildSequenceBatchesTxOk(t *testing.T) { Return(&types.Header{Number: big.NewInt(69)}, nil) testData.l1InfoTreeSync.On("GetLatestInfoUntilBlock", mock.Anything, mock.Anything). Return(&l1infotreesync.L1InfoTreeLeaf{L1InfoTreeIndex: 7}, nil) + testData.l1InfoTreeSync.EXPECT().GetInitL1InfoRootMap(mock.Anything).Return(nil, nil) + seq, err := newSequenceBananaZKEVMForTest(testData) require.NoError(t, err) @@ -61,6 +63,8 @@ func TestBananaZkevmBuildSequenceBatchesTxErr(t *testing.T) { Return(&types.Header{Number: big.NewInt(69)}, nil) testData.l1InfoTreeSync.On("GetLatestInfoUntilBlock", mock.Anything, mock.Anything). Return(&l1infotreesync.L1InfoTreeLeaf{L1InfoTreeIndex: 7}, nil) + testData.l1InfoTreeSync.EXPECT().GetInitL1InfoRootMap(mock.Anything).Return(nil, nil) + seq, err := newSequenceBananaZKEVMForTest(testData) require.NoError(t, err) diff --git a/sequencesender/txbuilder/mocks_txbuilder/l1_info_syncer.go b/sequencesender/txbuilder/mocks_txbuilder/l1_info_syncer.go index 65bf9394..12d641a8 100644 --- a/sequencesender/txbuilder/mocks_txbuilder/l1_info_syncer.go +++ b/sequencesender/txbuilder/mocks_txbuilder/l1_info_syncer.go @@ -22,6 +22,64 @@ func (_m *L1InfoSyncer) EXPECT() *L1InfoSyncer_Expecter { return &L1InfoSyncer_Expecter{mock: &_m.Mock} } +// GetInitL1InfoRootMap provides a mock function with given fields: ctx +func (_m *L1InfoSyncer) GetInitL1InfoRootMap(ctx context.Context) (*l1infotreesync.L1InfoTreeInitial, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetInitL1InfoRootMap") + } + + var r0 *l1infotreesync.L1InfoTreeInitial + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*l1infotreesync.L1InfoTreeInitial, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *l1infotreesync.L1InfoTreeInitial); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*l1infotreesync.L1InfoTreeInitial) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L1InfoSyncer_GetInitL1InfoRootMap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetInitL1InfoRootMap' +type L1InfoSyncer_GetInitL1InfoRootMap_Call struct { + *mock.Call +} + +// GetInitL1InfoRootMap is a helper method to define mock.On call +// - ctx context.Context +func (_e *L1InfoSyncer_Expecter) GetInitL1InfoRootMap(ctx interface{}) *L1InfoSyncer_GetInitL1InfoRootMap_Call { + return &L1InfoSyncer_GetInitL1InfoRootMap_Call{Call: _e.mock.On("GetInitL1InfoRootMap", ctx)} +} + +func (_c *L1InfoSyncer_GetInitL1InfoRootMap_Call) Run(run func(ctx context.Context)) *L1InfoSyncer_GetInitL1InfoRootMap_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *L1InfoSyncer_GetInitL1InfoRootMap_Call) Return(_a0 *l1infotreesync.L1InfoTreeInitial, _a1 error) *L1InfoSyncer_GetInitL1InfoRootMap_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L1InfoSyncer_GetInitL1InfoRootMap_Call) RunAndReturn(run func(context.Context) (*l1infotreesync.L1InfoTreeInitial, error)) *L1InfoSyncer_GetInitL1InfoRootMap_Call { + _c.Call.Return(run) + return _c +} + // GetLatestInfoUntilBlock provides a mock function with given fields: ctx, blockNum func (_m *L1InfoSyncer) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*l1infotreesync.L1InfoTreeLeaf, error) { ret := _m.Called(ctx, blockNum) diff --git a/sonar-project.properties b/sonar-project.properties index 559f7073..b8f78410 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,11 +7,11 @@ sonar.projectName=cdk sonar.organization=0xpolygon sonar.sources=. -sonar.exclusions=**/test/**,**/vendor/**,**/mocks/**,**/build/**,**/target/**,**/proto/include/**,**/*.pb.go,**/docs/**,**/*.sql +sonar.exclusions=**/test/**,**/vendor/**,**/mocks/**,**/build/**,**/target/**,**/proto/include/**,**/*.pb.go,**/docs/**,**/*.sql,**/mocks_*/* sonar.tests=. sonar.test.inclusions=**/*_test.go -sonar.test.exclusions=**/vendor/**,**/docs/**,**/mocks/**,**/*.pb.go,**/*.yml,**/*.yaml,**/*.json,**/*.xml,**/*.toml +sonar.test.exclusions=**/vendor/**,**/docs/**,**/mocks/**,**/*.pb.go,**/*.yml,**/*.yaml,**/*.json,**/*.xml,**/*.toml,**/mocks_*/* sonar.issue.enforceSemantic=true # ===================================================== diff --git a/sync/mock_l2_test.go b/sync/mock_l2_test.go index 78d75191..7a4bae36 100644 --- a/sync/mock_l2_test.go +++ b/sync/mock_l2_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.39.0. DO NOT EDIT. package sync diff --git a/sync/mock_processor_test.go b/sync/mock_processor_test.go index 8e562e9b..afbb34cb 100644 --- a/sync/mock_processor_test.go +++ b/sync/mock_processor_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.39.0. DO NOT EDIT. package sync diff --git a/sync/mock_reorgdetector_test.go b/sync/mock_reorgdetector_test.go index 52cd0cd0..9689f7e7 100644 --- a/sync/mock_reorgdetector_test.go +++ b/sync/mock_reorgdetector_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.39.0. DO NOT EDIT. package sync diff --git a/test/Makefile b/test/Makefile index 0864b8d2..4833f214 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,5 +1,5 @@ .PHONY: generate-mocks -generate-mocks: generate-mocks-bridgesync generate-mocks-reorgdetector generate-mocks-sequencesender generate-mocks-da generate-mocks-l1infotreesync generate-mocks-helpers generate-mocks-sync +generate-mocks: generate-mocks-bridgesync generate-mocks-reorgdetector generate-mocks-sequencesender generate-mocks-da generate-mocks-l1infotreesync generate-mocks-helpers generate-mocks-sync generate-mocks-l1infotreesync .PHONY: generate-mocks-bridgesync @@ -26,6 +26,25 @@ generate-mocks-rpc: ## Generates mocks for rpc, using mockery tool rm -Rf ../rpc/mocks export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --all --case snake --dir ../rpc --output ../rpc/mocks --outpkg mocks ${COMMON_MOCKERY_PARAMS} +.PHONY: generate-mocks-l1infotreesync +generate-mocks-l1infotreesync: ## Generates mocks for l1infotreesync, using mockery tool + rm -Rf ../l1infotreesync/mocks + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --all --case snake --dir ../l1infotreesync --output ../l1infotreesync/mocks --outpkg mocks_l1infotreesync ${COMMON_MOCKERY_PARAMS} + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ReorgDetector --dir=../sync --output=../l1infotreesync --outpkg=l1infotreesync --structname=ReorgDetectorMock --filename=mock_reorgdetector_test.go + + + +.PHONY: generate-mocks-aggoracle +generate-mocks-helpers: ## Generates mocks for helpers , using mockery tool + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthTxManager --dir=../aggoracle/chaingersender --output=./helpers --outpkg=helpers --structname=EthTxManagerMock --filename=mock_ethtxmanager.go + +.PHONY: generate-mocks-sync +generate-mocks-sync: ## Generates mocks for sync, using mockery tool + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthClienter --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=L2Mock --filename=mock_l2_test.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=evmDownloaderFull --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=EVMDownloaderMock --filename=mock_downloader_test.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=processorInterface --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=ProcessorMock --filename=mock_processor_test.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ReorgDetector --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=ReorgDetectorMock --filename=mock_reorgdetector_test.go + .PHONY: test-e2e-elderberry-validium test-e2e-elderberry-validium: stop ## Runs e2e tests checking elderberry/validium @@ -52,17 +71,3 @@ help: ## Prints this help @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \ | sort \ | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' -.PHONY: generate-mocks-l1infotreesync -generate-mocks-l1infotreesync: ## Generates mocks for l1infotreesync , using mockery tool - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ReorgDetector --dir=../sync --output=../l1infotreesync --outpkg=l1infotreesync --structname=ReorgDetectorMock --filename=mock_reorgdetector_test.go - -.PHONY: generate-mocks-aggoracle -generate-mocks-helpers: ## Generates mocks for helpers , using mockery tool - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthTxManager --dir=../aggoracle/chaingersender --output=./helpers --outpkg=helpers --structname=EthTxManagerMock --filename=mock_ethtxmanager.go - -.PHONY: generate-mocks-sync -generate-mocks-sync: ## Generates mocks for sync, using mockery tool - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthClienter --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=L2Mock --filename=mock_l2_test.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=evmDownloaderFull --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=EVMDownloaderMock --filename=mock_downloader_test.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=processorInterface --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=ProcessorMock --filename=mock_processor_test.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ReorgDetector --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=ReorgDetectorMock --filename=mock_reorgdetector_test.go diff --git a/test/config/test.kurtosis_template.toml b/test/config/test.kurtosis_template.toml index 66471c6a..c065cd6d 100644 --- a/test/config/test.kurtosis_template.toml +++ b/test/config/test.kurtosis_template.toml @@ -1,9 +1,12 @@ ForkUpgradeBatchNumber = 0 ForkUpgradeNewForkId = 0 +[Etherman] + URL = "http://127.0.0.1:${l1_rpc_port}" + [Common] IsValidiumMode = ${zkevm_is_validium} -ContractVersions = "elderberry" +ContractVersions = "${zkevm_contract_versions}" [Common.Translator] FullMatchRules = [ {Old="http://zkevm-dac-001:8484", New="http://127.0.0.1:${dac_port}"}, @@ -11,7 +14,7 @@ ContractVersions = "elderberry" [Log] Environment = "development" # "production" or "development" -Level = "info" +Level = "debug" Outputs = ["stderr"] [SequenceSender] @@ -27,7 +30,7 @@ SequencesTxFileName = "sequencesender.json" GasOffset = 80000 WaitPeriodPurgeTxFile = "15m" MaxPendingTx = 1 -SanityCheckRPCURL = "http://127.0.0.1:8123" +SanityCheckRPCURL = "${l2_rpc_addr}" [SequenceSender.StreamClient] Server = "127.0.0.1:${zkevm_data_streamer_port}" [SequenceSender.EthTxManager] @@ -124,3 +127,23 @@ SequencerPrivateKey = {} [Aggregator.Synchronizer.Etherman] [Aggregator.Synchronizer.Etherman.Validium] Enabled = ${zkevm_is_validium} + + +[L1InfoTreeSync] +DBPath = "/tmp/L1InfoTreeSync.sqlite" +GlobalExitRootAddr="${zkevm_global_exit_root_address}" +RollupManagerAddr="${zkevm_rollup_manager_address}" +SyncBlockChunkSize=100 +BlockFinality="LatestBlock" +# http://el-1-geth-lighthouse:8545 +URLRPCL1="${l1_rpc_addr}" +WaitForNewBlocksPeriod="100ms" +InitialBlock=0 + + +[NetworkConfig.L1] +L1ChainID = ${l1_chain_id} +PolAddr = "${pol_token_address}" +ZkEVMAddr = "${zkevm_rollup_address}" +RollupManagerAddr = "${zkevm_rollup_manager_address}" +GlobalExitRootManagerAddr = "${zkevm_global_exit_root_address}" diff --git a/test/helpers/aggoracle_e2e.go b/test/helpers/aggoracle_e2e.go index 8b5073fb..125d73cf 100644 --- a/test/helpers/aggoracle_e2e.go +++ b/test/helpers/aggoracle_e2e.go @@ -117,7 +117,8 @@ func CommonSetup(t *testing.T) ( require.NoError(t, err) // Syncer dbPathSyncer := path.Join(t.TempDir(), "file::memory:?cache=shared") - syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, common.Address{}, syncBlockChunkSize, etherman.LatestBlock, reorg, l1Client.Client(), time.Millisecond, 0, periodRetry, retries) + syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, common.Address{}, syncBlockChunkSize, etherman.LatestBlock, reorg, l1Client.Client(), time.Millisecond, 0, periodRetry, retries, + l1infotreesync.FlagAllowWrongContractsAddrs) require.NoError(t, err) go syncer.Start(ctx) diff --git a/test/helpers/mock_ethtxmanager.go b/test/helpers/mock_ethtxmanager.go index 848992f4..4dd103af 100644 --- a/test/helpers/mock_ethtxmanager.go +++ b/test/helpers/mock_ethtxmanager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.39.0. DO NOT EDIT. package helpers diff --git a/tree/tree.go b/tree/tree.go index 5d307e8a..0e3a0c69 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -172,8 +172,11 @@ func (t *Tree) storeRoot(tx db.Txer, root types.Root) error { } // GetLastRoot returns the last processed root -func (t *Tree) GetLastRoot(ctx context.Context) (types.Root, error) { - return t.getLastRootWithTx(t.db) +func (t *Tree) GetLastRoot(tx db.Querier) (types.Root, error) { + if tx == nil { + tx = t.db + } + return t.getLastRootWithTx(tx) } func (t *Tree) getLastRootWithTx(tx db.Querier) (types.Root, error) { @@ -223,10 +226,10 @@ func (t *Tree) GetRootByHash(ctx context.Context, hash common.Hash) (*types.Root return root, nil } -func (t *Tree) GetLeaf(ctx context.Context, index uint32, root common.Hash) (common.Hash, error) { +func (t *Tree) GetLeaf(tx db.Querier, index uint32, root common.Hash) (common.Hash, error) { currentNodeHash := root for h := int(types.DefaultHeight - 1); h >= 0; h-- { - currentNode, err := t.getRHTNode(t.db, currentNodeHash) + currentNode, err := t.getRHTNode(tx, currentNodeHash) if err != nil { return common.Hash{}, err } diff --git a/tree/tree_test.go b/tree/tree_test.go index dc2cfc9e..c2748856 100644 --- a/tree/tree_test.go +++ b/tree/tree_test.go @@ -55,7 +55,7 @@ func TestCheckExpectedRoot(t *testing.T) { addLeaves(merkleTree, treeDB, numOfLeavesToAdd, 0) - expectedRoot, err := merkleTree.GetLastRoot(context.Background()) + expectedRoot, err := merkleTree.GetLastRoot(nil) require.NoError(t, err) addLeaves(merkleTree, treeDB, numOfLeavesToAdd, numOfLeavesToAdd) @@ -74,7 +74,7 @@ func TestCheckExpectedRoot(t *testing.T) { addLeaves(merkleTree, treeDB, numOfLeavesToAdd, 0) - expectedRoot, err := merkleTree.GetLastRoot(context.Background()) + expectedRoot, err := merkleTree.GetLastRoot(nil) require.NoError(t, err) addLeaves(merkleTree, treeDB, numOfLeavesToAdd, numOfLeavesToAdd) @@ -134,7 +134,7 @@ func TestMTAddLeaf(t *testing.T) { } require.NoError(t, tx.Commit()) if len(testVector.ExistingLeaves) > 0 { - root, err := merkletree.GetLastRoot(ctx) + root, err := merkletree.GetLastRoot(nil) require.NoError(t, err) require.Equal(t, common.HexToHash(testVector.CurrentRoot), root.Hash) } @@ -149,7 +149,7 @@ func TestMTAddLeaf(t *testing.T) { require.NoError(t, err) require.NoError(t, tx.Commit()) - root, err := merkletree.GetLastRoot(ctx) + root, err := merkletree.GetLastRoot(nil) require.NoError(t, err) require.Equal(t, common.HexToHash(testVector.NewRoot), root.Hash) }) @@ -185,7 +185,7 @@ func TestMTGetProof(t *testing.T) { } require.NoError(t, tx.Commit()) - root, err := tre.GetLastRoot(ctx) + root, err := tre.GetLastRoot(nil) require.NoError(t, err) expectedRoot := common.HexToHash(testVector.ExpectedRoot) require.Equal(t, expectedRoot, root.Hash) @@ -198,3 +198,13 @@ func TestMTGetProof(t *testing.T) { }) } } + +func createTreeDBForTest(t *testing.T) *sql.DB { + t.Helper() + dbPath := "file::memory:?cache=shared" + err := migrations.RunMigrations(dbPath) + require.NoError(t, err) + treeDB, err := db.NewSQLiteDB(dbPath) + require.NoError(t, err) + return treeDB +} diff --git a/tree/updatabletree_test.go b/tree/updatabletree_test.go new file mode 100644 index 00000000..a684fd0e --- /dev/null +++ b/tree/updatabletree_test.go @@ -0,0 +1,49 @@ +package tree_test + +import ( + "context" + "testing" + + "github.com/0xPolygon/cdk/db" + "github.com/0xPolygon/cdk/tree" + "github.com/0xPolygon/cdk/tree/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestUpdatableTreeExploratory(t *testing.T) { + treeDB := createTreeDBForTest(t) + sut := tree.NewUpdatableTree(treeDB, "") + blockNum := uint64(1) + blockPosition := uint64(1) + leaf1 := types.Leaf{ + Index: 10, + Hash: common.HexToHash("0x123456"), + } + leaf2 := types.Leaf{ + Index: 1, + Hash: common.HexToHash("0x123478"), + } + ctx := context.TODO() + + tx, err := db.NewTx(ctx, treeDB) + require.NoError(t, err) + _, err = sut.UpsertLeaf(tx, blockNum, blockPosition, leaf1) + require.NoError(t, err) + + root2, err := sut.UpsertLeaf(tx, blockNum, blockPosition, leaf2) + require.NoError(t, err) + leaf1get, err := sut.GetLeaf(tx, leaf1.Index, root2) + require.NoError(t, err) + require.Equal(t, leaf1.Hash, leaf1get) + // If a leaf dont exist return 'not found' error + _, err = sut.GetLeaf(tx, 99, root2) + require.ErrorIs(t, err, db.ErrNotFound) + leaf99 := types.Leaf{ + Index: 99, + Hash: common.Hash{}, // 0x00000 + } + + _, err = sut.UpsertLeaf(tx, blockNum, blockPosition, leaf99) + require.Error(t, err, "insert 0x000 doesnt change root and return UNIQUE constraint failed: root.hash") +}