From 30880cd23b5e69ffb17ad9d4c56e3cbcd6a370ff Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 23 Jul 2024 19:30:57 +0200 Subject: [PATCH 01/49] implementation completed, missing tests --- l1infotreesync/downloader.go | 228 ++++++++++ l1infotreesync/downloader_test.go | 398 ++++++++++++++++++ l1infotreesync/driver.go | 140 +++++++ l1infotreesync/driver_test.go | 211 ++++++++++ l1infotreesync/e2e_test.go | 71 ++++ l1infotreesync/l1infotreesync.go | 55 +++ l1infotreesync/mock_downloader_test.go | 100 +++++ l1infotreesync/mock_l2_test.go | 484 +++++++++++++++++++++ l1infotreesync/mock_processor_test.go | 81 ++++ l1infotreesync/mock_reorgdetector_test.go | 63 +++ l1infotreesync/processor.go | 487 ++++++++++++++++++++++ l1infotreesync/processor_test.go | 373 +++++++++++++++++ test/Makefile | 7 + 13 files changed, 2698 insertions(+) create mode 100644 l1infotreesync/downloader.go create mode 100644 l1infotreesync/downloader_test.go create mode 100644 l1infotreesync/driver.go create mode 100644 l1infotreesync/driver_test.go create mode 100644 l1infotreesync/e2e_test.go create mode 100644 l1infotreesync/l1infotreesync.go create mode 100644 l1infotreesync/mock_downloader_test.go create mode 100644 l1infotreesync/mock_l2_test.go create mode 100644 l1infotreesync/mock_processor_test.go create mode 100644 l1infotreesync/mock_reorgdetector_test.go create mode 100644 l1infotreesync/processor.go create mode 100644 l1infotreesync/processor_test.go diff --git a/l1infotreesync/downloader.go b/l1infotreesync/downloader.go new file mode 100644 index 00000000..a8ca4cc2 --- /dev/null +++ b/l1infotreesync/downloader.go @@ -0,0 +1,228 @@ +package l1infotreesync + +import ( + "context" + "math/big" + "time" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry/polygonzkevmglobalexitrootv2" + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +const ( + waitForNewBlocksPeriod = time.Millisecond * 100 +) + +var ( + updateL1InfoTreeSignature = crypto.Keccak256Hash([]byte("UpdateL1InfoTree(bytes32,bytes32)")) +) + +type EthClienter interface { + ethereum.LogFilterer + ethereum.BlockNumberReader + ethereum.ChainReader + bind.ContractBackend +} + +type downloaderInterface interface { + waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) + getEventsByBlockRange(ctx context.Context, fromBlock, toBlock uint64) []block + getLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log + appendLog(b *block, l types.Log) + getBlockHeader(ctx context.Context, blockNum uint64) blockHeader +} + +type L1InfoTreeUpdate struct { + MainnetExitRoot common.Hash + RollupExitRoot common.Hash +} + +type block struct { + blockHeader + Events []L1InfoTreeUpdate +} + +type blockHeader struct { + Num uint64 + Hash common.Hash + ParentHash common.Hash + Timestamp uint64 +} + +type downloader struct { + syncBlockChunkSize uint64 + downloaderInterface +} + +func newDownloader( + GERAddr common.Address, + ethClient EthClienter, + syncBlockChunkSize uint64, + blockFinalityType etherman.BlockNumberFinality, +) (*downloader, error) { + GERContract, err := polygonzkevmglobalexitrootv2.NewPolygonzkevmglobalexitrootv2(GERAddr, ethClient) + if err != nil { + return nil, err + } + finality, err := blockFinalityType.ToBlockNum() + if err != nil { + return nil, err + } + return &downloader{ + syncBlockChunkSize: syncBlockChunkSize, + downloaderInterface: &downloaderImplementation{ + GERAddr: GERAddr, + GERContract: GERContract, + ethClient: ethClient, + blockFinality: finality, + }, + }, nil +} + +func (d *downloader) download(ctx context.Context, fromBlock uint64, downloadedCh chan block) { + lastBlock := d.waitForNewBlocks(ctx, 0) + for { + select { + case <-ctx.Done(): + log.Debug("closing channel") + close(downloadedCh) + return + default: + } + toBlock := fromBlock + d.syncBlockChunkSize + if toBlock > lastBlock { + toBlock = lastBlock + } + if fromBlock > toBlock { + log.Debug("waiting for new blocks, last block ", toBlock) + lastBlock = d.waitForNewBlocks(ctx, toBlock) + continue + } + log.Debugf("getting events from blocks %d to %d", fromBlock, toBlock) + blocks := d.getEventsByBlockRange(ctx, fromBlock, toBlock) + for _, b := range blocks { + log.Debugf("sending block %d to the driver (with events)", b.Num) + downloadedCh <- b + } + if len(blocks) == 0 || blocks[len(blocks)-1].Num < toBlock { + // Indicate the last downloaded block if there are not events on it + log.Debugf("sending block %d to the driver (without evvents)", toBlock) + downloadedCh <- block{ + blockHeader: d.getBlockHeader(ctx, toBlock), + } + } + fromBlock = toBlock + 1 + } +} + +type downloaderImplementation struct { + GERAddr common.Address + GERContract *polygonzkevmglobalexitrootv2.Polygonzkevmglobalexitrootv2 + ethClient EthClienter + blockFinality *big.Int +} + +func (d *downloaderImplementation) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) { + attempts := 0 + for { + header, err := d.ethClient.HeaderByNumber(ctx, d.blockFinality) + if err != nil { + attempts++ + log.Error("error geting last block num from eth client: ", err) + retryHandler("waitForNewBlocks", attempts) + continue + } + if header.Number.Uint64() > lastBlockSeen { + return header.Number.Uint64() + } + time.Sleep(waitForNewBlocksPeriod) + } +} + +func (d *downloaderImplementation) getEventsByBlockRange(ctx context.Context, fromBlock, toBlock uint64) []block { + blocks := []block{} + logs := d.getLogs(ctx, fromBlock, toBlock) + for _, l := range logs { + if len(blocks) == 0 || blocks[len(blocks)-1].Num < l.BlockNumber { + b := d.getBlockHeader(ctx, l.BlockNumber) + blocks = append(blocks, block{ + blockHeader: blockHeader{ + Num: l.BlockNumber, + Hash: l.BlockHash, + Timestamp: b.Timestamp, + ParentHash: b.ParentHash, + }, + Events: []L1InfoTreeUpdate{}, + }) + } + d.appendLog(&blocks[len(blocks)-1], l) + } + + return blocks +} + +func (d *downloaderImplementation) getLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log { + query := ethereum.FilterQuery{ + FromBlock: new(big.Int).SetUint64(fromBlock), + Addresses: []common.Address{d.GERAddr}, + Topics: [][]common.Hash{ + {updateL1InfoTreeSignature}, + }, + ToBlock: new(big.Int).SetUint64(toBlock), + } + attempts := 0 + for { + logs, err := d.ethClient.FilterLogs(ctx, query) + if err != nil { + attempts++ + log.Error("error calling FilterLogs to eth client: ", err) + retryHandler("getLogs", attempts) + continue + } + return logs + } +} + +func (d *downloaderImplementation) appendLog(b *block, l types.Log) { + switch l.Topics[0] { + case updateL1InfoTreeSignature: + l1InfoTreeUpdate, err := d.GERContract.ParseUpdateL1InfoTree(l) + if err != nil { + log.Fatalf( + "error parsing log %+v using d.GERContract.ParseUpdateL1InfoTree: %v", + l, err, + ) + } + b.Events = append(b.Events, L1InfoTreeUpdate{ + MainnetExitRoot: l1InfoTreeUpdate.MainnetExitRoot, + RollupExitRoot: l1InfoTreeUpdate.MainnetExitRoot, + }) + default: + log.Fatalf("unexpected log %+v", l) + } +} + +func (d *downloaderImplementation) getBlockHeader(ctx context.Context, blockNum uint64) blockHeader { + attempts := 0 + for { + header, err := d.ethClient.HeaderByNumber(ctx, big.NewInt(int64(blockNum))) + if err != nil { + attempts++ + log.Errorf("error getting block header for block %d, err: %v", blockNum, err) + retryHandler("getBlockHeader", attempts) + continue + } + return blockHeader{ + Num: header.Number.Uint64(), + Hash: header.Hash(), + ParentHash: header.ParentHash, + Timestamp: header.Time, + } + } +} diff --git a/l1infotreesync/downloader_test.go b/l1infotreesync/downloader_test.go new file mode 100644 index 00000000..28daf69b --- /dev/null +++ b/l1infotreesync/downloader_test.go @@ -0,0 +1,398 @@ +package l1infotreesync + +import ( + "context" + "errors" + "math/big" + "testing" + "time" + + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +var ( + contractAddr = common.HexToAddress("1234567890") +) + +const ( + syncBlockChunck = uint64(10) +) + +func TestGetEventsByBlockRange(t *testing.T) { + type testCase struct { + description string + inputLogs []types.Log + fromBlock, toBlock uint64 + expectedBlocks []block + } + testCases := []testCase{} + clientMock := NewL2Mock(t) + ctx := context.Background() + d, err := newDownloader(contractAddr, clientMock, syncBlockChunck, etherman.LatestBlock) + require.NoError(t, err) + + // case 0: single block, no events + case0 := testCase{ + description: "case 0: single block, no events", + inputLogs: []types.Log{}, + fromBlock: 1, + toBlock: 3, + expectedBlocks: []block{}, + } + testCases = append(testCases, case0) + + // case 1: single block, single event + logC1, updateC1 := generateUpdateL1InfoTree(t, 3) + logsC1 := []types.Log{ + *logC1, + } + blocksC1 := []block{ + { + blockHeader: blockHeader{ + Num: logC1.BlockNumber, + Hash: logC1.BlockHash, + ParentHash: common.HexToHash("foo"), + }, + Events: []L1InfoTreeUpdate{ + updateC1, + }, + }, + } + case1 := testCase{ + description: "case 1: single block, single event", + inputLogs: logsC1, + fromBlock: 3, + toBlock: 3, + expectedBlocks: blocksC1, + } + testCases = append(testCases, case1) + + // case 2: single block, multiple events + logC2_1, updateC2_1 := generateUpdateL1InfoTree(t, 5) + logC2_2, updateC2_2 := generateUpdateL1InfoTree(t, 5) + logC2_3, updateC2_3 := generateUpdateL1InfoTree(t, 5) + logC2_4, updateC2_4 := generateUpdateL1InfoTree(t, 5) + logsC2 := []types.Log{ + *logC2_1, + *logC2_2, + *logC2_3, + *logC2_4, + } + blocksC2 := []block{ + { + blockHeader: blockHeader{ + Num: logC2_1.BlockNumber, + Hash: logC2_1.BlockHash, + ParentHash: common.HexToHash("foo"), + }, + Events: []L1InfoTreeUpdate{ + updateC2_1, + updateC2_2, + updateC2_3, + updateC2_4, + }, + }, + } + case2 := testCase{ + description: "case 2: single block, multiple events", + inputLogs: logsC2, + fromBlock: 5, + toBlock: 5, + expectedBlocks: blocksC2, + } + testCases = append(testCases, case2) + + // case 3: multiple blocks, some events + logC3_1, updateC3_1 := generateUpdateL1InfoTree(t, 7) + logC3_2, updateC3_2 := generateUpdateL1InfoTree(t, 7) + logC3_3, updateC3_3 := generateUpdateL1InfoTree(t, 8) + logC3_4, updateC3_4 := generateUpdateL1InfoTree(t, 8) + logsC3 := []types.Log{ + *logC3_1, + *logC3_2, + *logC3_3, + *logC3_4, + } + blocksC3 := []block{ + { + blockHeader: blockHeader{ + Num: logC3_1.BlockNumber, + Hash: logC3_1.BlockHash, + ParentHash: common.HexToHash("foo"), + }, + Events: []L1InfoTreeUpdate{ + updateC3_1, + updateC3_2, + }, + }, + { + blockHeader: blockHeader{ + Num: logC3_3.BlockNumber, + Hash: logC3_3.BlockHash, + ParentHash: common.HexToHash("foo"), + }, + Events: []L1InfoTreeUpdate{ + updateC3_3, + updateC3_4, + }, + }, + } + case3 := testCase{ + description: "case 3: multiple blocks, some events", + inputLogs: logsC3, + fromBlock: 7, + toBlock: 8, + expectedBlocks: blocksC3, + } + testCases = append(testCases, case3) + + for _, tc := range testCases { + query := ethereum.FilterQuery{ + FromBlock: new(big.Int).SetUint64(tc.fromBlock), + Addresses: []common.Address{contractAddr}, + Topics: [][]common.Hash{ + {updateL1InfoTreeSignature}, + }, + ToBlock: new(big.Int).SetUint64(tc.toBlock), + } + clientMock. + On("FilterLogs", mock.Anything, query). + Return(tc.inputLogs, nil) + for _, b := range tc.expectedBlocks { + clientMock. + On("HeaderByNumber", mock.Anything, big.NewInt(int64(b.Num))). + Return(&types.Header{ + Number: big.NewInt(int64(b.Num)), + ParentHash: common.HexToHash("foo"), + }, nil) + } + + actualBlocks := d.getEventsByBlockRange(ctx, tc.fromBlock, tc.toBlock) + require.Equal(t, tc.expectedBlocks, actualBlocks, tc.description) + } +} + +func generateUpdateL1InfoTree(t *testing.T, blockNum uint32) (*types.Log, L1InfoTreeUpdate) { + b := L1InfoTreeUpdate{ + MainnetExitRoot: common.BigToHash(big.NewInt(int64(blockNum))), + RollupExitRoot: common.BigToHash(big.NewInt(int64(blockNum))), + } + var rollup, mainnet [32]byte + mainnet = b.MainnetExitRoot + rollup = b.RollupExitRoot + log := &types.Log{ + Address: contractAddr, + BlockNumber: uint64(blockNum), + BlockHash: common.BytesToHash(uint64ToBytes(uint64(blockNum))), + Topics: []common.Hash{ + updateL1InfoTreeSignature, + mainnet, + rollup, + }, + Data: nil, + } + return log, b +} + +func TestDownload(t *testing.T) { + /* + NOTE: due to the concurrent nature of this test (the function being tested runs through a goroutine) + if the mock doesn't match, the goroutine will get stuck and the test will timeout + */ + d := NewDownloaderMock(t) + downloadCh := make(chan block, 1) + ctx := context.Background() + ctx1, cancel := context.WithCancel(ctx) + expectedBlocks := []block{} + clientMock := NewL2Mock(t) + dwnldr, err := newDownloader(contractAddr, clientMock, syncBlockChunck, etherman.LatestBlock) + require.NoError(t, err) + dwnldr.downloaderInterface = d + + d.On("waitForNewBlocks", mock.Anything, uint64(0)). + Return(uint64(1)) + // iteratiion 0: + // last block is 1, download that block (no events and wait) + b1 := block{ + blockHeader: blockHeader{ + Num: 1, + Hash: common.HexToHash("01"), + }, + } + expectedBlocks = append(expectedBlocks, b1) + d.On("getEventsByBlockRange", mock.Anything, uint64(0), uint64(1)). + Return([]block{}) + d.On("getBlockHeader", mock.Anything, uint64(1)). + Return(b1.blockHeader) + + // iteration 1: wait for next block to be created + d.On("waitForNewBlocks", mock.Anything, uint64(1)). + After(time.Millisecond * 100). + Return(uint64(2)).Once() + + // iteration 2: block 2 has events + b2 := block{ + blockHeader: blockHeader{ + Num: 2, + Hash: common.HexToHash("02"), + }, + } + expectedBlocks = append(expectedBlocks, b2) + d.On("getEventsByBlockRange", mock.Anything, uint64(2), uint64(2)). + Return([]block{b2}) + + // iteration 3: wait for next block to be created (jump to block 8) + d.On("waitForNewBlocks", mock.Anything, uint64(2)). + After(time.Millisecond * 100). + Return(uint64(8)).Once() + + // iteration 4: blocks 6 and 7 have events + b6 := block{ + blockHeader: blockHeader{ + Num: 6, + Hash: common.HexToHash("06"), + }, + Events: []L1InfoTreeUpdate{ + {RollupExitRoot: common.HexToHash("06")}, + }, + } + b7 := block{ + blockHeader: blockHeader{ + Num: 7, + Hash: common.HexToHash("07"), + }, + Events: []L1InfoTreeUpdate{ + {MainnetExitRoot: common.HexToHash("07")}, + }, + } + b8 := block{ + blockHeader: blockHeader{ + Num: 8, + Hash: common.HexToHash("08"), + }, + } + expectedBlocks = append(expectedBlocks, b6, b7, b8) + d.On("getEventsByBlockRange", mock.Anything, uint64(3), uint64(8)). + Return([]block{b6, b7}) + d.On("getBlockHeader", mock.Anything, uint64(8)). + Return(b8.blockHeader) + + // iteration 5: wait for next block to be created (jump to block 30) + d.On("waitForNewBlocks", mock.Anything, uint64(8)). + After(time.Millisecond * 100). + Return(uint64(30)).Once() + + // iteration 6: from block 9 to 19, no events + b19 := block{ + blockHeader: blockHeader{ + Num: 19, + Hash: common.HexToHash("19"), + }, + } + expectedBlocks = append(expectedBlocks, b19) + d.On("getEventsByBlockRange", mock.Anything, uint64(9), uint64(19)). + Return([]block{}) + d.On("getBlockHeader", mock.Anything, uint64(19)). + Return(b19.blockHeader) + + // iteration 7: from block 20 to 30, events on last block + b30 := block{ + blockHeader: blockHeader{ + Num: 30, + Hash: common.HexToHash("30"), + }, + Events: []L1InfoTreeUpdate{ + {RollupExitRoot: common.HexToHash("30")}, + }, + } + expectedBlocks = append(expectedBlocks, b30) + d.On("getEventsByBlockRange", mock.Anything, uint64(20), uint64(30)). + Return([]block{b30}) + + // iteration 8: wait for next block to be created (jump to block 35) + d.On("waitForNewBlocks", mock.Anything, uint64(30)). + After(time.Millisecond * 100). + Return(uint64(35)).Once() + + go dwnldr.download(ctx1, 0, downloadCh) + for _, expectedBlock := range expectedBlocks { + actualBlock := <-downloadCh + log.Debugf("block %d received!", actualBlock.Num) + require.Equal(t, expectedBlock, actualBlock) + } + log.Debug("canceling") + cancel() + _, ok := <-downloadCh + require.False(t, ok) +} + +func TestWaitForNewBlocks(t *testing.T) { + retryAfterErrorPeriod = time.Millisecond * 100 + clientMock := NewL2Mock(t) + ctx := context.Background() + d, err := newDownloader(contractAddr, clientMock, syncBlockChunck, etherman.LatestBlock) + require.NoError(t, err) + + // at first attempt + currentBlock := uint64(5) + expectedBlock := uint64(6) + clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(&types.Header{ + Number: big.NewInt(6), + }, nil).Once() + actualBlock := d.waitForNewBlocks(ctx, currentBlock) + assert.Equal(t, expectedBlock, actualBlock) + + // 2 iterations + clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(&types.Header{ + Number: big.NewInt(5), + }, nil).Once() + clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(&types.Header{ + Number: big.NewInt(6), + }, nil).Once() + actualBlock = d.waitForNewBlocks(ctx, currentBlock) + assert.Equal(t, expectedBlock, actualBlock) + + // after error from client + clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(nil, errors.New("foo")).Once() + clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(&types.Header{ + Number: big.NewInt(6), + }, nil).Once() + actualBlock = d.waitForNewBlocks(ctx, currentBlock) + assert.Equal(t, expectedBlock, actualBlock) +} + +func TestGetBlockHeader(t *testing.T) { + retryAfterErrorPeriod = time.Millisecond * 100 + clientMock := NewL2Mock(t) + ctx := context.Background() + d, err := newDownloader(contractAddr, clientMock, syncBlockChunck, etherman.LatestBlock) + require.NoError(t, err) + + blockNum := uint64(5) + blockNumBig := big.NewInt(5) + returnedBlock := &types.Header{ + Number: blockNumBig, + } + expectedBlock := blockHeader{ + Num: 5, + Hash: returnedBlock.Hash(), + } + + // at first attempt + clientMock.On("HeaderByNumber", ctx, blockNumBig).Return(returnedBlock, nil).Once() + actualBlock := d.getBlockHeader(ctx, blockNum) + assert.Equal(t, expectedBlock, actualBlock) + + // after error from client + clientMock.On("HeaderByNumber", ctx, blockNumBig).Return(nil, errors.New("foo")).Once() + clientMock.On("HeaderByNumber", ctx, blockNumBig).Return(returnedBlock, nil).Once() + actualBlock = d.getBlockHeader(ctx, blockNum) + assert.Equal(t, expectedBlock, actualBlock) +} diff --git a/l1infotreesync/driver.go b/l1infotreesync/driver.go new file mode 100644 index 00000000..9952f797 --- /dev/null +++ b/l1infotreesync/driver.go @@ -0,0 +1,140 @@ +package l1infotreesync + +import ( + "context" + + "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/reorgdetector" + "github.com/ethereum/go-ethereum/common" +) + +const ( + downloadBufferSize = 1000 + reorgDetectorID = "localbridgesync" +) + +type downloaderFull interface { + downloaderInterface + download(ctx context.Context, fromBlock uint64, downloadedCh chan block) +} + +type driver struct { + reorgDetector ReorgDetector + reorgSub *reorgdetector.Subscription + processor processorInterface + downloader downloaderFull +} + +type processorInterface interface { + getLastProcessedBlock(ctx context.Context) (uint64, error) + processBlock(block block) error + reorg(firstReorgedBlock uint64) error +} + +type ReorgDetector interface { + Subscribe(id string) *reorgdetector.Subscription + AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error +} + +func newDriver( + reorgDetector ReorgDetector, + processor processorInterface, + downloader downloaderFull, +) (*driver, error) { + reorgSub := reorgDetector.Subscribe(reorgDetectorID) + return &driver{ + reorgDetector: reorgDetector, + reorgSub: reorgSub, + processor: processor, + downloader: downloader, + }, nil +} + +func (d *driver) Sync(ctx context.Context) { +reset: + var ( + lastProcessedBlock uint64 + attempts int + err error + ) + for { + lastProcessedBlock, err = d.processor.getLastProcessedBlock(ctx) + if err != nil { + attempts++ + log.Error("error geting last processed block: ", err) + retryHandler("Sync", attempts) + continue + } + break + } + cancellableCtx, cancel := context.WithCancel(ctx) + defer cancel() + + // start downloading + downloadCh := make(chan block, downloadBufferSize) + go d.downloader.download(cancellableCtx, lastProcessedBlock, downloadCh) + + for { + select { + case b := <-downloadCh: + log.Debug("handleNewBlock") + d.handleNewBlock(ctx, b) + case firstReorgedBlock := <-d.reorgSub.FirstReorgedBlock: + log.Debug("handleReorg") + d.handleReorg(cancel, downloadCh, firstReorgedBlock) + goto reset + } + } +} + +func (d *driver) handleNewBlock(ctx context.Context, b block) { + attempts := 0 + for { + err := d.reorgDetector.AddBlockToTrack(ctx, reorgDetectorID, b.Num, b.Hash) + if err != nil { + attempts++ + log.Errorf("error adding block %d to tracker: %v", b.Num, err) + retryHandler("handleNewBlock", attempts) + continue + } + break + } + attempts = 0 + for { + err := d.processor.processBlock(b) + if err != nil { + attempts++ + log.Errorf("error processing events for blcok %d, err: ", b.Num, err) + retryHandler("handleNewBlock", attempts) + continue + } + break + } +} + +func (d *driver) handleReorg( + cancel context.CancelFunc, downloadCh chan block, firstReorgedBlock uint64, +) { + // stop downloader + cancel() + _, ok := <-downloadCh + for ok { + _, ok = <-downloadCh + } + // handle reorg + attempts := 0 + for { + err := d.processor.reorg(firstReorgedBlock) + if err != nil { + attempts++ + log.Errorf( + "error processing reorg, last valid block %d, err: %v", + firstReorgedBlock, err, + ) + retryHandler("handleReorg", attempts) + continue + } + break + } + d.reorgSub.ReorgProcessed <- true +} diff --git a/l1infotreesync/driver_test.go b/l1infotreesync/driver_test.go new file mode 100644 index 00000000..e206973a --- /dev/null +++ b/l1infotreesync/driver_test.go @@ -0,0 +1,211 @@ +package l1infotreesync + +import ( + "context" + "errors" + "sync" + "testing" + "time" + + "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/reorgdetector" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestSync(t *testing.T) { + retryAfterErrorPeriod = time.Millisecond * 100 + rdm := NewReorgDetectorMock(t) + pm := NewProcessorMock(t) + dm := NewDownloaderMock(t) + firstReorgedBlock := make(chan uint64) + reorgProcessed := make(chan bool) + rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{ + FirstReorgedBlock: firstReorgedBlock, + ReorgProcessed: reorgProcessed, + }) + driver, err := newDriver(rdm, pm, dm) + require.NoError(t, err) + ctx := context.Background() + expectedBlock1 := block{ + blockHeader: blockHeader{ + Num: 3, + Hash: common.HexToHash("03"), + }, + } + expectedBlock2 := block{ + blockHeader: blockHeader{ + Num: 9, + Hash: common.HexToHash("09"), + }, + } + type reorgSemaphore struct { + mu sync.Mutex + green bool + } + reorg1Completed := reorgSemaphore{} + dm.On("download", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + ctx := args.Get(0).(context.Context) + downloadedCh := args.Get(2).(chan block) + log.Info("entering mock loop") + for { + select { + case <-ctx.Done(): + log.Info("closing channel") + close(downloadedCh) + return + default: + } + reorg1Completed.mu.Lock() + green := reorg1Completed.green + reorg1Completed.mu.Unlock() + if green { + downloadedCh <- expectedBlock2 + } else { + downloadedCh <- expectedBlock1 + } + time.Sleep(100 * time.Millisecond) + } + }) + + // Mocking this actions, the driver should "store" all the blocks from the downloader + pm.On("getLastProcessedBlock", ctx). + Return(uint64(3), nil) + rdm.On("AddBlockToTrack", ctx, reorgDetectorID, expectedBlock1.Num, expectedBlock1.Hash). + Return(nil) + pm.On("storeBridgeEvents", expectedBlock1.Num, expectedBlock1.Events). + Return(nil) + rdm.On("AddBlockToTrack", ctx, reorgDetectorID, expectedBlock2.Num, expectedBlock2.Hash). + Return(nil) + pm.On("storeBridgeEvents", expectedBlock2.Num, expectedBlock2.Events). + Return(nil) + go driver.Sync(ctx) + time.Sleep(time.Millisecond * 200) // time to download expectedBlock1 + + // Trigger reorg 1 + reorgedBlock1 := uint64(5) + pm.On("reorg", reorgedBlock1).Return(nil) + firstReorgedBlock <- reorgedBlock1 + ok := <-reorgProcessed + require.True(t, ok) + reorg1Completed.mu.Lock() + reorg1Completed.green = true + reorg1Completed.mu.Unlock() + time.Sleep(time.Millisecond * 200) // time to download expectedBlock2 + + // Trigger reorg 2: syncer restarts the porcess + reorgedBlock2 := uint64(7) + pm.On("reorg", reorgedBlock2).Return(nil) + firstReorgedBlock <- reorgedBlock2 + ok = <-reorgProcessed + require.True(t, ok) +} + +func TestHandleNewBlock(t *testing.T) { + retryAfterErrorPeriod = time.Millisecond * 100 + rdm := NewReorgDetectorMock(t) + pm := NewProcessorMock(t) + dm := NewDownloaderMock(t) + rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{}) + driver, err := newDriver(rdm, pm, dm) + require.NoError(t, err) + ctx := context.Background() + + // happy path + b1 := block{ + blockHeader: blockHeader{ + Num: 1, + Hash: common.HexToHash("f00"), + }, + } + rdm. + On("AddBlockToTrack", ctx, reorgDetectorID, b1.Num, b1.Hash). + Return(nil) + pm.On("storeBridgeEvents", b1.Num, b1.Events). + Return(nil) + driver.handleNewBlock(ctx, b1) + + // reorg deteector fails once + b2 := block{ + blockHeader: blockHeader{ + Num: 2, + Hash: common.HexToHash("f00"), + }, + } + rdm. + On("AddBlockToTrack", ctx, reorgDetectorID, b2.Num, b2.Hash). + Return(errors.New("foo")).Once() + rdm. + On("AddBlockToTrack", ctx, reorgDetectorID, b2.Num, b2.Hash). + Return(nil).Once() + pm.On("storeBridgeEvents", b2.Num, b2.Events). + Return(nil) + driver.handleNewBlock(ctx, b2) + + // processor fails once + b3 := block{ + blockHeader: blockHeader{ + Num: 3, + Hash: common.HexToHash("f00"), + }, + } + rdm. + On("AddBlockToTrack", ctx, reorgDetectorID, b3.Num, b3.Hash). + Return(nil) + pm.On("storeBridgeEvents", b3.Num, b3.Events). + Return(errors.New("foo")).Once() + pm.On("storeBridgeEvents", b3.Num, b3.Events). + Return(nil).Once() + driver.handleNewBlock(ctx, b3) + +} + +func TestHandleReorg(t *testing.T) { + retryAfterErrorPeriod = time.Millisecond * 100 + rdm := NewReorgDetectorMock(t) + pm := NewProcessorMock(t) + dm := NewDownloaderMock(t) + reorgProcessed := make(chan bool) + rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{ + ReorgProcessed: reorgProcessed, + }) + driver, err := newDriver(rdm, pm, dm) + require.NoError(t, err) + ctx := context.Background() + + // happy path + _, cancel := context.WithCancel(ctx) + downloadCh := make(chan block) + firstReorgedBlock := uint64(5) + pm.On("reorg", firstReorgedBlock).Return(nil) + go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) + close(downloadCh) + done := <-reorgProcessed + require.True(t, done) + + // download ch sends some garbage + _, cancel = context.WithCancel(ctx) + downloadCh = make(chan block) + firstReorgedBlock = uint64(6) + pm.On("reorg", firstReorgedBlock).Return(nil) + go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) + downloadCh <- block{} + downloadCh <- block{} + downloadCh <- block{} + close(downloadCh) + done = <-reorgProcessed + require.True(t, done) + + // processor fails 2 times + _, cancel = context.WithCancel(ctx) + downloadCh = make(chan block) + firstReorgedBlock = uint64(7) + pm.On("reorg", firstReorgedBlock).Return(errors.New("foo")).Once() + pm.On("reorg", firstReorgedBlock).Return(errors.New("foo")).Once() + pm.On("reorg", firstReorgedBlock).Return(nil).Once() + go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) + close(downloadCh) + done = <-reorgProcessed + require.True(t, done) +} diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go new file mode 100644 index 00000000..5e97e92a --- /dev/null +++ b/l1infotreesync/e2e_test.go @@ -0,0 +1,71 @@ +package l1infotreesync + +import ( + "context" + "math/big" + "strconv" + "testing" + "time" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry/polygonzkevmglobalexitrootv2" + "github.com/0xPolygon/cdk/etherman" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/stretchr/testify/require" +) + +func newSimulatedClient(auth *bind.TransactOpts) ( + client *simulated.Backend, + gerAddr common.Address, + gerContract *polygonzkevmglobalexitrootv2.Polygonzkevmglobalexitrootv2, + err error, +) { + balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd + address := auth.From + genesisAlloc := map[common.Address]types.Account{ + address: { + Balance: balance, + }, + } + blockGasLimit := uint64(999999999999999999) //nolint:gomnd + client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) + + gerAddr, _, gerContract, err = polygonzkevmglobalexitrootv2.DeployPolygonzkevmglobalexitrootv2(auth, client.Client(), auth.From, auth.From) + + client.Commit() + return +} + +func TestE2E(t *testing.T) { + ctx := context.Background() + dbPath := t.TempDir() + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337)) + require.NoError(t, err) + rdm := NewReorgDetectorMock(t) + client, gerAddr, gerSc, err := newSimulatedClient(auth) + syncer, err := New(ctx, dbPath, gerAddr, 10, etherman.LatestBlock, rdm, client.Client(), 32) + require.NoError(t, err) + go syncer.Sync(ctx) + + // Update GER 10 times + for i := 0; i < 10; i++ { + gerSc.UpdateExitRoot(auth, common.HexToHash(strconv.Itoa(i))) + client.Commit() + // Let the processor catch up + time.Sleep(time.Millisecond * 100) + + expectedRoot, err := gerSc.GetRoot(nil) + require.NoError(t, err) + info, err := syncer.GetInfoByRoot(ctx, expectedRoot) + require.NoError(t, err) + require.Equal(t, expectedRoot, info.L1InfoTreeRoot) + info2, err := syncer.GetInfoByIndex(ctx, uint32(i)) + require.NoError(t, err) + require.Equal(t, info, info2) + } +} diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go new file mode 100644 index 00000000..8a5ffde1 --- /dev/null +++ b/l1infotreesync/l1infotreesync.go @@ -0,0 +1,55 @@ +package l1infotreesync + +import ( + "context" + "time" + + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum/common" +) + +var ( + retryAfterErrorPeriod = time.Second * 10 + maxRetryAttemptsAfterError = 5 +) + +type L1InfoTreeSync struct { + *processor + *driver +} + +func New( + ctx context.Context, + dbPath string, + globalExitRoot common.Address, + syncBlockChunkSize uint64, + blockFinalityType etherman.BlockNumberFinality, + rd ReorgDetector, + l2Client EthClienter, + treeHeight uint8, +) (*L1InfoTreeSync, error) { + p, err := newProcessor(ctx, dbPath, treeHeight) + if err != nil { + return nil, err + } + dwn, err := newDownloader(globalExitRoot, l2Client, syncBlockChunkSize, blockFinalityType) + if err != nil { + return nil, err + } + dri, err := newDriver(rd, p, dwn) + if err != nil { + return nil, err + } + return &L1InfoTreeSync{p, dri}, nil +} + +func retryHandler(funcName string, attempts int) { + if attempts >= maxRetryAttemptsAfterError { + log.Fatalf( + "%s failed too many times (%d)", + funcName, maxRetryAttemptsAfterError, + ) + } + time.Sleep(retryAfterErrorPeriod) +} diff --git a/l1infotreesync/mock_downloader_test.go b/l1infotreesync/mock_downloader_test.go new file mode 100644 index 00000000..fca72678 --- /dev/null +++ b/l1infotreesync/mock_downloader_test.go @@ -0,0 +1,100 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package l1infotreesync + +import ( + context "context" + + types "github.com/ethereum/go-ethereum/core/types" + mock "github.com/stretchr/testify/mock" +) + +// DownloaderMock is an autogenerated mock type for the downloaderFull type +type DownloaderMock struct { + mock.Mock +} + +// appendLog provides a mock function with given fields: b, l +func (_m *DownloaderMock) appendLog(b *block, l types.Log) { + _m.Called(b, l) +} + +// download provides a mock function with given fields: ctx, fromBlock, downloadedCh +func (_m *DownloaderMock) download(ctx context.Context, fromBlock uint64, downloadedCh chan block) { + _m.Called(ctx, fromBlock, downloadedCh) +} + +// getBlockHeader provides a mock function with given fields: ctx, blockNum +func (_m *DownloaderMock) getBlockHeader(ctx context.Context, blockNum uint64) blockHeader { + ret := _m.Called(ctx, blockNum) + + var r0 blockHeader + if rf, ok := ret.Get(0).(func(context.Context, uint64) blockHeader); ok { + r0 = rf(ctx, blockNum) + } else { + r0 = ret.Get(0).(blockHeader) + } + + return r0 +} + +// getEventsByBlockRange provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *DownloaderMock) getEventsByBlockRange(ctx context.Context, fromBlock uint64, toBlock uint64) []block { + ret := _m.Called(ctx, fromBlock, toBlock) + + var r0 []block + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []block); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]block) + } + } + + return r0 +} + +// getLogs provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *DownloaderMock) getLogs(ctx context.Context, fromBlock uint64, toBlock uint64) []types.Log { + ret := _m.Called(ctx, fromBlock, toBlock) + + var r0 []types.Log + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []types.Log); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Log) + } + } + + return r0 +} + +// waitForNewBlocks provides a mock function with given fields: ctx, lastBlockSeen +func (_m *DownloaderMock) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) uint64 { + ret := _m.Called(ctx, lastBlockSeen) + + var r0 uint64 + if rf, ok := ret.Get(0).(func(context.Context, uint64) uint64); ok { + r0 = rf(ctx, lastBlockSeen) + } else { + r0 = ret.Get(0).(uint64) + } + + return r0 +} + +type mockConstructorTestingTNewDownloaderMock interface { + mock.TestingT + Cleanup(func()) +} + +// NewDownloaderMock creates a new instance of DownloaderMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewDownloaderMock(t mockConstructorTestingTNewDownloaderMock) *DownloaderMock { + mock := &DownloaderMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/l1infotreesync/mock_l2_test.go b/l1infotreesync/mock_l2_test.go new file mode 100644 index 00000000..9ab6868d --- /dev/null +++ b/l1infotreesync/mock_l2_test.go @@ -0,0 +1,484 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package 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" +) + +// L2Mock is an autogenerated mock type for the EthClienter type +type L2Mock struct { + mock.Mock +} + +// BlockByHash provides a mock function with given fields: ctx, hash +func (_m *L2Mock) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + ret := _m.Called(ctx, hash) + + 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 +} + +// BlockByNumber provides a mock function with given fields: ctx, number +func (_m *L2Mock) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + ret := _m.Called(ctx, number) + + 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 +} + +// BlockNumber provides a mock function with given fields: ctx +func (_m *L2Mock) BlockNumber(ctx context.Context) (uint64, error) { + ret := _m.Called(ctx) + + 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 +} + +// CallContract provides a mock function with given fields: ctx, call, blockNumber +func (_m *L2Mock) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + ret := _m.Called(ctx, call, blockNumber) + + 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 +} + +// 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) + + 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 +} + +// EstimateGas provides a mock function with given fields: ctx, call +func (_m *L2Mock) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { + ret := _m.Called(ctx, call) + + 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 +} + +// FilterLogs provides a mock function with given fields: ctx, q +func (_m *L2Mock) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + ret := _m.Called(ctx, q) + + 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 +} + +// HeaderByHash provides a mock function with given fields: ctx, hash +func (_m *L2Mock) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + ret := _m.Called(ctx, hash) + + 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 +} + +// HeaderByNumber provides a mock function with given fields: ctx, number +func (_m *L2Mock) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + ret := _m.Called(ctx, number) + + 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 +} + +// PendingCodeAt provides a mock function with given fields: ctx, account +func (_m *L2Mock) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + ret := _m.Called(ctx, account) + + 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 +} + +// PendingNonceAt provides a mock function with given fields: ctx, account +func (_m *L2Mock) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + ret := _m.Called(ctx, account) + + 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 +} + +// SendTransaction provides a mock function with given fields: ctx, tx +func (_m *L2Mock) SendTransaction(ctx context.Context, tx *types.Transaction) error { + ret := _m.Called(ctx, tx) + + 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 +} + +// SubscribeFilterLogs provides a mock function with given fields: ctx, q, ch +func (_m *L2Mock) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + ret := _m.Called(ctx, q, ch) + + 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 +} + +// SubscribeNewHead provides a mock function with given fields: ctx, ch +func (_m *L2Mock) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { + ret := _m.Called(ctx, ch) + + 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 +} + +// SuggestGasPrice provides a mock function with given fields: ctx +func (_m *L2Mock) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + 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 +} + +// SuggestGasTipCap provides a mock function with given fields: ctx +func (_m *L2Mock) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + 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 +} + +// TransactionCount provides a mock function with given fields: ctx, blockHash +func (_m *L2Mock) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { + ret := _m.Called(ctx, blockHash) + + 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 +} + +// TransactionInBlock provides a mock function with given fields: ctx, blockHash, index +func (_m *L2Mock) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { + ret := _m.Called(ctx, blockHash, index) + + 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 +} + +type mockConstructorTestingTNewL2Mock interface { + mock.TestingT + Cleanup(func()) +} + +// NewL2Mock creates a new instance of L2Mock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewL2Mock(t mockConstructorTestingTNewL2Mock) *L2Mock { + mock := &L2Mock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/l1infotreesync/mock_processor_test.go b/l1infotreesync/mock_processor_test.go new file mode 100644 index 00000000..f7105850 --- /dev/null +++ b/l1infotreesync/mock_processor_test.go @@ -0,0 +1,81 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package l1infotreesync + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// ProcessorMock is an autogenerated mock type for the processorInterface type +type ProcessorMock struct { + mock.Mock +} + +// getLastProcessedBlock provides a mock function with given fields: ctx +func (_m *ProcessorMock) getLastProcessedBlock(ctx context.Context) (uint64, error) { + ret := _m.Called(ctx) + + 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 +} + +// processBlock provides a mock function with given fields: block +func (_m *ProcessorMock) processBlock(b block) error { + ret := _m.Called(b) + + var r0 error + if rf, ok := ret.Get(0).(func(block) error); ok { + r0 = rf(b) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// reorg provides a mock function with given fields: firstReorgedBlock +func (_m *ProcessorMock) reorg(firstReorgedBlock uint64) error { + ret := _m.Called(firstReorgedBlock) + + var r0 error + if rf, ok := ret.Get(0).(func(uint64) error); ok { + r0 = rf(firstReorgedBlock) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewProcessorMock interface { + mock.TestingT + Cleanup(func()) +} + +// NewProcessorMock creates a new instance of ProcessorMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewProcessorMock(t mockConstructorTestingTNewProcessorMock) *ProcessorMock { + mock := &ProcessorMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/l1infotreesync/mock_reorgdetector_test.go b/l1infotreesync/mock_reorgdetector_test.go new file mode 100644 index 00000000..9737ba06 --- /dev/null +++ b/l1infotreesync/mock_reorgdetector_test.go @@ -0,0 +1,63 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package l1infotreesync + +import ( + context "context" + + common "github.com/ethereum/go-ethereum/common" + + mock "github.com/stretchr/testify/mock" + + reorgdetector "github.com/0xPolygon/cdk/reorgdetector" +) + +// ReorgDetectorMock is an autogenerated mock type for the ReorgDetector type +type ReorgDetectorMock struct { + mock.Mock +} + +// AddBlockToTrack provides a mock function with given fields: ctx, id, blockNum, blockHash +func (_m *ReorgDetectorMock) AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error { + ret := _m.Called(ctx, id, blockNum, blockHash) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, common.Hash) error); ok { + r0 = rf(ctx, id, blockNum, blockHash) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Subscribe provides a mock function with given fields: id +func (_m *ReorgDetectorMock) Subscribe(id string) *reorgdetector.Subscription { + ret := _m.Called(id) + + var r0 *reorgdetector.Subscription + if rf, ok := ret.Get(0).(func(string) *reorgdetector.Subscription); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*reorgdetector.Subscription) + } + } + + return r0 +} + +type mockConstructorTestingTNewReorgDetectorMock interface { + mock.TestingT + Cleanup(func()) +} + +// NewReorgDetectorMock creates a new instance of ReorgDetectorMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewReorgDetectorMock(t mockConstructorTestingTNewReorgDetectorMock) *ReorgDetectorMock { + mock := &ReorgDetectorMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go new file mode 100644 index 00000000..267ec0b4 --- /dev/null +++ b/l1infotreesync/processor.go @@ -0,0 +1,487 @@ +package l1infotreesync + +import ( + "context" + "encoding/binary" + "encoding/json" + "errors" + + "github.com/0xPolygon/cdk/l1infotree" + "github.com/ethereum/go-ethereum/common" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/kv/mdbx" + "golang.org/x/crypto/sha3" +) + +const ( + rootTable = "l1infotreesync-root" + indexTable = "l1infotreesync-index" + infoTable = "l1infotreesync-info" + blockTable = "l1infotreesync-block" + lastBlockTable = "l1infotreesync-lastBlock" +) + +var ( + ErrBlockNotProcessed = errors.New("given block(s) have not been processed yet") + ErrNotFound = errors.New("not found") + lastBlokcKey = []byte("lb") +) + +type processor struct { + db kv.RwDB + tree *l1infotree.L1InfoTree +} + +type L1InfoTreeLeaf struct { + L1InfoTreeRoot common.Hash + L1InfoTreeIndex uint32 + PreviousBlockHash common.Hash + BlockNumber uint64 + Timestamp uint64 + MainnetExitRoot common.Hash + RollupExitRoot common.Hash + GlobalExitRoot common.Hash +} + +type storeLeaf struct { + MainnetExitRoot common.Hash + RollupExitRoot common.Hash + ParentHash common.Hash + InfoRoot common.Hash + Index uint32 + Timestamp uint64 + BlockNumber uint64 +} + +type blockWithLeafs struct { + // inclusive + firstIndex uint32 + // not inclusive + lastIndex uint32 +} + +func (l *storeLeaf) GlobalExitRoot() common.Hash { + var gerBytes [32]byte + hasher := sha3.NewLegacyKeccak256() + hasher.Write(l.MainnetExitRoot[:]) + hasher.Write(l.RollupExitRoot[:]) + copy(gerBytes[:], hasher.Sum(nil)) + return gerBytes +} + +func tableCfgFunc(defaultBuckets kv.TableCfg) kv.TableCfg { + return kv.TableCfg{ + rootTable: {}, + indexTable: {}, + infoTable: {}, + blockTable: {}, + lastBlockTable: {}, + } +} + +func newProcessor(ctx context.Context, dbPath string, treeHeight uint8) (*processor, error) { + db, err := mdbx.NewMDBX(nil). + Path(dbPath). + WithTableCfg(tableCfgFunc). + Open() + if err != nil { + return nil, err + } + p := &processor{ + db: db, + } + leaves, err := p.getAllLeavesHashed(ctx) + if err != nil { + return nil, err + } + tree, err := l1infotree.NewL1InfoTree(treeHeight, leaves) + if err != nil { + return nil, err + } + p.tree = tree + return p, nil +} + +func (p *processor) getAllLeavesHashed(ctx context.Context) ([][32]byte, error) { + // TODO: same coment about refactor that appears at ComputeMerkleProofByIndex + tx, err := p.db.BeginRo(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + + index, err := p.getLastIndex(tx) + if err == ErrNotFound { + return nil, nil + } + if err != nil { + return nil, err + } + + return p.getHasedLeaves(tx, index) +} + +func (p *processor) ComputeMerkleProofByIndex(ctx context.Context, index uint32) ([][32]byte, common.Hash, error) { + // TODO: refactor the tree to store the nodes so it's not neede to load all the leaves and compute the tree + // every time this function is called. Since it's not a sparse MT, an alternative could be to store the proofs + // as part of the info + tx, err := p.db.BeginRo(ctx) + if err != nil { + return nil, common.Hash{}, err + } + defer tx.Rollback() + + leaves, err := p.getHasedLeaves(tx, index) + if err != nil { + return nil, common.Hash{}, err + } + return p.tree.ComputeMerkleProof(index, leaves) +} + +func (p *processor) getHasedLeaves(tx kv.Tx, untilIndex uint32) ([][32]byte, error) { + leaves := [][32]byte{} + for i := uint32(0); i <= untilIndex; i++ { + info, err := p.getInfoByIndexWithTx(tx, i) + if err != nil { + return nil, err + } + h := l1infotree.HashLeafData(info.GlobalExitRoot, info.PreviousBlockHash, info.Timestamp) + leaves = append(leaves, h) + } + return leaves, nil +} + +func (p *processor) ComputeMerkleProofByRoot(ctx context.Context, root common.Hash) ([][32]byte, common.Hash, error) { + info, err := p.GetInfoByRoot(ctx, root) + if err != nil { + return nil, common.Hash{}, err + } + return p.ComputeMerkleProofByIndex(ctx, info.L1InfoTreeIndex) +} + +func (p *processor) GetInfoByRoot(ctx context.Context, root common.Hash) (*L1InfoTreeLeaf, error) { + tx, err := p.db.BeginRo(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + hash, err := tx.GetOne(rootTable, root[:]) + if err != nil { + return nil, err + } + if hash == nil { + return nil, ErrNotFound + } + return p.getInfoByHashWithTx(tx, hash) +} + +// GetLatestInfoUntilBlock returns the most recent L1InfoTreeLeaf that occured before or at blockNum +func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { + tx, err := p.db.BeginRo(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + lpb, err := p.getLastProcessedBlockWithTx(tx) + if lpb < blockNum { + return nil, ErrBlockNotProcessed + } + c, err := tx.Cursor(blockTable) + if err != nil { + return nil, err + } + defer c.Close() + for k, v, err := c.Seek(uint64ToBytes(blockNum)); k != nil; k, v, err = c.Prev() { + if err != nil { + return nil, err + } + if bytes2Uint64(k) > blockNum { + // Seek function returns matching key or greater, therefore + // we could bed ealing with a block number greater than expected + continue + } + blk := blockWithLeafs{} + if err := json.Unmarshal(v, &blk); err != nil { + return nil, err + } + hash, err := tx.GetOne(indexTable, uint32ToBytes(blk.lastIndex-1)) + if err != nil { + return nil, err + } + return p.getInfoByHashWithTx(tx, hash) + } + + return nil, ErrNotFound +} + +func (p *processor) GetInfoByIndex(ctx context.Context, index uint32) (*L1InfoTreeLeaf, error) { + tx, err := p.db.BeginRo(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + return p.getInfoByIndexWithTx(tx, index) +} + +func (p *processor) getInfoByIndexWithTx(tx kv.Tx, index uint32) (*L1InfoTreeLeaf, error) { + hash, err := tx.GetOne(rootTable, uint32ToBytes(index)) + if err != nil { + return nil, err + } + if hash == nil { + return nil, ErrNotFound + } + return p.getInfoByHashWithTx(tx, hash) +} + +func (p *processor) GetInfoByHash(ctx context.Context, hash []byte) (*L1InfoTreeLeaf, error) { + tx, err := p.db.BeginRo(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + return p.getInfoByHashWithTx(tx, hash) +} + +func (p *processor) getInfoByHashWithTx(tx kv.Tx, hash []byte) (*L1InfoTreeLeaf, error) { + infoBytes, err := tx.GetOne(infoTable, hash) + if err != nil { + return nil, err + } + if infoBytes == nil { + return nil, ErrNotFound + } + var info storeLeaf + if err := json.Unmarshal(infoBytes, &info); err != nil { + return nil, err + } + + return &L1InfoTreeLeaf{ + L1InfoTreeRoot: info.InfoRoot, + L1InfoTreeIndex: info.Index, + PreviousBlockHash: info.ParentHash, + BlockNumber: info.BlockNumber, + Timestamp: info.Timestamp, + MainnetExitRoot: info.MainnetExitRoot, + RollupExitRoot: info.RollupExitRoot, + GlobalExitRoot: info.GlobalExitRoot(), + }, nil +} + +func (p *processor) getLastProcessedBlock(ctx context.Context) (uint64, error) { + tx, err := p.db.BeginRo(ctx) + if err != nil { + return 0, err + } + defer tx.Rollback() + return p.getLastProcessedBlockWithTx(tx) +} + +func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { + if blockNumBytes, err := tx.GetOne(lastBlockTable, lastBlokcKey); err != nil { + return 0, err + } else if blockNumBytes == nil { + return 0, nil + } else { + return bytes2Uint64(blockNumBytes), nil + } +} + +func (p *processor) reorg(firstReorgedBlock uint64) error { + // TODO: Does tree need to be reorged? + tx, err := p.db.BeginRw(context.Background()) + if err != nil { + return err + } + c, err := tx.Cursor(blockTable) + if err != nil { + return err + } + defer c.Close() + firstKey := uint64ToBytes(firstReorgedBlock) + for blkKey, blkValue, err := c.Seek(firstKey); blkKey != nil; blkKey, blkValue, err = c.Next() { + if err != nil { + tx.Rollback() + return err + } + var blk blockWithLeafs + if err := json.Unmarshal(blkValue, &blk); err != nil { + tx.Rollback() + return err + } + for i := blk.firstIndex; i < blk.lastIndex; i++ { + if err := p.deleteLeaf(tx, i); err != nil { + tx.Rollback() + return err + } + } + if err := tx.Delete(blockTable, blkKey); err != nil { + tx.Rollback() + return err + } + } + if err := p.updateLastProcessedBlock(tx, firstReorgedBlock-1); err != nil { + tx.Rollback() + return err + } + return tx.Commit() +} + +func (p *processor) deleteLeaf(tx kv.RwTx, index uint32) error { + // TODO: do we need to do something with p.tree here? + // Get leaf info to delete all relations + hash, err := tx.GetOne(indexTable, uint32ToBytes(index)) + if err != nil { + return err + } + if hash == nil { + return ErrNotFound + } + infoBytes, err := tx.GetOne(infoTable, hash) + if err != nil { + return err + } + if infoBytes == nil { + return ErrNotFound + } + var info storeLeaf + if err := json.Unmarshal(infoBytes, &info); err != nil { + return err + } + + // Delete + if err := tx.Delete(rootTable, info.InfoRoot[:]); err != nil { + return err + } + if err := tx.Delete(indexTable, uint32ToBytes(index)); err != nil { + return err + } + if err := tx.Delete(infoTable, hash); err != nil { + return err + } + return nil +} + +// processBlock process the leafs of the L1 info tree found on a block +// this function can be called without leafs with the intention to track the last processed block +func (p *processor) processBlock(b block) error { + tx, err := p.db.BeginRw(context.Background()) + if err != nil { + return err + } + if len(b.Events) > 0 { + lastIndex, err := p.getLastIndex(tx) + if err != nil { + tx.Rollback() + return err + } + initialIndex := lastIndex + 1 + for i, l := range b.Events { + leafToStore := storeLeaf{ + Index: initialIndex + uint32(i), + MainnetExitRoot: l.MainnetExitRoot, + RollupExitRoot: l.RollupExitRoot, + ParentHash: b.ParentHash, + Timestamp: b.Timestamp, + BlockNumber: b.Num, + } + if err := p.addLeaf(tx, leafToStore); err != nil { + tx.Rollback() + return err + } + } + bwl := blockWithLeafs{ + firstIndex: initialIndex, + lastIndex: initialIndex + uint32(len(b.Events)), + } + blockValue, err := json.Marshal(bwl) + if err != nil { + tx.Rollback() + return err + } + if err := tx.Put(blockTable, uint64ToBytes(b.Num), blockValue); err != nil { + tx.Rollback() + return err + } + } + if err := p.updateLastProcessedBlock(tx, b.Num); err != nil { + tx.Rollback() + return err + } + return tx.Commit() +} + +func (p *processor) getLastIndex(tx kv.Tx) (uint32, error) { + bNum, err := p.getLastProcessedBlockWithTx(tx) + if err != nil { + return 0, err + } + iter, err := tx.RangeDescend(blockTable, uint64ToBytes(bNum), uint64ToBytes(0), 1) + if err != nil { + return 0, err + } + _, blkBytes, err := iter.Next() + if err != nil { + return 0, err + } + if blkBytes == nil { + return 0, ErrNotFound + } + var blk blockWithLeafs + if err := json.Unmarshal(blkBytes, &blk); err != nil { + return 0, err + } + return blk.lastIndex - 1, nil +} + +func (p *processor) addLeaf(tx kv.RwTx, leaf storeLeaf) error { + // Update tree + hash := l1infotree.HashLeafData(leaf.GlobalExitRoot(), leaf.ParentHash, leaf.Timestamp) + root, err := p.tree.AddLeaf(leaf.Index, hash) + if err != nil { + return err + } + leaf.InfoRoot = root + // store info + leafValue, err := json.Marshal(leaf) + if err != nil { + return err + } + if err := tx.Put(infoTable, hash[:], leafValue); err != nil { + return err + } + // store index relation + if err := tx.Put(indexTable, uint32ToBytes(leaf.Index), hash[:]); err != nil { + return err + } + // store root relation + if err := tx.Put(rootTable, root.Bytes(), hash[:]); err != nil { + return err + } + return nil +} + +func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error { + blockNumBytes := uint64ToBytes(blockNum) + return tx.Put(lastBlockTable, lastBlokcKey, blockNumBytes) +} + +func uint64ToBytes(num uint64) []byte { + key := make([]byte, 8) + binary.LittleEndian.PutUint64(key, num) + return key +} + +func bytes2Uint64(key []byte) uint64 { + return binary.LittleEndian.Uint64(key) +} + +func uint32ToBytes(num uint32) []byte { + key := make([]byte, 4) + binary.LittleEndian.PutUint32(key, num) + return key +} + +func bytes2Uint32(key []byte) uint32 { + return binary.LittleEndian.Uint32(key) +} diff --git a/l1infotreesync/processor_test.go b/l1infotreesync/processor_test.go new file mode 100644 index 00000000..949a3fde --- /dev/null +++ b/l1infotreesync/processor_test.go @@ -0,0 +1,373 @@ +package l1infotreesync + +import ( + "context" + "fmt" + "slices" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestProceessor(t *testing.T) { + path := t.TempDir() + ctx := context.Background() + p, err := newProcessor(ctx, path, 32) + require.NoError(t, err) + actions := []processAction{ + // processed: ~ + &getLastProcessedBlockAction{ + p: p, + description: "on an empty processor", + ctx: context.Background(), + expectedLastProcessedBlock: 0, + expectedErr: nil, + }, + &reorgAction{ + p: p, + description: "on an empty processor: firstReorgedBlock = 0", + firstReorgedBlock: 0, + expectedErr: nil, + }, + &reorgAction{ + p: p, + description: "on an empty processor: firstReorgedBlock = 1", + firstReorgedBlock: 1, + expectedErr: nil, + }, + &getClaimsAndBridgesAction{ + p: p, + description: "on an empty processor", + ctx: context.Background(), + fromBlock: 0, + toBlock: 2, + expectedEvents: nil, + expectedErr: ErrBlockNotProcessed, + }, + &storeL1InfoTreeUpdatesAction{ + p: p, + description: "block1", + b: block1, + expectedErr: nil, + }, + // processed: block1 + &getLastProcessedBlockAction{ + p: p, + description: "after block1", + ctx: context.Background(), + expectedLastProcessedBlock: 1, + expectedErr: nil, + }, + &getClaimsAndBridgesAction{ + p: p, + description: "after block1: range 0, 2", + ctx: context.Background(), + fromBlock: 0, + toBlock: 2, + expectedEvents: nil, + expectedErr: ErrBlockNotProcessed, + }, + &getClaimsAndBridgesAction{ + p: p, + description: "after block1: range 1, 1", + ctx: context.Background(), + fromBlock: 1, + toBlock: 1, + expectedEvents: block1.Events, + expectedErr: nil, + }, + &reorgAction{ + p: p, + description: "after block1", + firstReorgedBlock: 1, + expectedErr: nil, + }, + // processed: ~ + &getClaimsAndBridgesAction{ + p: p, + description: "after block1 reorged", + ctx: context.Background(), + fromBlock: 0, + toBlock: 2, + expectedEvents: nil, + expectedErr: ErrBlockNotProcessed, + }, + &storeL1InfoTreeUpdatesAction{ + p: p, + description: "block1 (after it's reorged)", + b: block1, + expectedErr: nil, + }, + // processed: block3 + &storeL1InfoTreeUpdatesAction{ + p: p, + description: "block3", + b: block3, + expectedErr: nil, + }, + // processed: block1, block3 + &getLastProcessedBlockAction{ + p: p, + description: "after block3", + ctx: context.Background(), + expectedLastProcessedBlock: 3, + expectedErr: nil, + }, + &getClaimsAndBridgesAction{ + p: p, + description: "after block3: range 2, 2", + ctx: context.Background(), + fromBlock: 2, + toBlock: 2, + expectedEvents: []L1InfoTreeUpdate{}, + expectedErr: nil, + }, + &getClaimsAndBridgesAction{ + p: p, + description: "after block3: range 1, 3", + ctx: context.Background(), + fromBlock: 1, + toBlock: 3, + expectedEvents: append(block1.Events, block3.Events...), + expectedErr: nil, + }, + &reorgAction{ + p: p, + description: "after block3, with value 3", + firstReorgedBlock: 3, + expectedErr: nil, + }, + // processed: block1 + &getLastProcessedBlockAction{ + p: p, + description: "after block3 reorged", + ctx: context.Background(), + expectedLastProcessedBlock: 2, + expectedErr: nil, + }, + &reorgAction{ + p: p, + description: "after block3, with value 2", + firstReorgedBlock: 2, + expectedErr: nil, + }, + &getLastProcessedBlockAction{ + p: p, + description: "after block2 reorged", + ctx: context.Background(), + expectedLastProcessedBlock: 1, + expectedErr: nil, + }, + &storeL1InfoTreeUpdatesAction{ + p: p, + description: "block3 after reorg", + b: block3, + expectedErr: nil, + }, + // processed: block1, block3 + &storeL1InfoTreeUpdatesAction{ + p: p, + description: "block4", + b: block4, + expectedErr: nil, + }, + // processed: block1, block3, block4 + &storeL1InfoTreeUpdatesAction{ + p: p, + description: "block5", + b: block5, + expectedErr: nil, + }, + // processed: block1, block3, block4, block5 + &getLastProcessedBlockAction{ + p: p, + description: "after block5", + ctx: context.Background(), + expectedLastProcessedBlock: 5, + expectedErr: nil, + }, + &getClaimsAndBridgesAction{ + p: p, + description: "after block5: range 1, 3", + ctx: context.Background(), + fromBlock: 1, + toBlock: 3, + expectedEvents: append(block1.Events, block3.Events...), + expectedErr: nil, + }, + &getClaimsAndBridgesAction{ + p: p, + description: "after block5: range 4, 5", + ctx: context.Background(), + fromBlock: 4, + toBlock: 5, + expectedEvents: append(block4.Events, block5.Events...), + expectedErr: nil, + }, + &getClaimsAndBridgesAction{ + p: p, + description: "after block5: range 0, 5", + ctx: context.Background(), + fromBlock: 0, + toBlock: 5, + expectedEvents: slices.Concat( + block1.Events, + block3.Events, + block4.Events, + block5.Events, + ), + expectedErr: nil, + }, + } + + for _, a := range actions { + t.Run(fmt.Sprintf("%s: %s", a.method(), a.desc()), a.execute) + } +} + +// BOILERPLATE + +// blocks + +var ( + block1 = block{ + blockHeader: blockHeader{ + Num: 1, + Hash: common.HexToHash("01"), + }, + Events: []L1InfoTreeUpdate{ + {RollupExitRoot: common.HexToHash("01")}, + {MainnetExitRoot: common.HexToHash("01")}, + }, + } + block3 = block{ + blockHeader: blockHeader{ + Num: 3, + Hash: common.HexToHash("02"), + }, + Events: []L1InfoTreeUpdate{ + {RollupExitRoot: common.HexToHash("02"), MainnetExitRoot: common.HexToHash("02")}, + }, + } + block4 = block{ + blockHeader: blockHeader{ + Num: 4, + Hash: common.HexToHash("03"), + }, + Events: []L1InfoTreeUpdate{}, + } + block5 = block{ + blockHeader: blockHeader{ + Num: 5, + Hash: common.HexToHash("04"), + }, + Events: []L1InfoTreeUpdate{ + {RollupExitRoot: common.HexToHash("04")}, + {MainnetExitRoot: common.HexToHash("05")}, + }, + } +) + +// actions + +type processAction interface { + method() string + desc() string + execute(t *testing.T) +} + +// GetClaimsAndBridges + +type getClaimsAndBridgesAction struct { + p *processor + description string + ctx context.Context + fromBlock uint64 + toBlock uint64 + expectedEvents []L1InfoTreeUpdate + expectedErr error +} + +func (a *getClaimsAndBridgesAction) method() string { + return "GetClaimsAndBridges" +} + +func (a *getClaimsAndBridgesAction) desc() string { + return a.description +} + +func (a *getClaimsAndBridgesAction) execute(t *testing.T) { + // TODO: add relevant getters + // actualEvents, actualErr := a.p.GetClaimsAndBridges(a.ctx, a.fromBlock, a.toBlock) + // require.Equal(t, a.expectedEvents, actualEvents) + // require.Equal(t, a.expectedErr, actualErr) +} + +// getLastProcessedBlock + +type getLastProcessedBlockAction struct { + p *processor + description string + ctx context.Context + expectedLastProcessedBlock uint64 + expectedErr error +} + +func (a *getLastProcessedBlockAction) method() string { + return "getLastProcessedBlock" +} + +func (a *getLastProcessedBlockAction) desc() string { + return a.description +} + +func (a *getLastProcessedBlockAction) execute(t *testing.T) { + actualLastProcessedBlock, actualErr := a.p.getLastProcessedBlock(a.ctx) + require.Equal(t, a.expectedLastProcessedBlock, actualLastProcessedBlock) + require.Equal(t, a.expectedErr, actualErr) +} + +// reorg + +type reorgAction struct { + p *processor + description string + firstReorgedBlock uint64 + expectedErr error +} + +func (a *reorgAction) method() string { + return "reorg" +} + +func (a *reorgAction) desc() string { + return a.description +} + +func (a *reorgAction) execute(t *testing.T) { + actualErr := a.p.reorg(a.firstReorgedBlock) + require.Equal(t, a.expectedErr, actualErr) +} + +// storeL1InfoTreeUpdates + +type storeL1InfoTreeUpdatesAction struct { + p *processor + description string + b block + expectedErr error +} + +func (a *storeL1InfoTreeUpdatesAction) method() string { + return "storeL1InfoTreeUpdates" +} + +func (a *storeL1InfoTreeUpdatesAction) desc() string { + return a.description +} + +func (a *storeL1InfoTreeUpdatesAction) execute(t *testing.T) { + actualErr := a.p.processBlock(a.b) + require.Equal(t, a.expectedErr, actualErr) +} diff --git a/test/Makefile b/test/Makefile index 3511b4f7..151f7336 100644 --- a/test/Makefile +++ b/test/Makefile @@ -2,6 +2,7 @@ generate-mocks: $(MAKE) generate-mocks-localbridgesync $(MAKE) generate-mocks-reorgdetector + $(MAKE) generate-mocks-l1infotreesync .PHONY: generate-mocks-localbridgesync generate-mocks-localbridgesync: ## Generates mocks for localbridgesync, using mockery tool @@ -37,3 +38,9 @@ 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=EthClienter --dir=../l1infotreesync --output=../l1infotreesync --outpkg=l1infotreesync --inpackage --structname=L2Mock --filename=mock_l2_test.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=downloaderFull --dir=../l1infotreesync --output=../l1infotreesync --outpkg=l1infotreesync --inpackage --structname=DownloaderMock --filename=mock_downloader_test.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=processorInterface --dir=../l1infotreesync --output=../l1infotreesync --outpkg=l1infotreesync --inpackage --structname=ProcessorMock --filename=mock_processor_test.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ReorgDetector --dir=../l1infotreesync --output=../l1infotreesync --outpkg=l1infotreesync --inpackage --structname=ReorgDetectorMock --filename=mock_reorgdetector_test.go From 73128ae76551d1fa18065e75de77afeb48cb5f01 Mon Sep 17 00:00:00 2001 From: Arnau Date: Wed, 24 Jul 2024 17:51:10 +0200 Subject: [PATCH 02/49] WIP --- aggoracle/chaingersender/evm.go | 114 ++++++++++++++++++++++++ aggoracle/e2e_test.go | 147 +++++++++++++++++++++++++++++++ aggoracle/oracle.go | 100 +++++++++++++++++++++ go.mod | 6 +- go.sum | 12 +-- l1infotreesync/downloader.go | 7 +- l1infotreesync/e2e_test.go | 30 +++++-- l1infotreesync/l1infotreesync.go | 4 +- l1infotreesync/processor.go | 29 +++--- 9 files changed, 413 insertions(+), 36 deletions(-) create mode 100644 aggoracle/chaingersender/evm.go create mode 100644 aggoracle/e2e_test.go create mode 100644 aggoracle/oracle.go diff --git a/aggoracle/chaingersender/evm.go b/aggoracle/chaingersender/evm.go new file mode 100644 index 00000000..c7860528 --- /dev/null +++ b/aggoracle/chaingersender/evm.go @@ -0,0 +1,114 @@ +package chaingersender + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/pessimisticglobalexitroot" + "github.com/0xPolygon/cdk/log" + "github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +var ( + waitPeriodMonitorTx = time.Second * 5 +) + +type EthClienter interface { + ethereum.LogFilterer + ethereum.BlockNumberReader + ethereum.ChainReader + bind.ContractBackend +} + +type EthTxManager interface { + Remove(ctx context.Context, id common.Hash) error + ResultsByStatus(ctx context.Context, statuses []ethtxmanager.MonitoredTxStatus) ([]ethtxmanager.MonitoredTxResult, error) + Result(ctx context.Context, id common.Hash) (ethtxmanager.MonitoredTxResult, error) + Add(ctx context.Context, to *common.Address, forcedNonce *uint64, value *big.Int, data []byte, gasOffset uint64, sidecar *types.BlobTxSidecar) (common.Hash, error) +} + +type EVMChainGERSender struct { + gerContract *pessimisticglobalexitroot.Pessimisticglobalexitroot + gerAddr common.Address + sender common.Address + client EthClienter + ethTxMan EthTxManager + gasOffset uint64 +} + +func NewEVMChainGERSender( + globalExitRoot, sender common.Address, + client EthClienter, + ethTxMan EthTxManager, + gasOffset uint64, +) (*EVMChainGERSender, error) { + gerContract, err := pessimisticglobalexitroot.NewPessimisticglobalexitroot(globalExitRoot, client) + if err != nil { + return nil, err + } + return &EVMChainGERSender{ + gerContract: gerContract, + gerAddr: globalExitRoot, + sender: sender, + client: client, + ethTxMan: ethTxMan, + gasOffset: gasOffset, + }, nil +} + +func (c *EVMChainGERSender) IsGERAlreadyInjected(ger common.Hash) (bool, error) { + timestamp, err := c.gerContract.GlobalExitRootMap(&bind.CallOpts{Pending: false}, ger) + if err != nil { + return false, err + } + return timestamp.Cmp(big.NewInt(0)) == 0, nil +} + +func (c *EVMChainGERSender) UpdateGERWaitUntilMined(ctx context.Context, ger common.Hash) error { + opts := &bind.TransactOpts{ + From: c.sender, + // Hardcode dummy values to avoid calls to the client, + // ethtxmanager is going to handle that + NoSend: true, + Nonce: big.NewInt(1), + GasLimit: 1, + GasPrice: big.NewInt(1), + } + tx, err := c.gerContract.UpdateGlobalExitRoot(opts, ger) + if err != nil { + return err + } + id, err := c.ethTxMan.Add(ctx, &c.gerAddr, nil, big.NewInt(0), tx.Data(), c.gasOffset, nil) + if err != nil { + return err + } + for { + time.Sleep(waitPeriodMonitorTx) + res, err := c.ethTxMan.Result(ctx, id) + if err != nil { + log.Error("error calling ethTxMan.Result: ", err) + } + switch res.Status { + case ethtxmanager.MonitoredTxStatusCreated: + continue + case ethtxmanager.MonitoredTxStatusSent: + continue + case ethtxmanager.MonitoredTxStatusFailed: + return fmt.Errorf("tx %s failed", res.ID) + case ethtxmanager.MonitoredTxStatusMined: + return nil + case ethtxmanager.MonitoredTxStatusSafe: + return nil + case ethtxmanager.MonitoredTxStatusFinalized: + return nil + default: + log.Error("unexpected tx status: ", res.Status) + } + } +} diff --git a/aggoracle/e2e_test.go b/aggoracle/e2e_test.go new file mode 100644 index 00000000..04d16cb0 --- /dev/null +++ b/aggoracle/e2e_test.go @@ -0,0 +1,147 @@ +package aggoracle_test + +import ( + "context" + "math/big" + "strconv" + "testing" + "time" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/globalexitrootnopush0" + "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/pessimisticglobalexitroot" + "github.com/0xPolygon/cdk/aggoracle" + "github.com/0xPolygon/cdk/aggoracle/chaingersender" + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/reorgdetector" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestEVM(t *testing.T) { + ctx := context.Background() + l1Client, syncer, gerL1Contract, authL1 := commonSetup(t) + sender := evmSetup(t) + oracle, err := aggoracle.New(sender, l1Client.Client(), syncer, etherman.LatestBlock) + require.NoError(t, err) + oracle.Start(ctx) + + runTest(t, gerL1Contract, sender, l1Client, authL1) +} + +func commonSetup(t *testing.T) ( + *simulated.Backend, + *l1infotreesync.L1InfoTreeSync, + *globalexitrootnopush0.Globalexitrootnopush0, + *bind.TransactOpts, +) { + // Config and spin up + ctx := context.Background() + // Simulated L1 + privateKeyL1, err := crypto.GenerateKey() + require.NoError(t, err) + authL1, err := bind.NewKeyedTransactorWithChainID(privateKeyL1, big.NewInt(1337)) + require.NoError(t, err) + l1Client, gerL1Addr, gerL1Contract, err := newSimulatedL1(authL1) + require.NoError(t, err) + // Reorg detector + rdm := NewReorgDetectorMock(t) + rdm.On("Subscribe", mock.Anything).Return(&reorgdetector.Subscription{}) + rdm.On("AddBlockToTrack", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + reorg, err := reorgdetector.New(ctx) + require.NoError(t, err) + // Syncer + dbPath := t.TempDir() + syncer, err := l1infotreesync.New(ctx, dbPath, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), 32) + require.NoError(t, err) + syncer.Sync(ctx) + + return l1Client, syncer, gerL1Contract, authL1 +} + +func evmSetup(t *testing.T) aggoracle.ChainSender { + privateKeyL2, err := crypto.GenerateKey() + require.NoError(t, err) + authL2, err := bind.NewKeyedTransactorWithChainID(privateKeyL2, big.NewInt(1337)) + require.NoError(t, err) + l2Client, gerL2Addr, _, err := newSimulatedEVMAggSovereignChain(authL2) + require.NoError(t, err) + sender, err := chaingersender.NewEVMChainGERSender(gerL2Addr, authL2.From, l2Client.Client(), nil, 0) + require.NoError(t, err) + + return sender +} + +func newSimulatedL1(auth *bind.TransactOpts) ( + client *simulated.Backend, + gerAddr common.Address, + gerContract *globalexitrootnopush0.Globalexitrootnopush0, + err error, +) { + balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd + address := auth.From + genesisAlloc := map[common.Address]types.Account{ + address: { + Balance: balance, + }, + } + blockGasLimit := uint64(999999999999999999) //nolint:gomnd + client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) + + gerAddr, _, gerContract, err = globalexitrootnopush0.DeployGlobalexitrootnopush0(auth, client.Client(), auth.From, auth.From) + + client.Commit() + return +} + +func newSimulatedEVMAggSovereignChain(auth *bind.TransactOpts) ( + client *simulated.Backend, + gerAddr common.Address, + gerContract *pessimisticglobalexitroot.Pessimisticglobalexitroot, + err error, +) { + balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd + address := auth.From + genesisAlloc := map[common.Address]types.Account{ + address: { + Balance: balance, + }, + } + blockGasLimit := uint64(999999999999999999) //nolint:gomnd + client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) + + gerAddr, _, gerContract, err = pessimisticglobalexitroot.DeployPessimisticglobalexitroot(auth, client.Client(), auth.From) + + client.Commit() + return +} + +func runTest( + t *testing.T, + gerL1Contract *globalexitrootnopush0.Globalexitrootnopush0, + sender aggoracle.ChainSender, + l1Client *simulated.Backend, + authL1 *bind.TransactOpts, +) { + for i := 0; i < 10; i++ { + _, err := gerL1Contract.UpdateExitRoot(authL1, common.HexToHash(strconv.Itoa(i))) + require.NoError(t, err) + l1Client.Commit() + time.Sleep(time.Second * 30) + expectedGER, err := gerL1Contract.GetLastGlobalExitRoot(&bind.CallOpts{Pending: false}) + require.NoError(t, err) + isInjected, err := sender.IsGERAlreadyInjected(expectedGER) + require.NoError(t, err) + require.True(t, isInjected) + } +} + +type ReorgDetector interface { + Subscribe(id string) *reorgdetector.Subscription + AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error +} diff --git a/aggoracle/oracle.go b/aggoracle/oracle.go new file mode 100644 index 00000000..0e89a7f2 --- /dev/null +++ b/aggoracle/oracle.go @@ -0,0 +1,100 @@ +package aggoracle + +import ( + "context" + "math/big" + "time" + + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" +) + +var ( + waitPeriodNextGER = time.Second * 30 +) + +type EthClienter interface { + ethereum.LogFilterer + ethereum.BlockNumberReader + ethereum.ChainReader + bind.ContractBackend +} + +type L1InfoTreer interface { + GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*l1infotreesync.L1InfoTreeLeaf, error) +} + +type ChainSender interface { + IsGERAlreadyInjected(ger common.Hash) (bool, error) + UpdateGERWaitUntilMined(ctx context.Context, ger common.Hash) error +} + +type AggOracle struct { + ticker *time.Ticker + l1Client EthClienter + l1Info L1InfoTreer + chainSender ChainSender + blockFinality *big.Int +} + +func New( + chainSender ChainSender, + l1Client EthClienter, + l1InfoTreeSyncer L1InfoTreer, + blockFinalityType etherman.BlockNumberFinality, +) (*AggOracle, error) { + ticker := time.NewTicker(waitPeriodNextGER) + finality, err := blockFinalityType.ToBlockNum() + if err != nil { + return nil, err + } + return &AggOracle{ + ticker: ticker, + l1Client: l1Client, + l1Info: l1InfoTreeSyncer, + chainSender: chainSender, + blockFinality: finality, + }, nil +} + +func (a *AggOracle) Start(ctx context.Context) { + for { + select { + case <-a.ticker.C: + gerToInject, err := a.getLastFinalisedGER(ctx) + if err != nil { + log.Error("error calling getLastFinalisedGER: ", err) + continue + } + if alreadyInjectd, err := a.chainSender.IsGERAlreadyInjected(gerToInject); err != nil { + log.Error("error calling isGERAlreadyInjected: ", err) + continue + } else if alreadyInjectd { + log.Debugf("GER %s already injected", gerToInject.Hex()) + continue + } + if err := a.chainSender.UpdateGERWaitUntilMined(ctx, gerToInject); err != nil { + log.Error("error calling updateGERWaitUntilMined: ", err) + continue + } + case <-ctx.Done(): + return + } + } +} + +func (a *AggOracle) getLastFinalisedGER(ctx context.Context) (common.Hash, error) { + header, err := a.l1Client.HeaderByNumber(ctx, a.blockFinality) + if err != nil { + return common.Hash{}, err + } + info, err := a.l1Info.GetLatestInfoUntilBlock(ctx, header.Number.Uint64()) + if err != nil { + return common.Hash{}, err + } + return info.GlobalExitRoot, nil +} diff --git a/go.mod b/go.mod index 1a3673a9..fdcf2398 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/0xPolygon/cdk go 1.22.4 require ( - github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240627125553-80db9d8a41b5 - github.com/0xPolygon/cdk-data-availability v0.0.8 + github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240724124854-785bb2a0e4a2 + github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d - github.com/0xPolygonHermez/zkevm-data-streamer v0.2.2 + github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-RC4 github.com/0xPolygonHermez/zkevm-ethtx-manager v0.1.9 github.com/0xPolygonHermez/zkevm-synchronizer-l1 v0.6.3-0.20240712085301-0310358abb59 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index cb15f96d..96887d67 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ -github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240627125553-80db9d8a41b5 h1:bezOoVvpQvGvf4aCZQpMPFJGKXhhvktpApr+TsD8rdk= -github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240627125553-80db9d8a41b5/go.mod h1:mFlcEjsm2YBBsu8atHJ3zyVnwM+Z/fMXpVmIJge+WVU= -github.com/0xPolygon/cdk-data-availability v0.0.8 h1:bMmOYZ7Ei683y80ric3KzMPXtRGmchAmfjIRzghaHb4= -github.com/0xPolygon/cdk-data-availability v0.0.8/go.mod h1:3XkZ0zn0GsvAT01MPQMmukF534CVSFmtrcoK3F/BK6Q= +github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240724124854-785bb2a0e4a2 h1:vMRkvTf+sfCKtEDFRGlZ9saDzQIf+LR+ly2OiRyE/2Q= +github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240724124854-785bb2a0e4a2/go.mod h1:mFlcEjsm2YBBsu8atHJ3zyVnwM+Z/fMXpVmIJge+WVU= +github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf h1:VWxVYeDhDURGp8pHR4kq3jwoXRazVHsQLMMIqYsO+Fw= +github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf/go.mod h1:3XkZ0zn0GsvAT01MPQMmukF534CVSFmtrcoK3F/BK6Q= github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d h1:sxh6hZ2jF/sxxj2jd5o1vuNNCZjYmn4aRG9SRlVaEFs= github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d/go.mod h1:2scWqMMufrQXu7TikDgQ3BsyaKoX8qP26D6E262vSOg= -github.com/0xPolygonHermez/zkevm-data-streamer v0.2.2 h1:XRMTk+W6vtJVGVjuEznfWyNt7HkRkkuSmlN5Y6p60Sc= -github.com/0xPolygonHermez/zkevm-data-streamer v0.2.2/go.mod h1:0QkAXcFa92mFJrCbN3UPUJGJYes851yEgYHLONnaosE= +github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-RC4 h1:+4K+xSzv0ImbK30B/T9FauNTrTFUmWcNKYhIgwsE4C4= +github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-RC4/go.mod h1:0QkAXcFa92mFJrCbN3UPUJGJYes851yEgYHLONnaosE= github.com/0xPolygonHermez/zkevm-ethtx-manager v0.1.9 h1:vrAezzwTNke6NroDAltGh1k2AJ6ibmZPBsG0bCltbRc= github.com/0xPolygonHermez/zkevm-ethtx-manager v0.1.9/go.mod h1:pRqfLQVM3nbzdhy3buqjAgcVyNDKAXOHqTSgkwiKpic= github.com/0xPolygonHermez/zkevm-synchronizer-l1 v0.6.3-0.20240712085301-0310358abb59 h1:Qwh92vFEXnpmDggQaZA3648viEQfLdMnAw/WFSY+2i8= diff --git a/l1infotreesync/downloader.go b/l1infotreesync/downloader.go index a8ca4cc2..549e482e 100644 --- a/l1infotreesync/downloader.go +++ b/l1infotreesync/downloader.go @@ -15,11 +15,8 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -const ( - waitForNewBlocksPeriod = time.Millisecond * 100 -) - var ( + waitForNewBlocksPeriod = time.Millisecond * 100 updateL1InfoTreeSignature = crypto.Keccak256Hash([]byte("UpdateL1InfoTree(bytes32,bytes32)")) ) @@ -201,7 +198,7 @@ func (d *downloaderImplementation) appendLog(b *block, l types.Log) { } b.Events = append(b.Events, L1InfoTreeUpdate{ MainnetExitRoot: l1InfoTreeUpdate.MainnetExitRoot, - RollupExitRoot: l1InfoTreeUpdate.MainnetExitRoot, + RollupExitRoot: l1InfoTreeUpdate.RollupExitRoot, }) default: log.Fatalf("unexpected log %+v", l) diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index 5e97e92a..baad1d53 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -2,25 +2,28 @@ package l1infotreesync import ( "context" + "fmt" "math/big" "strconv" "testing" "time" - "github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry/polygonzkevmglobalexitrootv2" + "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/globalexitrootnopush0" "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/reorgdetector" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func newSimulatedClient(auth *bind.TransactOpts) ( client *simulated.Backend, gerAddr common.Address, - gerContract *polygonzkevmglobalexitrootv2.Polygonzkevmglobalexitrootv2, + gerContract *globalexitrootnopush0.Globalexitrootnopush0, err error, ) { balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd @@ -33,13 +36,14 @@ func newSimulatedClient(auth *bind.TransactOpts) ( blockGasLimit := uint64(999999999999999999) //nolint:gomnd client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) - gerAddr, _, gerContract, err = polygonzkevmglobalexitrootv2.DeployPolygonzkevmglobalexitrootv2(auth, client.Client(), auth.From, auth.From) + gerAddr, _, gerContract, err = globalexitrootnopush0.DeployGlobalexitrootnopush0(auth, client.Client(), auth.From, auth.From) client.Commit() return } func TestE2E(t *testing.T) { + waitForNewBlocksPeriod = time.Millisecond ctx := context.Background() dbPath := t.TempDir() privateKey, err := crypto.GenerateKey() @@ -47,25 +51,33 @@ func TestE2E(t *testing.T) { auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337)) require.NoError(t, err) rdm := NewReorgDetectorMock(t) + rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{}) + rdm.On("AddBlockToTrack", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) client, gerAddr, gerSc, err := newSimulatedClient(auth) + require.NoError(t, err) syncer, err := New(ctx, dbPath, gerAddr, 10, etherman.LatestBlock, rdm, client.Client(), 32) require.NoError(t, err) go syncer.Sync(ctx) // Update GER 10 times + // TODO: test syncer restart for i := 0; i < 10; i++ { - gerSc.UpdateExitRoot(auth, common.HexToHash(strconv.Itoa(i))) + _, err := gerSc.UpdateExitRoot(auth, common.HexToHash(strconv.Itoa(i))) + require.NoError(t, err) client.Commit() // Let the processor catch up - time.Sleep(time.Millisecond * 100) + time.Sleep(time.Millisecond * 10) - expectedRoot, err := gerSc.GetRoot(nil) + expectedRoot, err := gerSc.GetRoot(&bind.CallOpts{Pending: false}) require.NoError(t, err) - info, err := syncer.GetInfoByRoot(ctx, expectedRoot) + expectedGER, err := gerSc.GetLastGlobalExitRoot(&bind.CallOpts{Pending: false}) require.NoError(t, err) - require.Equal(t, expectedRoot, info.L1InfoTreeRoot) info2, err := syncer.GetInfoByIndex(ctx, uint32(i)) - require.NoError(t, err) + require.NoError(t, err, fmt.Sprintf("index: %d, root: %s", i, common.Bytes2Hex(expectedRoot[:]))) + require.Equal(t, common.Hash(expectedGER), info2.GlobalExitRoot, fmt.Sprintf("index: %d", i)) + info, err := syncer.GetInfoByRoot(ctx, expectedRoot) + require.NoError(t, err, fmt.Sprintf("index: %d, expected root: %s, actual root: %s", i, common.Bytes2Hex(expectedRoot[:]), info2.L1InfoTreeRoot)) + require.Equal(t, common.Hash(expectedRoot), info.L1InfoTreeRoot) require.Equal(t, info, info2) } } diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index 8a5ffde1..fa04f0e6 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -26,14 +26,14 @@ func New( syncBlockChunkSize uint64, blockFinalityType etherman.BlockNumberFinality, rd ReorgDetector, - l2Client EthClienter, + l1Client EthClienter, treeHeight uint8, ) (*L1InfoTreeSync, error) { p, err := newProcessor(ctx, dbPath, treeHeight) if err != nil { return nil, err } - dwn, err := newDownloader(globalExitRoot, l2Client, syncBlockChunkSize, blockFinalityType) + dwn, err := newDownloader(globalExitRoot, l1Client, syncBlockChunkSize, blockFinalityType) if err != nil { return nil, err } diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 267ec0b4..12047d77 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -55,9 +55,9 @@ type storeLeaf struct { type blockWithLeafs struct { // inclusive - firstIndex uint32 + FirstIndex uint32 // not inclusive - lastIndex uint32 + LastIndex uint32 } func (l *storeLeaf) GlobalExitRoot() common.Hash { @@ -111,7 +111,7 @@ func (p *processor) getAllLeavesHashed(ctx context.Context) ([][32]byte, error) defer tx.Rollback() index, err := p.getLastIndex(tx) - if err == ErrNotFound { + if err == ErrNotFound || index == 0 { return nil, nil } if err != nil { @@ -204,7 +204,7 @@ func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64 if err := json.Unmarshal(v, &blk); err != nil { return nil, err } - hash, err := tx.GetOne(indexTable, uint32ToBytes(blk.lastIndex-1)) + hash, err := tx.GetOne(indexTable, uint32ToBytes(blk.LastIndex-1)) if err != nil { return nil, err } @@ -224,7 +224,7 @@ func (p *processor) GetInfoByIndex(ctx context.Context, index uint32) (*L1InfoTr } func (p *processor) getInfoByIndexWithTx(tx kv.Tx, index uint32) (*L1InfoTreeLeaf, error) { - hash, err := tx.GetOne(rootTable, uint32ToBytes(index)) + hash, err := tx.GetOne(indexTable, uint32ToBytes(index)) if err != nil { return nil, err } @@ -309,7 +309,7 @@ func (p *processor) reorg(firstReorgedBlock uint64) error { tx.Rollback() return err } - for i := blk.firstIndex; i < blk.lastIndex; i++ { + for i := blk.FirstIndex; i < blk.LastIndex; i++ { if err := p.deleteLeaf(tx, i); err != nil { tx.Rollback() return err @@ -370,12 +370,16 @@ func (p *processor) processBlock(b block) error { return err } if len(b.Events) > 0 { + var initialIndex uint32 lastIndex, err := p.getLastIndex(tx) - if err != nil { + if err == ErrNotFound { + initialIndex = 0 + } else if err != nil { tx.Rollback() return err + } else { + initialIndex = lastIndex + 1 } - initialIndex := lastIndex + 1 for i, l := range b.Events { leafToStore := storeLeaf{ Index: initialIndex + uint32(i), @@ -391,8 +395,8 @@ func (p *processor) processBlock(b block) error { } } bwl := blockWithLeafs{ - firstIndex: initialIndex, - lastIndex: initialIndex + uint32(len(b.Events)), + FirstIndex: initialIndex, + LastIndex: initialIndex + uint32(len(b.Events)), } blockValue, err := json.Marshal(bwl) if err != nil { @@ -416,6 +420,9 @@ func (p *processor) getLastIndex(tx kv.Tx) (uint32, error) { if err != nil { return 0, err } + if bNum == 0 { + return 0, nil + } iter, err := tx.RangeDescend(blockTable, uint64ToBytes(bNum), uint64ToBytes(0), 1) if err != nil { return 0, err @@ -431,7 +438,7 @@ func (p *processor) getLastIndex(tx kv.Tx) (uint32, error) { if err := json.Unmarshal(blkBytes, &blk); err != nil { return 0, err } - return blk.lastIndex - 1, nil + return blk.LastIndex - 1, nil } func (p *processor) addLeaf(tx kv.RwTx, leaf storeLeaf) error { From c7d159a51db63fb146936091f1eff7ae5a7eeaf6 Mon Sep 17 00:00:00 2001 From: Arnau Date: Wed, 24 Jul 2024 18:13:08 +0200 Subject: [PATCH 03/49] WIP --- aggoracle/e2e_test.go | 20 ++++++-------------- aggoracle/oracle.go | 4 +++- l1infotreesync/driver.go | 7 +++++-- l1infotreesync/mock_reorgdetector_test.go | 14 ++++++++++++-- localbridgesync/driver.go | 7 +++++-- localbridgesync/mock_reorgdetector_test.go | 14 ++++++++++++-- reorgdetector/mock_eth_client.go | 19 ++++++------------- 7 files changed, 49 insertions(+), 36 deletions(-) diff --git a/aggoracle/e2e_test.go b/aggoracle/e2e_test.go index 04d16cb0..dcda8662 100644 --- a/aggoracle/e2e_test.go +++ b/aggoracle/e2e_test.go @@ -19,7 +19,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient/simulated" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -29,7 +28,7 @@ func TestEVM(t *testing.T) { sender := evmSetup(t) oracle, err := aggoracle.New(sender, l1Client.Client(), syncer, etherman.LatestBlock) require.NoError(t, err) - oracle.Start(ctx) + go oracle.Start(ctx) runTest(t, gerL1Contract, sender, l1Client, authL1) } @@ -50,16 +49,14 @@ func commonSetup(t *testing.T) ( l1Client, gerL1Addr, gerL1Contract, err := newSimulatedL1(authL1) require.NoError(t, err) // Reorg detector - rdm := NewReorgDetectorMock(t) - rdm.On("Subscribe", mock.Anything).Return(&reorgdetector.Subscription{}) - rdm.On("AddBlockToTrack", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - reorg, err := reorgdetector.New(ctx) + dbPathReorgDetector := t.TempDir() + reorg, err := reorgdetector.New(ctx, l1Client.Client(), dbPathReorgDetector) require.NoError(t, err) // Syncer - dbPath := t.TempDir() - syncer, err := l1infotreesync.New(ctx, dbPath, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), 32) + dbPathSyncer := t.TempDir() + syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), 32) require.NoError(t, err) - syncer.Sync(ctx) + go syncer.Sync(ctx) return l1Client, syncer, gerL1Contract, authL1 } @@ -140,8 +137,3 @@ func runTest( require.True(t, isInjected) } } - -type ReorgDetector interface { - Subscribe(id string) *reorgdetector.Subscription - AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error -} diff --git a/aggoracle/oracle.go b/aggoracle/oracle.go index 0e89a7f2..f4957c7a 100644 --- a/aggoracle/oracle.go +++ b/aggoracle/oracle.go @@ -77,10 +77,12 @@ func (a *AggOracle) Start(ctx context.Context) { log.Debugf("GER %s already injected", gerToInject.Hex()) continue } + log.Debugf("injecting new GER: %s", gerToInject.Hex()) if err := a.chainSender.UpdateGERWaitUntilMined(ctx, gerToInject); err != nil { - log.Error("error calling updateGERWaitUntilMined: ", err) + log.Errorf("error calling updateGERWaitUntilMined, when trying to inject GER %s: %v", gerToInject.Hex(), err) continue } + log.Debugf("GER %s injected", gerToInject.Hex()) case <-ctx.Done(): return } diff --git a/l1infotreesync/driver.go b/l1infotreesync/driver.go index 9952f797..c907f681 100644 --- a/l1infotreesync/driver.go +++ b/l1infotreesync/driver.go @@ -32,7 +32,7 @@ type processorInterface interface { } type ReorgDetector interface { - Subscribe(id string) *reorgdetector.Subscription + Subscribe(id string) (*reorgdetector.Subscription, error) AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error } @@ -41,7 +41,10 @@ func newDriver( processor processorInterface, downloader downloaderFull, ) (*driver, error) { - reorgSub := reorgDetector.Subscribe(reorgDetectorID) + reorgSub, err := reorgDetector.Subscribe(reorgDetectorID) + if err != nil { + return nil, err + } return &driver{ reorgDetector: reorgDetector, reorgSub: reorgSub, diff --git a/l1infotreesync/mock_reorgdetector_test.go b/l1infotreesync/mock_reorgdetector_test.go index 9737ba06..22d174d4 100644 --- a/l1infotreesync/mock_reorgdetector_test.go +++ b/l1infotreesync/mock_reorgdetector_test.go @@ -32,10 +32,14 @@ func (_m *ReorgDetectorMock) AddBlockToTrack(ctx context.Context, id string, blo } // Subscribe provides a mock function with given fields: id -func (_m *ReorgDetectorMock) Subscribe(id string) *reorgdetector.Subscription { +func (_m *ReorgDetectorMock) Subscribe(id string) (*reorgdetector.Subscription, error) { ret := _m.Called(id) var r0 *reorgdetector.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(string) (*reorgdetector.Subscription, error)); ok { + return rf(id) + } if rf, ok := ret.Get(0).(func(string) *reorgdetector.Subscription); ok { r0 = rf(id) } else { @@ -44,7 +48,13 @@ func (_m *ReorgDetectorMock) Subscribe(id string) *reorgdetector.Subscription { } } - return r0 + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } type mockConstructorTestingTNewReorgDetectorMock interface { diff --git a/localbridgesync/driver.go b/localbridgesync/driver.go index 997fd215..eaeed1c7 100644 --- a/localbridgesync/driver.go +++ b/localbridgesync/driver.go @@ -32,7 +32,7 @@ type processorInterface interface { } type ReorgDetector interface { - Subscribe(id string) *reorgdetector.Subscription + Subscribe(id string) (*reorgdetector.Subscription, error) AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error } @@ -41,7 +41,10 @@ func newDriver( processor processorInterface, downloader downloaderFull, ) (*driver, error) { - reorgSub := reorgDetector.Subscribe(reorgDetectorID) + reorgSub, err := reorgDetector.Subscribe(reorgDetectorID) + if err != nil { + return nil, err + } return &driver{ reorgDetector: reorgDetector, reorgSub: reorgSub, diff --git a/localbridgesync/mock_reorgdetector_test.go b/localbridgesync/mock_reorgdetector_test.go index d11434a1..3639cb9a 100644 --- a/localbridgesync/mock_reorgdetector_test.go +++ b/localbridgesync/mock_reorgdetector_test.go @@ -32,10 +32,14 @@ func (_m *ReorgDetectorMock) AddBlockToTrack(ctx context.Context, id string, blo } // Subscribe provides a mock function with given fields: id -func (_m *ReorgDetectorMock) Subscribe(id string) *reorgdetector.Subscription { +func (_m *ReorgDetectorMock) Subscribe(id string) (*reorgdetector.Subscription, error) { ret := _m.Called(id) var r0 *reorgdetector.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(string) (*reorgdetector.Subscription, error)); ok { + return rf(id) + } if rf, ok := ret.Get(0).(func(string) *reorgdetector.Subscription); ok { r0 = rf(id) } else { @@ -44,7 +48,13 @@ func (_m *ReorgDetectorMock) Subscribe(id string) *reorgdetector.Subscription { } } - return r0 + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } type mockConstructorTestingTNewReorgDetectorMock interface { diff --git a/reorgdetector/mock_eth_client.go b/reorgdetector/mock_eth_client.go index add883f6..85376cc4 100644 --- a/reorgdetector/mock_eth_client.go +++ b/reorgdetector/mock_eth_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.1. DO NOT EDIT. +// Code generated by mockery v2.22.1. DO NOT EDIT. package reorgdetector @@ -20,10 +20,6 @@ type EthClientMock struct { func (_m *EthClientMock) 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 { @@ -48,10 +44,6 @@ func (_m *EthClientMock) BlockNumber(ctx context.Context) (uint64, error) { func (_m *EthClientMock) 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 { @@ -74,12 +66,13 @@ func (_m *EthClientMock) HeaderByNumber(ctx context.Context, number *big.Int) (* return r0, r1 } -// NewEthClientMock creates a new instance of EthClientMock. 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 NewEthClientMock(t interface { +type mockConstructorTestingTNewEthClientMock interface { mock.TestingT Cleanup(func()) -}) *EthClientMock { +} + +// NewEthClientMock creates a new instance of EthClientMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewEthClientMock(t mockConstructorTestingTNewEthClientMock) *EthClientMock { mock := &EthClientMock{} mock.Mock.Test(t) From 5cc0c6fd11ad41933d6bb0f2b9f77c27d9a036b4 Mon Sep 17 00:00:00 2001 From: Arnau Date: Thu, 25 Jul 2024 09:39:08 +0200 Subject: [PATCH 04/49] WIP --- aggoracle/chaingersender/evm.go | 16 +--- aggoracle/e2e_test.go | 48 +++++++++-- aggoracle/mock_ethtxmanager_test.go | 127 ++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- test/Makefile | 4 + 6 files changed, 177 insertions(+), 24 deletions(-) create mode 100644 aggoracle/mock_ethtxmanager_test.go diff --git a/aggoracle/chaingersender/evm.go b/aggoracle/chaingersender/evm.go index c7860528..3c9a8771 100644 --- a/aggoracle/chaingersender/evm.go +++ b/aggoracle/chaingersender/evm.go @@ -67,24 +67,16 @@ func (c *EVMChainGERSender) IsGERAlreadyInjected(ger common.Hash) (bool, error) if err != nil { return false, err } - return timestamp.Cmp(big.NewInt(0)) == 0, nil + return timestamp.Cmp(big.NewInt(0)) != 0, nil } func (c *EVMChainGERSender) UpdateGERWaitUntilMined(ctx context.Context, ger common.Hash) error { - opts := &bind.TransactOpts{ - From: c.sender, - // Hardcode dummy values to avoid calls to the client, - // ethtxmanager is going to handle that - NoSend: true, - Nonce: big.NewInt(1), - GasLimit: 1, - GasPrice: big.NewInt(1), - } - tx, err := c.gerContract.UpdateGlobalExitRoot(opts, ger) + abi, err := pessimisticglobalexitroot.PessimisticglobalexitrootMetaData.GetAbi() if err != nil { return err } - id, err := c.ethTxMan.Add(ctx, &c.gerAddr, nil, big.NewInt(0), tx.Data(), c.gasOffset, nil) + data, err := abi.Pack("updateGlobalExitRoot", ger) + id, err := c.ethTxMan.Add(ctx, &c.gerAddr, nil, big.NewInt(0), data, c.gasOffset, nil) if err != nil { return err } diff --git a/aggoracle/e2e_test.go b/aggoracle/e2e_test.go index dcda8662..b8b76f4b 100644 --- a/aggoracle/e2e_test.go +++ b/aggoracle/e2e_test.go @@ -7,18 +7,21 @@ import ( "testing" "time" - "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/globalexitrootnopush0" - "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/pessimisticglobalexitroot" + gerContractL1 "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/globalexitrootnopush0" + gerContractEVMChain "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/pessimisticglobalexitrootnopush0" "github.com/0xPolygon/cdk/aggoracle" "github.com/0xPolygon/cdk/aggoracle/chaingersender" "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/reorgdetector" + ethtxmanager "github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient/simulated" + mock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -36,7 +39,7 @@ func TestEVM(t *testing.T) { func commonSetup(t *testing.T) ( *simulated.Backend, *l1infotreesync.L1InfoTreeSync, - *globalexitrootnopush0.Globalexitrootnopush0, + *gerContractL1.Globalexitrootnopush0, *bind.TransactOpts, ) { // Config and spin up @@ -68,7 +71,34 @@ func evmSetup(t *testing.T) aggoracle.ChainSender { require.NoError(t, err) l2Client, gerL2Addr, _, err := newSimulatedEVMAggSovereignChain(authL2) require.NoError(t, err) - sender, err := chaingersender.NewEVMChainGERSender(gerL2Addr, authL2.From, l2Client.Client(), nil, 0) + ethTxManMock := aggoracle.NewEthTxManagerMock(t) + // id, err := c.ethTxMan.Add(ctx, &c.gerAddr, nil, big.NewInt(0), tx.Data(), c.gasOffset, nil) + ethTxManMock.On("Add", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + ctx := context.Background() + nonce, err := l2Client.Client().PendingNonceAt(ctx, authL2.From) + if err != nil { + log.Error(err) + return + } + tx := types.NewTx(&types.LegacyTx{ + To: args.Get(1).(*common.Address), + Nonce: nonce, + Value: big.NewInt(0), + Data: args.Get(4).([]byte), + }) + signedTx, err := authL2.Signer(authL2.From, tx) + if err != nil { + log.Error(err) + return + } + l2Client.Client().SendTransaction(ctx, signedTx) + l2Client.Commit() + }). + Return(common.Hash{}, nil) + // res, err := c.ethTxMan.Result(ctx, id) + ethTxManMock.On("Add", mock.Anything, mock.Anything).Return(ethtxmanager.MonitoredTxStatusMined, nil) + sender, err := chaingersender.NewEVMChainGERSender(gerL2Addr, authL2.From, l2Client.Client(), ethTxManMock, 0) require.NoError(t, err) return sender @@ -77,7 +107,7 @@ func evmSetup(t *testing.T) aggoracle.ChainSender { func newSimulatedL1(auth *bind.TransactOpts) ( client *simulated.Backend, gerAddr common.Address, - gerContract *globalexitrootnopush0.Globalexitrootnopush0, + gerContract *gerContractL1.Globalexitrootnopush0, err error, ) { balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd @@ -90,7 +120,7 @@ func newSimulatedL1(auth *bind.TransactOpts) ( blockGasLimit := uint64(999999999999999999) //nolint:gomnd client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) - gerAddr, _, gerContract, err = globalexitrootnopush0.DeployGlobalexitrootnopush0(auth, client.Client(), auth.From, auth.From) + gerAddr, _, gerContract, err = gerContractL1.DeployGlobalexitrootnopush0(auth, client.Client(), auth.From, auth.From) client.Commit() return @@ -99,7 +129,7 @@ func newSimulatedL1(auth *bind.TransactOpts) ( func newSimulatedEVMAggSovereignChain(auth *bind.TransactOpts) ( client *simulated.Backend, gerAddr common.Address, - gerContract *pessimisticglobalexitroot.Pessimisticglobalexitroot, + gerContract *gerContractEVMChain.Pessimisticglobalexitrootnopush0, err error, ) { balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd @@ -112,7 +142,7 @@ func newSimulatedEVMAggSovereignChain(auth *bind.TransactOpts) ( blockGasLimit := uint64(999999999999999999) //nolint:gomnd client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) - gerAddr, _, gerContract, err = pessimisticglobalexitroot.DeployPessimisticglobalexitroot(auth, client.Client(), auth.From) + gerAddr, _, gerContract, err = gerContractEVMChain.DeployPessimisticglobalexitrootnopush0(auth, client.Client(), auth.From) client.Commit() return @@ -120,7 +150,7 @@ func newSimulatedEVMAggSovereignChain(auth *bind.TransactOpts) ( func runTest( t *testing.T, - gerL1Contract *globalexitrootnopush0.Globalexitrootnopush0, + gerL1Contract *gerContractL1.Globalexitrootnopush0, sender aggoracle.ChainSender, l1Client *simulated.Backend, authL1 *bind.TransactOpts, diff --git a/aggoracle/mock_ethtxmanager_test.go b/aggoracle/mock_ethtxmanager_test.go new file mode 100644 index 00000000..37bcbeda --- /dev/null +++ b/aggoracle/mock_ethtxmanager_test.go @@ -0,0 +1,127 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package aggoracle + +import ( + big "math/big" + + common "github.com/ethereum/go-ethereum/common" + + context "context" + + ethtxmanager "github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager" + + mock "github.com/stretchr/testify/mock" + + types "github.com/ethereum/go-ethereum/core/types" +) + +// EthTxManagerMock is an autogenerated mock type for the EthTxManager type +type EthTxManagerMock struct { + mock.Mock +} + +// Add provides a mock function with given fields: ctx, to, forcedNonce, value, data, gasOffset, sidecar +func (_m *EthTxManagerMock) Add(ctx context.Context, to *common.Address, forcedNonce *uint64, value *big.Int, data []byte, gasOffset uint64, sidecar *types.BlobTxSidecar) (common.Hash, error) { + ret := _m.Called(ctx, to, forcedNonce, value, data, gasOffset, sidecar) + + var r0 common.Hash + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *common.Address, *uint64, *big.Int, []byte, uint64, *types.BlobTxSidecar) (common.Hash, error)); ok { + return rf(ctx, to, forcedNonce, value, data, gasOffset, sidecar) + } + if rf, ok := ret.Get(0).(func(context.Context, *common.Address, *uint64, *big.Int, []byte, uint64, *types.BlobTxSidecar) common.Hash); ok { + r0 = rf(ctx, to, forcedNonce, value, data, gasOffset, sidecar) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *common.Address, *uint64, *big.Int, []byte, uint64, *types.BlobTxSidecar) error); ok { + r1 = rf(ctx, to, forcedNonce, value, data, gasOffset, sidecar) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Remove provides a mock function with given fields: ctx, id +func (_m *EthTxManagerMock) Remove(ctx context.Context, id common.Hash) error { + ret := _m.Called(ctx, id) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Result provides a mock function with given fields: ctx, id +func (_m *EthTxManagerMock) Result(ctx context.Context, id common.Hash) (ethtxmanager.MonitoredTxResult, error) { + ret := _m.Called(ctx, id) + + var r0 ethtxmanager.MonitoredTxResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (ethtxmanager.MonitoredTxResult, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) ethtxmanager.MonitoredTxResult); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(ethtxmanager.MonitoredTxResult) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ResultsByStatus provides a mock function with given fields: ctx, statuses +func (_m *EthTxManagerMock) ResultsByStatus(ctx context.Context, statuses []ethtxmanager.MonitoredTxStatus) ([]ethtxmanager.MonitoredTxResult, error) { + ret := _m.Called(ctx, statuses) + + var r0 []ethtxmanager.MonitoredTxResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []ethtxmanager.MonitoredTxStatus) ([]ethtxmanager.MonitoredTxResult, error)); ok { + return rf(ctx, statuses) + } + if rf, ok := ret.Get(0).(func(context.Context, []ethtxmanager.MonitoredTxStatus) []ethtxmanager.MonitoredTxResult); ok { + r0 = rf(ctx, statuses) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]ethtxmanager.MonitoredTxResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []ethtxmanager.MonitoredTxStatus) error); ok { + r1 = rf(ctx, statuses) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewEthTxManagerMock interface { + mock.TestingT + Cleanup(func()) +} + +// NewEthTxManagerMock creates a new instance of EthTxManagerMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewEthTxManagerMock(t mockConstructorTestingTNewEthTxManagerMock) *EthTxManagerMock { + mock := &EthTxManagerMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/go.mod b/go.mod index fdcf2398..ded6db29 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/0xPolygon/cdk go 1.22.4 require ( - github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240724124854-785bb2a0e4a2 + github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884 github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-RC4 diff --git a/go.sum b/go.sum index 96887d67..e5d5f8f9 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240724124854-785bb2a0e4a2 h1:vMRkvTf+sfCKtEDFRGlZ9saDzQIf+LR+ly2OiRyE/2Q= -github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240724124854-785bb2a0e4a2/go.mod h1:mFlcEjsm2YBBsu8atHJ3zyVnwM+Z/fMXpVmIJge+WVU= +github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884 h1:oXUct6UWuGs15WyCEKipY0Kc0BsCnMzniAz0EIFoPxs= +github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884/go.mod h1:mFlcEjsm2YBBsu8atHJ3zyVnwM+Z/fMXpVmIJge+WVU= github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf h1:VWxVYeDhDURGp8pHR4kq3jwoXRazVHsQLMMIqYsO+Fw= github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf/go.mod h1:3XkZ0zn0GsvAT01MPQMmukF534CVSFmtrcoK3F/BK6Q= github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d h1:sxh6hZ2jF/sxxj2jd5o1vuNNCZjYmn4aRG9SRlVaEFs= diff --git a/test/Makefile b/test/Makefile index 151f7336..07d49fd3 100644 --- a/test/Makefile +++ b/test/Makefile @@ -44,3 +44,7 @@ generate-mocks-l1infotreesync: ## Generates mocks for l1infotreesync , using moc export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=downloaderFull --dir=../l1infotreesync --output=../l1infotreesync --outpkg=l1infotreesync --inpackage --structname=DownloaderMock --filename=mock_downloader_test.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=processorInterface --dir=../l1infotreesync --output=../l1infotreesync --outpkg=l1infotreesync --inpackage --structname=ProcessorMock --filename=mock_processor_test.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ReorgDetector --dir=../l1infotreesync --output=../l1infotreesync --outpkg=l1infotreesync --inpackage --structname=ReorgDetectorMock --filename=mock_reorgdetector_test.go + +.PHONY: generate-mocks-aggoracle +generate-mocks-aggoracle: ## Generates mocks for aggoracle , using mockery tool + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthTxManager --dir=../aggoracle/chaingersender --output=../aggoracle --outpkg=aggoracle --structname=EthTxManagerMock --filename=mock_ethtxmanager_test.go From fd0eb733338f030d6c85187b96a95683535ea1cb Mon Sep 17 00:00:00 2001 From: Arnau Date: Thu, 25 Jul 2024 14:57:53 +0200 Subject: [PATCH 05/49] sync refactor --- aggoracle/chaingersender/evm.go | 33 +- aggoracle/e2e_test.go | 62 +- aggoracle/oracle.go | 5 +- l1infotreesync/downloader.go | 195 +---- l1infotreesync/driver.go | 143 ---- l1infotreesync/driver_test.go | 211 ------ l1infotreesync/e2e_test.go | 7 +- l1infotreesync/l1infotreesync.go | 72 +- l1infotreesync/processor.go | 16 +- l1infotreesync/processor_test.go | 680 +++++++++--------- localbridgesync/downloader.go | 211 ++---- localbridgesync/downloader_test.go | 467 ------------ localbridgesync/localbridgesync.go | 48 +- localbridgesync/mock_downloader_test.go | 100 --- localbridgesync/mock_l2_test.go | 484 ------------- localbridgesync/mock_processor_test.go | 81 --- localbridgesync/processor.go | 17 +- localbridgesync/processor_test.go | 153 ++-- localbridgesync/types.go | 42 -- sync/common.go | 21 + sync/evmdownloader.go | 198 +++++ .../evmdownloader_test.go | 178 +++-- .../driver.go => sync/evmdriver.go | 79 +- .../driver_test.go => sync/evmdriver_test.go | 82 ++- sync/evmtypes.go | 15 + .../mock_downloader_test.go | 41 +- {l1infotreesync => sync}/mock_l2_test.go | 2 +- .../mock_processor_test.go | 20 +- .../mock_reorgdetector_test.go | 2 +- test/Makefile | 22 +- 30 files changed, 1097 insertions(+), 2590 deletions(-) delete mode 100644 l1infotreesync/driver.go delete mode 100644 l1infotreesync/driver_test.go delete mode 100644 localbridgesync/downloader_test.go delete mode 100644 localbridgesync/mock_downloader_test.go delete mode 100644 localbridgesync/mock_l2_test.go delete mode 100644 localbridgesync/mock_processor_test.go delete mode 100644 localbridgesync/types.go create mode 100644 sync/common.go create mode 100644 sync/evmdownloader.go rename l1infotreesync/downloader_test.go => sync/evmdownloader_test.go (72%) rename localbridgesync/driver.go => sync/evmdriver.go (54%) rename localbridgesync/driver_test.go => sync/evmdriver_test.go (77%) create mode 100644 sync/evmtypes.go rename {l1infotreesync => sync}/mock_downloader_test.go (54%) rename {l1infotreesync => sync}/mock_l2_test.go (99%) rename {l1infotreesync => sync}/mock_processor_test.go (73%) rename {localbridgesync => sync}/mock_reorgdetector_test.go (98%) diff --git a/aggoracle/chaingersender/evm.go b/aggoracle/chaingersender/evm.go index 3c9a8771..b3b6e405 100644 --- a/aggoracle/chaingersender/evm.go +++ b/aggoracle/chaingersender/evm.go @@ -15,10 +15,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -var ( - waitPeriodMonitorTx = time.Second * 5 -) - type EthClienter interface { ethereum.LogFilterer ethereum.BlockNumberReader @@ -34,12 +30,13 @@ type EthTxManager interface { } type EVMChainGERSender struct { - gerContract *pessimisticglobalexitroot.Pessimisticglobalexitroot - gerAddr common.Address - sender common.Address - client EthClienter - ethTxMan EthTxManager - gasOffset uint64 + gerContract *pessimisticglobalexitroot.Pessimisticglobalexitroot + gerAddr common.Address + sender common.Address + client EthClienter + ethTxMan EthTxManager + gasOffset uint64 + waitPeriodMonitorTx time.Duration } func NewEVMChainGERSender( @@ -47,18 +44,20 @@ func NewEVMChainGERSender( client EthClienter, ethTxMan EthTxManager, gasOffset uint64, + waitPeriodMonitorTx time.Duration, ) (*EVMChainGERSender, error) { gerContract, err := pessimisticglobalexitroot.NewPessimisticglobalexitroot(globalExitRoot, client) if err != nil { return nil, err } return &EVMChainGERSender{ - gerContract: gerContract, - gerAddr: globalExitRoot, - sender: sender, - client: client, - ethTxMan: ethTxMan, - gasOffset: gasOffset, + gerContract: gerContract, + gerAddr: globalExitRoot, + sender: sender, + client: client, + ethTxMan: ethTxMan, + gasOffset: gasOffset, + waitPeriodMonitorTx: waitPeriodMonitorTx, }, nil } @@ -81,7 +80,7 @@ func (c *EVMChainGERSender) UpdateGERWaitUntilMined(ctx context.Context, ger com return err } for { - time.Sleep(waitPeriodMonitorTx) + time.Sleep(c.waitPeriodMonitorTx) res, err := c.ethTxMan.Result(ctx, id) if err != nil { log.Error("error calling ethTxMan.Result: ", err) diff --git a/aggoracle/e2e_test.go b/aggoracle/e2e_test.go index b8b76f4b..7d616be9 100644 --- a/aggoracle/e2e_test.go +++ b/aggoracle/e2e_test.go @@ -2,6 +2,7 @@ package aggoracle_test import ( "context" + "errors" "math/big" "strconv" "testing" @@ -16,6 +17,7 @@ import ( "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/reorgdetector" ethtxmanager "github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -29,7 +31,7 @@ func TestEVM(t *testing.T) { ctx := context.Background() l1Client, syncer, gerL1Contract, authL1 := commonSetup(t) sender := evmSetup(t) - oracle, err := aggoracle.New(sender, l1Client.Client(), syncer, etherman.LatestBlock) + oracle, err := aggoracle.New(sender, l1Client.Client(), syncer, etherman.LatestBlock, time.Millisecond) require.NoError(t, err) go oracle.Start(ctx) @@ -57,9 +59,9 @@ func commonSetup(t *testing.T) ( require.NoError(t, err) // Syncer dbPathSyncer := t.TempDir() - syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), 32) + syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), 32, time.Millisecond) require.NoError(t, err) - go syncer.Sync(ctx) + go syncer.Start(ctx) return l1Client, syncer, gerL1Contract, authL1 } @@ -81,24 +83,56 @@ func evmSetup(t *testing.T) aggoracle.ChainSender { log.Error(err) return } - tx := types.NewTx(&types.LegacyTx{ + gas, err := l2Client.Client().EstimateGas(ctx, ethereum.CallMsg{ + From: authL2.From, To: args.Get(1).(*common.Address), - Nonce: nonce, Value: big.NewInt(0), Data: args.Get(4).([]byte), }) + if err != nil { + log.Error(err) + res, err := l2Client.Client().CallContract(ctx, ethereum.CallMsg{ + From: authL2.From, + To: args.Get(1).(*common.Address), + Value: big.NewInt(0), + Data: args.Get(4).([]byte), + }, nil) + log.Debugf("contract call: %s", res) + if err != nil { + log.Error(err) + } + return + } + price, err := l2Client.Client().SuggestGasPrice(ctx) + if err != nil { + log.Error(err) + } + tx := types.NewTx(&types.LegacyTx{ + To: args.Get(1).(*common.Address), + Nonce: nonce, + Value: big.NewInt(0), + Data: args.Get(4).([]byte), + Gas: gas, + GasPrice: price, + }) + tx.Gas() signedTx, err := authL2.Signer(authL2.From, tx) if err != nil { log.Error(err) return } - l2Client.Client().SendTransaction(ctx, signedTx) + err = l2Client.Client().SendTransaction(ctx, signedTx) + if err != nil { + log.Error(err) + return + } l2Client.Commit() }). Return(common.Hash{}, nil) // res, err := c.ethTxMan.Result(ctx, id) - ethTxManMock.On("Add", mock.Anything, mock.Anything).Return(ethtxmanager.MonitoredTxStatusMined, nil) - sender, err := chaingersender.NewEVMChainGERSender(gerL2Addr, authL2.From, l2Client.Client(), ethTxManMock, 0) + ethTxManMock.On("Result", mock.Anything, mock.Anything). + Return(ethtxmanager.MonitoredTxResult{Status: ethtxmanager.MonitoredTxStatusMined}, nil) + sender, err := chaingersender.NewEVMChainGERSender(gerL2Addr, authL2.From, l2Client.Client(), ethTxManMock, 0, time.Millisecond*50) require.NoError(t, err) return sender @@ -143,8 +177,18 @@ func newSimulatedEVMAggSovereignChain(auth *bind.TransactOpts) ( client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) gerAddr, _, gerContract, err = gerContractEVMChain.DeployPessimisticglobalexitrootnopush0(auth, client.Client(), auth.From) + if err != nil { + return + } + client.Commit() + _GLOBAL_EXIT_ROOT_SETTER_ROLE := common.HexToHash("0x7b95520991dfda409891be0afa2635b63540f92ee996fda0bf695a166e5c5176") + _, err = gerContract.GrantRole(auth, _GLOBAL_EXIT_ROOT_SETTER_ROLE, auth.From) client.Commit() + hasRole, _ := gerContract.HasRole(&bind.CallOpts{Pending: false}, _GLOBAL_EXIT_ROOT_SETTER_ROLE, auth.From) + if !hasRole { + err = errors.New("failed to set role") + } return } @@ -159,7 +203,7 @@ func runTest( _, err := gerL1Contract.UpdateExitRoot(authL1, common.HexToHash(strconv.Itoa(i))) require.NoError(t, err) l1Client.Commit() - time.Sleep(time.Second * 30) + time.Sleep(time.Millisecond * 50) expectedGER, err := gerL1Contract.GetLastGlobalExitRoot(&bind.CallOpts{Pending: false}) require.NoError(t, err) isInjected, err := sender.IsGERAlreadyInjected(expectedGER) diff --git a/aggoracle/oracle.go b/aggoracle/oracle.go index f4957c7a..8f4b7a67 100644 --- a/aggoracle/oracle.go +++ b/aggoracle/oracle.go @@ -13,10 +13,6 @@ import ( "github.com/ethereum/go-ethereum/common" ) -var ( - waitPeriodNextGER = time.Second * 30 -) - type EthClienter interface { ethereum.LogFilterer ethereum.BlockNumberReader @@ -46,6 +42,7 @@ func New( l1Client EthClienter, l1InfoTreeSyncer L1InfoTreer, blockFinalityType etherman.BlockNumberFinality, + waitPeriodNextGER time.Duration, ) (*AggOracle, error) { ticker := time.NewTicker(waitPeriodNextGER) finality, err := blockFinalityType.ToBlockNum() diff --git a/l1infotreesync/downloader.go b/l1infotreesync/downloader.go index 549e482e..ba9b219b 100644 --- a/l1infotreesync/downloader.go +++ b/l1infotreesync/downloader.go @@ -1,13 +1,10 @@ package l1infotreesync import ( - "context" - "math/big" - "time" + "fmt" "github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry/polygonzkevmglobalexitrootv2" - "github.com/0xPolygon/cdk/etherman" - "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -16,7 +13,6 @@ import ( ) var ( - waitForNewBlocksPeriod = time.Millisecond * 100 updateL1InfoTreeSignature = crypto.Keccak256Hash([]byte("UpdateL1InfoTree(bytes32,bytes32)")) ) @@ -27,172 +23,22 @@ type EthClienter interface { bind.ContractBackend } -type downloaderInterface interface { - waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) - getEventsByBlockRange(ctx context.Context, fromBlock, toBlock uint64) []block - getLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log - appendLog(b *block, l types.Log) - getBlockHeader(ctx context.Context, blockNum uint64) blockHeader -} - type L1InfoTreeUpdate struct { MainnetExitRoot common.Hash RollupExitRoot common.Hash } -type block struct { - blockHeader - Events []L1InfoTreeUpdate -} - -type blockHeader struct { - Num uint64 - Hash common.Hash - ParentHash common.Hash - Timestamp uint64 -} - -type downloader struct { - syncBlockChunkSize uint64 - downloaderInterface -} - -func newDownloader( - GERAddr common.Address, - ethClient EthClienter, - syncBlockChunkSize uint64, - blockFinalityType etherman.BlockNumberFinality, -) (*downloader, error) { - GERContract, err := polygonzkevmglobalexitrootv2.NewPolygonzkevmglobalexitrootv2(GERAddr, ethClient) - if err != nil { - return nil, err - } - finality, err := blockFinalityType.ToBlockNum() +func buildAppender(client EthClienter, globalExitRoot common.Address) (sync.LogAppenderMap, error) { + contract, err := polygonzkevmglobalexitrootv2.NewPolygonzkevmglobalexitrootv2(globalExitRoot, client) if err != nil { return nil, err } - return &downloader{ - syncBlockChunkSize: syncBlockChunkSize, - downloaderInterface: &downloaderImplementation{ - GERAddr: GERAddr, - GERContract: GERContract, - ethClient: ethClient, - blockFinality: finality, - }, - }, nil -} - -func (d *downloader) download(ctx context.Context, fromBlock uint64, downloadedCh chan block) { - lastBlock := d.waitForNewBlocks(ctx, 0) - for { - select { - case <-ctx.Done(): - log.Debug("closing channel") - close(downloadedCh) - return - default: - } - toBlock := fromBlock + d.syncBlockChunkSize - if toBlock > lastBlock { - toBlock = lastBlock - } - if fromBlock > toBlock { - log.Debug("waiting for new blocks, last block ", toBlock) - lastBlock = d.waitForNewBlocks(ctx, toBlock) - continue - } - log.Debugf("getting events from blocks %d to %d", fromBlock, toBlock) - blocks := d.getEventsByBlockRange(ctx, fromBlock, toBlock) - for _, b := range blocks { - log.Debugf("sending block %d to the driver (with events)", b.Num) - downloadedCh <- b - } - if len(blocks) == 0 || blocks[len(blocks)-1].Num < toBlock { - // Indicate the last downloaded block if there are not events on it - log.Debugf("sending block %d to the driver (without evvents)", toBlock) - downloadedCh <- block{ - blockHeader: d.getBlockHeader(ctx, toBlock), - } - } - fromBlock = toBlock + 1 - } -} - -type downloaderImplementation struct { - GERAddr common.Address - GERContract *polygonzkevmglobalexitrootv2.Polygonzkevmglobalexitrootv2 - ethClient EthClienter - blockFinality *big.Int -} - -func (d *downloaderImplementation) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) { - attempts := 0 - for { - header, err := d.ethClient.HeaderByNumber(ctx, d.blockFinality) - if err != nil { - attempts++ - log.Error("error geting last block num from eth client: ", err) - retryHandler("waitForNewBlocks", attempts) - continue - } - if header.Number.Uint64() > lastBlockSeen { - return header.Number.Uint64() - } - time.Sleep(waitForNewBlocksPeriod) - } -} - -func (d *downloaderImplementation) getEventsByBlockRange(ctx context.Context, fromBlock, toBlock uint64) []block { - blocks := []block{} - logs := d.getLogs(ctx, fromBlock, toBlock) - for _, l := range logs { - if len(blocks) == 0 || blocks[len(blocks)-1].Num < l.BlockNumber { - b := d.getBlockHeader(ctx, l.BlockNumber) - blocks = append(blocks, block{ - blockHeader: blockHeader{ - Num: l.BlockNumber, - Hash: l.BlockHash, - Timestamp: b.Timestamp, - ParentHash: b.ParentHash, - }, - Events: []L1InfoTreeUpdate{}, - }) - } - d.appendLog(&blocks[len(blocks)-1], l) - } - - return blocks -} - -func (d *downloaderImplementation) getLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log { - query := ethereum.FilterQuery{ - FromBlock: new(big.Int).SetUint64(fromBlock), - Addresses: []common.Address{d.GERAddr}, - Topics: [][]common.Hash{ - {updateL1InfoTreeSignature}, - }, - ToBlock: new(big.Int).SetUint64(toBlock), - } - attempts := 0 - for { - logs, err := d.ethClient.FilterLogs(ctx, query) + appender := make(sync.LogAppenderMap) + appender[updateL1InfoTreeSignature] = func(b *sync.EVMBlock, l types.Log) error { + l1InfoTreeUpdate, err := contract.ParseUpdateL1InfoTree(l) if err != nil { - attempts++ - log.Error("error calling FilterLogs to eth client: ", err) - retryHandler("getLogs", attempts) - continue - } - return logs - } -} - -func (d *downloaderImplementation) appendLog(b *block, l types.Log) { - switch l.Topics[0] { - case updateL1InfoTreeSignature: - l1InfoTreeUpdate, err := d.GERContract.ParseUpdateL1InfoTree(l) - if err != nil { - log.Fatalf( - "error parsing log %+v using d.GERContract.ParseUpdateL1InfoTree: %v", + return fmt.Errorf( + "error parsing log %+v using contract.ParseUpdateL1InfoTree: %v", l, err, ) } @@ -200,26 +46,7 @@ func (d *downloaderImplementation) appendLog(b *block, l types.Log) { MainnetExitRoot: l1InfoTreeUpdate.MainnetExitRoot, RollupExitRoot: l1InfoTreeUpdate.RollupExitRoot, }) - default: - log.Fatalf("unexpected log %+v", l) - } -} - -func (d *downloaderImplementation) getBlockHeader(ctx context.Context, blockNum uint64) blockHeader { - attempts := 0 - for { - header, err := d.ethClient.HeaderByNumber(ctx, big.NewInt(int64(blockNum))) - if err != nil { - attempts++ - log.Errorf("error getting block header for block %d, err: %v", blockNum, err) - retryHandler("getBlockHeader", attempts) - continue - } - return blockHeader{ - Num: header.Number.Uint64(), - Hash: header.Hash(), - ParentHash: header.ParentHash, - Timestamp: header.Time, - } + return nil } + return appender, nil } diff --git a/l1infotreesync/driver.go b/l1infotreesync/driver.go deleted file mode 100644 index c907f681..00000000 --- a/l1infotreesync/driver.go +++ /dev/null @@ -1,143 +0,0 @@ -package l1infotreesync - -import ( - "context" - - "github.com/0xPolygon/cdk/log" - "github.com/0xPolygon/cdk/reorgdetector" - "github.com/ethereum/go-ethereum/common" -) - -const ( - downloadBufferSize = 1000 - reorgDetectorID = "localbridgesync" -) - -type downloaderFull interface { - downloaderInterface - download(ctx context.Context, fromBlock uint64, downloadedCh chan block) -} - -type driver struct { - reorgDetector ReorgDetector - reorgSub *reorgdetector.Subscription - processor processorInterface - downloader downloaderFull -} - -type processorInterface interface { - getLastProcessedBlock(ctx context.Context) (uint64, error) - processBlock(block block) error - reorg(firstReorgedBlock uint64) error -} - -type ReorgDetector interface { - Subscribe(id string) (*reorgdetector.Subscription, error) - AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error -} - -func newDriver( - reorgDetector ReorgDetector, - processor processorInterface, - downloader downloaderFull, -) (*driver, error) { - reorgSub, err := reorgDetector.Subscribe(reorgDetectorID) - if err != nil { - return nil, err - } - return &driver{ - reorgDetector: reorgDetector, - reorgSub: reorgSub, - processor: processor, - downloader: downloader, - }, nil -} - -func (d *driver) Sync(ctx context.Context) { -reset: - var ( - lastProcessedBlock uint64 - attempts int - err error - ) - for { - lastProcessedBlock, err = d.processor.getLastProcessedBlock(ctx) - if err != nil { - attempts++ - log.Error("error geting last processed block: ", err) - retryHandler("Sync", attempts) - continue - } - break - } - cancellableCtx, cancel := context.WithCancel(ctx) - defer cancel() - - // start downloading - downloadCh := make(chan block, downloadBufferSize) - go d.downloader.download(cancellableCtx, lastProcessedBlock, downloadCh) - - for { - select { - case b := <-downloadCh: - log.Debug("handleNewBlock") - d.handleNewBlock(ctx, b) - case firstReorgedBlock := <-d.reorgSub.FirstReorgedBlock: - log.Debug("handleReorg") - d.handleReorg(cancel, downloadCh, firstReorgedBlock) - goto reset - } - } -} - -func (d *driver) handleNewBlock(ctx context.Context, b block) { - attempts := 0 - for { - err := d.reorgDetector.AddBlockToTrack(ctx, reorgDetectorID, b.Num, b.Hash) - if err != nil { - attempts++ - log.Errorf("error adding block %d to tracker: %v", b.Num, err) - retryHandler("handleNewBlock", attempts) - continue - } - break - } - attempts = 0 - for { - err := d.processor.processBlock(b) - if err != nil { - attempts++ - log.Errorf("error processing events for blcok %d, err: ", b.Num, err) - retryHandler("handleNewBlock", attempts) - continue - } - break - } -} - -func (d *driver) handleReorg( - cancel context.CancelFunc, downloadCh chan block, firstReorgedBlock uint64, -) { - // stop downloader - cancel() - _, ok := <-downloadCh - for ok { - _, ok = <-downloadCh - } - // handle reorg - attempts := 0 - for { - err := d.processor.reorg(firstReorgedBlock) - if err != nil { - attempts++ - log.Errorf( - "error processing reorg, last valid block %d, err: %v", - firstReorgedBlock, err, - ) - retryHandler("handleReorg", attempts) - continue - } - break - } - d.reorgSub.ReorgProcessed <- true -} diff --git a/l1infotreesync/driver_test.go b/l1infotreesync/driver_test.go deleted file mode 100644 index e206973a..00000000 --- a/l1infotreesync/driver_test.go +++ /dev/null @@ -1,211 +0,0 @@ -package l1infotreesync - -import ( - "context" - "errors" - "sync" - "testing" - "time" - - "github.com/0xPolygon/cdk/log" - "github.com/0xPolygon/cdk/reorgdetector" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" -) - -func TestSync(t *testing.T) { - retryAfterErrorPeriod = time.Millisecond * 100 - rdm := NewReorgDetectorMock(t) - pm := NewProcessorMock(t) - dm := NewDownloaderMock(t) - firstReorgedBlock := make(chan uint64) - reorgProcessed := make(chan bool) - rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{ - FirstReorgedBlock: firstReorgedBlock, - ReorgProcessed: reorgProcessed, - }) - driver, err := newDriver(rdm, pm, dm) - require.NoError(t, err) - ctx := context.Background() - expectedBlock1 := block{ - blockHeader: blockHeader{ - Num: 3, - Hash: common.HexToHash("03"), - }, - } - expectedBlock2 := block{ - blockHeader: blockHeader{ - Num: 9, - Hash: common.HexToHash("09"), - }, - } - type reorgSemaphore struct { - mu sync.Mutex - green bool - } - reorg1Completed := reorgSemaphore{} - dm.On("download", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - ctx := args.Get(0).(context.Context) - downloadedCh := args.Get(2).(chan block) - log.Info("entering mock loop") - for { - select { - case <-ctx.Done(): - log.Info("closing channel") - close(downloadedCh) - return - default: - } - reorg1Completed.mu.Lock() - green := reorg1Completed.green - reorg1Completed.mu.Unlock() - if green { - downloadedCh <- expectedBlock2 - } else { - downloadedCh <- expectedBlock1 - } - time.Sleep(100 * time.Millisecond) - } - }) - - // Mocking this actions, the driver should "store" all the blocks from the downloader - pm.On("getLastProcessedBlock", ctx). - Return(uint64(3), nil) - rdm.On("AddBlockToTrack", ctx, reorgDetectorID, expectedBlock1.Num, expectedBlock1.Hash). - Return(nil) - pm.On("storeBridgeEvents", expectedBlock1.Num, expectedBlock1.Events). - Return(nil) - rdm.On("AddBlockToTrack", ctx, reorgDetectorID, expectedBlock2.Num, expectedBlock2.Hash). - Return(nil) - pm.On("storeBridgeEvents", expectedBlock2.Num, expectedBlock2.Events). - Return(nil) - go driver.Sync(ctx) - time.Sleep(time.Millisecond * 200) // time to download expectedBlock1 - - // Trigger reorg 1 - reorgedBlock1 := uint64(5) - pm.On("reorg", reorgedBlock1).Return(nil) - firstReorgedBlock <- reorgedBlock1 - ok := <-reorgProcessed - require.True(t, ok) - reorg1Completed.mu.Lock() - reorg1Completed.green = true - reorg1Completed.mu.Unlock() - time.Sleep(time.Millisecond * 200) // time to download expectedBlock2 - - // Trigger reorg 2: syncer restarts the porcess - reorgedBlock2 := uint64(7) - pm.On("reorg", reorgedBlock2).Return(nil) - firstReorgedBlock <- reorgedBlock2 - ok = <-reorgProcessed - require.True(t, ok) -} - -func TestHandleNewBlock(t *testing.T) { - retryAfterErrorPeriod = time.Millisecond * 100 - rdm := NewReorgDetectorMock(t) - pm := NewProcessorMock(t) - dm := NewDownloaderMock(t) - rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{}) - driver, err := newDriver(rdm, pm, dm) - require.NoError(t, err) - ctx := context.Background() - - // happy path - b1 := block{ - blockHeader: blockHeader{ - Num: 1, - Hash: common.HexToHash("f00"), - }, - } - rdm. - On("AddBlockToTrack", ctx, reorgDetectorID, b1.Num, b1.Hash). - Return(nil) - pm.On("storeBridgeEvents", b1.Num, b1.Events). - Return(nil) - driver.handleNewBlock(ctx, b1) - - // reorg deteector fails once - b2 := block{ - blockHeader: blockHeader{ - Num: 2, - Hash: common.HexToHash("f00"), - }, - } - rdm. - On("AddBlockToTrack", ctx, reorgDetectorID, b2.Num, b2.Hash). - Return(errors.New("foo")).Once() - rdm. - On("AddBlockToTrack", ctx, reorgDetectorID, b2.Num, b2.Hash). - Return(nil).Once() - pm.On("storeBridgeEvents", b2.Num, b2.Events). - Return(nil) - driver.handleNewBlock(ctx, b2) - - // processor fails once - b3 := block{ - blockHeader: blockHeader{ - Num: 3, - Hash: common.HexToHash("f00"), - }, - } - rdm. - On("AddBlockToTrack", ctx, reorgDetectorID, b3.Num, b3.Hash). - Return(nil) - pm.On("storeBridgeEvents", b3.Num, b3.Events). - Return(errors.New("foo")).Once() - pm.On("storeBridgeEvents", b3.Num, b3.Events). - Return(nil).Once() - driver.handleNewBlock(ctx, b3) - -} - -func TestHandleReorg(t *testing.T) { - retryAfterErrorPeriod = time.Millisecond * 100 - rdm := NewReorgDetectorMock(t) - pm := NewProcessorMock(t) - dm := NewDownloaderMock(t) - reorgProcessed := make(chan bool) - rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{ - ReorgProcessed: reorgProcessed, - }) - driver, err := newDriver(rdm, pm, dm) - require.NoError(t, err) - ctx := context.Background() - - // happy path - _, cancel := context.WithCancel(ctx) - downloadCh := make(chan block) - firstReorgedBlock := uint64(5) - pm.On("reorg", firstReorgedBlock).Return(nil) - go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) - close(downloadCh) - done := <-reorgProcessed - require.True(t, done) - - // download ch sends some garbage - _, cancel = context.WithCancel(ctx) - downloadCh = make(chan block) - firstReorgedBlock = uint64(6) - pm.On("reorg", firstReorgedBlock).Return(nil) - go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) - downloadCh <- block{} - downloadCh <- block{} - downloadCh <- block{} - close(downloadCh) - done = <-reorgProcessed - require.True(t, done) - - // processor fails 2 times - _, cancel = context.WithCancel(ctx) - downloadCh = make(chan block) - firstReorgedBlock = uint64(7) - pm.On("reorg", firstReorgedBlock).Return(errors.New("foo")).Once() - pm.On("reorg", firstReorgedBlock).Return(errors.New("foo")).Once() - pm.On("reorg", firstReorgedBlock).Return(nil).Once() - go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) - close(downloadCh) - done = <-reorgProcessed - require.True(t, done) -} diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index baad1d53..dc5734d5 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -43,7 +43,6 @@ func newSimulatedClient(auth *bind.TransactOpts) ( } func TestE2E(t *testing.T) { - waitForNewBlocksPeriod = time.Millisecond ctx := context.Background() dbPath := t.TempDir() privateKey, err := crypto.GenerateKey() @@ -51,13 +50,13 @@ func TestE2E(t *testing.T) { auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337)) require.NoError(t, err) rdm := NewReorgDetectorMock(t) - rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{}) + rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{}, nil) rdm.On("AddBlockToTrack", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) client, gerAddr, gerSc, err := newSimulatedClient(auth) require.NoError(t, err) - syncer, err := New(ctx, dbPath, gerAddr, 10, etherman.LatestBlock, rdm, client.Client(), 32) + syncer, err := New(ctx, dbPath, gerAddr, 10, etherman.LatestBlock, rdm, client.Client(), 32, time.Millisecond) require.NoError(t, err) - go syncer.Sync(ctx) + go syncer.Start(ctx) // Update GER 10 times // TODO: test syncer restart diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index fa04f0e6..847488df 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -5,18 +5,23 @@ import ( "time" "github.com/0xPolygon/cdk/etherman" - "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum/common" ) +const ( + reorgDetectorID = "l1infotreesync" + downloadBufferSize = 1000 +) + var ( retryAfterErrorPeriod = time.Second * 10 maxRetryAttemptsAfterError = 5 ) type L1InfoTreeSync struct { - *processor - *driver + processor *processor + driver *sync.EVMDriver } func New( @@ -25,31 +30,66 @@ func New( globalExitRoot common.Address, syncBlockChunkSize uint64, blockFinalityType etherman.BlockNumberFinality, - rd ReorgDetector, + rd sync.ReorgDetector, l1Client EthClienter, treeHeight uint8, + waitForNewBlocksPeriod time.Duration, ) (*L1InfoTreeSync, error) { - p, err := newProcessor(ctx, dbPath, treeHeight) + processor, err := newProcessor(ctx, dbPath, treeHeight) if err != nil { return nil, err } - dwn, err := newDownloader(globalExitRoot, l1Client, syncBlockChunkSize, blockFinalityType) + + appender, err := buildAppender(l1Client, globalExitRoot) if err != nil { return nil, err } - dri, err := newDriver(rd, p, dwn) + downloader, err := sync.NewEVMDownloader( + l1Client, + syncBlockChunkSize, + blockFinalityType, + waitForNewBlocksPeriod, + appender, + []common.Address{globalExitRoot}, + ) if err != nil { return nil, err } - return &L1InfoTreeSync{p, dri}, nil -} -func retryHandler(funcName string, attempts int) { - if attempts >= maxRetryAttemptsAfterError { - log.Fatalf( - "%s failed too many times (%d)", - funcName, maxRetryAttemptsAfterError, - ) + driver, err := sync.NewEVMDriver(rd, processor, downloader, reorgDetectorID, downloadBufferSize) + if err != nil { + return nil, err } - time.Sleep(retryAfterErrorPeriod) + return &L1InfoTreeSync{ + processor: processor, + driver: driver, + }, nil +} + +func (s *L1InfoTreeSync) Start(ctx context.Context) { + s.driver.Sync(ctx) +} + +func (s *L1InfoTreeSync) ComputeMerkleProofByIndex(ctx context.Context, index uint32) ([][32]byte, common.Hash, error) { + return s.processor.ComputeMerkleProofByIndex(ctx, index) +} + +func (s *L1InfoTreeSync) ComputeMerkleProofByRoot(ctx context.Context, root common.Hash) ([][32]byte, common.Hash, error) { + return s.processor.ComputeMerkleProofByRoot(ctx, root) +} + +func (s *L1InfoTreeSync) GetInfoByRoot(ctx context.Context, root common.Hash) (*L1InfoTreeLeaf, error) { + return s.processor.GetInfoByRoot(ctx, root) +} + +func (s *L1InfoTreeSync) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { + return s.processor.GetLatestInfoUntilBlock(ctx, blockNum) +} + +func (s *L1InfoTreeSync) GetInfoByIndex(ctx context.Context, index uint32) (*L1InfoTreeLeaf, error) { + return s.processor.GetInfoByIndex(ctx, index) +} + +func (s *L1InfoTreeSync) GetInfoByHash(ctx context.Context, hash []byte) (*L1InfoTreeLeaf, error) { + return s.processor.GetInfoByHash(ctx, hash) } diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 12047d77..2247fae3 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -7,6 +7,7 @@ import ( "errors" "github.com/0xPolygon/cdk/l1infotree" + "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum/common" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/kv/mdbx" @@ -268,7 +269,7 @@ func (p *processor) getInfoByHashWithTx(tx kv.Tx, hash []byte) (*L1InfoTreeLeaf, }, nil } -func (p *processor) getLastProcessedBlock(ctx context.Context) (uint64, error) { +func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { tx, err := p.db.BeginRo(ctx) if err != nil { return 0, err @@ -287,7 +288,7 @@ func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { } } -func (p *processor) reorg(firstReorgedBlock uint64) error { +func (p *processor) Reorg(firstReorgedBlock uint64) error { // TODO: Does tree need to be reorged? tx, err := p.db.BeginRw(context.Background()) if err != nil { @@ -362,9 +363,9 @@ func (p *processor) deleteLeaf(tx kv.RwTx, index uint32) error { return nil } -// processBlock process the leafs of the L1 info tree found on a block +// ProcessBlock process the leafs of the L1 info tree found on a block // this function can be called without leafs with the intention to track the last processed block -func (p *processor) processBlock(b block) error { +func (p *processor) ProcessBlock(b sync.EVMBlock) error { tx, err := p.db.BeginRw(context.Background()) if err != nil { return err @@ -380,11 +381,12 @@ func (p *processor) processBlock(b block) error { } else { initialIndex = lastIndex + 1 } - for i, l := range b.Events { + for i, e := range b.Events { + event := e.(L1InfoTreeUpdate) leafToStore := storeLeaf{ Index: initialIndex + uint32(i), - MainnetExitRoot: l.MainnetExitRoot, - RollupExitRoot: l.RollupExitRoot, + MainnetExitRoot: event.MainnetExitRoot, + RollupExitRoot: event.RollupExitRoot, ParentHash: b.ParentHash, Timestamp: b.Timestamp, BlockNumber: b.Num, diff --git a/l1infotreesync/processor_test.go b/l1infotreesync/processor_test.go index 949a3fde..b6f6c942 100644 --- a/l1infotreesync/processor_test.go +++ b/l1infotreesync/processor_test.go @@ -1,373 +1,363 @@ package l1infotreesync -import ( - "context" - "fmt" - "slices" - "testing" +// func TestProceessor(t *testing.T) { +// path := t.TempDir() +// ctx := context.Background() +// p, err := newProcessor(ctx, path, 32) +// require.NoError(t, err) +// actions := []processAction{ +// // processed: ~ +// &getLastProcessedBlockAction{ +// p: p, +// description: "on an empty processor", +// ctx: context.Background(), +// expectedLastProcessedBlock: 0, +// expectedErr: nil, +// }, +// &reorgAction{ +// p: p, +// description: "on an empty processor: firstReorgedBlock = 0", +// firstReorgedBlock: 0, +// expectedErr: nil, +// }, +// &reorgAction{ +// p: p, +// description: "on an empty processor: firstReorgedBlock = 1", +// firstReorgedBlock: 1, +// expectedErr: nil, +// }, +// &getClaimsAndBridgesAction{ +// p: p, +// description: "on an empty processor", +// ctx: context.Background(), +// fromBlock: 0, +// toBlock: 2, +// expectedEvents: nil, +// expectedErr: ErrBlockNotProcessed, +// }, +// &storeL1InfoTreeUpdatesAction{ +// p: p, +// description: "block1", +// b: block1, +// expectedErr: nil, +// }, +// // processed: block1 +// &getLastProcessedBlockAction{ +// p: p, +// description: "after block1", +// ctx: context.Background(), +// expectedLastProcessedBlock: 1, +// expectedErr: nil, +// }, +// &getClaimsAndBridgesAction{ +// p: p, +// description: "after block1: range 0, 2", +// ctx: context.Background(), +// fromBlock: 0, +// toBlock: 2, +// expectedEvents: nil, +// expectedErr: ErrBlockNotProcessed, +// }, +// &getClaimsAndBridgesAction{ +// p: p, +// description: "after block1: range 1, 1", +// ctx: context.Background(), +// fromBlock: 1, +// toBlock: 1, +// expectedEvents: block1.Events, +// expectedErr: nil, +// }, +// &reorgAction{ +// p: p, +// description: "after block1", +// firstReorgedBlock: 1, +// expectedErr: nil, +// }, +// // processed: ~ +// &getClaimsAndBridgesAction{ +// p: p, +// description: "after block1 reorged", +// ctx: context.Background(), +// fromBlock: 0, +// toBlock: 2, +// expectedEvents: nil, +// expectedErr: ErrBlockNotProcessed, +// }, +// &storeL1InfoTreeUpdatesAction{ +// p: p, +// description: "block1 (after it's reorged)", +// b: block1, +// expectedErr: nil, +// }, +// // processed: block3 +// &storeL1InfoTreeUpdatesAction{ +// p: p, +// description: "block3", +// b: block3, +// expectedErr: nil, +// }, +// // processed: block1, block3 +// &getLastProcessedBlockAction{ +// p: p, +// description: "after block3", +// ctx: context.Background(), +// expectedLastProcessedBlock: 3, +// expectedErr: nil, +// }, +// &getClaimsAndBridgesAction{ +// p: p, +// description: "after block3: range 2, 2", +// ctx: context.Background(), +// fromBlock: 2, +// toBlock: 2, +// expectedEvents: []L1InfoTreeUpdate{}, +// expectedErr: nil, +// }, +// &getClaimsAndBridgesAction{ +// p: p, +// description: "after block3: range 1, 3", +// ctx: context.Background(), +// fromBlock: 1, +// toBlock: 3, +// expectedEvents: append(block1.Events, block3.Events...), +// expectedErr: nil, +// }, +// &reorgAction{ +// p: p, +// description: "after block3, with value 3", +// firstReorgedBlock: 3, +// expectedErr: nil, +// }, +// // processed: block1 +// &getLastProcessedBlockAction{ +// p: p, +// description: "after block3 reorged", +// ctx: context.Background(), +// expectedLastProcessedBlock: 2, +// expectedErr: nil, +// }, +// &reorgAction{ +// p: p, +// description: "after block3, with value 2", +// firstReorgedBlock: 2, +// expectedErr: nil, +// }, +// &getLastProcessedBlockAction{ +// p: p, +// description: "after block2 reorged", +// ctx: context.Background(), +// expectedLastProcessedBlock: 1, +// expectedErr: nil, +// }, +// &storeL1InfoTreeUpdatesAction{ +// p: p, +// description: "block3 after reorg", +// b: block3, +// expectedErr: nil, +// }, +// // processed: block1, block3 +// &storeL1InfoTreeUpdatesAction{ +// p: p, +// description: "block4", +// b: block4, +// expectedErr: nil, +// }, +// // processed: block1, block3, block4 +// &storeL1InfoTreeUpdatesAction{ +// p: p, +// description: "block5", +// b: block5, +// expectedErr: nil, +// }, +// // processed: block1, block3, block4, block5 +// &getLastProcessedBlockAction{ +// p: p, +// description: "after block5", +// ctx: context.Background(), +// expectedLastProcessedBlock: 5, +// expectedErr: nil, +// }, +// &getClaimsAndBridgesAction{ +// p: p, +// description: "after block5: range 1, 3", +// ctx: context.Background(), +// fromBlock: 1, +// toBlock: 3, +// expectedEvents: append(block1.Events, block3.Events...), +// expectedErr: nil, +// }, +// &getClaimsAndBridgesAction{ +// p: p, +// description: "after block5: range 4, 5", +// ctx: context.Background(), +// fromBlock: 4, +// toBlock: 5, +// expectedEvents: append(block4.Events, block5.Events...), +// expectedErr: nil, +// }, +// &getClaimsAndBridgesAction{ +// p: p, +// description: "after block5: range 0, 5", +// ctx: context.Background(), +// fromBlock: 0, +// toBlock: 5, +// expectedEvents: slices.Concat( +// block1.Events, +// block3.Events, +// block4.Events, +// block5.Events, +// ), +// expectedErr: nil, +// }, +// } - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" -) +// for _, a := range actions { +// t.Run(fmt.Sprintf("%s: %s", a.method(), a.desc()), a.execute) +// } +// } -func TestProceessor(t *testing.T) { - path := t.TempDir() - ctx := context.Background() - p, err := newProcessor(ctx, path, 32) - require.NoError(t, err) - actions := []processAction{ - // processed: ~ - &getLastProcessedBlockAction{ - p: p, - description: "on an empty processor", - ctx: context.Background(), - expectedLastProcessedBlock: 0, - expectedErr: nil, - }, - &reorgAction{ - p: p, - description: "on an empty processor: firstReorgedBlock = 0", - firstReorgedBlock: 0, - expectedErr: nil, - }, - &reorgAction{ - p: p, - description: "on an empty processor: firstReorgedBlock = 1", - firstReorgedBlock: 1, - expectedErr: nil, - }, - &getClaimsAndBridgesAction{ - p: p, - description: "on an empty processor", - ctx: context.Background(), - fromBlock: 0, - toBlock: 2, - expectedEvents: nil, - expectedErr: ErrBlockNotProcessed, - }, - &storeL1InfoTreeUpdatesAction{ - p: p, - description: "block1", - b: block1, - expectedErr: nil, - }, - // processed: block1 - &getLastProcessedBlockAction{ - p: p, - description: "after block1", - ctx: context.Background(), - expectedLastProcessedBlock: 1, - expectedErr: nil, - }, - &getClaimsAndBridgesAction{ - p: p, - description: "after block1: range 0, 2", - ctx: context.Background(), - fromBlock: 0, - toBlock: 2, - expectedEvents: nil, - expectedErr: ErrBlockNotProcessed, - }, - &getClaimsAndBridgesAction{ - p: p, - description: "after block1: range 1, 1", - ctx: context.Background(), - fromBlock: 1, - toBlock: 1, - expectedEvents: block1.Events, - expectedErr: nil, - }, - &reorgAction{ - p: p, - description: "after block1", - firstReorgedBlock: 1, - expectedErr: nil, - }, - // processed: ~ - &getClaimsAndBridgesAction{ - p: p, - description: "after block1 reorged", - ctx: context.Background(), - fromBlock: 0, - toBlock: 2, - expectedEvents: nil, - expectedErr: ErrBlockNotProcessed, - }, - &storeL1InfoTreeUpdatesAction{ - p: p, - description: "block1 (after it's reorged)", - b: block1, - expectedErr: nil, - }, - // processed: block3 - &storeL1InfoTreeUpdatesAction{ - p: p, - description: "block3", - b: block3, - expectedErr: nil, - }, - // processed: block1, block3 - &getLastProcessedBlockAction{ - p: p, - description: "after block3", - ctx: context.Background(), - expectedLastProcessedBlock: 3, - expectedErr: nil, - }, - &getClaimsAndBridgesAction{ - p: p, - description: "after block3: range 2, 2", - ctx: context.Background(), - fromBlock: 2, - toBlock: 2, - expectedEvents: []L1InfoTreeUpdate{}, - expectedErr: nil, - }, - &getClaimsAndBridgesAction{ - p: p, - description: "after block3: range 1, 3", - ctx: context.Background(), - fromBlock: 1, - toBlock: 3, - expectedEvents: append(block1.Events, block3.Events...), - expectedErr: nil, - }, - &reorgAction{ - p: p, - description: "after block3, with value 3", - firstReorgedBlock: 3, - expectedErr: nil, - }, - // processed: block1 - &getLastProcessedBlockAction{ - p: p, - description: "after block3 reorged", - ctx: context.Background(), - expectedLastProcessedBlock: 2, - expectedErr: nil, - }, - &reorgAction{ - p: p, - description: "after block3, with value 2", - firstReorgedBlock: 2, - expectedErr: nil, - }, - &getLastProcessedBlockAction{ - p: p, - description: "after block2 reorged", - ctx: context.Background(), - expectedLastProcessedBlock: 1, - expectedErr: nil, - }, - &storeL1InfoTreeUpdatesAction{ - p: p, - description: "block3 after reorg", - b: block3, - expectedErr: nil, - }, - // processed: block1, block3 - &storeL1InfoTreeUpdatesAction{ - p: p, - description: "block4", - b: block4, - expectedErr: nil, - }, - // processed: block1, block3, block4 - &storeL1InfoTreeUpdatesAction{ - p: p, - description: "block5", - b: block5, - expectedErr: nil, - }, - // processed: block1, block3, block4, block5 - &getLastProcessedBlockAction{ - p: p, - description: "after block5", - ctx: context.Background(), - expectedLastProcessedBlock: 5, - expectedErr: nil, - }, - &getClaimsAndBridgesAction{ - p: p, - description: "after block5: range 1, 3", - ctx: context.Background(), - fromBlock: 1, - toBlock: 3, - expectedEvents: append(block1.Events, block3.Events...), - expectedErr: nil, - }, - &getClaimsAndBridgesAction{ - p: p, - description: "after block5: range 4, 5", - ctx: context.Background(), - fromBlock: 4, - toBlock: 5, - expectedEvents: append(block4.Events, block5.Events...), - expectedErr: nil, - }, - &getClaimsAndBridgesAction{ - p: p, - description: "after block5: range 0, 5", - ctx: context.Background(), - fromBlock: 0, - toBlock: 5, - expectedEvents: slices.Concat( - block1.Events, - block3.Events, - block4.Events, - block5.Events, - ), - expectedErr: nil, - }, - } +// // BOILERPLATE - for _, a := range actions { - t.Run(fmt.Sprintf("%s: %s", a.method(), a.desc()), a.execute) - } -} +// // blocks -// BOILERPLATE +// var ( +// block1 = block{ +// blockHeader: blockHeader{ +// Num: 1, +// Hash: common.HexToHash("01"), +// }, +// Events: []L1InfoTreeUpdate{ +// {RollupExitRoot: common.HexToHash("01")}, +// {MainnetExitRoot: common.HexToHash("01")}, +// }, +// } +// block3 = block{ +// blockHeader: blockHeader{ +// Num: 3, +// Hash: common.HexToHash("02"), +// }, +// Events: []L1InfoTreeUpdate{ +// {RollupExitRoot: common.HexToHash("02"), MainnetExitRoot: common.HexToHash("02")}, +// }, +// } +// block4 = block{ +// blockHeader: blockHeader{ +// Num: 4, +// Hash: common.HexToHash("03"), +// }, +// Events: []L1InfoTreeUpdate{}, +// } +// block5 = block{ +// blockHeader: blockHeader{ +// Num: 5, +// Hash: common.HexToHash("04"), +// }, +// Events: []L1InfoTreeUpdate{ +// {RollupExitRoot: common.HexToHash("04")}, +// {MainnetExitRoot: common.HexToHash("05")}, +// }, +// } +// ) -// blocks +// // actions -var ( - block1 = block{ - blockHeader: blockHeader{ - Num: 1, - Hash: common.HexToHash("01"), - }, - Events: []L1InfoTreeUpdate{ - {RollupExitRoot: common.HexToHash("01")}, - {MainnetExitRoot: common.HexToHash("01")}, - }, - } - block3 = block{ - blockHeader: blockHeader{ - Num: 3, - Hash: common.HexToHash("02"), - }, - Events: []L1InfoTreeUpdate{ - {RollupExitRoot: common.HexToHash("02"), MainnetExitRoot: common.HexToHash("02")}, - }, - } - block4 = block{ - blockHeader: blockHeader{ - Num: 4, - Hash: common.HexToHash("03"), - }, - Events: []L1InfoTreeUpdate{}, - } - block5 = block{ - blockHeader: blockHeader{ - Num: 5, - Hash: common.HexToHash("04"), - }, - Events: []L1InfoTreeUpdate{ - {RollupExitRoot: common.HexToHash("04")}, - {MainnetExitRoot: common.HexToHash("05")}, - }, - } -) +// type processAction interface { +// method() string +// desc() string +// execute(t *testing.T) +// } -// actions +// // GetClaimsAndBridges -type processAction interface { - method() string - desc() string - execute(t *testing.T) -} +// type getClaimsAndBridgesAction struct { +// p *processor +// description string +// ctx context.Context +// fromBlock uint64 +// toBlock uint64 +// expectedEvents []L1InfoTreeUpdate +// expectedErr error +// } -// GetClaimsAndBridges +// func (a *getClaimsAndBridgesAction) method() string { +// return "GetClaimsAndBridges" +// } -type getClaimsAndBridgesAction struct { - p *processor - description string - ctx context.Context - fromBlock uint64 - toBlock uint64 - expectedEvents []L1InfoTreeUpdate - expectedErr error -} +// func (a *getClaimsAndBridgesAction) desc() string { +// return a.description +// } -func (a *getClaimsAndBridgesAction) method() string { - return "GetClaimsAndBridges" -} +// func (a *getClaimsAndBridgesAction) execute(t *testing.T) { +// // TODO: add relevant getters +// // actualEvents, actualErr := a.p.GetClaimsAndBridges(a.ctx, a.fromBlock, a.toBlock) +// // require.Equal(t, a.expectedEvents, actualEvents) +// // require.Equal(t, a.expectedErr, actualErr) +// } -func (a *getClaimsAndBridgesAction) desc() string { - return a.description -} +// // getLastProcessedBlock -func (a *getClaimsAndBridgesAction) execute(t *testing.T) { - // TODO: add relevant getters - // actualEvents, actualErr := a.p.GetClaimsAndBridges(a.ctx, a.fromBlock, a.toBlock) - // require.Equal(t, a.expectedEvents, actualEvents) - // require.Equal(t, a.expectedErr, actualErr) -} +// type getLastProcessedBlockAction struct { +// p *processor +// description string +// ctx context.Context +// expectedLastProcessedBlock uint64 +// expectedErr error +// } -// getLastProcessedBlock +// func (a *getLastProcessedBlockAction) method() string { +// return "getLastProcessedBlock" +// } -type getLastProcessedBlockAction struct { - p *processor - description string - ctx context.Context - expectedLastProcessedBlock uint64 - expectedErr error -} +// func (a *getLastProcessedBlockAction) desc() string { +// return a.description +// } -func (a *getLastProcessedBlockAction) method() string { - return "getLastProcessedBlock" -} +// func (a *getLastProcessedBlockAction) execute(t *testing.T) { +// actualLastProcessedBlock, actualErr := a.p.getLastProcessedBlock(a.ctx) +// require.Equal(t, a.expectedLastProcessedBlock, actualLastProcessedBlock) +// require.Equal(t, a.expectedErr, actualErr) +// } -func (a *getLastProcessedBlockAction) desc() string { - return a.description -} +// // reorg -func (a *getLastProcessedBlockAction) execute(t *testing.T) { - actualLastProcessedBlock, actualErr := a.p.getLastProcessedBlock(a.ctx) - require.Equal(t, a.expectedLastProcessedBlock, actualLastProcessedBlock) - require.Equal(t, a.expectedErr, actualErr) -} +// type reorgAction struct { +// p *processor +// description string +// firstReorgedBlock uint64 +// expectedErr error +// } -// reorg +// func (a *reorgAction) method() string { +// return "reorg" +// } -type reorgAction struct { - p *processor - description string - firstReorgedBlock uint64 - expectedErr error -} +// func (a *reorgAction) desc() string { +// return a.description +// } -func (a *reorgAction) method() string { - return "reorg" -} +// func (a *reorgAction) execute(t *testing.T) { +// actualErr := a.p.reorg(a.firstReorgedBlock) +// require.Equal(t, a.expectedErr, actualErr) +// } -func (a *reorgAction) desc() string { - return a.description -} +// // storeL1InfoTreeUpdates -func (a *reorgAction) execute(t *testing.T) { - actualErr := a.p.reorg(a.firstReorgedBlock) - require.Equal(t, a.expectedErr, actualErr) -} +// type storeL1InfoTreeUpdatesAction struct { +// p *processor +// description string +// b block +// expectedErr error +// } -// storeL1InfoTreeUpdates +// func (a *storeL1InfoTreeUpdatesAction) method() string { +// return "storeL1InfoTreeUpdates" +// } -type storeL1InfoTreeUpdatesAction struct { - p *processor - description string - b block - expectedErr error -} +// func (a *storeL1InfoTreeUpdatesAction) desc() string { +// return a.description +// } -func (a *storeL1InfoTreeUpdatesAction) method() string { - return "storeL1InfoTreeUpdates" -} - -func (a *storeL1InfoTreeUpdatesAction) desc() string { - return a.description -} - -func (a *storeL1InfoTreeUpdatesAction) execute(t *testing.T) { - actualErr := a.p.processBlock(a.b) - require.Equal(t, a.expectedErr, actualErr) -} +// func (a *storeL1InfoTreeUpdatesAction) execute(t *testing.T) { +// actualErr := a.p.processBlock(a.b) +// require.Equal(t, a.expectedErr, actualErr) +// } diff --git a/localbridgesync/downloader.go b/localbridgesync/downloader.go index 9763f818..955655f1 100644 --- a/localbridgesync/downloader.go +++ b/localbridgesync/downloader.go @@ -1,14 +1,13 @@ package localbridgesync import ( - "context" + "fmt" "math/big" "time" "github.com/0xPolygon/cdk-contracts-tooling/contracts/etrog/polygonzkevmbridge" "github.com/0xPolygon/cdk-contracts-tooling/contracts/etrog/polygonzkevmbridgev2" - "github.com/0xPolygon/cdk/etherman" - "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -33,159 +32,45 @@ type EthClienter interface { bind.ContractBackend } -type downloaderInterface interface { - waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) - getEventsByBlockRange(ctx context.Context, fromBlock, toBlock uint64) []block - getLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log - appendLog(b *block, l types.Log) - getBlockHeader(ctx context.Context, blockNum uint64) blockHeader +type Bridge struct { + LeafType uint8 + OriginNetwork uint32 + OriginAddress common.Address + DestinationNetwork uint32 + DestinationAddress common.Address + Amount *big.Int + Metadata []byte + DepositCount uint32 } -type downloader struct { - syncBlockChunkSize uint64 - downloaderInterface +type Claim struct { + GlobalIndex *big.Int + OriginNetwork uint32 + OriginAddress common.Address + DestinationAddress common.Address + Amount *big.Int } -func newDownloader( - bridgeAddr common.Address, - ethClient EthClienter, - syncBlockChunkSize uint64, - blockFinalityType etherman.BlockNumberFinality, -) (*downloader, error) { - bridgeContractV1, err := polygonzkevmbridge.NewPolygonzkevmbridge(bridgeAddr, ethClient) - if err != nil { - return nil, err - } - bridgeContractV2, err := polygonzkevmbridgev2.NewPolygonzkevmbridgev2(bridgeAddr, ethClient) +type BridgeEvent struct { + Bridge *Bridge + Claim *Claim +} + +func buildAppender(client EthClienter, bridge common.Address) (sync.LogAppenderMap, error) { + bridgeContractV1, err := polygonzkevmbridge.NewPolygonzkevmbridge(bridge, client) if err != nil { return nil, err } - finality, err := blockFinalityType.ToBlockNum() + bridgeContractV2, err := polygonzkevmbridgev2.NewPolygonzkevmbridgev2(bridge, client) if err != nil { return nil, err } - return &downloader{ - syncBlockChunkSize: syncBlockChunkSize, - downloaderInterface: &downloaderImplementation{ - bridgeAddr: bridgeAddr, - bridgeContractV1: bridgeContractV1, - bridgeContractV2: bridgeContractV2, - ethClient: ethClient, - blockFinality: finality, - }, - }, nil -} - -func (d *downloader) download(ctx context.Context, fromBlock uint64, downloadedCh chan block) { - lastBlock := d.waitForNewBlocks(ctx, 0) - for { - select { - case <-ctx.Done(): - log.Debug("closing channel") - close(downloadedCh) - return - default: - } - toBlock := fromBlock + d.syncBlockChunkSize - if toBlock > lastBlock { - toBlock = lastBlock - } - if fromBlock > toBlock { - log.Debug("waiting for new blocks, last block ", toBlock) - lastBlock = d.waitForNewBlocks(ctx, toBlock) - continue - } - log.Debugf("getting events from blocks %d to %d", fromBlock, toBlock) - blocks := d.getEventsByBlockRange(ctx, fromBlock, toBlock) - for _, b := range blocks { - log.Debugf("sending block %d to the driver (with events)", b.Num) - downloadedCh <- b - } - if len(blocks) == 0 || blocks[len(blocks)-1].Num < toBlock { - // Indicate the last downloaded block if there are not events on it - log.Debugf("sending block %d to the driver (without evvents)", toBlock) - downloadedCh <- block{ - blockHeader: d.getBlockHeader(ctx, toBlock), - } - } - fromBlock = toBlock + 1 - } -} - -type downloaderImplementation struct { - bridgeAddr common.Address - bridgeContractV1 *polygonzkevmbridge.Polygonzkevmbridge - bridgeContractV2 *polygonzkevmbridgev2.Polygonzkevmbridgev2 - ethClient EthClienter - blockFinality *big.Int -} - -func (d *downloaderImplementation) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) { - attempts := 0 - for { - header, err := d.ethClient.HeaderByNumber(ctx, d.blockFinality) - if err != nil { - attempts++ - log.Error("error geting last block num from eth client: ", err) - retryHandler("waitForNewBlocks", attempts) - continue - } - if header.Number.Uint64() > lastBlockSeen { - return header.Number.Uint64() - } - time.Sleep(waitForNewBlocksPeriod) - } -} - -func (d *downloaderImplementation) getEventsByBlockRange(ctx context.Context, fromBlock, toBlock uint64) []block { - blocks := []block{} - logs := d.getLogs(ctx, fromBlock, toBlock) - for _, l := range logs { - if len(blocks) == 0 || blocks[len(blocks)-1].Num < l.BlockNumber { - blocks = append(blocks, block{ - blockHeader: blockHeader{ - Num: l.BlockNumber, - Hash: l.BlockHash, - }, - Events: []BridgeEvent{}, - }) - } - d.appendLog(&blocks[len(blocks)-1], l) - } - - return blocks -} + appender := make(sync.LogAppenderMap) -func (d *downloaderImplementation) getLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log { - query := ethereum.FilterQuery{ - FromBlock: new(big.Int).SetUint64(fromBlock), - Addresses: []common.Address{d.bridgeAddr}, - Topics: [][]common.Hash{ - {bridgeEventSignature}, - {claimEventSignature}, - {claimEventSignaturePreEtrog}, - }, - ToBlock: new(big.Int).SetUint64(toBlock), - } - attempts := 0 - for { - logs, err := d.ethClient.FilterLogs(ctx, query) + appender[bridgeEventSignature] = func(b *sync.EVMBlock, l types.Log) error { + bridge, err := bridgeContractV2.ParseBridgeEvent(l) if err != nil { - attempts++ - log.Error("error calling FilterLogs to eth client: ", err) - retryHandler("getLogs", attempts) - continue - } - return logs - } -} - -func (d *downloaderImplementation) appendLog(b *block, l types.Log) { - switch l.Topics[0] { - case bridgeEventSignature: - bridge, err := d.bridgeContractV2.ParseBridgeEvent(l) - if err != nil { - log.Fatalf( + return fmt.Errorf( "error parsing log %+v using d.bridgeContractV2.ParseBridgeEvent: %v", l, err, ) @@ -200,10 +85,13 @@ func (d *downloaderImplementation) appendLog(b *block, l types.Log) { Metadata: bridge.Metadata, DepositCount: bridge.DepositCount, }}) - case claimEventSignature: - claim, err := d.bridgeContractV2.ParseClaimEvent(l) + return nil + } + + appender[claimEventSignature] = func(b *sync.EVMBlock, l types.Log) error { + claim, err := bridgeContractV2.ParseClaimEvent(l) if err != nil { - log.Fatalf( + return fmt.Errorf( "error parsing log %+v using d.bridgeContractV2.ParseClaimEvent: %v", l, err, ) @@ -215,10 +103,13 @@ func (d *downloaderImplementation) appendLog(b *block, l types.Log) { DestinationAddress: claim.DestinationAddress, Amount: claim.Amount, }}) - case claimEventSignaturePreEtrog: - claim, err := d.bridgeContractV1.ParseClaimEvent(l) + return nil + } + + appender[claimEventSignature] = func(b *sync.EVMBlock, l types.Log) error { + claim, err := bridgeContractV1.ParseClaimEvent(l) if err != nil { - log.Fatalf( + return fmt.Errorf( "error parsing log %+v using d.bridgeContractV1.ParseClaimEvent: %v", l, err, ) @@ -230,24 +121,8 @@ func (d *downloaderImplementation) appendLog(b *block, l types.Log) { DestinationAddress: claim.DestinationAddress, Amount: claim.Amount, }}) - default: - log.Fatalf("unexpected log %+v", l) + return nil } -} -func (d *downloaderImplementation) getBlockHeader(ctx context.Context, blockNum uint64) blockHeader { - attempts := 0 - for { - header, err := d.ethClient.HeaderByNumber(ctx, big.NewInt(int64(blockNum))) - if err != nil { - attempts++ - log.Errorf("error getting block header for block %d, err: %v", blockNum, err) - retryHandler("getBlockHeader", attempts) - continue - } - return blockHeader{ - Num: header.Number.Uint64(), - Hash: header.Hash(), - } - } + return appender, nil } diff --git a/localbridgesync/downloader_test.go b/localbridgesync/downloader_test.go deleted file mode 100644 index 553efbec..00000000 --- a/localbridgesync/downloader_test.go +++ /dev/null @@ -1,467 +0,0 @@ -package localbridgesync - -import ( - "context" - "errors" - "math/big" - "testing" - "time" - - "github.com/0xPolygon/cdk-contracts-tooling/contracts/etrog/polygonzkevmbridge" - "github.com/0xPolygon/cdk-contracts-tooling/contracts/etrog/polygonzkevmbridgev2" - cdkcommon "github.com/0xPolygon/cdk/common" - "github.com/0xPolygon/cdk/etherman" - "github.com/0xPolygon/cdk/log" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" -) - -var ( - contractAddr = common.HexToAddress("1234567890") -) - -const ( - syncBlockChunck = uint64(10) -) - -func TestGetEventsByBlockRange(t *testing.T) { - type testCase struct { - description string - inputLogs []types.Log - fromBlock, toBlock uint64 - expectedBlocks []block - } - testCases := []testCase{} - clientMock := NewL2Mock(t) - ctx := context.Background() - d, err := newDownloader(contractAddr, clientMock, syncBlockChunck, etherman.LatestBlock) - require.NoError(t, err) - - // case 0: single block, no events - case0 := testCase{ - description: "case 0: single block, no events", - inputLogs: []types.Log{}, - fromBlock: 1, - toBlock: 3, - expectedBlocks: []block{}, - } - testCases = append(testCases, case0) - - // case 1: single block, single event - logC1, bridgeC1 := generateBridge(t, 3) - logsC1 := []types.Log{ - *logC1, - } - blocksC1 := []block{ - { - blockHeader: blockHeader{ - Num: logC1.BlockNumber, - Hash: logC1.BlockHash, - }, - Events: []BridgeEvent{ - { - Bridge: &bridgeC1, - }, - }, - }, - } - case1 := testCase{ - description: "case 1: single block, single event", - inputLogs: logsC1, - fromBlock: 3, - toBlock: 3, - expectedBlocks: blocksC1, - } - testCases = append(testCases, case1) - - // case 2: single block, multiple events - logC2_1, bridgeC2_1 := generateBridge(t, 5) - logC2_2, bridgeC2_2 := generateBridge(t, 5) - logC2_3, claimC2_1 := generateClaimV1(t, 5) - logC2_4, claimC2_2 := generateClaimV2(t, 5) - logsC2 := []types.Log{ - *logC2_1, - *logC2_2, - *logC2_3, - *logC2_4, - } - blocksC2 := []block{ - { - blockHeader: blockHeader{ - Num: logC2_1.BlockNumber, - Hash: logC2_1.BlockHash, - }, - Events: []BridgeEvent{ - {Bridge: &bridgeC2_1}, - {Bridge: &bridgeC2_2}, - {Claim: &claimC2_1}, - {Claim: &claimC2_2}, - }, - }, - } - case2 := testCase{ - description: "case 2: single block, multiple events", - inputLogs: logsC2, - fromBlock: 5, - toBlock: 5, - expectedBlocks: blocksC2, - } - testCases = append(testCases, case2) - - // case 3: multiple blocks, some events - logC3_1, bridgeC3_1 := generateBridge(t, 7) - logC3_2, bridgeC3_2 := generateBridge(t, 7) - logC3_3, claimC3_1 := generateClaimV1(t, 8) - logC3_4, claimC3_2 := generateClaimV2(t, 8) - logsC3 := []types.Log{ - *logC3_1, - *logC3_2, - *logC3_3, - *logC3_4, - } - blocksC3 := []block{ - { - blockHeader: blockHeader{ - Num: logC3_1.BlockNumber, - Hash: logC3_1.BlockHash, - }, - Events: []BridgeEvent{ - {Bridge: &bridgeC3_1}, - {Bridge: &bridgeC3_2}, - }, - }, - { - blockHeader: blockHeader{ - Num: logC3_3.BlockNumber, - Hash: logC3_3.BlockHash, - }, - Events: []BridgeEvent{ - {Claim: &claimC3_1}, - {Claim: &claimC3_2}, - }, - }, - } - case3 := testCase{ - description: "case 3: multiple blocks, some events", - inputLogs: logsC3, - fromBlock: 7, - toBlock: 8, - expectedBlocks: blocksC3, - } - testCases = append(testCases, case3) - - for _, tc := range testCases { - query := ethereum.FilterQuery{ - FromBlock: new(big.Int).SetUint64(tc.fromBlock), - Addresses: []common.Address{contractAddr}, - Topics: [][]common.Hash{ - {bridgeEventSignature}, - {claimEventSignature}, - {claimEventSignaturePreEtrog}, - }, - ToBlock: new(big.Int).SetUint64(tc.toBlock), - } - clientMock. - On("FilterLogs", mock.Anything, query). - Return(tc.inputLogs, nil) - - actualBlocks := d.getEventsByBlockRange(ctx, tc.fromBlock, tc.toBlock) - require.Equal(t, tc.expectedBlocks, actualBlocks, tc.description) - } -} - -func generateBridge(t *testing.T, blockNum uint32) (*types.Log, Bridge) { - b := Bridge{ - LeafType: 1, - OriginNetwork: blockNum, - OriginAddress: contractAddr, - DestinationNetwork: blockNum, - DestinationAddress: contractAddr, - Amount: big.NewInt(int64(blockNum)), - Metadata: common.Hex2Bytes("01"), - DepositCount: blockNum, - } - abi, err := polygonzkevmbridgev2.Polygonzkevmbridgev2MetaData.GetAbi() - require.NoError(t, err) - event, err := abi.EventByID(bridgeEventSignature) - require.NoError(t, err) - data, err := event.Inputs.Pack( - b.LeafType, - b.OriginNetwork, - b.OriginAddress, - b.DestinationNetwork, - b.DestinationAddress, - b.Amount, - b.Metadata, - b.DepositCount, - ) - require.NoError(t, err) - log := &types.Log{ - Address: contractAddr, - BlockNumber: uint64(blockNum), - BlockHash: common.BytesToHash(cdkcommon.BlockNum2Bytes(uint64(blockNum))), - Topics: []common.Hash{bridgeEventSignature}, - Data: data, - } - return log, b -} - -func generateClaimV1(t *testing.T, blockNum uint32) (*types.Log, Claim) { - abi, err := polygonzkevmbridge.PolygonzkevmbridgeMetaData.GetAbi() - require.NoError(t, err) - event, err := abi.EventByID(claimEventSignaturePreEtrog) - require.NoError(t, err) - return generateClaim(t, blockNum, event, true) -} - -func generateClaimV2(t *testing.T, blockNum uint32) (*types.Log, Claim) { - abi, err := polygonzkevmbridgev2.Polygonzkevmbridgev2MetaData.GetAbi() - require.NoError(t, err) - event, err := abi.EventByID(claimEventSignature) - require.NoError(t, err) - return generateClaim(t, blockNum, event, false) -} - -func generateClaim(t *testing.T, blockNum uint32, event *abi.Event, isV1 bool) (*types.Log, Claim) { - c := Claim{ - GlobalIndex: big.NewInt(int64(blockNum)), - OriginNetwork: blockNum, - OriginAddress: contractAddr, - DestinationAddress: contractAddr, - Amount: big.NewInt(int64(blockNum)), - } - var ( - data []byte - err error - signature common.Hash - ) - if isV1 { - data, err = event.Inputs.Pack( - uint32(c.GlobalIndex.Uint64()), - c.OriginNetwork, - c.OriginAddress, - c.DestinationAddress, - c.Amount, - ) - signature = claimEventSignaturePreEtrog - } else { - data, err = event.Inputs.Pack( - c.GlobalIndex, - c.OriginNetwork, - c.OriginAddress, - c.DestinationAddress, - c.Amount, - ) - signature = claimEventSignature - } - require.NoError(t, err) - log := &types.Log{ - Address: contractAddr, - BlockNumber: uint64(blockNum), - BlockHash: common.BytesToHash(cdkcommon.BlockNum2Bytes(uint64(blockNum))), - Topics: []common.Hash{signature}, - Data: data, - } - return log, c -} - -func TestDownload(t *testing.T) { - /* - NOTE: due to the concurrent nature of this test (the function being tested runs through a goroutine) - if the mock doesn't match, the goroutine will get stuck and the test will timeout - */ - d := NewDownloaderMock(t) - downloadCh := make(chan block, 1) - ctx := context.Background() - ctx1, cancel := context.WithCancel(ctx) - expectedBlocks := []block{} - clientMock := NewL2Mock(t) - dwnldr, err := newDownloader(contractAddr, clientMock, syncBlockChunck, etherman.LatestBlock) - require.NoError(t, err) - dwnldr.downloaderInterface = d - - d.On("waitForNewBlocks", mock.Anything, uint64(0)). - Return(uint64(1)) - // iteratiion 0: - // last block is 1, download that block (no events and wait) - b1 := block{ - blockHeader: blockHeader{ - Num: 1, - Hash: common.HexToHash("01"), - }, - } - expectedBlocks = append(expectedBlocks, b1) - d.On("getEventsByBlockRange", mock.Anything, uint64(0), uint64(1)). - Return([]block{}) - d.On("getBlockHeader", mock.Anything, uint64(1)). - Return(b1.blockHeader) - - // iteration 1: wait for next block to be created - d.On("waitForNewBlocks", mock.Anything, uint64(1)). - After(time.Millisecond * 100). - Return(uint64(2)).Once() - - // iteration 2: block 2 has events - b2 := block{ - blockHeader: blockHeader{ - Num: 2, - Hash: common.HexToHash("02"), - }, - } - expectedBlocks = append(expectedBlocks, b2) - d.On("getEventsByBlockRange", mock.Anything, uint64(2), uint64(2)). - Return([]block{b2}) - - // iteration 3: wait for next block to be created (jump to block 8) - d.On("waitForNewBlocks", mock.Anything, uint64(2)). - After(time.Millisecond * 100). - Return(uint64(8)).Once() - - // iteration 4: blocks 6 and 7 have events - b6 := block{ - blockHeader: blockHeader{ - Num: 6, - Hash: common.HexToHash("06"), - }, - Events: []BridgeEvent{ - {Claim: &Claim{OriginNetwork: 6}}, - }, - } - b7 := block{ - blockHeader: blockHeader{ - Num: 7, - Hash: common.HexToHash("07"), - }, - Events: []BridgeEvent{ - {Bridge: &Bridge{DestinationNetwork: 7}}, - }, - } - b8 := block{ - blockHeader: blockHeader{ - Num: 8, - Hash: common.HexToHash("08"), - }, - } - expectedBlocks = append(expectedBlocks, b6, b7, b8) - d.On("getEventsByBlockRange", mock.Anything, uint64(3), uint64(8)). - Return([]block{b6, b7}) - d.On("getBlockHeader", mock.Anything, uint64(8)). - Return(b8.blockHeader) - - // iteration 5: wait for next block to be created (jump to block 30) - d.On("waitForNewBlocks", mock.Anything, uint64(8)). - After(time.Millisecond * 100). - Return(uint64(30)).Once() - - // iteration 6: from block 9 to 19, no events - b19 := block{ - blockHeader: blockHeader{ - Num: 19, - Hash: common.HexToHash("19"), - }, - } - expectedBlocks = append(expectedBlocks, b19) - d.On("getEventsByBlockRange", mock.Anything, uint64(9), uint64(19)). - Return([]block{}) - d.On("getBlockHeader", mock.Anything, uint64(19)). - Return(b19.blockHeader) - - // iteration 7: from block 20 to 30, events on last block - b30 := block{ - blockHeader: blockHeader{ - Num: 30, - Hash: common.HexToHash("30"), - }, - Events: []BridgeEvent{ - {Bridge: &Bridge{DestinationNetwork: 30}}, - }, - } - expectedBlocks = append(expectedBlocks, b30) - d.On("getEventsByBlockRange", mock.Anything, uint64(20), uint64(30)). - Return([]block{b30}) - - // iteration 8: wait for next block to be created (jump to block 35) - d.On("waitForNewBlocks", mock.Anything, uint64(30)). - After(time.Millisecond * 100). - Return(uint64(35)).Once() - - go dwnldr.download(ctx1, 0, downloadCh) - for _, expectedBlock := range expectedBlocks { - actualBlock := <-downloadCh - log.Debugf("block %d received!", actualBlock.Num) - require.Equal(t, expectedBlock, actualBlock) - } - log.Debug("canceling") - cancel() - _, ok := <-downloadCh - require.False(t, ok) -} - -func TestWaitForNewBlocks(t *testing.T) { - retryAfterErrorPeriod = time.Millisecond * 100 - clientMock := NewL2Mock(t) - ctx := context.Background() - d, err := newDownloader(contractAddr, clientMock, syncBlockChunck, etherman.LatestBlock) - require.NoError(t, err) - - // at first attempt - currentBlock := uint64(5) - expectedBlock := uint64(6) - clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(&types.Header{ - Number: big.NewInt(6), - }, nil).Once() - actualBlock := d.waitForNewBlocks(ctx, currentBlock) - assert.Equal(t, expectedBlock, actualBlock) - - // 2 iterations - clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(&types.Header{ - Number: big.NewInt(5), - }, nil).Once() - clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(&types.Header{ - Number: big.NewInt(6), - }, nil).Once() - actualBlock = d.waitForNewBlocks(ctx, currentBlock) - assert.Equal(t, expectedBlock, actualBlock) - - // after error from client - clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(nil, errors.New("foo")).Once() - clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(&types.Header{ - Number: big.NewInt(6), - }, nil).Once() - actualBlock = d.waitForNewBlocks(ctx, currentBlock) - assert.Equal(t, expectedBlock, actualBlock) -} - -func TestGetBlockHeader(t *testing.T) { - retryAfterErrorPeriod = time.Millisecond * 100 - clientMock := NewL2Mock(t) - ctx := context.Background() - d, err := newDownloader(contractAddr, clientMock, syncBlockChunck, etherman.LatestBlock) - require.NoError(t, err) - - blockNum := uint64(5) - blockNumBig := big.NewInt(5) - returnedBlock := &types.Header{ - Number: blockNumBig, - } - expectedBlock := blockHeader{ - Num: 5, - Hash: returnedBlock.Hash(), - } - - // at first attempt - clientMock.On("HeaderByNumber", ctx, blockNumBig).Return(returnedBlock, nil).Once() - actualBlock := d.getBlockHeader(ctx, blockNum) - assert.Equal(t, expectedBlock, actualBlock) - - // after error from client - clientMock.On("HeaderByNumber", ctx, blockNumBig).Return(nil, errors.New("foo")).Once() - clientMock.On("HeaderByNumber", ctx, blockNumBig).Return(returnedBlock, nil).Once() - actualBlock = d.getBlockHeader(ctx, blockNum) - assert.Equal(t, expectedBlock, actualBlock) -} diff --git a/localbridgesync/localbridgesync.go b/localbridgesync/localbridgesync.go index 42ab67d1..6707bbad 100644 --- a/localbridgesync/localbridgesync.go +++ b/localbridgesync/localbridgesync.go @@ -1,21 +1,27 @@ package localbridgesync import ( + "context" "time" "github.com/0xPolygon/cdk/etherman" - "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum/common" ) +const ( + reorgDetectorID = "localbridgesync" + downloadBufferSize = 1000 +) + var ( retryAfterErrorPeriod = time.Second * 10 maxRetryAttemptsAfterError = 5 ) type LocalBridgeSync struct { - *processor - *driver + processor *processor + driver *sync.EVMDriver } func New( @@ -23,30 +29,40 @@ func New( bridge common.Address, syncBlockChunkSize uint64, blockFinalityType etherman.BlockNumberFinality, - rd ReorgDetector, + rd sync.ReorgDetector, l2Client EthClienter, ) (*LocalBridgeSync, error) { - p, err := newProcessor(dbPath) + processor, err := newProcessor(dbPath) if err != nil { return nil, err } - dwn, err := newDownloader(bridge, l2Client, syncBlockChunkSize, blockFinalityType) + + appender, err := buildAppender(l2Client, bridge) if err != nil { return nil, err } - dri, err := newDriver(rd, p, dwn) + downloader, err := sync.NewEVMDownloader( + l2Client, + syncBlockChunkSize, + blockFinalityType, + waitForNewBlocksPeriod, + appender, + []common.Address{bridge}, + ) if err != nil { return nil, err } - return &LocalBridgeSync{p, dri}, nil -} -func retryHandler(funcName string, attempts int) { - if attempts >= maxRetryAttemptsAfterError { - log.Fatalf( - "%s failed too many times (%d)", - funcName, maxRetryAttemptsAfterError, - ) + driver, err := sync.NewEVMDriver(rd, processor, downloader, reorgDetectorID, downloadBufferSize) + if err != nil { + return nil, err } - time.Sleep(retryAfterErrorPeriod) + return &LocalBridgeSync{ + processor: processor, + driver: driver, + }, nil +} + +func (s *LocalBridgeSync) Start(ctx context.Context) { + s.driver.Sync(ctx) } diff --git a/localbridgesync/mock_downloader_test.go b/localbridgesync/mock_downloader_test.go deleted file mode 100644 index f2df97d0..00000000 --- a/localbridgesync/mock_downloader_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// Code generated by mockery v2.22.1. DO NOT EDIT. - -package localbridgesync - -import ( - context "context" - - types "github.com/ethereum/go-ethereum/core/types" - mock "github.com/stretchr/testify/mock" -) - -// DownloaderMock is an autogenerated mock type for the downloaderFull type -type DownloaderMock struct { - mock.Mock -} - -// appendLog provides a mock function with given fields: b, l -func (_m *DownloaderMock) appendLog(b *block, l types.Log) { - _m.Called(b, l) -} - -// download provides a mock function with given fields: ctx, fromBlock, downloadedCh -func (_m *DownloaderMock) download(ctx context.Context, fromBlock uint64, downloadedCh chan block) { - _m.Called(ctx, fromBlock, downloadedCh) -} - -// getBlockHeader provides a mock function with given fields: ctx, blockNum -func (_m *DownloaderMock) getBlockHeader(ctx context.Context, blockNum uint64) blockHeader { - ret := _m.Called(ctx, blockNum) - - var r0 blockHeader - if rf, ok := ret.Get(0).(func(context.Context, uint64) blockHeader); ok { - r0 = rf(ctx, blockNum) - } else { - r0 = ret.Get(0).(blockHeader) - } - - return r0 -} - -// getEventsByBlockRange provides a mock function with given fields: ctx, fromBlock, toBlock -func (_m *DownloaderMock) getEventsByBlockRange(ctx context.Context, fromBlock uint64, toBlock uint64) []block { - ret := _m.Called(ctx, fromBlock, toBlock) - - var r0 []block - if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []block); ok { - r0 = rf(ctx, fromBlock, toBlock) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]block) - } - } - - return r0 -} - -// getLogs provides a mock function with given fields: ctx, fromBlock, toBlock -func (_m *DownloaderMock) getLogs(ctx context.Context, fromBlock uint64, toBlock uint64) []types.Log { - ret := _m.Called(ctx, fromBlock, toBlock) - - var r0 []types.Log - if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []types.Log); ok { - r0 = rf(ctx, fromBlock, toBlock) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]types.Log) - } - } - - return r0 -} - -// waitForNewBlocks provides a mock function with given fields: ctx, lastBlockSeen -func (_m *DownloaderMock) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) uint64 { - ret := _m.Called(ctx, lastBlockSeen) - - var r0 uint64 - if rf, ok := ret.Get(0).(func(context.Context, uint64) uint64); ok { - r0 = rf(ctx, lastBlockSeen) - } else { - r0 = ret.Get(0).(uint64) - } - - return r0 -} - -type mockConstructorTestingTNewDownloaderMock interface { - mock.TestingT - Cleanup(func()) -} - -// NewDownloaderMock creates a new instance of DownloaderMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewDownloaderMock(t mockConstructorTestingTNewDownloaderMock) *DownloaderMock { - mock := &DownloaderMock{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/localbridgesync/mock_l2_test.go b/localbridgesync/mock_l2_test.go deleted file mode 100644 index 78baa614..00000000 --- a/localbridgesync/mock_l2_test.go +++ /dev/null @@ -1,484 +0,0 @@ -// Code generated by mockery v2.22.1. DO NOT EDIT. - -package localbridgesync - -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" -) - -// L2Mock is an autogenerated mock type for the EthClienter type -type L2Mock struct { - mock.Mock -} - -// BlockByHash provides a mock function with given fields: ctx, hash -func (_m *L2Mock) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - ret := _m.Called(ctx, hash) - - 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 -} - -// BlockByNumber provides a mock function with given fields: ctx, number -func (_m *L2Mock) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { - ret := _m.Called(ctx, number) - - 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 -} - -// BlockNumber provides a mock function with given fields: ctx -func (_m *L2Mock) BlockNumber(ctx context.Context) (uint64, error) { - ret := _m.Called(ctx) - - 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 -} - -// CallContract provides a mock function with given fields: ctx, call, blockNumber -func (_m *L2Mock) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { - ret := _m.Called(ctx, call, blockNumber) - - 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 -} - -// 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) - - 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 -} - -// EstimateGas provides a mock function with given fields: ctx, call -func (_m *L2Mock) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { - ret := _m.Called(ctx, call) - - 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 -} - -// FilterLogs provides a mock function with given fields: ctx, q -func (_m *L2Mock) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { - ret := _m.Called(ctx, q) - - 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 -} - -// HeaderByHash provides a mock function with given fields: ctx, hash -func (_m *L2Mock) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { - ret := _m.Called(ctx, hash) - - 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 -} - -// HeaderByNumber provides a mock function with given fields: ctx, number -func (_m *L2Mock) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { - ret := _m.Called(ctx, number) - - 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 -} - -// PendingCodeAt provides a mock function with given fields: ctx, account -func (_m *L2Mock) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { - ret := _m.Called(ctx, account) - - 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 -} - -// PendingNonceAt provides a mock function with given fields: ctx, account -func (_m *L2Mock) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { - ret := _m.Called(ctx, account) - - 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 -} - -// SendTransaction provides a mock function with given fields: ctx, tx -func (_m *L2Mock) SendTransaction(ctx context.Context, tx *types.Transaction) error { - ret := _m.Called(ctx, tx) - - 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 -} - -// SubscribeFilterLogs provides a mock function with given fields: ctx, q, ch -func (_m *L2Mock) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { - ret := _m.Called(ctx, q, ch) - - 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 -} - -// SubscribeNewHead provides a mock function with given fields: ctx, ch -func (_m *L2Mock) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { - ret := _m.Called(ctx, ch) - - 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 -} - -// SuggestGasPrice provides a mock function with given fields: ctx -func (_m *L2Mock) SuggestGasPrice(ctx context.Context) (*big.Int, error) { - ret := _m.Called(ctx) - - 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 -} - -// SuggestGasTipCap provides a mock function with given fields: ctx -func (_m *L2Mock) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - ret := _m.Called(ctx) - - 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 -} - -// TransactionCount provides a mock function with given fields: ctx, blockHash -func (_m *L2Mock) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { - ret := _m.Called(ctx, blockHash) - - 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 -} - -// TransactionInBlock provides a mock function with given fields: ctx, blockHash, index -func (_m *L2Mock) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { - ret := _m.Called(ctx, blockHash, index) - - 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 -} - -type mockConstructorTestingTNewL2Mock interface { - mock.TestingT - Cleanup(func()) -} - -// NewL2Mock creates a new instance of L2Mock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewL2Mock(t mockConstructorTestingTNewL2Mock) *L2Mock { - mock := &L2Mock{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/localbridgesync/mock_processor_test.go b/localbridgesync/mock_processor_test.go deleted file mode 100644 index 4a629f5c..00000000 --- a/localbridgesync/mock_processor_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// Code generated by mockery v2.22.1. DO NOT EDIT. - -package localbridgesync - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" -) - -// ProcessorMock is an autogenerated mock type for the processorInterface type -type ProcessorMock struct { - mock.Mock -} - -// getLastProcessedBlock provides a mock function with given fields: ctx -func (_m *ProcessorMock) getLastProcessedBlock(ctx context.Context) (uint64, error) { - ret := _m.Called(ctx) - - 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 -} - -// reorg provides a mock function with given fields: firstReorgedBlock -func (_m *ProcessorMock) reorg(firstReorgedBlock uint64) error { - ret := _m.Called(firstReorgedBlock) - - var r0 error - if rf, ok := ret.Get(0).(func(uint64) error); ok { - r0 = rf(firstReorgedBlock) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// storeBridgeEvents provides a mock function with given fields: blockNum, events -func (_m *ProcessorMock) storeBridgeEvents(blockNum uint64, events []BridgeEvent) error { - ret := _m.Called(blockNum, events) - - var r0 error - if rf, ok := ret.Get(0).(func(uint64, []BridgeEvent) error); ok { - r0 = rf(blockNum, events) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -type mockConstructorTestingTNewProcessorMock interface { - mock.TestingT - Cleanup(func()) -} - -// NewProcessorMock creates a new instance of ProcessorMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewProcessorMock(t mockConstructorTestingTNewProcessorMock) *ProcessorMock { - mock := &ProcessorMock{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/localbridgesync/processor.go b/localbridgesync/processor.go index 69c885b8..443a7925 100644 --- a/localbridgesync/processor.go +++ b/localbridgesync/processor.go @@ -6,6 +6,7 @@ import ( "errors" "github.com/0xPolygon/cdk/common" + "github.com/0xPolygon/cdk/sync" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/kv/mdbx" ) @@ -87,7 +88,7 @@ func (p *processor) GetClaimsAndBridges( return events, nil } -func (p *processor) getLastProcessedBlock(ctx context.Context) (uint64, error) { +func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { tx, err := p.db.BeginRo(ctx) if err != nil { return 0, err @@ -106,7 +107,7 @@ func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { } } -func (p *processor) reorg(firstReorgedBlock uint64) error { +func (p *processor) Reorg(firstReorgedBlock uint64) error { tx, err := p.db.BeginRw(context.Background()) if err != nil { return err @@ -134,23 +135,27 @@ func (p *processor) reorg(firstReorgedBlock uint64) error { return tx.Commit() } -func (p *processor) storeBridgeEvents(blockNum uint64, events []BridgeEvent) error { +func (p *processor) ProcessBlock(block sync.EVMBlock) error { tx, err := p.db.BeginRw(context.Background()) if err != nil { return err } - if len(events) > 0 { + if len(block.Events) > 0 { + events := []BridgeEvent{} + for _, e := range block.Events { + events = append(events, e.(BridgeEvent)) + } value, err := json.Marshal(events) if err != nil { tx.Rollback() return err } - if err := tx.Put(eventsTable, common.BlockNum2Bytes(blockNum), value); err != nil { + if err := tx.Put(eventsTable, common.BlockNum2Bytes(block.Num), value); err != nil { tx.Rollback() return err } } - if err := p.updateLastProcessedBlock(tx, blockNum); err != nil { + if err := p.updateLastProcessedBlock(tx, block.Num); err != nil { tx.Rollback() return err } diff --git a/localbridgesync/processor_test.go b/localbridgesync/processor_test.go index 8e6884c2..a7835256 100644 --- a/localbridgesync/processor_test.go +++ b/localbridgesync/processor_test.go @@ -7,6 +7,7 @@ import ( "slices" "testing" + "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -45,11 +46,10 @@ func TestProceessor(t *testing.T) { expectedEvents: nil, expectedErr: ErrBlockNotProcessed, }, - &storeBridgeEventsAction{ + &processBlockAction{ p: p, description: "block1", - blockNum: block1.Num, - events: block1.Events, + block: block1, expectedErr: nil, }, // processed: block1 @@ -75,7 +75,7 @@ func TestProceessor(t *testing.T) { ctx: context.Background(), fromBlock: 1, toBlock: 1, - expectedEvents: block1.Events, + expectedEvents: eventsToBridgeEvents(block1.Events), expectedErr: nil, }, &reorgAction{ @@ -94,19 +94,17 @@ func TestProceessor(t *testing.T) { expectedEvents: nil, expectedErr: ErrBlockNotProcessed, }, - &storeBridgeEventsAction{ + &processBlockAction{ p: p, description: "block1 (after it's reorged)", - blockNum: block1.Num, - events: block1.Events, + block: block1, expectedErr: nil, }, // processed: block3 - &storeBridgeEventsAction{ + &processBlockAction{ p: p, description: "block3", - blockNum: block3.Num, - events: block3.Events, + block: block3, expectedErr: nil, }, // processed: block1, block3 @@ -127,13 +125,16 @@ func TestProceessor(t *testing.T) { expectedErr: nil, }, &getClaimsAndBridgesAction{ - p: p, - description: "after block3: range 1, 3", - ctx: context.Background(), - fromBlock: 1, - toBlock: 3, - expectedEvents: append(block1.Events, block3.Events...), - expectedErr: nil, + p: p, + description: "after block3: range 1, 3", + ctx: context.Background(), + fromBlock: 1, + toBlock: 3, + expectedEvents: append( + eventsToBridgeEvents(block1.Events), + eventsToBridgeEvents(block3.Events)..., + ), + expectedErr: nil, }, &reorgAction{ p: p, @@ -162,27 +163,24 @@ func TestProceessor(t *testing.T) { expectedLastProcessedBlock: 1, expectedErr: nil, }, - &storeBridgeEventsAction{ + &processBlockAction{ p: p, description: "block3 after reorg", - blockNum: block3.Num, - events: block3.Events, + block: block3, expectedErr: nil, }, // processed: block1, block3 - &storeBridgeEventsAction{ + &processBlockAction{ p: p, description: "block4", - blockNum: block4.Num, - events: block4.Events, + block: block4, expectedErr: nil, }, // processed: block1, block3, block4 - &storeBridgeEventsAction{ + &processBlockAction{ p: p, description: "block5", - blockNum: block5.Num, - events: block5.Events, + block: block5, expectedErr: nil, }, // processed: block1, block3, block4, block5 @@ -194,22 +192,28 @@ func TestProceessor(t *testing.T) { expectedErr: nil, }, &getClaimsAndBridgesAction{ - p: p, - description: "after block5: range 1, 3", - ctx: context.Background(), - fromBlock: 1, - toBlock: 3, - expectedEvents: append(block1.Events, block3.Events...), - expectedErr: nil, + p: p, + description: "after block5: range 1, 3", + ctx: context.Background(), + fromBlock: 1, + toBlock: 3, + expectedEvents: append( + eventsToBridgeEvents(block1.Events), + eventsToBridgeEvents(block3.Events)..., + ), + expectedErr: nil, }, &getClaimsAndBridgesAction{ - p: p, - description: "after block5: range 4, 5", - ctx: context.Background(), - fromBlock: 4, - toBlock: 5, - expectedEvents: append(block4.Events, block5.Events...), - expectedErr: nil, + p: p, + description: "after block5: range 4, 5", + ctx: context.Background(), + fromBlock: 4, + toBlock: 5, + expectedEvents: append( + eventsToBridgeEvents(block4.Events), + eventsToBridgeEvents(block5.Events)..., + ), + expectedErr: nil, }, &getClaimsAndBridgesAction{ p: p, @@ -218,10 +222,10 @@ func TestProceessor(t *testing.T) { fromBlock: 0, toBlock: 5, expectedEvents: slices.Concat( - block1.Events, - block3.Events, - block4.Events, - block5.Events, + eventsToBridgeEvents(block1.Events), + eventsToBridgeEvents(block3.Events), + eventsToBridgeEvents(block4.Events), + eventsToBridgeEvents(block5.Events), ), expectedErr: nil, }, @@ -237,13 +241,13 @@ func TestProceessor(t *testing.T) { // blocks var ( - block1 = block{ - blockHeader: blockHeader{ + block1 = sync.EVMBlock{ + EVMBlockHeader: sync.EVMBlockHeader{ Num: 1, Hash: common.HexToHash("01"), }, - Events: []BridgeEvent{ - {Bridge: &Bridge{ + Events: []interface{}{ + BridgeEvent{Bridge: &Bridge{ LeafType: 1, OriginNetwork: 1, OriginAddress: common.HexToAddress("01"), @@ -253,7 +257,7 @@ var ( Metadata: common.Hex2Bytes("01"), DepositCount: 1, }}, - {Claim: &Claim{ + BridgeEvent{Claim: &Claim{ GlobalIndex: big.NewInt(1), OriginNetwork: 1, OriginAddress: common.HexToAddress("01"), @@ -262,13 +266,13 @@ var ( }}, }, } - block3 = block{ - blockHeader: blockHeader{ + block3 = sync.EVMBlock{ + EVMBlockHeader: sync.EVMBlockHeader{ Num: 3, Hash: common.HexToHash("02"), }, - Events: []BridgeEvent{ - {Bridge: &Bridge{ + Events: []interface{}{ + BridgeEvent{Bridge: &Bridge{ LeafType: 2, OriginNetwork: 2, OriginAddress: common.HexToAddress("02"), @@ -278,7 +282,7 @@ var ( Metadata: common.Hex2Bytes("02"), DepositCount: 2, }}, - {Bridge: &Bridge{ + BridgeEvent{Bridge: &Bridge{ LeafType: 3, OriginNetwork: 3, OriginAddress: common.HexToAddress("03"), @@ -290,27 +294,27 @@ var ( }}, }, } - block4 = block{ - blockHeader: blockHeader{ + block4 = sync.EVMBlock{ + EVMBlockHeader: sync.EVMBlockHeader{ Num: 4, Hash: common.HexToHash("03"), }, - Events: []BridgeEvent{}, + Events: []interface{}{}, } - block5 = block{ - blockHeader: blockHeader{ + block5 = sync.EVMBlock{ + EVMBlockHeader: sync.EVMBlockHeader{ Num: 5, Hash: common.HexToHash("04"), }, - Events: []BridgeEvent{ - {Claim: &Claim{ + Events: []interface{}{ + BridgeEvent{Claim: &Claim{ GlobalIndex: big.NewInt(4), OriginNetwork: 4, OriginAddress: common.HexToAddress("04"), DestinationAddress: common.HexToAddress("04"), Amount: big.NewInt(4), }}, - {Claim: &Claim{ + BridgeEvent{Claim: &Claim{ GlobalIndex: big.NewInt(5), OriginNetwork: 5, OriginAddress: common.HexToAddress("05"), @@ -374,7 +378,7 @@ func (a *getLastProcessedBlockAction) desc() string { } func (a *getLastProcessedBlockAction) execute(t *testing.T) { - actualLastProcessedBlock, actualErr := a.p.getLastProcessedBlock(a.ctx) + actualLastProcessedBlock, actualErr := a.p.GetLastProcessedBlock(a.ctx) require.Equal(t, a.expectedLastProcessedBlock, actualLastProcessedBlock) require.Equal(t, a.expectedErr, actualErr) } @@ -397,29 +401,36 @@ func (a *reorgAction) desc() string { } func (a *reorgAction) execute(t *testing.T) { - actualErr := a.p.reorg(a.firstReorgedBlock) + actualErr := a.p.Reorg(a.firstReorgedBlock) require.Equal(t, a.expectedErr, actualErr) } // storeBridgeEvents -type storeBridgeEventsAction struct { +type processBlockAction struct { p *processor description string - blockNum uint64 - events []BridgeEvent + block sync.EVMBlock expectedErr error } -func (a *storeBridgeEventsAction) method() string { +func (a *processBlockAction) method() string { return "storeBridgeEvents" } -func (a *storeBridgeEventsAction) desc() string { +func (a *processBlockAction) desc() string { return a.description } -func (a *storeBridgeEventsAction) execute(t *testing.T) { - actualErr := a.p.storeBridgeEvents(a.blockNum, a.events) +func (a *processBlockAction) execute(t *testing.T) { + actualErr := a.p.ProcessBlock(a.block) require.Equal(t, a.expectedErr, actualErr) } + +func eventsToBridgeEvents(events []interface{}) []BridgeEvent { + bridgeEvents := []BridgeEvent{} + for _, event := range events { + bridgeEvents = append(bridgeEvents, event.(BridgeEvent)) + } + return bridgeEvents +} diff --git a/localbridgesync/types.go b/localbridgesync/types.go deleted file mode 100644 index 3a6a508e..00000000 --- a/localbridgesync/types.go +++ /dev/null @@ -1,42 +0,0 @@ -package localbridgesync - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" -) - -type Bridge struct { - LeafType uint8 - OriginNetwork uint32 - OriginAddress common.Address - DestinationNetwork uint32 - DestinationAddress common.Address - Amount *big.Int - Metadata []byte - DepositCount uint32 -} - -type Claim struct { - // TODO: pre uLxLy there was Index instead of GlobalIndex, should we treat this differently? - GlobalIndex *big.Int - OriginNetwork uint32 - OriginAddress common.Address - DestinationAddress common.Address - Amount *big.Int -} - -type BridgeEvent struct { - Bridge *Bridge - Claim *Claim -} - -type block struct { - blockHeader - Events []BridgeEvent -} - -type blockHeader struct { - Num uint64 - Hash common.Hash -} diff --git a/sync/common.go b/sync/common.go new file mode 100644 index 00000000..6d1011f5 --- /dev/null +++ b/sync/common.go @@ -0,0 +1,21 @@ +package sync + +import ( + "log" + "time" +) + +var ( + RetryAfterErrorPeriod = time.Second * 10 + MaxRetryAttemptsAfterError = 5 +) + +func RetryHandler(funcName string, attempts int) { + if attempts >= MaxRetryAttemptsAfterError { + log.Fatalf( + "%s failed too many times (%d)", + funcName, MaxRetryAttemptsAfterError, + ) + } + time.Sleep(RetryAfterErrorPeriod) +} diff --git a/sync/evmdownloader.go b/sync/evmdownloader.go new file mode 100644 index 00000000..e6d9dc8d --- /dev/null +++ b/sync/evmdownloader.go @@ -0,0 +1,198 @@ +package sync + +import ( + "context" + "math/big" + "time" + + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type EthClienter interface { + ethereum.LogFilterer + ethereum.BlockNumberReader + ethereum.ChainReader + bind.ContractBackend +} + +type evmDownloaderInterface interface { + waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) + getEventsByBlockRange(ctx context.Context, fromBlock, toBlock uint64) []EVMBlock + getLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log + getBlockHeader(ctx context.Context, blockNum uint64) EVMBlockHeader +} + +type LogAppenderMap map[common.Hash]func(b *EVMBlock, l types.Log) error + +type EVMDownloader struct { + syncBlockChunkSize uint64 + evmDownloaderInterface +} + +func NewEVMDownloader( + ethClient EthClienter, + syncBlockChunkSize uint64, + blockFinalityType etherman.BlockNumberFinality, + waitForNewBlocksPeriod time.Duration, + appender LogAppenderMap, + adressessToQuery []common.Address, +) (*EVMDownloader, error) { + finality, err := blockFinalityType.ToBlockNum() + if err != nil { + return nil, err + } + topicsToQuery := [][]common.Hash{} + for topic := range appender { + topicsToQuery = append(topicsToQuery, []common.Hash{topic}) + } + return &EVMDownloader{ + syncBlockChunkSize: syncBlockChunkSize, + evmDownloaderInterface: &downloaderImplementation{ + ethClient: ethClient, + blockFinality: finality, + waitForNewBlocksPeriod: waitForNewBlocksPeriod, + appender: appender, + topicsToQuery: topicsToQuery, + adressessToQuery: adressessToQuery, + }, + }, nil +} + +func (d *EVMDownloader) download(ctx context.Context, fromBlock uint64, downloadedCh chan EVMBlock) { + lastBlock := d.waitForNewBlocks(ctx, 0) + for { + select { + case <-ctx.Done(): + log.Debug("closing channel") + close(downloadedCh) + return + default: + } + toBlock := fromBlock + d.syncBlockChunkSize + if toBlock > lastBlock { + toBlock = lastBlock + } + if fromBlock > toBlock { + log.Debug("waiting for new blocks, last block ", toBlock) + lastBlock = d.waitForNewBlocks(ctx, toBlock) + continue + } + log.Debugf("getting events from blocks %d to %d", fromBlock, toBlock) + blocks := d.getEventsByBlockRange(ctx, fromBlock, toBlock) + for _, b := range blocks { + log.Debugf("sending block %d to the driver (with events)", b.Num) + downloadedCh <- b + } + if len(blocks) == 0 || blocks[len(blocks)-1].Num < toBlock { + // Indicate the last downloaded block if there are not events on it + log.Debugf("sending block %d to the driver (without evvents)", toBlock) + downloadedCh <- EVMBlock{ + EVMBlockHeader: d.getBlockHeader(ctx, toBlock), + } + } + fromBlock = toBlock + 1 + } +} + +type downloaderImplementation struct { + ethClient EthClienter + blockFinality *big.Int + waitForNewBlocksPeriod time.Duration + appender LogAppenderMap + topicsToQuery [][]common.Hash + adressessToQuery []common.Address +} + +func (d *downloaderImplementation) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) { + attempts := 0 + for { + header, err := d.ethClient.HeaderByNumber(ctx, d.blockFinality) + if err != nil { + attempts++ + log.Error("error geting last block num from eth client: ", err) + RetryHandler("waitForNewBlocks", attempts) + continue + } + if header.Number.Uint64() > lastBlockSeen { + return header.Number.Uint64() + } + time.Sleep(d.waitForNewBlocksPeriod) + } +} + +func (d *downloaderImplementation) getEventsByBlockRange(ctx context.Context, fromBlock, toBlock uint64) []EVMBlock { + blocks := []EVMBlock{} + logs := d.getLogs(ctx, fromBlock, toBlock) + for _, l := range logs { + if len(blocks) == 0 || blocks[len(blocks)-1].Num < l.BlockNumber { + b := d.getBlockHeader(ctx, l.BlockNumber) + blocks = append(blocks, EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ + Num: l.BlockNumber, + Hash: l.BlockHash, + Timestamp: b.Timestamp, + ParentHash: b.ParentHash, + }, + Events: []interface{}{}, + }) + } + + for { + attempts := 0 + err := d.appender[l.Topics[0]](&blocks[len(blocks)-1], l) + if err != nil { + attempts++ + log.Error("error trying to append log: ", err) + RetryHandler("getLogs", attempts) + continue + } + break + } + } + + return blocks +} + +func (d *downloaderImplementation) getLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log { + query := ethereum.FilterQuery{ + FromBlock: new(big.Int).SetUint64(fromBlock), + Addresses: d.adressessToQuery, + Topics: d.topicsToQuery, + ToBlock: new(big.Int).SetUint64(toBlock), + } + attempts := 0 + for { + logs, err := d.ethClient.FilterLogs(ctx, query) + if err != nil { + attempts++ + log.Error("error calling FilterLogs to eth client: ", err) + RetryHandler("getLogs", attempts) + continue + } + return logs + } +} + +func (d *downloaderImplementation) getBlockHeader(ctx context.Context, blockNum uint64) EVMBlockHeader { + attempts := 0 + for { + header, err := d.ethClient.HeaderByNumber(ctx, big.NewInt(int64(blockNum))) + if err != nil { + attempts++ + log.Errorf("error getting block header for block %d, err: %v", blockNum, err) + RetryHandler("getBlockHeader", attempts) + continue + } + return EVMBlockHeader{ + Num: header.Number.Uint64(), + Hash: header.Hash(), + ParentHash: header.ParentHash, + Timestamp: header.Time, + } + } +} diff --git a/l1infotreesync/downloader_test.go b/sync/evmdownloader_test.go similarity index 72% rename from l1infotreesync/downloader_test.go rename to sync/evmdownloader_test.go index 28daf69b..c733aabd 100644 --- a/l1infotreesync/downloader_test.go +++ b/sync/evmdownloader_test.go @@ -1,9 +1,10 @@ -package l1infotreesync +package sync import ( "context" "errors" "math/big" + "strconv" "testing" "time" @@ -12,31 +13,33 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) var ( - contractAddr = common.HexToAddress("1234567890") + contractAddr = common.HexToAddress("f00") + eventSignature = crypto.Keccak256Hash([]byte("foo")) ) const ( syncBlockChunck = uint64(10) ) +type testEvent common.Hash + func TestGetEventsByBlockRange(t *testing.T) { type testCase struct { description string inputLogs []types.Log fromBlock, toBlock uint64 - expectedBlocks []block + expectedBlocks []EVMBlock } testCases := []testCase{} - clientMock := NewL2Mock(t) ctx := context.Background() - d, err := newDownloader(contractAddr, clientMock, syncBlockChunck, etherman.LatestBlock) - require.NoError(t, err) + d, clientMock := NewTestDownloader(t) // case 0: single block, no events case0 := testCase{ @@ -44,25 +47,23 @@ func TestGetEventsByBlockRange(t *testing.T) { inputLogs: []types.Log{}, fromBlock: 1, toBlock: 3, - expectedBlocks: []block{}, + expectedBlocks: []EVMBlock{}, } testCases = append(testCases, case0) // case 1: single block, single event - logC1, updateC1 := generateUpdateL1InfoTree(t, 3) + logC1, updateC1 := generateEvent(3) logsC1 := []types.Log{ *logC1, } - blocksC1 := []block{ + blocksC1 := []EVMBlock{ { - blockHeader: blockHeader{ + EVMBlockHeader: EVMBlockHeader{ Num: logC1.BlockNumber, Hash: logC1.BlockHash, ParentHash: common.HexToHash("foo"), }, - Events: []L1InfoTreeUpdate{ - updateC1, - }, + Events: []interface{}{updateC1}, }, } case1 := testCase{ @@ -75,24 +76,24 @@ func TestGetEventsByBlockRange(t *testing.T) { testCases = append(testCases, case1) // case 2: single block, multiple events - logC2_1, updateC2_1 := generateUpdateL1InfoTree(t, 5) - logC2_2, updateC2_2 := generateUpdateL1InfoTree(t, 5) - logC2_3, updateC2_3 := generateUpdateL1InfoTree(t, 5) - logC2_4, updateC2_4 := generateUpdateL1InfoTree(t, 5) + logC2_1, updateC2_1 := generateEvent(5) + logC2_2, updateC2_2 := generateEvent(5) + logC2_3, updateC2_3 := generateEvent(5) + logC2_4, updateC2_4 := generateEvent(5) logsC2 := []types.Log{ *logC2_1, *logC2_2, *logC2_3, *logC2_4, } - blocksC2 := []block{ + blocksC2 := []EVMBlock{ { - blockHeader: blockHeader{ + EVMBlockHeader: EVMBlockHeader{ Num: logC2_1.BlockNumber, Hash: logC2_1.BlockHash, ParentHash: common.HexToHash("foo"), }, - Events: []L1InfoTreeUpdate{ + Events: []interface{}{ updateC2_1, updateC2_2, updateC2_3, @@ -110,35 +111,35 @@ func TestGetEventsByBlockRange(t *testing.T) { testCases = append(testCases, case2) // case 3: multiple blocks, some events - logC3_1, updateC3_1 := generateUpdateL1InfoTree(t, 7) - logC3_2, updateC3_2 := generateUpdateL1InfoTree(t, 7) - logC3_3, updateC3_3 := generateUpdateL1InfoTree(t, 8) - logC3_4, updateC3_4 := generateUpdateL1InfoTree(t, 8) + logC3_1, updateC3_1 := generateEvent(7) + logC3_2, updateC3_2 := generateEvent(7) + logC3_3, updateC3_3 := generateEvent(8) + logC3_4, updateC3_4 := generateEvent(8) logsC3 := []types.Log{ *logC3_1, *logC3_2, *logC3_3, *logC3_4, } - blocksC3 := []block{ + blocksC3 := []EVMBlock{ { - blockHeader: blockHeader{ + EVMBlockHeader: EVMBlockHeader{ Num: logC3_1.BlockNumber, Hash: logC3_1.BlockHash, ParentHash: common.HexToHash("foo"), }, - Events: []L1InfoTreeUpdate{ + Events: []interface{}{ updateC3_1, updateC3_2, }, }, { - blockHeader: blockHeader{ + EVMBlockHeader: EVMBlockHeader{ Num: logC3_3.BlockNumber, Hash: logC3_3.BlockHash, ParentHash: common.HexToHash("foo"), }, - Events: []L1InfoTreeUpdate{ + Events: []interface{}{ updateC3_3, updateC3_4, }, @@ -158,7 +159,7 @@ func TestGetEventsByBlockRange(t *testing.T) { FromBlock: new(big.Int).SetUint64(tc.fromBlock), Addresses: []common.Address{contractAddr}, Topics: [][]common.Hash{ - {updateL1InfoTreeSignature}, + {eventSignature}, }, ToBlock: new(big.Int).SetUint64(tc.toBlock), } @@ -179,26 +180,19 @@ func TestGetEventsByBlockRange(t *testing.T) { } } -func generateUpdateL1InfoTree(t *testing.T, blockNum uint32) (*types.Log, L1InfoTreeUpdate) { - b := L1InfoTreeUpdate{ - MainnetExitRoot: common.BigToHash(big.NewInt(int64(blockNum))), - RollupExitRoot: common.BigToHash(big.NewInt(int64(blockNum))), - } - var rollup, mainnet [32]byte - mainnet = b.MainnetExitRoot - rollup = b.RollupExitRoot +func generateEvent(blockNum uint32) (*types.Log, testEvent) { + h := common.HexToHash(strconv.Itoa(int(blockNum))) log := &types.Log{ Address: contractAddr, BlockNumber: uint64(blockNum), - BlockHash: common.BytesToHash(uint64ToBytes(uint64(blockNum))), + BlockHash: h, Topics: []common.Hash{ - updateL1InfoTreeSignature, - mainnet, - rollup, + eventSignature, + h, }, Data: nil, } - return log, b + return log, testEvent(h) } func TestDownload(t *testing.T) { @@ -206,31 +200,29 @@ func TestDownload(t *testing.T) { NOTE: due to the concurrent nature of this test (the function being tested runs through a goroutine) if the mock doesn't match, the goroutine will get stuck and the test will timeout */ - d := NewDownloaderMock(t) - downloadCh := make(chan block, 1) + d := NewEVMDownloaderMock(t) + downloadCh := make(chan EVMBlock, 1) ctx := context.Background() ctx1, cancel := context.WithCancel(ctx) - expectedBlocks := []block{} - clientMock := NewL2Mock(t) - dwnldr, err := newDownloader(contractAddr, clientMock, syncBlockChunck, etherman.LatestBlock) - require.NoError(t, err) - dwnldr.downloaderInterface = d + expectedBlocks := []EVMBlock{} + dwnldr, _ := NewTestDownloader(t) + dwnldr.evmDownloaderInterface = d d.On("waitForNewBlocks", mock.Anything, uint64(0)). Return(uint64(1)) // iteratiion 0: // last block is 1, download that block (no events and wait) - b1 := block{ - blockHeader: blockHeader{ + b1 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ Num: 1, Hash: common.HexToHash("01"), }, } expectedBlocks = append(expectedBlocks, b1) d.On("getEventsByBlockRange", mock.Anything, uint64(0), uint64(1)). - Return([]block{}) + Return([]EVMBlock{}) d.On("getBlockHeader", mock.Anything, uint64(1)). - Return(b1.blockHeader) + Return(b1.EVMBlockHeader) // iteration 1: wait for next block to be created d.On("waitForNewBlocks", mock.Anything, uint64(1)). @@ -238,15 +230,15 @@ func TestDownload(t *testing.T) { Return(uint64(2)).Once() // iteration 2: block 2 has events - b2 := block{ - blockHeader: blockHeader{ + b2 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ Num: 2, Hash: common.HexToHash("02"), }, } expectedBlocks = append(expectedBlocks, b2) d.On("getEventsByBlockRange", mock.Anything, uint64(2), uint64(2)). - Return([]block{b2}) + Return([]EVMBlock{b2}) // iteration 3: wait for next block to be created (jump to block 8) d.On("waitForNewBlocks", mock.Anything, uint64(2)). @@ -254,35 +246,31 @@ func TestDownload(t *testing.T) { Return(uint64(8)).Once() // iteration 4: blocks 6 and 7 have events - b6 := block{ - blockHeader: blockHeader{ + b6 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ Num: 6, Hash: common.HexToHash("06"), }, - Events: []L1InfoTreeUpdate{ - {RollupExitRoot: common.HexToHash("06")}, - }, + Events: []interface{}{"06"}, } - b7 := block{ - blockHeader: blockHeader{ + b7 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ Num: 7, Hash: common.HexToHash("07"), }, - Events: []L1InfoTreeUpdate{ - {MainnetExitRoot: common.HexToHash("07")}, - }, + Events: []interface{}{"07"}, } - b8 := block{ - blockHeader: blockHeader{ + b8 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ Num: 8, Hash: common.HexToHash("08"), }, } expectedBlocks = append(expectedBlocks, b6, b7, b8) d.On("getEventsByBlockRange", mock.Anything, uint64(3), uint64(8)). - Return([]block{b6, b7}) + Return([]EVMBlock{b6, b7}) d.On("getBlockHeader", mock.Anything, uint64(8)). - Return(b8.blockHeader) + Return(b8.EVMBlockHeader) // iteration 5: wait for next block to be created (jump to block 30) d.On("waitForNewBlocks", mock.Anything, uint64(8)). @@ -290,31 +278,29 @@ func TestDownload(t *testing.T) { Return(uint64(30)).Once() // iteration 6: from block 9 to 19, no events - b19 := block{ - blockHeader: blockHeader{ + b19 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ Num: 19, Hash: common.HexToHash("19"), }, } expectedBlocks = append(expectedBlocks, b19) d.On("getEventsByBlockRange", mock.Anything, uint64(9), uint64(19)). - Return([]block{}) + Return([]EVMBlock{}) d.On("getBlockHeader", mock.Anything, uint64(19)). - Return(b19.blockHeader) + Return(b19.EVMBlockHeader) // iteration 7: from block 20 to 30, events on last block - b30 := block{ - blockHeader: blockHeader{ + b30 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ Num: 30, Hash: common.HexToHash("30"), }, - Events: []L1InfoTreeUpdate{ - {RollupExitRoot: common.HexToHash("30")}, - }, + Events: []interface{}{testEvent(common.HexToHash("30"))}, } expectedBlocks = append(expectedBlocks, b30) d.On("getEventsByBlockRange", mock.Anything, uint64(20), uint64(30)). - Return([]block{b30}) + Return([]EVMBlock{b30}) // iteration 8: wait for next block to be created (jump to block 35) d.On("waitForNewBlocks", mock.Anything, uint64(30)). @@ -334,11 +320,9 @@ func TestDownload(t *testing.T) { } func TestWaitForNewBlocks(t *testing.T) { - retryAfterErrorPeriod = time.Millisecond * 100 - clientMock := NewL2Mock(t) + RetryAfterErrorPeriod = time.Millisecond * 100 ctx := context.Background() - d, err := newDownloader(contractAddr, clientMock, syncBlockChunck, etherman.LatestBlock) - require.NoError(t, err) + d, clientMock := NewTestDownloader(t) // at first attempt currentBlock := uint64(5) @@ -369,18 +353,16 @@ func TestWaitForNewBlocks(t *testing.T) { } func TestGetBlockHeader(t *testing.T) { - retryAfterErrorPeriod = time.Millisecond * 100 - clientMock := NewL2Mock(t) + RetryAfterErrorPeriod = time.Millisecond * 100 ctx := context.Background() - d, err := newDownloader(contractAddr, clientMock, syncBlockChunck, etherman.LatestBlock) - require.NoError(t, err) + d, clientMock := NewTestDownloader(t) blockNum := uint64(5) blockNumBig := big.NewInt(5) returnedBlock := &types.Header{ Number: blockNumBig, } - expectedBlock := blockHeader{ + expectedBlock := EVMBlockHeader{ Num: 5, Hash: returnedBlock.Hash(), } @@ -396,3 +378,19 @@ func TestGetBlockHeader(t *testing.T) { actualBlock = d.getBlockHeader(ctx, blockNum) assert.Equal(t, expectedBlock, actualBlock) } + +func buildAppender() LogAppenderMap { + appender := make(LogAppenderMap) + appender[eventSignature] = func(b *EVMBlock, l types.Log) error { + b.Events = append(b.Events, testEvent(l.Topics[1])) + return nil + } + return appender +} + +func NewTestDownloader(t *testing.T) (*EVMDownloader, *L2Mock) { + clientMock := NewL2Mock(t) + d, err := NewEVMDownloader(clientMock, syncBlockChunck, etherman.LatestBlock, time.Millisecond, buildAppender(), []common.Address{contractAddr}) + require.NoError(t, err) + return d, clientMock +} diff --git a/localbridgesync/driver.go b/sync/evmdriver.go similarity index 54% rename from localbridgesync/driver.go rename to sync/evmdriver.go index eaeed1c7..0363e00c 100644 --- a/localbridgesync/driver.go +++ b/sync/evmdriver.go @@ -1,4 +1,4 @@ -package localbridgesync +package sync import ( "context" @@ -8,27 +8,24 @@ import ( "github.com/ethereum/go-ethereum/common" ) -const ( - downloadBufferSize = 1000 - reorgDetectorID = "localbridgesync" -) - -type downloaderFull interface { - downloaderInterface - download(ctx context.Context, fromBlock uint64, downloadedCh chan block) +type evmDownloaderFull interface { + evmDownloaderInterface + download(ctx context.Context, fromBlock uint64, downloadedCh chan EVMBlock) } -type driver struct { - reorgDetector ReorgDetector - reorgSub *reorgdetector.Subscription - processor processorInterface - downloader downloaderFull +type EVMDriver struct { + reorgDetector ReorgDetector + reorgSub *reorgdetector.Subscription + processor processorInterface + downloader evmDownloaderFull + reorgDetectorID string + downloadBufferSize int } type processorInterface interface { - getLastProcessedBlock(ctx context.Context) (uint64, error) - storeBridgeEvents(blockNum uint64, events []BridgeEvent) error - reorg(firstReorgedBlock uint64) error + GetLastProcessedBlock(ctx context.Context) (uint64, error) + ProcessBlock(block EVMBlock) error + Reorg(firstReorgedBlock uint64) error } type ReorgDetector interface { @@ -36,24 +33,28 @@ type ReorgDetector interface { AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error } -func newDriver( +func NewEVMDriver( reorgDetector ReorgDetector, processor processorInterface, - downloader downloaderFull, -) (*driver, error) { + downloader evmDownloaderFull, + reorgDetectorID string, + downloadBufferSize int, +) (*EVMDriver, error) { reorgSub, err := reorgDetector.Subscribe(reorgDetectorID) if err != nil { return nil, err } - return &driver{ - reorgDetector: reorgDetector, - reorgSub: reorgSub, - processor: processor, - downloader: downloader, + return &EVMDriver{ + reorgDetector: reorgDetector, + reorgSub: reorgSub, + processor: processor, + downloader: downloader, + reorgDetectorID: reorgDetectorID, + downloadBufferSize: downloadBufferSize, }, nil } -func (d *driver) Sync(ctx context.Context) { +func (d *EVMDriver) Sync(ctx context.Context) { reset: var ( lastProcessedBlock uint64 @@ -61,11 +62,11 @@ reset: err error ) for { - lastProcessedBlock, err = d.processor.getLastProcessedBlock(ctx) + lastProcessedBlock, err = d.processor.GetLastProcessedBlock(ctx) if err != nil { attempts++ log.Error("error geting last processed block: ", err) - retryHandler("Sync", attempts) + RetryHandler("Sync", attempts) continue } break @@ -74,7 +75,7 @@ reset: defer cancel() // start downloading - downloadCh := make(chan block, downloadBufferSize) + downloadCh := make(chan EVMBlock, d.downloadBufferSize) go d.downloader.download(cancellableCtx, lastProcessedBlock, downloadCh) for { @@ -90,33 +91,33 @@ reset: } } -func (d *driver) handleNewBlock(ctx context.Context, b block) { +func (d *EVMDriver) handleNewBlock(ctx context.Context, b EVMBlock) { attempts := 0 for { - err := d.reorgDetector.AddBlockToTrack(ctx, reorgDetectorID, b.Num, b.Hash) + err := d.reorgDetector.AddBlockToTrack(ctx, d.reorgDetectorID, b.Num, b.Hash) if err != nil { attempts++ log.Errorf("error adding block %d to tracker: %v", b.Num, err) - retryHandler("handleNewBlock", attempts) + RetryHandler("handleNewBlock", attempts) continue } break } attempts = 0 for { - err := d.processor.storeBridgeEvents(b.Num, b.Events) + err := d.processor.ProcessBlock(b) if err != nil { attempts++ log.Errorf("error processing events for blcok %d, err: ", b.Num, err) - retryHandler("handleNewBlock", attempts) + RetryHandler("handleNewBlock", attempts) continue } break } } -func (d *driver) handleReorg( - cancel context.CancelFunc, downloadCh chan block, firstReorgedBlock uint64, +func (d *EVMDriver) handleReorg( + cancel context.CancelFunc, downloadCh chan EVMBlock, firstReorgedBlock uint64, ) { // stop downloader cancel() @@ -127,14 +128,14 @@ func (d *driver) handleReorg( // handle reorg attempts := 0 for { - err := d.processor.reorg(firstReorgedBlock) + err := d.processor.Reorg(firstReorgedBlock) if err != nil { attempts++ log.Errorf( - "error processing reorg, last valid block %d, err: %v", + "error processing reorg, last valid Block %d, err: %v", firstReorgedBlock, err, ) - retryHandler("handleReorg", attempts) + RetryHandler("handleReorg", attempts) continue } break diff --git a/localbridgesync/driver_test.go b/sync/evmdriver_test.go similarity index 77% rename from localbridgesync/driver_test.go rename to sync/evmdriver_test.go index 543542f7..1f063cdb 100644 --- a/localbridgesync/driver_test.go +++ b/sync/evmdriver_test.go @@ -1,4 +1,4 @@ -package localbridgesync +package sync import ( "context" @@ -14,28 +14,32 @@ import ( "github.com/stretchr/testify/require" ) +var ( + reorgDetectorID = "foo" +) + func TestSync(t *testing.T) { - retryAfterErrorPeriod = time.Millisecond * 100 + RetryAfterErrorPeriod = time.Millisecond * 100 rdm := NewReorgDetectorMock(t) pm := NewProcessorMock(t) - dm := NewDownloaderMock(t) + dm := NewEVMDownloaderMock(t) firstReorgedBlock := make(chan uint64) reorgProcessed := make(chan bool) rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{ FirstReorgedBlock: firstReorgedBlock, ReorgProcessed: reorgProcessed, - }) - driver, err := newDriver(rdm, pm, dm) + }, nil) + driver, err := NewEVMDriver(rdm, pm, dm, reorgDetectorID, 10) require.NoError(t, err) ctx := context.Background() - expectedBlock1 := block{ - blockHeader: blockHeader{ + expectedBlock1 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ Num: 3, Hash: common.HexToHash("03"), }, } - expectedBlock2 := block{ - blockHeader: blockHeader{ + expectedBlock2 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ Num: 9, Hash: common.HexToHash("09"), }, @@ -47,7 +51,7 @@ func TestSync(t *testing.T) { reorg1Completed := reorgSemaphore{} dm.On("download", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { ctx := args.Get(0).(context.Context) - downloadedCh := args.Get(2).(chan block) + downloadedCh := args.Get(2).(chan EVMBlock) log.Info("entering mock loop") for { select { @@ -70,22 +74,22 @@ func TestSync(t *testing.T) { }) // Mocking this actions, the driver should "store" all the blocks from the downloader - pm.On("getLastProcessedBlock", ctx). + pm.On("GetLastProcessedBlock", ctx). Return(uint64(3), nil) rdm.On("AddBlockToTrack", ctx, reorgDetectorID, expectedBlock1.Num, expectedBlock1.Hash). Return(nil) - pm.On("storeBridgeEvents", expectedBlock1.Num, expectedBlock1.Events). + pm.On("ProcessBlock", expectedBlock1). Return(nil) rdm.On("AddBlockToTrack", ctx, reorgDetectorID, expectedBlock2.Num, expectedBlock2.Hash). Return(nil) - pm.On("storeBridgeEvents", expectedBlock2.Num, expectedBlock2.Events). + pm.On("ProcessBlock", expectedBlock2). Return(nil) go driver.Sync(ctx) time.Sleep(time.Millisecond * 200) // time to download expectedBlock1 // Trigger reorg 1 reorgedBlock1 := uint64(5) - pm.On("reorg", reorgedBlock1).Return(nil) + pm.On("Reorg", reorgedBlock1).Return(nil) firstReorgedBlock <- reorgedBlock1 ok := <-reorgProcessed require.True(t, ok) @@ -96,25 +100,25 @@ func TestSync(t *testing.T) { // Trigger reorg 2: syncer restarts the porcess reorgedBlock2 := uint64(7) - pm.On("reorg", reorgedBlock2).Return(nil) + pm.On("Reorg", reorgedBlock2).Return(nil) firstReorgedBlock <- reorgedBlock2 ok = <-reorgProcessed require.True(t, ok) } func TestHandleNewBlock(t *testing.T) { - retryAfterErrorPeriod = time.Millisecond * 100 + RetryAfterErrorPeriod = time.Millisecond * 100 rdm := NewReorgDetectorMock(t) pm := NewProcessorMock(t) - dm := NewDownloaderMock(t) - rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{}) - driver, err := newDriver(rdm, pm, dm) + dm := NewEVMDownloaderMock(t) + rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{}, nil) + driver, err := NewEVMDriver(rdm, pm, dm, reorgDetectorID, 10) require.NoError(t, err) ctx := context.Background() // happy path - b1 := block{ - blockHeader: blockHeader{ + b1 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ Num: 1, Hash: common.HexToHash("f00"), }, @@ -122,13 +126,13 @@ func TestHandleNewBlock(t *testing.T) { rdm. On("AddBlockToTrack", ctx, reorgDetectorID, b1.Num, b1.Hash). Return(nil) - pm.On("storeBridgeEvents", b1.Num, b1.Events). + pm.On("ProcessBlock", b1). Return(nil) driver.handleNewBlock(ctx, b1) // reorg deteector fails once - b2 := block{ - blockHeader: blockHeader{ + b2 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ Num: 2, Hash: common.HexToHash("f00"), }, @@ -139,13 +143,13 @@ func TestHandleNewBlock(t *testing.T) { rdm. On("AddBlockToTrack", ctx, reorgDetectorID, b2.Num, b2.Hash). Return(nil).Once() - pm.On("storeBridgeEvents", b2.Num, b2.Events). + pm.On("ProcessBlock", b2). Return(nil) driver.handleNewBlock(ctx, b2) // processor fails once - b3 := block{ - blockHeader: blockHeader{ + b3 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ Num: 3, Hash: common.HexToHash("f00"), }, @@ -153,30 +157,30 @@ func TestHandleNewBlock(t *testing.T) { rdm. On("AddBlockToTrack", ctx, reorgDetectorID, b3.Num, b3.Hash). Return(nil) - pm.On("storeBridgeEvents", b3.Num, b3.Events). + pm.On("ProcessBlock", b3). Return(errors.New("foo")).Once() - pm.On("storeBridgeEvents", b3.Num, b3.Events). + pm.On("ProcessBlock", b3). Return(nil).Once() driver.handleNewBlock(ctx, b3) } func TestHandleReorg(t *testing.T) { - retryAfterErrorPeriod = time.Millisecond * 100 + RetryAfterErrorPeriod = time.Millisecond * 100 rdm := NewReorgDetectorMock(t) pm := NewProcessorMock(t) - dm := NewDownloaderMock(t) + dm := NewEVMDownloaderMock(t) reorgProcessed := make(chan bool) rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{ ReorgProcessed: reorgProcessed, - }) - driver, err := newDriver(rdm, pm, dm) + }, nil) + driver, err := NewEVMDriver(rdm, pm, dm, reorgDetectorID, 10) require.NoError(t, err) ctx := context.Background() // happy path _, cancel := context.WithCancel(ctx) - downloadCh := make(chan block) + downloadCh := make(chan EVMBlock) firstReorgedBlock := uint64(5) pm.On("reorg", firstReorgedBlock).Return(nil) go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) @@ -186,20 +190,20 @@ func TestHandleReorg(t *testing.T) { // download ch sends some garbage _, cancel = context.WithCancel(ctx) - downloadCh = make(chan block) + downloadCh = make(chan EVMBlock) firstReorgedBlock = uint64(6) pm.On("reorg", firstReorgedBlock).Return(nil) go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) - downloadCh <- block{} - downloadCh <- block{} - downloadCh <- block{} + downloadCh <- EVMBlock{} + downloadCh <- EVMBlock{} + downloadCh <- EVMBlock{} close(downloadCh) done = <-reorgProcessed require.True(t, done) // processor fails 2 times _, cancel = context.WithCancel(ctx) - downloadCh = make(chan block) + downloadCh = make(chan EVMBlock) firstReorgedBlock = uint64(7) pm.On("reorg", firstReorgedBlock).Return(errors.New("foo")).Once() pm.On("reorg", firstReorgedBlock).Return(errors.New("foo")).Once() diff --git a/sync/evmtypes.go b/sync/evmtypes.go new file mode 100644 index 00000000..d242dbc4 --- /dev/null +++ b/sync/evmtypes.go @@ -0,0 +1,15 @@ +package sync + +import "github.com/ethereum/go-ethereum/common" + +type EVMBlock struct { + EVMBlockHeader + Events []interface{} +} + +type EVMBlockHeader struct { + Num uint64 + Hash common.Hash + ParentHash common.Hash + Timestamp uint64 +} diff --git a/l1infotreesync/mock_downloader_test.go b/sync/mock_downloader_test.go similarity index 54% rename from l1infotreesync/mock_downloader_test.go rename to sync/mock_downloader_test.go index fca72678..738fc873 100644 --- a/l1infotreesync/mock_downloader_test.go +++ b/sync/mock_downloader_test.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.22.1. DO NOT EDIT. -package l1infotreesync +package sync import ( context "context" @@ -9,45 +9,40 @@ import ( mock "github.com/stretchr/testify/mock" ) -// DownloaderMock is an autogenerated mock type for the downloaderFull type -type DownloaderMock struct { +// EVMDownloaderMock is an autogenerated mock type for the evmDownloaderFull type +type EVMDownloaderMock struct { mock.Mock } -// appendLog provides a mock function with given fields: b, l -func (_m *DownloaderMock) appendLog(b *block, l types.Log) { - _m.Called(b, l) -} - // download provides a mock function with given fields: ctx, fromBlock, downloadedCh -func (_m *DownloaderMock) download(ctx context.Context, fromBlock uint64, downloadedCh chan block) { +func (_m *EVMDownloaderMock) download(ctx context.Context, fromBlock uint64, downloadedCh chan EVMBlock) { _m.Called(ctx, fromBlock, downloadedCh) } // getBlockHeader provides a mock function with given fields: ctx, blockNum -func (_m *DownloaderMock) getBlockHeader(ctx context.Context, blockNum uint64) blockHeader { +func (_m *EVMDownloaderMock) getBlockHeader(ctx context.Context, blockNum uint64) EVMBlockHeader { ret := _m.Called(ctx, blockNum) - var r0 blockHeader - if rf, ok := ret.Get(0).(func(context.Context, uint64) blockHeader); ok { + var r0 EVMBlockHeader + if rf, ok := ret.Get(0).(func(context.Context, uint64) EVMBlockHeader); ok { r0 = rf(ctx, blockNum) } else { - r0 = ret.Get(0).(blockHeader) + r0 = ret.Get(0).(EVMBlockHeader) } return r0 } // getEventsByBlockRange provides a mock function with given fields: ctx, fromBlock, toBlock -func (_m *DownloaderMock) getEventsByBlockRange(ctx context.Context, fromBlock uint64, toBlock uint64) []block { +func (_m *EVMDownloaderMock) getEventsByBlockRange(ctx context.Context, fromBlock uint64, toBlock uint64) []EVMBlock { ret := _m.Called(ctx, fromBlock, toBlock) - var r0 []block - if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []block); ok { + var r0 []EVMBlock + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []EVMBlock); ok { r0 = rf(ctx, fromBlock, toBlock) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]block) + r0 = ret.Get(0).([]EVMBlock) } } @@ -55,7 +50,7 @@ func (_m *DownloaderMock) getEventsByBlockRange(ctx context.Context, fromBlock u } // getLogs provides a mock function with given fields: ctx, fromBlock, toBlock -func (_m *DownloaderMock) getLogs(ctx context.Context, fromBlock uint64, toBlock uint64) []types.Log { +func (_m *EVMDownloaderMock) getLogs(ctx context.Context, fromBlock uint64, toBlock uint64) []types.Log { ret := _m.Called(ctx, fromBlock, toBlock) var r0 []types.Log @@ -71,7 +66,7 @@ func (_m *DownloaderMock) getLogs(ctx context.Context, fromBlock uint64, toBlock } // waitForNewBlocks provides a mock function with given fields: ctx, lastBlockSeen -func (_m *DownloaderMock) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) uint64 { +func (_m *EVMDownloaderMock) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) uint64 { ret := _m.Called(ctx, lastBlockSeen) var r0 uint64 @@ -84,14 +79,14 @@ func (_m *DownloaderMock) waitForNewBlocks(ctx context.Context, lastBlockSeen ui return r0 } -type mockConstructorTestingTNewDownloaderMock interface { +type mockConstructorTestingTNewEVMDownloaderMock interface { mock.TestingT Cleanup(func()) } -// NewDownloaderMock creates a new instance of DownloaderMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewDownloaderMock(t mockConstructorTestingTNewDownloaderMock) *DownloaderMock { - mock := &DownloaderMock{} +// NewEVMDownloaderMock creates a new instance of EVMDownloaderMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewEVMDownloaderMock(t mockConstructorTestingTNewEVMDownloaderMock) *EVMDownloaderMock { + mock := &EVMDownloaderMock{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) diff --git a/l1infotreesync/mock_l2_test.go b/sync/mock_l2_test.go similarity index 99% rename from l1infotreesync/mock_l2_test.go rename to sync/mock_l2_test.go index 9ab6868d..0d1e03da 100644 --- a/l1infotreesync/mock_l2_test.go +++ b/sync/mock_l2_test.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.22.1. DO NOT EDIT. -package l1infotreesync +package sync import ( context "context" diff --git a/l1infotreesync/mock_processor_test.go b/sync/mock_processor_test.go similarity index 73% rename from l1infotreesync/mock_processor_test.go rename to sync/mock_processor_test.go index f7105850..f0959b29 100644 --- a/l1infotreesync/mock_processor_test.go +++ b/sync/mock_processor_test.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.22.1. DO NOT EDIT. -package l1infotreesync +package sync import ( context "context" @@ -13,8 +13,8 @@ type ProcessorMock struct { mock.Mock } -// getLastProcessedBlock provides a mock function with given fields: ctx -func (_m *ProcessorMock) getLastProcessedBlock(ctx context.Context) (uint64, error) { +// GetLastProcessedBlock provides a mock function with given fields: ctx +func (_m *ProcessorMock) GetLastProcessedBlock(ctx context.Context) (uint64, error) { ret := _m.Called(ctx) var r0 uint64 @@ -37,13 +37,13 @@ func (_m *ProcessorMock) getLastProcessedBlock(ctx context.Context) (uint64, err return r0, r1 } -// processBlock provides a mock function with given fields: block -func (_m *ProcessorMock) processBlock(b block) error { - ret := _m.Called(b) +// ProcessBlock provides a mock function with given fields: block +func (_m *ProcessorMock) ProcessBlock(block EVMBlock) error { + ret := _m.Called(block) var r0 error - if rf, ok := ret.Get(0).(func(block) error); ok { - r0 = rf(b) + if rf, ok := ret.Get(0).(func(EVMBlock) error); ok { + r0 = rf(block) } else { r0 = ret.Error(0) } @@ -51,8 +51,8 @@ func (_m *ProcessorMock) processBlock(b block) error { return r0 } -// reorg provides a mock function with given fields: firstReorgedBlock -func (_m *ProcessorMock) reorg(firstReorgedBlock uint64) error { +// Reorg provides a mock function with given fields: firstReorgedBlock +func (_m *ProcessorMock) Reorg(firstReorgedBlock uint64) error { ret := _m.Called(firstReorgedBlock) var r0 error diff --git a/localbridgesync/mock_reorgdetector_test.go b/sync/mock_reorgdetector_test.go similarity index 98% rename from localbridgesync/mock_reorgdetector_test.go rename to sync/mock_reorgdetector_test.go index 3639cb9a..056da2a1 100644 --- a/localbridgesync/mock_reorgdetector_test.go +++ b/sync/mock_reorgdetector_test.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.22.1. DO NOT EDIT. -package localbridgesync +package sync import ( context "context" diff --git a/test/Makefile b/test/Makefile index 07d49fd3..0e671023 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,15 +1,9 @@ .PHONY: generate-mocks generate-mocks: - $(MAKE) generate-mocks-localbridgesync $(MAKE) generate-mocks-reorgdetector $(MAKE) generate-mocks-l1infotreesync - -.PHONY: generate-mocks-localbridgesync -generate-mocks-localbridgesync: ## Generates mocks for localbridgesync, using mockery tool - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthClienter --dir=../localbridgesync --output=../localbridgesync --outpkg=localbridgesync --inpackage --structname=L2Mock --filename=mock_l2_test.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=downloaderFull --dir=../localbridgesync --output=../localbridgesync --outpkg=localbridgesync --inpackage --structname=DownloaderMock --filename=mock_downloader_test.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=processorInterface --dir=../localbridgesync --output=../localbridgesync --outpkg=localbridgesync --inpackage --structname=ProcessorMock --filename=mock_processor_test.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ReorgDetector --dir=../localbridgesync --output=../localbridgesync --outpkg=localbridgesync --inpackage --structname=ReorgDetectorMock --filename=mock_reorgdetector_test.go + $(MAKE) generate-mocks-aggoracle + $(MAKE) generate-mocks-sync .PHONY: generate-mocks-reorgdetector generate-mocks-reorgdetector: ## Generates mocks for reorgdetector, using mockery tool @@ -40,11 +34,15 @@ help: ## Prints this help | 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=EthClienter --dir=../l1infotreesync --output=../l1infotreesync --outpkg=l1infotreesync --inpackage --structname=L2Mock --filename=mock_l2_test.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=downloaderFull --dir=../l1infotreesync --output=../l1infotreesync --outpkg=l1infotreesync --inpackage --structname=DownloaderMock --filename=mock_downloader_test.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=processorInterface --dir=../l1infotreesync --output=../l1infotreesync --outpkg=l1infotreesync --inpackage --structname=ProcessorMock --filename=mock_processor_test.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ReorgDetector --dir=../l1infotreesync --output=../l1infotreesync --outpkg=l1infotreesync --inpackage --structname=ReorgDetectorMock --filename=mock_reorgdetector_test.go + 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-aggoracle: ## Generates mocks for aggoracle , using mockery tool export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthTxManager --dir=../aggoracle/chaingersender --output=../aggoracle --outpkg=aggoracle --structname=EthTxManagerMock --filename=mock_ethtxmanager_test.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 From 4bd7ef1232d02440d79bebb768ca75fcbb372974 Mon Sep 17 00:00:00 2001 From: Arnau Date: Thu, 25 Jul 2024 18:09:01 +0200 Subject: [PATCH 06/49] decouple sync processors from EVM --- aggoracle/e2e_test.go | 2 +- common/common.go | 26 ++++++++--- l1infotreesync/downloader.go | 9 ++-- l1infotreesync/e2e_test.go | 2 +- l1infotreesync/l1infotreesync.go | 14 ++++++ l1infotreesync/processor.go | 70 +++++++++++++++--------------- localbridgesync/downloader.go | 30 ++----------- localbridgesync/localbridgesync.go | 14 ++++++ localbridgesync/processor.go | 50 ++++++++++++++++----- localbridgesync/processor_test.go | 52 +++++++++------------- sync/driver.go | 14 ++++++ sync/evmdriver.go | 8 +++- sync/evmdriver_test.go | 22 +++++----- sync/mock_processor_test.go | 4 +- 14 files changed, 180 insertions(+), 137 deletions(-) create mode 100644 sync/driver.go diff --git a/aggoracle/e2e_test.go b/aggoracle/e2e_test.go index 7d616be9..18b7572d 100644 --- a/aggoracle/e2e_test.go +++ b/aggoracle/e2e_test.go @@ -59,7 +59,7 @@ func commonSetup(t *testing.T) ( require.NoError(t, err) // Syncer dbPathSyncer := t.TempDir() - syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), 32, time.Millisecond) + syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), 32, time.Millisecond, 0) require.NoError(t, err) go syncer.Start(ctx) diff --git a/common/common.go b/common/common.go index 4e93d5f8..d2f440d1 100644 --- a/common/common.go +++ b/common/common.go @@ -9,17 +9,29 @@ import ( "github.com/iden3/go-iden3-crypto/keccak256" ) -// BlockNum2Bytes converts a block number to a byte slice -func BlockNum2Bytes(blockNum uint64) []byte { - key := make([]byte, 8) - binary.LittleEndian.PutUint64(key, blockNum) +// Uint64To2Bytes converts a block number to a byte slice +func Uint64To2Bytes(num uint64) []byte { + bytes := make([]byte, 8) + binary.LittleEndian.PutUint64(bytes, num) + return bytes +} + +// BytesToUint64 converts a byte slice to a block number +func BytesToUint64(bytes []byte) uint64 { + return binary.LittleEndian.Uint64(bytes) +} + +// Uint32To2Bytes converts a block number to a byte slice +func Uint32ToBytes(num uint32) []byte { + key := make([]byte, 4) + binary.LittleEndian.PutUint32(key, num) return key } -// Bytes2BlockNum converts a byte slice to a block number -func Bytes2BlockNum(key []byte) uint64 { - return binary.LittleEndian.Uint64(key) +// BytesToUint32 converts a byte slice to a block number +func BytesToUint32(bytes []byte) uint32 { + return binary.LittleEndian.Uint32(bytes) } func CalculateAccInputHash( diff --git a/l1infotreesync/downloader.go b/l1infotreesync/downloader.go index ba9b219b..255395dd 100644 --- a/l1infotreesync/downloader.go +++ b/l1infotreesync/downloader.go @@ -23,11 +23,6 @@ type EthClienter interface { bind.ContractBackend } -type L1InfoTreeUpdate struct { - MainnetExitRoot common.Hash - RollupExitRoot common.Hash -} - func buildAppender(client EthClienter, globalExitRoot common.Address) (sync.LogAppenderMap, error) { contract, err := polygonzkevmglobalexitrootv2.NewPolygonzkevmglobalexitrootv2(globalExitRoot, client) if err != nil { @@ -42,9 +37,11 @@ func buildAppender(client EthClienter, globalExitRoot common.Address) (sync.LogA l, err, ) } - b.Events = append(b.Events, L1InfoTreeUpdate{ + b.Events = append(b.Events, Event{ MainnetExitRoot: l1InfoTreeUpdate.MainnetExitRoot, RollupExitRoot: l1InfoTreeUpdate.RollupExitRoot, + ParentHash: b.ParentHash, + Timestamp: b.Timestamp, }) return nil } diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index dc5734d5..81be3d9a 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -54,7 +54,7 @@ func TestE2E(t *testing.T) { rdm.On("AddBlockToTrack", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) client, gerAddr, gerSc, err := newSimulatedClient(auth) require.NoError(t, err) - syncer, err := New(ctx, dbPath, gerAddr, 10, etherman.LatestBlock, rdm, client.Client(), 32, time.Millisecond) + syncer, err := New(ctx, dbPath, gerAddr, 10, etherman.LatestBlock, rdm, client.Client(), 32, time.Millisecond, 0) require.NoError(t, err) go syncer.Start(ctx) diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index 847488df..695035f3 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -34,11 +34,25 @@ func New( l1Client EthClienter, treeHeight uint8, waitForNewBlocksPeriod time.Duration, + initialBlock uint64, ) (*L1InfoTreeSync, error) { processor, err := newProcessor(ctx, dbPath, treeHeight) if err != nil { return nil, err } + // TODO: get the initialBlock from L1 to simplify config + lastProcessedBlock, err := processor.GetLastProcessedBlock(ctx) + if err != nil { + return nil, err + } + if lastProcessedBlock < initialBlock { + err = processor.ProcessBlock(sync.Block{ + Num: initialBlock, + }) + if err != nil { + return nil, err + } + } appender, err := buildAppender(l1Client, globalExitRoot) if err != nil { diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 2247fae3..8fea2d60 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -6,9 +6,10 @@ import ( "encoding/json" "errors" + "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/l1infotree" "github.com/0xPolygon/cdk/sync" - "github.com/ethereum/go-ethereum/common" + ethCommon "github.com/ethereum/go-ethereum/common" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/kv/mdbx" "golang.org/x/crypto/sha3" @@ -33,22 +34,29 @@ type processor struct { tree *l1infotree.L1InfoTree } +type Event struct { + MainnetExitRoot ethCommon.Hash + RollupExitRoot ethCommon.Hash + ParentHash ethCommon.Hash + Timestamp uint64 +} + type L1InfoTreeLeaf struct { - L1InfoTreeRoot common.Hash + L1InfoTreeRoot ethCommon.Hash L1InfoTreeIndex uint32 - PreviousBlockHash common.Hash + PreviousBlockHash ethCommon.Hash BlockNumber uint64 Timestamp uint64 - MainnetExitRoot common.Hash - RollupExitRoot common.Hash - GlobalExitRoot common.Hash + MainnetExitRoot ethCommon.Hash + RollupExitRoot ethCommon.Hash + GlobalExitRoot ethCommon.Hash } type storeLeaf struct { - MainnetExitRoot common.Hash - RollupExitRoot common.Hash - ParentHash common.Hash - InfoRoot common.Hash + MainnetExitRoot ethCommon.Hash + RollupExitRoot ethCommon.Hash + ParentHash ethCommon.Hash + InfoRoot ethCommon.Hash Index uint32 Timestamp uint64 BlockNumber uint64 @@ -61,7 +69,7 @@ type blockWithLeafs struct { LastIndex uint32 } -func (l *storeLeaf) GlobalExitRoot() common.Hash { +func (l *storeLeaf) GlobalExitRoot() ethCommon.Hash { var gerBytes [32]byte hasher := sha3.NewLegacyKeccak256() hasher.Write(l.MainnetExitRoot[:]) @@ -122,19 +130,19 @@ func (p *processor) getAllLeavesHashed(ctx context.Context) ([][32]byte, error) return p.getHasedLeaves(tx, index) } -func (p *processor) ComputeMerkleProofByIndex(ctx context.Context, index uint32) ([][32]byte, common.Hash, error) { +func (p *processor) ComputeMerkleProofByIndex(ctx context.Context, index uint32) ([][32]byte, ethCommon.Hash, error) { // TODO: refactor the tree to store the nodes so it's not neede to load all the leaves and compute the tree // every time this function is called. Since it's not a sparse MT, an alternative could be to store the proofs // as part of the info tx, err := p.db.BeginRo(ctx) if err != nil { - return nil, common.Hash{}, err + return nil, ethCommon.Hash{}, err } defer tx.Rollback() leaves, err := p.getHasedLeaves(tx, index) if err != nil { - return nil, common.Hash{}, err + return nil, ethCommon.Hash{}, err } return p.tree.ComputeMerkleProof(index, leaves) } @@ -152,15 +160,15 @@ func (p *processor) getHasedLeaves(tx kv.Tx, untilIndex uint32) ([][32]byte, err return leaves, nil } -func (p *processor) ComputeMerkleProofByRoot(ctx context.Context, root common.Hash) ([][32]byte, common.Hash, error) { +func (p *processor) ComputeMerkleProofByRoot(ctx context.Context, root ethCommon.Hash) ([][32]byte, ethCommon.Hash, error) { info, err := p.GetInfoByRoot(ctx, root) if err != nil { - return nil, common.Hash{}, err + return nil, ethCommon.Hash{}, err } return p.ComputeMerkleProofByIndex(ctx, info.L1InfoTreeIndex) } -func (p *processor) GetInfoByRoot(ctx context.Context, root common.Hash) (*L1InfoTreeLeaf, error) { +func (p *processor) GetInfoByRoot(ctx context.Context, root ethCommon.Hash) (*L1InfoTreeLeaf, error) { tx, err := p.db.BeginRo(ctx) if err != nil { return nil, err @@ -205,7 +213,7 @@ func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64 if err := json.Unmarshal(v, &blk); err != nil { return nil, err } - hash, err := tx.GetOne(indexTable, uint32ToBytes(blk.LastIndex-1)) + hash, err := tx.GetOne(indexTable, common.Uint32ToBytes(blk.LastIndex-1)) if err != nil { return nil, err } @@ -225,7 +233,7 @@ func (p *processor) GetInfoByIndex(ctx context.Context, index uint32) (*L1InfoTr } func (p *processor) getInfoByIndexWithTx(tx kv.Tx, index uint32) (*L1InfoTreeLeaf, error) { - hash, err := tx.GetOne(indexTable, uint32ToBytes(index)) + hash, err := tx.GetOne(indexTable, common.Uint32ToBytes(index)) if err != nil { return nil, err } @@ -331,7 +339,7 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { func (p *processor) deleteLeaf(tx kv.RwTx, index uint32) error { // TODO: do we need to do something with p.tree here? // Get leaf info to delete all relations - hash, err := tx.GetOne(indexTable, uint32ToBytes(index)) + hash, err := tx.GetOne(indexTable, common.Uint32ToBytes(index)) if err != nil { return err } @@ -354,7 +362,7 @@ func (p *processor) deleteLeaf(tx kv.RwTx, index uint32) error { if err := tx.Delete(rootTable, info.InfoRoot[:]); err != nil { return err } - if err := tx.Delete(indexTable, uint32ToBytes(index)); err != nil { + if err := tx.Delete(indexTable, common.Uint32ToBytes(index)); err != nil { return err } if err := tx.Delete(infoTable, hash); err != nil { @@ -365,7 +373,7 @@ func (p *processor) deleteLeaf(tx kv.RwTx, index uint32) error { // ProcessBlock process the leafs of the L1 info tree found on a block // this function can be called without leafs with the intention to track the last processed block -func (p *processor) ProcessBlock(b sync.EVMBlock) error { +func (p *processor) ProcessBlock(b sync.Block) error { tx, err := p.db.BeginRw(context.Background()) if err != nil { return err @@ -382,13 +390,13 @@ func (p *processor) ProcessBlock(b sync.EVMBlock) error { initialIndex = lastIndex + 1 } for i, e := range b.Events { - event := e.(L1InfoTreeUpdate) + event := e.(Event) leafToStore := storeLeaf{ Index: initialIndex + uint32(i), MainnetExitRoot: event.MainnetExitRoot, RollupExitRoot: event.RollupExitRoot, - ParentHash: b.ParentHash, - Timestamp: b.Timestamp, + ParentHash: event.ParentHash, + Timestamp: event.Timestamp, BlockNumber: b.Num, } if err := p.addLeaf(tx, leafToStore); err != nil { @@ -460,7 +468,7 @@ func (p *processor) addLeaf(tx kv.RwTx, leaf storeLeaf) error { return err } // store index relation - if err := tx.Put(indexTable, uint32ToBytes(leaf.Index), hash[:]); err != nil { + if err := tx.Put(indexTable, common.Uint32ToBytes(leaf.Index), hash[:]); err != nil { return err } // store root relation @@ -484,13 +492,3 @@ func uint64ToBytes(num uint64) []byte { func bytes2Uint64(key []byte) uint64 { return binary.LittleEndian.Uint64(key) } - -func uint32ToBytes(num uint32) []byte { - key := make([]byte, 4) - binary.LittleEndian.PutUint32(key, num) - return key -} - -func bytes2Uint32(key []byte) uint32 { - return binary.LittleEndian.Uint32(key) -} diff --git a/localbridgesync/downloader.go b/localbridgesync/downloader.go index 955655f1..5b6ab8f6 100644 --- a/localbridgesync/downloader.go +++ b/localbridgesync/downloader.go @@ -32,30 +32,6 @@ type EthClienter interface { bind.ContractBackend } -type Bridge struct { - LeafType uint8 - OriginNetwork uint32 - OriginAddress common.Address - DestinationNetwork uint32 - DestinationAddress common.Address - Amount *big.Int - Metadata []byte - DepositCount uint32 -} - -type Claim struct { - GlobalIndex *big.Int - OriginNetwork uint32 - OriginAddress common.Address - DestinationAddress common.Address - Amount *big.Int -} - -type BridgeEvent struct { - Bridge *Bridge - Claim *Claim -} - func buildAppender(client EthClienter, bridge common.Address) (sync.LogAppenderMap, error) { bridgeContractV1, err := polygonzkevmbridge.NewPolygonzkevmbridge(bridge, client) if err != nil { @@ -75,7 +51,7 @@ func buildAppender(client EthClienter, bridge common.Address) (sync.LogAppenderM l, err, ) } - b.Events = append(b.Events, BridgeEvent{Bridge: &Bridge{ + b.Events = append(b.Events, Event{Bridge: &Bridge{ LeafType: bridge.LeafType, OriginNetwork: bridge.OriginNetwork, OriginAddress: bridge.OriginAddress, @@ -96,7 +72,7 @@ func buildAppender(client EthClienter, bridge common.Address) (sync.LogAppenderM l, err, ) } - b.Events = append(b.Events, BridgeEvent{Claim: &Claim{ + b.Events = append(b.Events, Event{Claim: &Claim{ GlobalIndex: claim.GlobalIndex, OriginNetwork: claim.OriginNetwork, OriginAddress: claim.OriginAddress, @@ -114,7 +90,7 @@ func buildAppender(client EthClienter, bridge common.Address) (sync.LogAppenderM l, err, ) } - b.Events = append(b.Events, BridgeEvent{Claim: &Claim{ + b.Events = append(b.Events, Event{Claim: &Claim{ GlobalIndex: big.NewInt(int64(claim.Index)), OriginNetwork: claim.OriginNetwork, OriginAddress: claim.OriginAddress, diff --git a/localbridgesync/localbridgesync.go b/localbridgesync/localbridgesync.go index 6707bbad..d0617584 100644 --- a/localbridgesync/localbridgesync.go +++ b/localbridgesync/localbridgesync.go @@ -25,17 +25,31 @@ type LocalBridgeSync struct { } func New( + ctx context.Context, dbPath string, bridge common.Address, syncBlockChunkSize uint64, blockFinalityType etherman.BlockNumberFinality, rd sync.ReorgDetector, l2Client EthClienter, + initialBlock uint64, ) (*LocalBridgeSync, error) { processor, err := newProcessor(dbPath) if err != nil { return nil, err } + lastProcessedBlock, err := processor.GetLastProcessedBlock(ctx) + if err != nil { + return nil, err + } + if lastProcessedBlock < initialBlock { + err = processor.ProcessBlock(sync.Block{ + Num: initialBlock, + }) + if err != nil { + return nil, err + } + } appender, err := buildAppender(l2Client, bridge) if err != nil { diff --git a/localbridgesync/processor.go b/localbridgesync/processor.go index 443a7925..4a45c663 100644 --- a/localbridgesync/processor.go +++ b/localbridgesync/processor.go @@ -4,9 +4,11 @@ import ( "context" "encoding/json" "errors" + "math/big" "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/sync" + ethCommon "github.com/ethereum/go-ethereum/common" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/kv/mdbx" ) @@ -21,6 +23,30 @@ var ( lastBlokcKey = []byte("lb") ) +type Bridge struct { + LeafType uint8 + OriginNetwork uint32 + OriginAddress ethCommon.Address + DestinationNetwork uint32 + DestinationAddress ethCommon.Address + Amount *big.Int + Metadata []byte + DepositCount uint32 +} + +type Claim struct { + GlobalIndex *big.Int + OriginNetwork uint32 + OriginAddress ethCommon.Address + DestinationAddress ethCommon.Address + Amount *big.Int +} + +type Event struct { + Bridge *Bridge + Claim *Claim +} + type processor struct { db kv.RwDB } @@ -49,8 +75,8 @@ func newProcessor(dbPath string) (*processor, error) { // If toBlock has not been porcessed yet, ErrBlockNotProcessed will be returned func (p *processor) GetClaimsAndBridges( ctx context.Context, fromBlock, toBlock uint64, -) ([]BridgeEvent, error) { - events := []BridgeEvent{} +) ([]Event, error) { + events := []Event{} tx, err := p.db.BeginRo(ctx) if err != nil { @@ -70,14 +96,14 @@ func (p *processor) GetClaimsAndBridges( } defer c.Close() - for k, v, err := c.Seek(common.BlockNum2Bytes(fromBlock)); k != nil; k, v, err = c.Next() { + for k, v, err := c.Seek(common.Uint64To2Bytes(fromBlock)); k != nil; k, v, err = c.Next() { if err != nil { return nil, err } - if common.Bytes2BlockNum(k) > toBlock { + if common.BytesToUint64(k) > toBlock { break } - blockEvents := []BridgeEvent{} + blockEvents := []Event{} err := json.Unmarshal(v, &blockEvents) if err != nil { return nil, err @@ -103,7 +129,7 @@ func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { } else if blockNumBytes == nil { return 0, nil } else { - return common.Bytes2BlockNum(blockNumBytes), nil + return common.BytesToUint64(blockNumBytes), nil } } @@ -117,7 +143,7 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { return err } defer c.Close() - firstKey := common.BlockNum2Bytes(firstReorgedBlock) + firstKey := common.Uint64To2Bytes(firstReorgedBlock) for k, _, err := c.Seek(firstKey); k != nil; k, _, err = c.Next() { if err != nil { tx.Rollback() @@ -135,22 +161,22 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { return tx.Commit() } -func (p *processor) ProcessBlock(block sync.EVMBlock) error { +func (p *processor) ProcessBlock(block sync.Block) error { tx, err := p.db.BeginRw(context.Background()) if err != nil { return err } if len(block.Events) > 0 { - events := []BridgeEvent{} + events := []Event{} for _, e := range block.Events { - events = append(events, e.(BridgeEvent)) + events = append(events, e.(Event)) } value, err := json.Marshal(events) if err != nil { tx.Rollback() return err } - if err := tx.Put(eventsTable, common.BlockNum2Bytes(block.Num), value); err != nil { + if err := tx.Put(eventsTable, common.Uint64To2Bytes(block.Num), value); err != nil { tx.Rollback() return err } @@ -163,6 +189,6 @@ func (p *processor) ProcessBlock(block sync.EVMBlock) error { } func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error { - blockNumBytes := common.BlockNum2Bytes(blockNum) + blockNumBytes := common.Uint64To2Bytes(blockNum) return tx.Put(lastBlockTable, lastBlokcKey, blockNumBytes) } diff --git a/localbridgesync/processor_test.go b/localbridgesync/processor_test.go index a7835256..dbf0d74c 100644 --- a/localbridgesync/processor_test.go +++ b/localbridgesync/processor_test.go @@ -121,7 +121,7 @@ func TestProceessor(t *testing.T) { ctx: context.Background(), fromBlock: 2, toBlock: 2, - expectedEvents: []BridgeEvent{}, + expectedEvents: []Event{}, expectedErr: nil, }, &getClaimsAndBridgesAction{ @@ -241,13 +241,10 @@ func TestProceessor(t *testing.T) { // blocks var ( - block1 = sync.EVMBlock{ - EVMBlockHeader: sync.EVMBlockHeader{ - Num: 1, - Hash: common.HexToHash("01"), - }, + block1 = sync.Block{ + Num: 1, Events: []interface{}{ - BridgeEvent{Bridge: &Bridge{ + Event{Bridge: &Bridge{ LeafType: 1, OriginNetwork: 1, OriginAddress: common.HexToAddress("01"), @@ -257,7 +254,7 @@ var ( Metadata: common.Hex2Bytes("01"), DepositCount: 1, }}, - BridgeEvent{Claim: &Claim{ + Event{Claim: &Claim{ GlobalIndex: big.NewInt(1), OriginNetwork: 1, OriginAddress: common.HexToAddress("01"), @@ -266,13 +263,10 @@ var ( }}, }, } - block3 = sync.EVMBlock{ - EVMBlockHeader: sync.EVMBlockHeader{ - Num: 3, - Hash: common.HexToHash("02"), - }, + block3 = sync.Block{ + Num: 3, Events: []interface{}{ - BridgeEvent{Bridge: &Bridge{ + Event{Bridge: &Bridge{ LeafType: 2, OriginNetwork: 2, OriginAddress: common.HexToAddress("02"), @@ -282,7 +276,7 @@ var ( Metadata: common.Hex2Bytes("02"), DepositCount: 2, }}, - BridgeEvent{Bridge: &Bridge{ + Event{Bridge: &Bridge{ LeafType: 3, OriginNetwork: 3, OriginAddress: common.HexToAddress("03"), @@ -294,27 +288,21 @@ var ( }}, }, } - block4 = sync.EVMBlock{ - EVMBlockHeader: sync.EVMBlockHeader{ - Num: 4, - Hash: common.HexToHash("03"), - }, + block4 = sync.Block{ + Num: 4, Events: []interface{}{}, } - block5 = sync.EVMBlock{ - EVMBlockHeader: sync.EVMBlockHeader{ - Num: 5, - Hash: common.HexToHash("04"), - }, + block5 = sync.Block{ + Num: 5, Events: []interface{}{ - BridgeEvent{Claim: &Claim{ + Event{Claim: &Claim{ GlobalIndex: big.NewInt(4), OriginNetwork: 4, OriginAddress: common.HexToAddress("04"), DestinationAddress: common.HexToAddress("04"), Amount: big.NewInt(4), }}, - BridgeEvent{Claim: &Claim{ + Event{Claim: &Claim{ GlobalIndex: big.NewInt(5), OriginNetwork: 5, OriginAddress: common.HexToAddress("05"), @@ -341,7 +329,7 @@ type getClaimsAndBridgesAction struct { ctx context.Context fromBlock uint64 toBlock uint64 - expectedEvents []BridgeEvent + expectedEvents []Event expectedErr error } @@ -410,7 +398,7 @@ func (a *reorgAction) execute(t *testing.T) { type processBlockAction struct { p *processor description string - block sync.EVMBlock + block sync.Block expectedErr error } @@ -427,10 +415,10 @@ func (a *processBlockAction) execute(t *testing.T) { require.Equal(t, a.expectedErr, actualErr) } -func eventsToBridgeEvents(events []interface{}) []BridgeEvent { - bridgeEvents := []BridgeEvent{} +func eventsToBridgeEvents(events []interface{}) []Event { + bridgeEvents := []Event{} for _, event := range events { - bridgeEvents = append(bridgeEvents, event.(BridgeEvent)) + bridgeEvents = append(bridgeEvents, event.(Event)) } return bridgeEvents } diff --git a/sync/driver.go b/sync/driver.go new file mode 100644 index 00000000..bd066ba1 --- /dev/null +++ b/sync/driver.go @@ -0,0 +1,14 @@ +package sync + +import "context" + +type Block struct { + Num uint64 + Events []interface{} +} + +type ProcessorInterface interface { + GetLastProcessedBlock(ctx context.Context) (uint64, error) + ProcessBlock(block Block) error + Reorg(firstReorgedBlock uint64) error +} diff --git a/sync/evmdriver.go b/sync/evmdriver.go index 0363e00c..a30b96d6 100644 --- a/sync/evmdriver.go +++ b/sync/evmdriver.go @@ -24,7 +24,7 @@ type EVMDriver struct { type processorInterface interface { GetLastProcessedBlock(ctx context.Context) (uint64, error) - ProcessBlock(block EVMBlock) error + ProcessBlock(block Block) error Reorg(firstReorgedBlock uint64) error } @@ -105,7 +105,11 @@ func (d *EVMDriver) handleNewBlock(ctx context.Context, b EVMBlock) { } attempts = 0 for { - err := d.processor.ProcessBlock(b) + blockToProcess := Block{ + Num: b.Num, + Events: b.Events, + } + err := d.processor.ProcessBlock(blockToProcess) if err != nil { attempts++ log.Errorf("error processing events for blcok %d, err: ", b.Num, err) diff --git a/sync/evmdriver_test.go b/sync/evmdriver_test.go index 1f063cdb..502722f6 100644 --- a/sync/evmdriver_test.go +++ b/sync/evmdriver_test.go @@ -78,11 +78,11 @@ func TestSync(t *testing.T) { Return(uint64(3), nil) rdm.On("AddBlockToTrack", ctx, reorgDetectorID, expectedBlock1.Num, expectedBlock1.Hash). Return(nil) - pm.On("ProcessBlock", expectedBlock1). + pm.On("ProcessBlock", Block{Num: expectedBlock1.Num, Events: expectedBlock1.Events}). Return(nil) rdm.On("AddBlockToTrack", ctx, reorgDetectorID, expectedBlock2.Num, expectedBlock2.Hash). Return(nil) - pm.On("ProcessBlock", expectedBlock2). + pm.On("ProcessBlock", Block{Num: expectedBlock2.Num, Events: expectedBlock2.Events}). Return(nil) go driver.Sync(ctx) time.Sleep(time.Millisecond * 200) // time to download expectedBlock1 @@ -126,7 +126,7 @@ func TestHandleNewBlock(t *testing.T) { rdm. On("AddBlockToTrack", ctx, reorgDetectorID, b1.Num, b1.Hash). Return(nil) - pm.On("ProcessBlock", b1). + pm.On("ProcessBlock", Block{Num: b1.Num, Events: b1.Events}). Return(nil) driver.handleNewBlock(ctx, b1) @@ -143,7 +143,7 @@ func TestHandleNewBlock(t *testing.T) { rdm. On("AddBlockToTrack", ctx, reorgDetectorID, b2.Num, b2.Hash). Return(nil).Once() - pm.On("ProcessBlock", b2). + pm.On("ProcessBlock", Block{Num: b2.Num, Events: b2.Events}). Return(nil) driver.handleNewBlock(ctx, b2) @@ -157,9 +157,9 @@ func TestHandleNewBlock(t *testing.T) { rdm. On("AddBlockToTrack", ctx, reorgDetectorID, b3.Num, b3.Hash). Return(nil) - pm.On("ProcessBlock", b3). + pm.On("ProcessBlock", Block{Num: b3.Num, Events: b3.Events}). Return(errors.New("foo")).Once() - pm.On("ProcessBlock", b3). + pm.On("ProcessBlock", Block{Num: b3.Num, Events: b3.Events}). Return(nil).Once() driver.handleNewBlock(ctx, b3) @@ -182,7 +182,7 @@ func TestHandleReorg(t *testing.T) { _, cancel := context.WithCancel(ctx) downloadCh := make(chan EVMBlock) firstReorgedBlock := uint64(5) - pm.On("reorg", firstReorgedBlock).Return(nil) + pm.On("Reorg", firstReorgedBlock).Return(nil) go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) close(downloadCh) done := <-reorgProcessed @@ -192,7 +192,7 @@ func TestHandleReorg(t *testing.T) { _, cancel = context.WithCancel(ctx) downloadCh = make(chan EVMBlock) firstReorgedBlock = uint64(6) - pm.On("reorg", firstReorgedBlock).Return(nil) + pm.On("Reorg", firstReorgedBlock).Return(nil) go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) downloadCh <- EVMBlock{} downloadCh <- EVMBlock{} @@ -205,9 +205,9 @@ func TestHandleReorg(t *testing.T) { _, cancel = context.WithCancel(ctx) downloadCh = make(chan EVMBlock) firstReorgedBlock = uint64(7) - pm.On("reorg", firstReorgedBlock).Return(errors.New("foo")).Once() - pm.On("reorg", firstReorgedBlock).Return(errors.New("foo")).Once() - pm.On("reorg", firstReorgedBlock).Return(nil).Once() + pm.On("Reorg", firstReorgedBlock).Return(errors.New("foo")).Once() + pm.On("Reorg", firstReorgedBlock).Return(errors.New("foo")).Once() + pm.On("Reorg", firstReorgedBlock).Return(nil).Once() go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) close(downloadCh) done = <-reorgProcessed diff --git a/sync/mock_processor_test.go b/sync/mock_processor_test.go index f0959b29..d2c3e299 100644 --- a/sync/mock_processor_test.go +++ b/sync/mock_processor_test.go @@ -38,11 +38,11 @@ func (_m *ProcessorMock) GetLastProcessedBlock(ctx context.Context) (uint64, err } // ProcessBlock provides a mock function with given fields: block -func (_m *ProcessorMock) ProcessBlock(block EVMBlock) error { +func (_m *ProcessorMock) ProcessBlock(block Block) error { ret := _m.Called(block) var r0 error - if rf, ok := ret.Get(0).(func(EVMBlock) error); ok { + if rf, ok := ret.Get(0).(func(Block) error); ok { r0 = rf(block) } else { r0 = ret.Error(0) From 80157a440a111c2c32de9c906897730ee0fb2bff Mon Sep 17 00:00:00 2001 From: Arnau Date: Fri, 26 Jul 2024 12:06:30 +0200 Subject: [PATCH 07/49] Add CLI for aggOracle --- aggoracle/chaingersender/evm.go | 21 +- aggoracle/config.go | 24 ++ aggoracle/e2e_test.go | 2 +- cmd/main.go | 4 +- cmd/run.go | 97 +++++++++ config/config.go | 9 + l1infotreesync/e2e_test.go | 2 +- l1infotreesync/l1infotreesync.go | 14 +- l1infotreesync/processor.go | 4 +- l1infotreesync/processor_test.go | 362 +------------------------------ localbridgesync/e2e_test.go | 3 + reorgdetector/reorgdetector.go | 4 + 12 files changed, 174 insertions(+), 372 deletions(-) create mode 100644 aggoracle/config.go create mode 100644 localbridgesync/e2e_test.go diff --git a/aggoracle/chaingersender/evm.go b/aggoracle/chaingersender/evm.go index b3b6e405..42cd49a4 100644 --- a/aggoracle/chaingersender/evm.go +++ b/aggoracle/chaingersender/evm.go @@ -7,6 +7,7 @@ import ( "time" "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/pessimisticglobalexitroot" + cfgTypes "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/log" "github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager" "github.com/ethereum/go-ethereum" @@ -39,22 +40,32 @@ type EVMChainGERSender struct { waitPeriodMonitorTx time.Duration } +type EVMConfig struct { + GlobalExitRootL2 common.Address `mapstructure:"GlobalExitRootL2"` + URLRPCL2 string `mapstructure:"URLRPCL2"` + ChainIDL2 uint64 `mapstructure:"ChainIDL2"` + GasOffset uint64 `mapstructure:"GasOffset"` + WaitPeriodMonitorTx cfgTypes.Duration `mapstructure:"WaitPeriodMonitorTx"` + SenderAddr common.Address `mapstructure:"SenderAddr"` + EthTxManager ethtxmanager.Config `mapstructure:"EthTxManager"` +} + func NewEVMChainGERSender( - globalExitRoot, sender common.Address, - client EthClienter, + l2GlobalExitRoot, sender common.Address, + l2Client EthClienter, ethTxMan EthTxManager, gasOffset uint64, waitPeriodMonitorTx time.Duration, ) (*EVMChainGERSender, error) { - gerContract, err := pessimisticglobalexitroot.NewPessimisticglobalexitroot(globalExitRoot, client) + gerContract, err := pessimisticglobalexitroot.NewPessimisticglobalexitroot(l2GlobalExitRoot, l2Client) if err != nil { return nil, err } return &EVMChainGERSender{ gerContract: gerContract, - gerAddr: globalExitRoot, + gerAddr: l2GlobalExitRoot, sender: sender, - client: client, + client: l2Client, ethTxMan: ethTxMan, gasOffset: gasOffset, waitPeriodMonitorTx: waitPeriodMonitorTx, diff --git a/aggoracle/config.go b/aggoracle/config.go new file mode 100644 index 00000000..d78747da --- /dev/null +++ b/aggoracle/config.go @@ -0,0 +1,24 @@ +package aggoracle + +import ( + "github.com/0xPolygon/cdk/aggoracle/chaingersender" + "github.com/0xPolygon/cdk/config/types" +) + +type TargetChainType string + +const ( + EVMChain TargetChainType = "EVM" +) + +var ( + SupportedChainTypes = []TargetChainType{EVMChain} +) + +type Config struct { + TargetChainType TargetChainType `mapstructure:"TargetChainType"` + EVMSender chaingersender.EVMConfig `mapstructure:"EVMSender"` + URLRPCL1 string `mapstructure:"URLRPCL1"` + BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` + WaitPeriodNextGER types.Duration `mapstructure:"WaitPeriodNextGER"` +} diff --git a/aggoracle/e2e_test.go b/aggoracle/e2e_test.go index 18b7572d..ad1ff9c3 100644 --- a/aggoracle/e2e_test.go +++ b/aggoracle/e2e_test.go @@ -59,7 +59,7 @@ func commonSetup(t *testing.T) ( require.NoError(t, err) // Syncer dbPathSyncer := t.TempDir() - syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), 32, time.Millisecond, 0) + syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), time.Millisecond, 0) require.NoError(t, err) go syncer.Start(ctx) diff --git a/cmd/main.go b/cmd/main.go index ce319729..a13f43e1 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -16,6 +16,8 @@ const ( SEQUENCE_SENDER = "sequence-sender" // AGGREGATOR name to identify the aggregator component AGGREGATOR = "aggregator" + // AGGORACLE name to identify the aggoracle component + AGGORACLE = "aggoracle" ) const ( @@ -47,7 +49,7 @@ var ( Aliases: []string{"co"}, Usage: "List of components to run", Required: false, - Value: cli.NewStringSlice(SEQUENCE_SENDER, AGGREGATOR), + Value: cli.NewStringSlice(SEQUENCE_SENDER, AGGREGATOR, AGGORACLE), } ) diff --git a/cmd/run.go b/cmd/run.go index 15d24c29..57fc2e4d 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -10,6 +10,8 @@ import ( zkevm "github.com/0xPolygon/cdk" dataCommitteeClient "github.com/0xPolygon/cdk-data-availability/client" + "github.com/0xPolygon/cdk/aggoracle" + "github.com/0xPolygon/cdk/aggoracle/chaingersender" "github.com/0xPolygon/cdk/aggregator" "github.com/0xPolygon/cdk/aggregator/db" "github.com/0xPolygon/cdk/config" @@ -18,7 +20,9 @@ import ( "github.com/0xPolygon/cdk/etherman" ethermanconfig "github.com/0xPolygon/cdk/etherman/config" "github.com/0xPolygon/cdk/etherman/contracts" + "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/reorgdetector" "github.com/0xPolygon/cdk/sequencesender" "github.com/0xPolygon/cdk/sequencesender/txbuilder" "github.com/0xPolygon/cdk/state" @@ -26,6 +30,9 @@ import ( "github.com/0xPolygon/cdk/translator" ethtxman "github.com/0xPolygonHermez/zkevm-ethtx-manager/etherman" "github.com/0xPolygonHermez/zkevm-ethtx-manager/etherman/etherscan" + "github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager" + ethtxlog "github.com/0xPolygonHermez/zkevm-ethtx-manager/log" + "github.com/ethereum/go-ethereum/ethclient" "github.com/jackc/pgx/v4/pgxpool" "github.com/urfave/cli/v2" ) @@ -63,6 +70,16 @@ func start(cliCtx *cli.Context) error { log.Fatal(err) } }() + case AGGORACLE: + l1Client, err := ethclient.Dial(c.AggOracle.URLRPCL1) + if err != nil { + log.Fatal(err) + } + reorgDetector := newReorgDetectorL1(cliCtx.Context, *c, l1Client) + syncer := newL1InfoTreeSyncer(cliCtx.Context, *c, l1Client, reorgDetector) + go syncer.Start(cliCtx.Context) + aggOracle := createAggoracle(*c, l1Client, syncer) + go aggOracle.Start(cliCtx.Context) } } @@ -180,6 +197,51 @@ func newTxBuilder(cfg config.Config, ethman *etherman.Client) (txbuilder.TxBuild return txBuilder, err } +func createAggoracle(cfg config.Config, l1Client *ethclient.Client, syncer *l1infotreesync.L1InfoTreeSync) *aggoracle.AggOracle { + var sender aggoracle.ChainSender + switch cfg.AggOracle.TargetChainType { + case aggoracle.EVMChain: + cfg.AggOracle.EVMSender.EthTxManager.Log = ethtxlog.Config{ + Environment: ethtxlog.LogEnvironment(cfg.Log.Environment), + Level: cfg.Log.Level, + Outputs: cfg.Log.Outputs, + } + ethTxManager, err := ethtxmanager.New(cfg.AggOracle.EVMSender.EthTxManager) + l2CLient, err := ethclient.Dial(cfg.AggOracle.EVMSender.URLRPCL2) + if err != nil { + log.Fatal(err) + } + sender, err = chaingersender.NewEVMChainGERSender( + cfg.AggOracle.EVMSender.GlobalExitRootL2, + cfg.AggOracle.EVMSender.SenderAddr, + l2CLient, + ethTxManager, + cfg.AggOracle.EVMSender.GasOffset, + cfg.AggOracle.EVMSender.WaitPeriodMonitorTx.Duration, + ) + if err != nil { + log.Fatal(err) + } + default: + log.Fatalf( + "Unsupported chaintype %s. Supported values: %v", + cfg.AggOracle.TargetChainType, aggoracle.SupportedChainTypes, + ) + } + aggOracle, err := aggoracle.New( + sender, + l1Client, + syncer, + etherman.BlockNumberFinality(cfg.AggOracle.BlockFinality), + cfg.AggOracle.WaitPeriodNextGER.Duration, + ) + if err != nil { + log.Fatal(err) + } + + return aggOracle +} + func newDataAvailability(c config.Config, etherman *etherman.Client) (*dataavailability.DataAvailability, error) { if !c.Common.IsValidiumMode { return nil, nil @@ -292,3 +354,38 @@ func newState(c *config.Config, l2ChainID uint64, sqlDB *pgxpool.Pool) *state.St st := state.NewState(stateCfg, stateDb) return st } + +func newReorgDetectorL1( + ctx context.Context, + cfg config.Config, + l1Client *ethclient.Client, +) *reorgdetector.ReorgDetector { + rd, err := reorgdetector.New(ctx, l1Client, cfg.ReorgDetectorL1.DBPath) + if err != nil { + log.Fatal(err) + } + return rd +} + +func newL1InfoTreeSyncer( + ctx context.Context, + cfg config.Config, + l1Client *ethclient.Client, + reorgDetector *reorgdetector.ReorgDetector, +) *l1infotreesync.L1InfoTreeSync { + syncer, err := l1infotreesync.New( + ctx, + cfg.L1InfoTreeSync.DBPath, + cfg.L1InfoTreeSync.GlobalExitRootAddr, + cfg.L1InfoTreeSync.SyncBlockChunkSize, + etherman.BlockNumberFinality(cfg.L1InfoTreeSync.BlockFinality), + reorgDetector, + l1Client, + cfg.L1InfoTreeSync.WaitForNewBlocksPeriod.Duration, + cfg.L1InfoTreeSync.InitialBlock, + ) + if err != nil { + log.Fatal(err) + } + return syncer +} diff --git a/config/config.go b/config/config.go index 02ede0fb..ed452417 100644 --- a/config/config.go +++ b/config/config.go @@ -5,10 +5,13 @@ import ( "path/filepath" "strings" + "github.com/0xPolygon/cdk/aggoracle" "github.com/0xPolygon/cdk/aggregator" "github.com/0xPolygon/cdk/common" ethermanconfig "github.com/0xPolygon/cdk/etherman/config" + "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/reorgdetector" "github.com/0xPolygon/cdk/sequencesender" "github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager" "github.com/mitchellh/mapstructure" @@ -70,6 +73,12 @@ type Config struct { // Common Config that affects all the services Common common.Config + // Configuration of the reorg detector service to be used for the L1 + ReorgDetectorL1 reorgdetector.Config + // Configuration of the aggOracle service + AggOracle aggoracle.Config + // Configuration of the L1 Info Treee Sync service + L1InfoTreeSync l1infotreesync.Config } // Default parses the default configuration values. diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index 81be3d9a..35958f08 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -54,7 +54,7 @@ func TestE2E(t *testing.T) { rdm.On("AddBlockToTrack", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) client, gerAddr, gerSc, err := newSimulatedClient(auth) require.NoError(t, err) - syncer, err := New(ctx, dbPath, gerAddr, 10, etherman.LatestBlock, rdm, client.Client(), 32, time.Millisecond, 0) + syncer, err := New(ctx, dbPath, gerAddr, 10, etherman.LatestBlock, rdm, client.Client(), time.Millisecond, 0) require.NoError(t, err) go syncer.Start(ctx) diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index 695035f3..6c8390ef 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum/common" @@ -19,6 +20,16 @@ var ( maxRetryAttemptsAfterError = 5 ) +type Config struct { + DBPath string `mapstructure:"DBPath"` + GlobalExitRootAddr common.Address `mapstructure:"GlobalExitRootAddr"` + SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"` + BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` + URLRPCL1 string `mapstructure:"URLRPCL1"` + WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"` + InitialBlock uint64 `mapstructure:"InitialBlock"` +} + type L1InfoTreeSync struct { processor *processor driver *sync.EVMDriver @@ -32,11 +43,10 @@ func New( blockFinalityType etherman.BlockNumberFinality, rd sync.ReorgDetector, l1Client EthClienter, - treeHeight uint8, waitForNewBlocksPeriod time.Duration, initialBlock uint64, ) (*L1InfoTreeSync, error) { - processor, err := newProcessor(ctx, dbPath, treeHeight) + processor, err := newProcessor(ctx, dbPath) if err != nil { return nil, err } diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 8fea2d60..fcb8b028 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -21,6 +21,8 @@ const ( infoTable = "l1infotreesync-info" blockTable = "l1infotreesync-block" lastBlockTable = "l1infotreesync-lastBlock" + + treeHeight uint8 = 32 ) var ( @@ -88,7 +90,7 @@ func tableCfgFunc(defaultBuckets kv.TableCfg) kv.TableCfg { } } -func newProcessor(ctx context.Context, dbPath string, treeHeight uint8) (*processor, error) { +func newProcessor(ctx context.Context, dbPath string) (*processor, error) { db, err := mdbx.NewMDBX(nil). Path(dbPath). WithTableCfg(tableCfgFunc). diff --git a/l1infotreesync/processor_test.go b/l1infotreesync/processor_test.go index b6f6c942..01550f31 100644 --- a/l1infotreesync/processor_test.go +++ b/l1infotreesync/processor_test.go @@ -1,363 +1,3 @@ package l1infotreesync -// func TestProceessor(t *testing.T) { -// path := t.TempDir() -// ctx := context.Background() -// p, err := newProcessor(ctx, path, 32) -// require.NoError(t, err) -// actions := []processAction{ -// // processed: ~ -// &getLastProcessedBlockAction{ -// p: p, -// description: "on an empty processor", -// ctx: context.Background(), -// expectedLastProcessedBlock: 0, -// expectedErr: nil, -// }, -// &reorgAction{ -// p: p, -// description: "on an empty processor: firstReorgedBlock = 0", -// firstReorgedBlock: 0, -// expectedErr: nil, -// }, -// &reorgAction{ -// p: p, -// description: "on an empty processor: firstReorgedBlock = 1", -// firstReorgedBlock: 1, -// expectedErr: nil, -// }, -// &getClaimsAndBridgesAction{ -// p: p, -// description: "on an empty processor", -// ctx: context.Background(), -// fromBlock: 0, -// toBlock: 2, -// expectedEvents: nil, -// expectedErr: ErrBlockNotProcessed, -// }, -// &storeL1InfoTreeUpdatesAction{ -// p: p, -// description: "block1", -// b: block1, -// expectedErr: nil, -// }, -// // processed: block1 -// &getLastProcessedBlockAction{ -// p: p, -// description: "after block1", -// ctx: context.Background(), -// expectedLastProcessedBlock: 1, -// expectedErr: nil, -// }, -// &getClaimsAndBridgesAction{ -// p: p, -// description: "after block1: range 0, 2", -// ctx: context.Background(), -// fromBlock: 0, -// toBlock: 2, -// expectedEvents: nil, -// expectedErr: ErrBlockNotProcessed, -// }, -// &getClaimsAndBridgesAction{ -// p: p, -// description: "after block1: range 1, 1", -// ctx: context.Background(), -// fromBlock: 1, -// toBlock: 1, -// expectedEvents: block1.Events, -// expectedErr: nil, -// }, -// &reorgAction{ -// p: p, -// description: "after block1", -// firstReorgedBlock: 1, -// expectedErr: nil, -// }, -// // processed: ~ -// &getClaimsAndBridgesAction{ -// p: p, -// description: "after block1 reorged", -// ctx: context.Background(), -// fromBlock: 0, -// toBlock: 2, -// expectedEvents: nil, -// expectedErr: ErrBlockNotProcessed, -// }, -// &storeL1InfoTreeUpdatesAction{ -// p: p, -// description: "block1 (after it's reorged)", -// b: block1, -// expectedErr: nil, -// }, -// // processed: block3 -// &storeL1InfoTreeUpdatesAction{ -// p: p, -// description: "block3", -// b: block3, -// expectedErr: nil, -// }, -// // processed: block1, block3 -// &getLastProcessedBlockAction{ -// p: p, -// description: "after block3", -// ctx: context.Background(), -// expectedLastProcessedBlock: 3, -// expectedErr: nil, -// }, -// &getClaimsAndBridgesAction{ -// p: p, -// description: "after block3: range 2, 2", -// ctx: context.Background(), -// fromBlock: 2, -// toBlock: 2, -// expectedEvents: []L1InfoTreeUpdate{}, -// expectedErr: nil, -// }, -// &getClaimsAndBridgesAction{ -// p: p, -// description: "after block3: range 1, 3", -// ctx: context.Background(), -// fromBlock: 1, -// toBlock: 3, -// expectedEvents: append(block1.Events, block3.Events...), -// expectedErr: nil, -// }, -// &reorgAction{ -// p: p, -// description: "after block3, with value 3", -// firstReorgedBlock: 3, -// expectedErr: nil, -// }, -// // processed: block1 -// &getLastProcessedBlockAction{ -// p: p, -// description: "after block3 reorged", -// ctx: context.Background(), -// expectedLastProcessedBlock: 2, -// expectedErr: nil, -// }, -// &reorgAction{ -// p: p, -// description: "after block3, with value 2", -// firstReorgedBlock: 2, -// expectedErr: nil, -// }, -// &getLastProcessedBlockAction{ -// p: p, -// description: "after block2 reorged", -// ctx: context.Background(), -// expectedLastProcessedBlock: 1, -// expectedErr: nil, -// }, -// &storeL1InfoTreeUpdatesAction{ -// p: p, -// description: "block3 after reorg", -// b: block3, -// expectedErr: nil, -// }, -// // processed: block1, block3 -// &storeL1InfoTreeUpdatesAction{ -// p: p, -// description: "block4", -// b: block4, -// expectedErr: nil, -// }, -// // processed: block1, block3, block4 -// &storeL1InfoTreeUpdatesAction{ -// p: p, -// description: "block5", -// b: block5, -// expectedErr: nil, -// }, -// // processed: block1, block3, block4, block5 -// &getLastProcessedBlockAction{ -// p: p, -// description: "after block5", -// ctx: context.Background(), -// expectedLastProcessedBlock: 5, -// expectedErr: nil, -// }, -// &getClaimsAndBridgesAction{ -// p: p, -// description: "after block5: range 1, 3", -// ctx: context.Background(), -// fromBlock: 1, -// toBlock: 3, -// expectedEvents: append(block1.Events, block3.Events...), -// expectedErr: nil, -// }, -// &getClaimsAndBridgesAction{ -// p: p, -// description: "after block5: range 4, 5", -// ctx: context.Background(), -// fromBlock: 4, -// toBlock: 5, -// expectedEvents: append(block4.Events, block5.Events...), -// expectedErr: nil, -// }, -// &getClaimsAndBridgesAction{ -// p: p, -// description: "after block5: range 0, 5", -// ctx: context.Background(), -// fromBlock: 0, -// toBlock: 5, -// expectedEvents: slices.Concat( -// block1.Events, -// block3.Events, -// block4.Events, -// block5.Events, -// ), -// expectedErr: nil, -// }, -// } - -// for _, a := range actions { -// t.Run(fmt.Sprintf("%s: %s", a.method(), a.desc()), a.execute) -// } -// } - -// // BOILERPLATE - -// // blocks - -// var ( -// block1 = block{ -// blockHeader: blockHeader{ -// Num: 1, -// Hash: common.HexToHash("01"), -// }, -// Events: []L1InfoTreeUpdate{ -// {RollupExitRoot: common.HexToHash("01")}, -// {MainnetExitRoot: common.HexToHash("01")}, -// }, -// } -// block3 = block{ -// blockHeader: blockHeader{ -// Num: 3, -// Hash: common.HexToHash("02"), -// }, -// Events: []L1InfoTreeUpdate{ -// {RollupExitRoot: common.HexToHash("02"), MainnetExitRoot: common.HexToHash("02")}, -// }, -// } -// block4 = block{ -// blockHeader: blockHeader{ -// Num: 4, -// Hash: common.HexToHash("03"), -// }, -// Events: []L1InfoTreeUpdate{}, -// } -// block5 = block{ -// blockHeader: blockHeader{ -// Num: 5, -// Hash: common.HexToHash("04"), -// }, -// Events: []L1InfoTreeUpdate{ -// {RollupExitRoot: common.HexToHash("04")}, -// {MainnetExitRoot: common.HexToHash("05")}, -// }, -// } -// ) - -// // actions - -// type processAction interface { -// method() string -// desc() string -// execute(t *testing.T) -// } - -// // GetClaimsAndBridges - -// type getClaimsAndBridgesAction struct { -// p *processor -// description string -// ctx context.Context -// fromBlock uint64 -// toBlock uint64 -// expectedEvents []L1InfoTreeUpdate -// expectedErr error -// } - -// func (a *getClaimsAndBridgesAction) method() string { -// return "GetClaimsAndBridges" -// } - -// func (a *getClaimsAndBridgesAction) desc() string { -// return a.description -// } - -// func (a *getClaimsAndBridgesAction) execute(t *testing.T) { -// // TODO: add relevant getters -// // actualEvents, actualErr := a.p.GetClaimsAndBridges(a.ctx, a.fromBlock, a.toBlock) -// // require.Equal(t, a.expectedEvents, actualEvents) -// // require.Equal(t, a.expectedErr, actualErr) -// } - -// // getLastProcessedBlock - -// type getLastProcessedBlockAction struct { -// p *processor -// description string -// ctx context.Context -// expectedLastProcessedBlock uint64 -// expectedErr error -// } - -// func (a *getLastProcessedBlockAction) method() string { -// return "getLastProcessedBlock" -// } - -// func (a *getLastProcessedBlockAction) desc() string { -// return a.description -// } - -// func (a *getLastProcessedBlockAction) execute(t *testing.T) { -// actualLastProcessedBlock, actualErr := a.p.getLastProcessedBlock(a.ctx) -// require.Equal(t, a.expectedLastProcessedBlock, actualLastProcessedBlock) -// require.Equal(t, a.expectedErr, actualErr) -// } - -// // reorg - -// type reorgAction struct { -// p *processor -// description string -// firstReorgedBlock uint64 -// expectedErr error -// } - -// func (a *reorgAction) method() string { -// return "reorg" -// } - -// func (a *reorgAction) desc() string { -// return a.description -// } - -// func (a *reorgAction) execute(t *testing.T) { -// actualErr := a.p.reorg(a.firstReorgedBlock) -// require.Equal(t, a.expectedErr, actualErr) -// } - -// // storeL1InfoTreeUpdates - -// type storeL1InfoTreeUpdatesAction struct { -// p *processor -// description string -// b block -// expectedErr error -// } - -// func (a *storeL1InfoTreeUpdatesAction) method() string { -// return "storeL1InfoTreeUpdates" -// } - -// func (a *storeL1InfoTreeUpdatesAction) desc() string { -// return a.description -// } - -// func (a *storeL1InfoTreeUpdatesAction) execute(t *testing.T) { -// actualErr := a.p.processBlock(a.b) -// require.Equal(t, a.expectedErr, actualErr) -// } +// TODO: add unit test diff --git a/localbridgesync/e2e_test.go b/localbridgesync/e2e_test.go new file mode 100644 index 00000000..c84d8d33 --- /dev/null +++ b/localbridgesync/e2e_test.go @@ -0,0 +1,3 @@ +package localbridgesync + +// TODO: add E2E test, prolly need a mock contract diff --git a/reorgdetector/reorgdetector.go b/reorgdetector/reorgdetector.go index 4b5a963b..c0b0caa2 100644 --- a/reorgdetector/reorgdetector.go +++ b/reorgdetector/reorgdetector.go @@ -140,6 +140,10 @@ type ReorgDetector struct { waitPeriodBlockAdder time.Duration } +type Config struct { + DBPath string `mapstructure:"DBPath"` +} + // New creates a new instance of ReorgDetector func New(ctx context.Context, client EthClient, dbPath string) (*ReorgDetector, error) { db, err := mdbx.NewMDBX(nil). From 08bb5f265fbdd35bbceeb7b634d9e641dfc81746 Mon Sep 17 00:00:00 2001 From: Arnau Date: Fri, 26 Jul 2024 13:38:49 +0200 Subject: [PATCH 08/49] WIP --- go.sum | 586 --------------------------------------------------------- 1 file changed, 586 deletions(-) diff --git a/go.sum b/go.sum index e5d5f8f9..e69de29b 100644 --- a/go.sum +++ b/go.sum @@ -1,586 +0,0 @@ -github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884 h1:oXUct6UWuGs15WyCEKipY0Kc0BsCnMzniAz0EIFoPxs= -github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884/go.mod h1:mFlcEjsm2YBBsu8atHJ3zyVnwM+Z/fMXpVmIJge+WVU= -github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf h1:VWxVYeDhDURGp8pHR4kq3jwoXRazVHsQLMMIqYsO+Fw= -github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf/go.mod h1:3XkZ0zn0GsvAT01MPQMmukF534CVSFmtrcoK3F/BK6Q= -github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d h1:sxh6hZ2jF/sxxj2jd5o1vuNNCZjYmn4aRG9SRlVaEFs= -github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d/go.mod h1:2scWqMMufrQXu7TikDgQ3BsyaKoX8qP26D6E262vSOg= -github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-RC4 h1:+4K+xSzv0ImbK30B/T9FauNTrTFUmWcNKYhIgwsE4C4= -github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-RC4/go.mod h1:0QkAXcFa92mFJrCbN3UPUJGJYes851yEgYHLONnaosE= -github.com/0xPolygonHermez/zkevm-ethtx-manager v0.1.9 h1:vrAezzwTNke6NroDAltGh1k2AJ6ibmZPBsG0bCltbRc= -github.com/0xPolygonHermez/zkevm-ethtx-manager v0.1.9/go.mod h1:pRqfLQVM3nbzdhy3buqjAgcVyNDKAXOHqTSgkwiKpic= -github.com/0xPolygonHermez/zkevm-synchronizer-l1 v0.6.3-0.20240712085301-0310358abb59 h1:Qwh92vFEXnpmDggQaZA3648viEQfLdMnAw/WFSY+2i8= -github.com/0xPolygonHermez/zkevm-synchronizer-l1 v0.6.3-0.20240712085301-0310358abb59/go.mod h1:/LHf8jPQeBYKABM1xUmN1dKaFVIJc9jMQDSGBDJ7CS0= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= -github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= -github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= -github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= -github.com/VictoriaMetrics/metrics v1.23.1 h1:/j8DzeJBxSpL2qSIdqnRFLvQQhbJyJbbEi22yMm7oL0= -github.com/VictoriaMetrics/metrics v1.23.1/go.mod h1:rAr/llLpEnAdTehiNlUxKgnjcOuROSzpw0GvjpEbvFc= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= -github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= -github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= -github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= -github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b h1:6+ZFm0flnudZzdSE0JxlhR2hKnGPcNB35BjQf4RYQDY= -github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= -github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= -github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= -github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4= -github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= -github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= -github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= -github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= -github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= -github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= -github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= -github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= -github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= -github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= -github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= -github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= -github.com/didip/tollbooth/v6 v6.1.2 h1:Kdqxmqw9YTv0uKajBUiWQg+GURL/k4vy9gmLCL01PjQ= -github.com/didip/tollbooth/v6 v6.1.2/go.mod h1:xjcse6CTHCLuOkzsWrEgdy9WPJFv+p/x6v+MyfP+O9s= -github.com/erigontech/mdbx-go v0.27.14 h1:IVVeQVCAjZRpAR8bThlP2ISxrOwdV35NZdGwAgotaRw= -github.com/erigontech/mdbx-go v0.27.14/go.mod h1:FAMxbOgqOnRDx51j8HjuJZIgznbDwjX7LItd+/UWyA4= -github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= -github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.14.5 h1:szuFzO1MhJmweXjoM5nSAeDvjNUH3vIQoMzzQnfvjpw= -github.com/ethereum/go-ethereum v1.14.5/go.mod h1:VEDGGhSxY7IEjn98hJRFXl/uFvpRgbIIf2PpXiyGGgc= -github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4= -github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= -github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= -github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= -github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= -github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= -github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-pkgz/expirable-cache v0.0.3 h1:rTh6qNPp78z0bQE6HDhXBHUwqnV9i09Vm6dksJLXQDc= -github.com/go-pkgz/expirable-cache v0.0.3/go.mod h1:+IauqN00R2FqNRLCLA+X5YljQJrwB179PfiAoMPlTlQ= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= -github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= -github.com/hashicorp/hcl v1.0.1-0.20180906183839-65a6292f0157 h1:uyodBE3xDz0ynKs1tLBU26wOQoEkAqqiY18DbZ+FZrA= -github.com/hashicorp/hcl v1.0.1-0.20180906183839-65a6292f0157/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hermeznetwork/tracerr v0.3.2 h1:QB3TlQxO/4XHyixsg+nRZPuoel/FFQlQ7oAoHDD5l1c= -github.com/hermeznetwork/tracerr v0.3.2/go.mod h1:nsWC1+tc4qUEbUGRv4DcPJJTjLsedlPajlFmpJoohK4= -github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= -github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= -github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= -github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= -github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= -github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/iden3/go-iden3-crypto v0.0.16 h1:zN867xiz6HgErXVIV/6WyteGcOukE9gybYTorBMEdsk= -github.com/iden3/go-iden3-crypto v0.0.16/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBeOT/3UEhXsEsP3E= -github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= -github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= -github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= -github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= -github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= -github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= -github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/ledgerwatch/erigon-lib v1.0.0 h1:2o7EfgB/6CyjXAaQ8+Dh7AmY5rWvwSKg0kGp/U9kwqE= -github.com/ledgerwatch/erigon-lib v1.0.0/go.mod h1:l1i6+H9MgizD+ObQ5cXsfA9S3egYTOCnnYGjbrJMqR4= -github.com/ledgerwatch/log/v3 v3.9.0 h1:iDwrXe0PVwBC68Dd94YSsHbMgQ3ufsgjzXtFNFVZFRk= -github.com/ledgerwatch/log/v3 v3.9.0/go.mod h1:EiAY6upmI/6LkNhOVxb4eVsmsP11HZCnZ3PlJMjYiqE= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= -github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= -github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/miguelmota/go-solidity-sha3 v0.1.1 h1:3Y08sKZDtudtE5kbTBPC9RYJznoSYyWI9VD6mghU0CA= -github.com/miguelmota/go-solidity-sha3 v0.1.1/go.mod h1:sax1FvQF+f71j8W1uUHMZn8NxKyl5rYLks2nqj8RFEw= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= -github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= -github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= -github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= -github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= -github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= -github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/rubenv/sql-migrate v1.6.1 h1:bo6/sjsan9HaXAsNxYP/jCEDUGibHp8JmOBw7NTGRos= -github.com/rubenv/sql-migrate v1.6.1/go.mod h1:tPzespupJS0jacLfhbwto/UjSX+8h2FdWB7ar+QlHa0= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= -github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= -github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= -github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= -github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= -github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= -github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= -github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= -github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= -github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= -github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ= -github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= -github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= -github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= -rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= From 11ec3af1ecb98b04928e4645a2d38fabb5b77a19 Mon Sep 17 00:00:00 2001 From: bros Date: Fri, 26 Jul 2024 12:40:30 +0000 Subject: [PATCH 09/49] wip --- Makefile | 2 +- go.sum | 586 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 587 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d092a0f3..e081dcb7 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ else endif GOBASE := $(shell pwd) GOBIN := $(GOBASE)/dist -GOENVVARS := GOBIN=$(GOBIN) CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) +GOENVVARS := GOBIN=$(GOBIN) CGO_ENABLED=1 GOOS=linux GOARCH=$(ARCH) GOBINARY := cdk GOCMD := $(GOBASE)/cmd diff --git a/go.sum b/go.sum index e69de29b..e5d5f8f9 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,586 @@ +github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884 h1:oXUct6UWuGs15WyCEKipY0Kc0BsCnMzniAz0EIFoPxs= +github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884/go.mod h1:mFlcEjsm2YBBsu8atHJ3zyVnwM+Z/fMXpVmIJge+WVU= +github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf h1:VWxVYeDhDURGp8pHR4kq3jwoXRazVHsQLMMIqYsO+Fw= +github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf/go.mod h1:3XkZ0zn0GsvAT01MPQMmukF534CVSFmtrcoK3F/BK6Q= +github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d h1:sxh6hZ2jF/sxxj2jd5o1vuNNCZjYmn4aRG9SRlVaEFs= +github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d/go.mod h1:2scWqMMufrQXu7TikDgQ3BsyaKoX8qP26D6E262vSOg= +github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-RC4 h1:+4K+xSzv0ImbK30B/T9FauNTrTFUmWcNKYhIgwsE4C4= +github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-RC4/go.mod h1:0QkAXcFa92mFJrCbN3UPUJGJYes851yEgYHLONnaosE= +github.com/0xPolygonHermez/zkevm-ethtx-manager v0.1.9 h1:vrAezzwTNke6NroDAltGh1k2AJ6ibmZPBsG0bCltbRc= +github.com/0xPolygonHermez/zkevm-ethtx-manager v0.1.9/go.mod h1:pRqfLQVM3nbzdhy3buqjAgcVyNDKAXOHqTSgkwiKpic= +github.com/0xPolygonHermez/zkevm-synchronizer-l1 v0.6.3-0.20240712085301-0310358abb59 h1:Qwh92vFEXnpmDggQaZA3648viEQfLdMnAw/WFSY+2i8= +github.com/0xPolygonHermez/zkevm-synchronizer-l1 v0.6.3-0.20240712085301-0310358abb59/go.mod h1:/LHf8jPQeBYKABM1xUmN1dKaFVIJc9jMQDSGBDJ7CS0= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= +github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/VictoriaMetrics/metrics v1.23.1 h1:/j8DzeJBxSpL2qSIdqnRFLvQQhbJyJbbEi22yMm7oL0= +github.com/VictoriaMetrics/metrics v1.23.1/go.mod h1:rAr/llLpEnAdTehiNlUxKgnjcOuROSzpw0GvjpEbvFc= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= +github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b h1:6+ZFm0flnudZzdSE0JxlhR2hKnGPcNB35BjQf4RYQDY= +github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= +github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4= +github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= +github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/didip/tollbooth/v6 v6.1.2 h1:Kdqxmqw9YTv0uKajBUiWQg+GURL/k4vy9gmLCL01PjQ= +github.com/didip/tollbooth/v6 v6.1.2/go.mod h1:xjcse6CTHCLuOkzsWrEgdy9WPJFv+p/x6v+MyfP+O9s= +github.com/erigontech/mdbx-go v0.27.14 h1:IVVeQVCAjZRpAR8bThlP2ISxrOwdV35NZdGwAgotaRw= +github.com/erigontech/mdbx-go v0.27.14/go.mod h1:FAMxbOgqOnRDx51j8HjuJZIgznbDwjX7LItd+/UWyA4= +github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= +github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-ethereum v1.14.5 h1:szuFzO1MhJmweXjoM5nSAeDvjNUH3vIQoMzzQnfvjpw= +github.com/ethereum/go-ethereum v1.14.5/go.mod h1:VEDGGhSxY7IEjn98hJRFXl/uFvpRgbIIf2PpXiyGGgc= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= +github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= +github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= +github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= +github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-pkgz/expirable-cache v0.0.3 h1:rTh6qNPp78z0bQE6HDhXBHUwqnV9i09Vm6dksJLXQDc= +github.com/go-pkgz/expirable-cache v0.0.3/go.mod h1:+IauqN00R2FqNRLCLA+X5YljQJrwB179PfiAoMPlTlQ= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= +github.com/hashicorp/hcl v1.0.1-0.20180906183839-65a6292f0157 h1:uyodBE3xDz0ynKs1tLBU26wOQoEkAqqiY18DbZ+FZrA= +github.com/hashicorp/hcl v1.0.1-0.20180906183839-65a6292f0157/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hermeznetwork/tracerr v0.3.2 h1:QB3TlQxO/4XHyixsg+nRZPuoel/FFQlQ7oAoHDD5l1c= +github.com/hermeznetwork/tracerr v0.3.2/go.mod h1:nsWC1+tc4qUEbUGRv4DcPJJTjLsedlPajlFmpJoohK4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/iden3/go-iden3-crypto v0.0.16 h1:zN867xiz6HgErXVIV/6WyteGcOukE9gybYTorBMEdsk= +github.com/iden3/go-iden3-crypto v0.0.16/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBeOT/3UEhXsEsP3E= +github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= +github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/ledgerwatch/erigon-lib v1.0.0 h1:2o7EfgB/6CyjXAaQ8+Dh7AmY5rWvwSKg0kGp/U9kwqE= +github.com/ledgerwatch/erigon-lib v1.0.0/go.mod h1:l1i6+H9MgizD+ObQ5cXsfA9S3egYTOCnnYGjbrJMqR4= +github.com/ledgerwatch/log/v3 v3.9.0 h1:iDwrXe0PVwBC68Dd94YSsHbMgQ3ufsgjzXtFNFVZFRk= +github.com/ledgerwatch/log/v3 v3.9.0/go.mod h1:EiAY6upmI/6LkNhOVxb4eVsmsP11HZCnZ3PlJMjYiqE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= +github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/miguelmota/go-solidity-sha3 v0.1.1 h1:3Y08sKZDtudtE5kbTBPC9RYJznoSYyWI9VD6mghU0CA= +github.com/miguelmota/go-solidity-sha3 v0.1.1/go.mod h1:sax1FvQF+f71j8W1uUHMZn8NxKyl5rYLks2nqj8RFEw= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/rubenv/sql-migrate v1.6.1 h1:bo6/sjsan9HaXAsNxYP/jCEDUGibHp8JmOBw7NTGRos= +github.com/rubenv/sql-migrate v1.6.1/go.mod h1:tPzespupJS0jacLfhbwto/UjSX+8h2FdWB7ar+QlHa0= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= +github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= +github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= +github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= +github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= +github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= +github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= +github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ= +github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= +github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= From f6bf0ad71d5184b06322e4127db7dd2dd71c8b0b Mon Sep 17 00:00:00 2001 From: bros Date: Fri, 26 Jul 2024 12:55:06 +0000 Subject: [PATCH 10/49] WIP --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index ded6db29..d40eaa56 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22.4 require ( github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884 - github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf + github.com/0xPolygon/cdk-data-availability v0.0.8 github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-RC4 github.com/0xPolygonHermez/zkevm-ethtx-manager v0.1.9 diff --git a/go.sum b/go.sum index e5d5f8f9..41d4b0f0 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884 h1 github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884/go.mod h1:mFlcEjsm2YBBsu8atHJ3zyVnwM+Z/fMXpVmIJge+WVU= github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf h1:VWxVYeDhDURGp8pHR4kq3jwoXRazVHsQLMMIqYsO+Fw= github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf/go.mod h1:3XkZ0zn0GsvAT01MPQMmukF534CVSFmtrcoK3F/BK6Q= +github.com/0xPolygon/cdk-data-availability v0.0.8 h1:bMmOYZ7Ei683y80ric3KzMPXtRGmchAmfjIRzghaHb4= +github.com/0xPolygon/cdk-data-availability v0.0.8/go.mod h1:3XkZ0zn0GsvAT01MPQMmukF534CVSFmtrcoK3F/BK6Q= github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d h1:sxh6hZ2jF/sxxj2jd5o1vuNNCZjYmn4aRG9SRlVaEFs= github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d/go.mod h1:2scWqMMufrQXu7TikDgQ3BsyaKoX8qP26D6E262vSOg= github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-RC4 h1:+4K+xSzv0ImbK30B/T9FauNTrTFUmWcNKYhIgwsE4C4= From 26ce165f72f598317a1842595a4adfacc2e7d2b1 Mon Sep 17 00:00:00 2001 From: bros Date: Fri, 26 Jul 2024 13:18:50 +0000 Subject: [PATCH 11/49] start reorg detector --- Makefile | 2 +- cmd/run.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e081dcb7..d092a0f3 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ else endif GOBASE := $(shell pwd) GOBIN := $(GOBASE)/dist -GOENVVARS := GOBIN=$(GOBIN) CGO_ENABLED=1 GOOS=linux GOARCH=$(ARCH) +GOENVVARS := GOBIN=$(GOBIN) CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) GOBINARY := cdk GOCMD := $(GOBASE)/cmd diff --git a/cmd/run.go b/cmd/run.go index 57fc2e4d..ab97f089 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -76,6 +76,7 @@ func start(cliCtx *cli.Context) error { log.Fatal(err) } reorgDetector := newReorgDetectorL1(cliCtx.Context, *c, l1Client) + go reorgDetector.Start(cliCtx.Context) syncer := newL1InfoTreeSyncer(cliCtx.Context, *c, l1Client, reorgDetector) go syncer.Start(cliCtx.Context) aggOracle := createAggoracle(*c, l1Client, syncer) From 4354830742ebb5afb3687e892b5ca386d0879e37 Mon Sep 17 00:00:00 2001 From: bros Date: Fri, 26 Jul 2024 13:36:56 +0000 Subject: [PATCH 12/49] fix docker --- Dockerfile | 7 ++++--- Makefile | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0b509075..18224428 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,9 @@ # CONTAINER FOR BUILDING BINARY -FROM golang:1.22.4 AS build +FROM golang:1.22.5-alpine3.20 AS build WORKDIR $GOPATH/src/github.com/0xPolygon/cdk +RUN apk update && apk add --no-cache make build-base git # INSTALL DEPENDENCIES COPY go.mod go.sum /src/ RUN cd /src && go mod download @@ -12,9 +13,9 @@ COPY . /src RUN cd /src && make build # CONTAINER FOR RUNNING BINARY -FROM alpine:3.18.4 +FROM alpine:3.20 COPY --from=build /src/dist/cdk /app/cdk RUN mkdir /app/data && apk update && apk add postgresql15-client EXPOSE 8123 -CMD ["/bin/sh", "-c", "/app/cdk run"] +CMD ["/bin/sh", "-c", "/app/cdk run"] \ No newline at end of file diff --git a/Makefile b/Makefile index d092a0f3..e081dcb7 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ else endif GOBASE := $(shell pwd) GOBIN := $(GOBASE)/dist -GOENVVARS := GOBIN=$(GOBIN) CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) +GOENVVARS := GOBIN=$(GOBIN) CGO_ENABLED=1 GOOS=linux GOARCH=$(ARCH) GOBINARY := cdk GOCMD := $(GOBASE)/cmd From f66c3373f5b5b78e68b325ee5ee554ae722d9527 Mon Sep 17 00:00:00 2001 From: bros Date: Fri, 26 Jul 2024 15:36:05 +0000 Subject: [PATCH 13/49] pass test with docker --- aggoracle/config.go | 7 +++--- aggoracle/oracle.go | 6 +++++- cmd/run.go | 4 ++++ l1infotreesync/processor.go | 43 +++++++++++++++++-------------------- 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/aggoracle/config.go b/aggoracle/config.go index d78747da..2dd39403 100644 --- a/aggoracle/config.go +++ b/aggoracle/config.go @@ -16,9 +16,10 @@ var ( ) type Config struct { - TargetChainType TargetChainType `mapstructure:"TargetChainType"` - EVMSender chaingersender.EVMConfig `mapstructure:"EVMSender"` - URLRPCL1 string `mapstructure:"URLRPCL1"` + TargetChainType TargetChainType `mapstructure:"TargetChainType"` + URLRPCL1 string `mapstructure:"URLRPCL1"` + // TODO: BlockFinality doesnt work as per the jsonschema BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` WaitPeriodNextGER types.Duration `mapstructure:"WaitPeriodNextGER"` + EVMSender chaingersender.EVMConfig `mapstructure:"EVMSender"` } diff --git a/aggoracle/oracle.go b/aggoracle/oracle.go index 8f4b7a67..291950c5 100644 --- a/aggoracle/oracle.go +++ b/aggoracle/oracle.go @@ -64,7 +64,11 @@ func (a *AggOracle) Start(ctx context.Context) { case <-a.ticker.C: gerToInject, err := a.getLastFinalisedGER(ctx) if err != nil { - log.Error("error calling getLastFinalisedGER: ", err) + if err == l1infotreesync.ErrBlockNotProcessed || err == l1infotreesync.ErrNotFound { + log.Debugf("syncer is not ready: %v", err) + } else { + log.Error("error calling isGERAlreadyInjected: ", err) + } continue } if alreadyInjectd, err := a.chainSender.IsGERAlreadyInjected(gerToInject); err != nil { diff --git a/cmd/run.go b/cmd/run.go index ab97f089..4bb24fc4 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -208,6 +208,10 @@ func createAggoracle(cfg config.Config, l1Client *ethclient.Client, syncer *l1in Outputs: cfg.Log.Outputs, } ethTxManager, err := ethtxmanager.New(cfg.AggOracle.EVMSender.EthTxManager) + if err != nil { + log.Fatal(err) + } + go ethTxManager.Start() l2CLient, err := ethclient.Dial(cfg.AggOracle.EVMSender.URLRPCL2) if err != nil { log.Fatal(err) diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index fcb8b028..2e6116c4 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -8,6 +8,7 @@ import ( "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/l1infotree" + "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/sync" ethCommon "github.com/ethereum/go-ethereum/common" "github.com/ledgerwatch/erigon-lib/kv" @@ -186,7 +187,8 @@ func (p *processor) GetInfoByRoot(ctx context.Context, root ethCommon.Hash) (*L1 return p.getInfoByHashWithTx(tx, hash) } -// GetLatestInfoUntilBlock returns the most recent L1InfoTreeLeaf that occured before or at blockNum +// GetLatestInfoUntilBlock returns the most recent L1InfoTreeLeaf that occured before or at blockNum. +// If the blockNum has not been processed yet the error ErrBlockNotProcessed will be returned func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { tx, err := p.db.BeginRo(ctx) if err != nil { @@ -197,32 +199,26 @@ func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64 if lpb < blockNum { return nil, ErrBlockNotProcessed } - c, err := tx.Cursor(blockTable) + iter, err := tx.RangeDescend(blockTable, uint64ToBytes(blockNum), uint64ToBytes(0), 1) if err != nil { return nil, err } - defer c.Close() - for k, v, err := c.Seek(uint64ToBytes(blockNum)); k != nil; k, v, err = c.Prev() { - if err != nil { - return nil, err - } - if bytes2Uint64(k) > blockNum { - // Seek function returns matching key or greater, therefore - // we could bed ealing with a block number greater than expected - continue - } - blk := blockWithLeafs{} - if err := json.Unmarshal(v, &blk); err != nil { - return nil, err - } - hash, err := tx.GetOne(indexTable, common.Uint32ToBytes(blk.LastIndex-1)) - if err != nil { - return nil, err - } - return p.getInfoByHashWithTx(tx, hash) + if !iter.HasNext() { + return nil, ErrNotFound } - - return nil, ErrNotFound + _, v, err := iter.Next() + if err != nil { + return nil, err + } + blk := blockWithLeafs{} + if err := json.Unmarshal(v, &blk); err != nil { + return nil, err + } + hash, err := tx.GetOne(indexTable, common.Uint32ToBytes(blk.LastIndex-1)) + if err != nil { + return nil, err + } + return p.getInfoByHashWithTx(tx, hash) } func (p *processor) GetInfoByIndex(ctx context.Context, index uint32) (*L1InfoTreeLeaf, error) { @@ -424,6 +420,7 @@ func (p *processor) ProcessBlock(b sync.Block) error { tx.Rollback() return err } + log.Debugf("block %d processed with events: %+v", b.Num, b.Events) return tx.Commit() } From 698f2bdc740f45cf1f55a6baf489a741134d2b85 Mon Sep 17 00:00:00 2001 From: bros Date: Fri, 26 Jul 2024 15:44:35 +0000 Subject: [PATCH 14/49] add TODO --- l1infotreesync/l1infotreesync.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index 6c8390ef..ed9056bc 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -21,9 +21,10 @@ var ( ) type Config struct { - DBPath string `mapstructure:"DBPath"` - GlobalExitRootAddr common.Address `mapstructure:"GlobalExitRootAddr"` - SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"` + DBPath string `mapstructure:"DBPath"` + GlobalExitRootAddr common.Address `mapstructure:"GlobalExitRootAddr"` + SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"` + // TODO: BlockFinality doesnt work as per the jsonschema BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` URLRPCL1 string `mapstructure:"URLRPCL1"` WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"` From d88238bb0ef14032017cd3ca15ff5af23dce04bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= Date: Mon, 29 Jul 2024 12:01:50 +0200 Subject: [PATCH 15/49] go mod tidy --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index 41d4b0f0..6b5b219a 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884 h1:oXUct6UWuGs15WyCEKipY0Kc0BsCnMzniAz0EIFoPxs= github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884/go.mod h1:mFlcEjsm2YBBsu8atHJ3zyVnwM+Z/fMXpVmIJge+WVU= -github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf h1:VWxVYeDhDURGp8pHR4kq3jwoXRazVHsQLMMIqYsO+Fw= -github.com/0xPolygon/cdk-data-availability v0.0.8-0.20240712072318-72ae67613cbf/go.mod h1:3XkZ0zn0GsvAT01MPQMmukF534CVSFmtrcoK3F/BK6Q= github.com/0xPolygon/cdk-data-availability v0.0.8 h1:bMmOYZ7Ei683y80ric3KzMPXtRGmchAmfjIRzghaHb4= github.com/0xPolygon/cdk-data-availability v0.0.8/go.mod h1:3XkZ0zn0GsvAT01MPQMmukF534CVSFmtrcoK3F/BK6Q= github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d h1:sxh6hZ2jF/sxxj2jd5o1vuNNCZjYmn4aRG9SRlVaEFs= From fc3df5e9aaa16f11761f939f3dbff81036da2fd2 Mon Sep 17 00:00:00 2001 From: Arnau Date: Mon, 29 Jul 2024 18:44:00 +0200 Subject: [PATCH 16/49] Implement base addLeaf --- .../bridgesync.go | 66 +++++++++++++-- {localbridgesync => bridgesync}/downloader.go | 2 +- {localbridgesync => bridgesync}/e2e_test.go | 2 +- {localbridgesync => bridgesync}/processor.go | 44 +++++----- .../processor_test.go | 4 +- bridgesync/tree.go | 82 +++++++++++++++++++ 6 files changed, 169 insertions(+), 31 deletions(-) rename localbridgesync/localbridgesync.go => bridgesync/bridgesync.go (54%) rename {localbridgesync => bridgesync}/downloader.go (99%) rename {localbridgesync => bridgesync}/e2e_test.go (68%) rename {localbridgesync => bridgesync}/processor.go (80%) rename {localbridgesync => bridgesync}/processor_test.go (99%) create mode 100644 bridgesync/tree.go diff --git a/localbridgesync/localbridgesync.go b/bridgesync/bridgesync.go similarity index 54% rename from localbridgesync/localbridgesync.go rename to bridgesync/bridgesync.go index d0617584..2a95acb9 100644 --- a/localbridgesync/localbridgesync.go +++ b/bridgesync/bridgesync.go @@ -1,4 +1,4 @@ -package localbridgesync +package bridgesync import ( "context" @@ -10,7 +10,10 @@ import ( ) const ( - reorgDetectorID = "localbridgesync" + reorgDetectorIDL1 = "bridgesyncl1" + reorgDetectorIDL2 = "bridgesyncl2" + dbPrefixL1 = "bridgesyncl1" + dbPrefixL2 = "bridgesyncl2" downloadBufferSize = 1000 ) @@ -24,17 +27,66 @@ type LocalBridgeSync struct { driver *sync.EVMDriver } -func New( +func NewL1( ctx context.Context, dbPath string, bridge common.Address, syncBlockChunkSize uint64, blockFinalityType etherman.BlockNumberFinality, rd sync.ReorgDetector, - l2Client EthClienter, + ethClient EthClienter, initialBlock uint64, ) (*LocalBridgeSync, error) { - processor, err := newProcessor(dbPath) + return new( + ctx, + dbPath, + bridge, + syncBlockChunkSize, + blockFinalityType, + rd, + ethClient, + initialBlock, + dbPrefixL1, + reorgDetectorIDL1, + ) +} + +func NewL2( + ctx context.Context, + dbPath string, + bridge common.Address, + syncBlockChunkSize uint64, + blockFinalityType etherman.BlockNumberFinality, + rd sync.ReorgDetector, + ethClient EthClienter, + initialBlock uint64, +) (*LocalBridgeSync, error) { + return new( + ctx, + dbPath, + bridge, + syncBlockChunkSize, + blockFinalityType, + rd, + ethClient, + initialBlock, + dbPrefixL1, + reorgDetectorIDL1, + ) +} + +func new( + ctx context.Context, + dbPath string, + bridge common.Address, + syncBlockChunkSize uint64, + blockFinalityType etherman.BlockNumberFinality, + rd sync.ReorgDetector, + ethClient EthClienter, + initialBlock uint64, + dbPrefix, reorgDetectorID string, +) (*LocalBridgeSync, error) { + processor, err := newProcessor(dbPath, dbPrefix) if err != nil { return nil, err } @@ -51,12 +103,12 @@ func New( } } - appender, err := buildAppender(l2Client, bridge) + appender, err := buildAppender(ethClient, bridge) if err != nil { return nil, err } downloader, err := sync.NewEVMDownloader( - l2Client, + ethClient, syncBlockChunkSize, blockFinalityType, waitForNewBlocksPeriod, diff --git a/localbridgesync/downloader.go b/bridgesync/downloader.go similarity index 99% rename from localbridgesync/downloader.go rename to bridgesync/downloader.go index 5b6ab8f6..d2ef4e7c 100644 --- a/localbridgesync/downloader.go +++ b/bridgesync/downloader.go @@ -1,4 +1,4 @@ -package localbridgesync +package bridgesync import ( "fmt" diff --git a/localbridgesync/e2e_test.go b/bridgesync/e2e_test.go similarity index 68% rename from localbridgesync/e2e_test.go rename to bridgesync/e2e_test.go index c84d8d33..b7834fdf 100644 --- a/localbridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -1,3 +1,3 @@ -package localbridgesync +package bridgesync // TODO: add E2E test, prolly need a mock contract diff --git a/localbridgesync/processor.go b/bridgesync/processor.go similarity index 80% rename from localbridgesync/processor.go rename to bridgesync/processor.go index 4a45c663..30738d01 100644 --- a/localbridgesync/processor.go +++ b/bridgesync/processor.go @@ -1,4 +1,4 @@ -package localbridgesync +package bridgesync import ( "context" @@ -14,8 +14,8 @@ import ( ) const ( - eventsTable = "localbridgesync-events" - lastBlockTable = "localbridgesync-lastBlock" + eventsTableSufix = "-events" + lastBlockTableSufix = "-lastBlock" ) var ( @@ -48,26 +48,30 @@ type Event struct { } type processor struct { - db kv.RwDB + db kv.RwDB + eventsTable string + lastBlockTable string } -func tableCfgFunc(defaultBuckets kv.TableCfg) kv.TableCfg { - return kv.TableCfg{ - eventsTable: {}, - lastBlockTable: {}, - } -} - -func newProcessor(dbPath string) (*processor, error) { +func newProcessor(dbPath, dbPrefix string) (*processor, error) { + eventsTable := dbPrefix + eventsTableSufix + lastBlockTable := dbPrefix + lastBlockTableSufix db, err := mdbx.NewMDBX(nil). Path(dbPath). - WithTableCfg(tableCfgFunc). + WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { + return kv.TableCfg{ + eventsTable: {}, + lastBlockTable: {}, + } + }). Open() if err != nil { return nil, err } return &processor{ - db: db, + db: db, + eventsTable: eventsTable, + lastBlockTable: lastBlockTable, }, nil } @@ -90,7 +94,7 @@ func (p *processor) GetClaimsAndBridges( if lpb < toBlock { return nil, ErrBlockNotProcessed } - c, err := tx.Cursor(eventsTable) + c, err := tx.Cursor(p.eventsTable) if err != nil { return nil, err } @@ -124,7 +128,7 @@ func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { } func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { - if blockNumBytes, err := tx.GetOne(lastBlockTable, lastBlokcKey); err != nil { + if blockNumBytes, err := tx.GetOne(p.lastBlockTable, lastBlokcKey); err != nil { return 0, err } else if blockNumBytes == nil { return 0, nil @@ -138,7 +142,7 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { if err != nil { return err } - c, err := tx.Cursor(eventsTable) + c, err := tx.Cursor(p.eventsTable) if err != nil { return err } @@ -149,7 +153,7 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { tx.Rollback() return err } - if err := tx.Delete(eventsTable, k); err != nil { + if err := tx.Delete(p.eventsTable, k); err != nil { tx.Rollback() return err } @@ -176,7 +180,7 @@ func (p *processor) ProcessBlock(block sync.Block) error { tx.Rollback() return err } - if err := tx.Put(eventsTable, common.Uint64To2Bytes(block.Num), value); err != nil { + if err := tx.Put(p.eventsTable, common.Uint64To2Bytes(block.Num), value); err != nil { tx.Rollback() return err } @@ -190,5 +194,5 @@ func (p *processor) ProcessBlock(block sync.Block) error { func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error { blockNumBytes := common.Uint64To2Bytes(blockNum) - return tx.Put(lastBlockTable, lastBlokcKey, blockNumBytes) + return tx.Put(p.lastBlockTable, lastBlokcKey, blockNumBytes) } diff --git a/localbridgesync/processor_test.go b/bridgesync/processor_test.go similarity index 99% rename from localbridgesync/processor_test.go rename to bridgesync/processor_test.go index dbf0d74c..60bc6b19 100644 --- a/localbridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -1,4 +1,4 @@ -package localbridgesync +package bridgesync import ( "context" @@ -14,7 +14,7 @@ import ( func TestProceessor(t *testing.T) { path := t.TempDir() - p, err := newProcessor(path) + p, err := newProcessor(path, "foo") require.NoError(t, err) actions := []processAction{ // processed: ~ diff --git a/bridgesync/tree.go b/bridgesync/tree.go new file mode 100644 index 00000000..0dbf7d84 --- /dev/null +++ b/bridgesync/tree.go @@ -0,0 +1,82 @@ +package bridgesync + +import ( + "errors" + "fmt" + + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/kv" + "golang.org/x/crypto/sha3" +) + +type tree struct { + db kv.RwDB + lastIndex int64 + height uint8 + rightPathCache []common.Hash +} + +type treeNode struct { + left common.Hash + right common.Hash +} + +func (n *treeNode) hash() common.Hash { + var hash common.Hash + hasher := sha3.NewLegacyKeccak256() + hasher.Write(n.left[:]) + hasher.Write(n.right[:]) + copy(hash[:], hasher.Sum(nil)) + return hash +} + +func newTree() (*tree, error) { + // TODO: init lastIndex & rightPathCache + return &tree{}, errors.New("not implemented") +} + +func (t *tree) addLeaf(index uint, hash common.Hash) error { + if int64(index) != t.lastIndex+1 { + return fmt.Errorf("mismatched index. Expected: %d, actual: %d", t.lastIndex+1, index) + } + + currentChildHash := hash + leftIsFilled := true + newNodes := []treeNode{} + for h := uint8(0); h < t.height; h++ { + var parent treeNode + if index&(1< 0 { + // Add child to the right + var child common.Hash + copy(child[:], currentChildHash[:]) + parent = treeNode{ + left: t.rightPathCache[h], + right: child, + } + } else { + // Add child to the left + if leftIsFilled { + // if at this level the left is filled, it means that the new node will be in the right path + copy(t.rightPathCache[h][:], currentChildHash[:]) + leftIsFilled = false + } + var child common.Hash + copy(child[:], currentChildHash[:]) + parent = treeNode{ + left: child, + right: common.Hash{}, + } + } + currentChildHash = parent.hash() + newNodes = append(newNodes, parent) + } + + // store root + root := currentChildHash + // store nodes + + t.lastIndex++ + return nil +} + +// TODO: handle rerog: lastIndex & rightPathCache From abf8be83afd9de2a9be350da6adbe86b76be343f Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 30 Jul 2024 14:59:43 +0200 Subject: [PATCH 17/49] pass test vectors --- bridgesync/bridgesync.go | 4 +- bridgesync/processor.go | 116 ++++++-- bridgesync/processor_test.go | 43 ++- bridgesync/testvectors/claim-vectors.json | 306 ++++++++++++++++++++++ bridgesync/testvectors/leaf-vectors.json | 38 +++ bridgesync/testvectors/root-vectors.json | 67 +++++ bridgesync/tree.go | 292 +++++++++++++++++++-- bridgesync/tree_test.go | 141 ++++++++++ 8 files changed, 960 insertions(+), 47 deletions(-) create mode 100644 bridgesync/testvectors/claim-vectors.json create mode 100644 bridgesync/testvectors/leaf-vectors.json create mode 100644 bridgesync/testvectors/root-vectors.json create mode 100644 bridgesync/tree_test.go diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index 2a95acb9..c9dceb77 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -86,7 +86,7 @@ func new( initialBlock uint64, dbPrefix, reorgDetectorID string, ) (*LocalBridgeSync, error) { - processor, err := newProcessor(dbPath, dbPrefix) + processor, err := newProcessor(ctx, dbPath, dbPrefix) if err != nil { return nil, err } @@ -132,3 +132,5 @@ func new( func (s *LocalBridgeSync) Start(ctx context.Context) { s.driver.Sync(ctx) } + +// TODO: expose methods from the processor for consumers diff --git a/bridgesync/processor.go b/bridgesync/processor.go index 30738d01..49c1c1b4 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -2,13 +2,16 @@ package bridgesync import ( "context" + "encoding/binary" "encoding/json" "errors" + "log" "math/big" - "github.com/0xPolygon/cdk/common" + dbCommon "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/sync" - ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common" + "github.com/iden3/go-iden3-crypto/keccak256" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/kv/mdbx" ) @@ -16,29 +19,56 @@ import ( const ( eventsTableSufix = "-events" lastBlockTableSufix = "-lastBlock" + rootTableSufix = "-root" + rhtTableSufix = "-rht" ) var ( ErrBlockNotProcessed = errors.New("given block(s) have not been processed yet") + ErrNotFound = errors.New("not found") lastBlokcKey = []byte("lb") ) type Bridge struct { LeafType uint8 OriginNetwork uint32 - OriginAddress ethCommon.Address + OriginAddress common.Address DestinationNetwork uint32 - DestinationAddress ethCommon.Address + DestinationAddress common.Address Amount *big.Int Metadata []byte DepositCount uint32 } +func (b *Bridge) Hash() common.Hash { + origNet := make([]byte, 4) //nolint:gomnd + binary.BigEndian.PutUint32(origNet, uint32(b.OriginNetwork)) + destNet := make([]byte, 4) //nolint:gomnd + binary.BigEndian.PutUint32(destNet, uint32(b.DestinationNetwork)) + + metaHash := keccak256.Hash(b.Metadata) + hash := common.Hash{} + var buf [32]byte //nolint:gomnd + copy( + hash[:], + keccak256.Hash( + []byte{b.LeafType}, + origNet, + b.OriginAddress[:], + destNet, + b.DestinationAddress[:], + b.Amount.FillBytes(buf[:]), + metaHash, + ), + ) + return hash +} + type Claim struct { GlobalIndex *big.Int OriginNetwork uint32 - OriginAddress ethCommon.Address - DestinationAddress ethCommon.Address + OriginAddress common.Address + DestinationAddress common.Address Amount *big.Int } @@ -51,27 +81,38 @@ type processor struct { db kv.RwDB eventsTable string lastBlockTable string + tree *tree } -func newProcessor(dbPath, dbPrefix string) (*processor, error) { +func newProcessor(ctx context.Context, dbPath, dbPrefix string) (*processor, error) { eventsTable := dbPrefix + eventsTableSufix lastBlockTable := dbPrefix + lastBlockTableSufix + rootTable := dbPrefix + rootTableSufix + rhtTable := dbPrefix + rhtTableSufix db, err := mdbx.NewMDBX(nil). Path(dbPath). WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { return kv.TableCfg{ eventsTable: {}, lastBlockTable: {}, + rootTable: {}, + rhtTable: {}, } }). Open() if err != nil { return nil, err } + + tree, err := newTree(ctx, rhtTable, rootTable, db) + if err != nil { + return nil, err + } return &processor{ db: db, eventsTable: eventsTable, lastBlockTable: lastBlockTable, + tree: tree, }, nil } @@ -100,11 +141,11 @@ func (p *processor) GetClaimsAndBridges( } defer c.Close() - for k, v, err := c.Seek(common.Uint64To2Bytes(fromBlock)); k != nil; k, v, err = c.Next() { + for k, v, err := c.Seek(dbCommon.Uint64To2Bytes(fromBlock)); k != nil; k, v, err = c.Next() { if err != nil { return nil, err } - if common.BytesToUint64(k) > toBlock { + if dbCommon.BytesToUint64(k) > toBlock { break } blockEvents := []Event{} @@ -133,7 +174,7 @@ func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { } else if blockNumBytes == nil { return 0, nil } else { - return common.BytesToUint64(blockNumBytes), nil + return dbCommon.BytesToUint64(blockNumBytes), nil } } @@ -147,8 +188,9 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { return err } defer c.Close() - firstKey := common.Uint64To2Bytes(firstReorgedBlock) - for k, _, err := c.Seek(firstKey); k != nil; k, _, err = c.Next() { + firstKey := dbCommon.Uint64To2Bytes(firstReorgedBlock) + firstDepositCountReorged := int64(-1) + for k, v, err := c.Seek(firstKey); k != nil; k, _, err = c.Next() { if err != nil { tx.Rollback() return err @@ -157,42 +199,84 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { tx.Rollback() return err } + if firstDepositCountReorged == -1 { + events := []Event{} + if err := json.Unmarshal(v, &events); err != nil { + tx.Rollback() + return err + } + for _, event := range events { + if event.Bridge != nil { + firstDepositCountReorged = int64(event.Bridge.DepositCount) + break + } + } + } } if err := p.updateLastProcessedBlock(tx, firstReorgedBlock-1); err != nil { tx.Rollback() return err } + if firstDepositCountReorged != -1 { + lastValidDepositCount := uint32(firstDepositCountReorged) - 1 + if err := p.tree.reorg(tx, lastValidDepositCount); err != nil { + tx.Rollback() + return err + } + } return tx.Commit() } func (p *processor) ProcessBlock(block sync.Block) error { - tx, err := p.db.BeginRw(context.Background()) + ctx := context.Background() + tx, err := p.db.BeginRw(ctx) if err != nil { return err } + bridges := []Bridge{} if len(block.Events) > 0 { events := []Event{} for _, e := range block.Events { - events = append(events, e.(Event)) + event := e.(Event) + events = append(events, event) + if event.Bridge != nil { + bridges = append(bridges, *event.Bridge) + } } value, err := json.Marshal(events) if err != nil { tx.Rollback() return err } - if err := tx.Put(p.eventsTable, common.Uint64To2Bytes(block.Num), value); err != nil { + if err := tx.Put(p.eventsTable, dbCommon.Uint64To2Bytes(block.Num), value); err != nil { tx.Rollback() return err } } + if err := p.updateLastProcessedBlock(tx, block.Num); err != nil { tx.Rollback() return err } + + for i, bridge := range bridges { + if err := p.tree.addLeaf(tx, bridge.DepositCount, bridge.Hash()); err != nil { + if i != 0 { + tx.Rollback() + if err2 := p.tree.initLastLeftCacheAndLastDepositCount(ctx); err2 != nil { + log.Fatalf( + "after failing to add a leaf to the tree with error: %v, error initializing the cache with error: %v", + err, err2, + ) + } + return err + } + } + } return tx.Commit() } func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error { - blockNumBytes := common.Uint64To2Bytes(blockNum) + blockNumBytes := dbCommon.Uint64To2Bytes(blockNum) return tx.Put(p.lastBlockTable, lastBlokcKey, blockNumBytes) } diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index 60bc6b19..24e65e5c 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -2,8 +2,10 @@ package bridgesync import ( "context" + "encoding/json" "fmt" "math/big" + "os" "slices" "testing" @@ -14,7 +16,7 @@ import ( func TestProceessor(t *testing.T) { path := t.TempDir() - p, err := newProcessor(path, "foo") + p, err := newProcessor(context.Background(), path, "foo") require.NoError(t, err) actions := []processAction{ // processed: ~ @@ -422,3 +424,42 @@ func eventsToBridgeEvents(events []interface{}) []Event { } return bridgeEvents } + +// DepositVectorRaw represents the deposit vector +type DepositVectorRaw struct { + OriginalNetwork uint32 `json:"originNetwork"` + TokenAddress string `json:"tokenAddress"` + Amount string `json:"amount"` + DestinationNetwork uint32 `json:"destinationNetwork"` + DestinationAddress string `json:"destinationAddress"` + ExpectedHash string `json:"leafValue"` + CurrentHash string `json:"currentLeafValue"` + Metadata string `json:"metadata"` +} + +func TestHashBridge(t *testing.T) { + data, err := os.ReadFile("testvectors/leaf-vectors.json") + require.NoError(t, err) + + var leafVectors []DepositVectorRaw + err = json.Unmarshal(data, &leafVectors) + require.NoError(t, err) + + for ti, testVector := range leafVectors { + t.Run(fmt.Sprintf("Test vector %d", ti), func(t *testing.T) { + amount, err := big.NewInt(0).SetString(testVector.Amount, 0) + require.True(t, err) + + bridge := Bridge{ + OriginNetwork: testVector.OriginalNetwork, + OriginAddress: common.HexToAddress(testVector.TokenAddress), + Amount: amount, + DestinationNetwork: testVector.DestinationNetwork, + DestinationAddress: common.HexToAddress(testVector.DestinationAddress), + DepositCount: uint32(ti + 1), + Metadata: common.FromHex(testVector.Metadata), + } + require.Equal(t, common.HexToHash(testVector.ExpectedHash), bridge.Hash()) + }) + } +} diff --git a/bridgesync/testvectors/claim-vectors.json b/bridgesync/testvectors/claim-vectors.json new file mode 100644 index 00000000..4778e9e4 --- /dev/null +++ b/bridgesync/testvectors/claim-vectors.json @@ -0,0 +1,306 @@ +[ + { + "leafs": [ + { + "originNetwork": 0, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x", + "leafValue": "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5" + }, + { + "originNetwork": 1, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 0, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345670", + "leafValue": "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f" + }, + { + "originNetwork": 0, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345678", + "leafValue": "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d" + }, + { + "originNetwork": 10, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x01", + "destinationNetwork": 4, + "destinationAddress": "0x0000000000000000000000000000000000000000", + "metadata": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922661234e51aad88F6F4ce6aB8827279cffFb92266", + "leafValue": "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242" + } + ], + "index": 0, + "proof": [ + "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f", + "0xb48c8301099f75206bc93b1512c7b3855b60b4f8cbaedf8679a184d1d450a4f1", + "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", + "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", + "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", + "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", + "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", + "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", + "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", + "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", + "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", + "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", + "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", + "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", + "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", + "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", + "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", + "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", + "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", + "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", + "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", + "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", + "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", + "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", + "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", + "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", + "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", + "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", + "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", + "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", + "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", + "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" + ], + "root": "0x42d3339fe8eb57770953423f20a029e778a707e8d58aaf110b40d5eb4dd25721" + }, + { + "leafs": [ + { + "originNetwork": 0, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x", + "leafValue": "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5" + }, + { + "originNetwork": 1, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 0, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345670", + "leafValue": "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f" + }, + { + "originNetwork": 0, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345678", + "leafValue": "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d" + }, + { + "originNetwork": 10, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x01", + "destinationNetwork": 4, + "destinationAddress": "0x0000000000000000000000000000000000000000", + "metadata": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922661234e51aad88F6F4ce6aB8827279cffFb92266", + "leafValue": "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242" + } + ], + "index": 1, + "proof": [ + "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5", + "0xb48c8301099f75206bc93b1512c7b3855b60b4f8cbaedf8679a184d1d450a4f1", + "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", + "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", + "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", + "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", + "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", + "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", + "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", + "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", + "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", + "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", + "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", + "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", + "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", + "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", + "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", + "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", + "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", + "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", + "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", + "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", + "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", + "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", + "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", + "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", + "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", + "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", + "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", + "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", + "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", + "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" + ], + "root": "0x42d3339fe8eb57770953423f20a029e778a707e8d58aaf110b40d5eb4dd25721" + }, + { + "leafs": [ + { + "originNetwork": 0, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x", + "leafValue": "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5" + }, + { + "originNetwork": 1, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 0, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345670", + "leafValue": "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f" + }, + { + "originNetwork": 0, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345678", + "leafValue": "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d" + }, + { + "originNetwork": 10, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x01", + "destinationNetwork": 4, + "destinationAddress": "0x0000000000000000000000000000000000000000", + "metadata": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922661234e51aad88F6F4ce6aB8827279cffFb92266", + "leafValue": "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242" + } + ], + "index": 2, + "proof": [ + "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242", + "0x653142d4a4d6f7985a3f33cad31e011dbee8909846b34c38c7b235ca08828521", + "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", + "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", + "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", + "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", + "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", + "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", + "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", + "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", + "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", + "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", + "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", + "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", + "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", + "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", + "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", + "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", + "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", + "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", + "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", + "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", + "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", + "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", + "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", + "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", + "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", + "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", + "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", + "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", + "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", + "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" + ], + "root": "0x42d3339fe8eb57770953423f20a029e778a707e8d58aaf110b40d5eb4dd25721" + }, + { + "leafs": [ + { + "originNetwork": 0, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x", + "leafValue": "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5" + }, + { + "originNetwork": 1, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 0, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345670", + "leafValue": "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f" + }, + { + "originNetwork": 0, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345678", + "leafValue": "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d" + }, + { + "originNetwork": 10, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x01", + "destinationNetwork": 4, + "destinationAddress": "0x0000000000000000000000000000000000000000", + "metadata": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922661234e51aad88F6F4ce6aB8827279cffFb92266", + "leafValue": "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242" + } + ], + "index": 3, + "proof": [ + "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d", + "0x653142d4a4d6f7985a3f33cad31e011dbee8909846b34c38c7b235ca08828521", + "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", + "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", + "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", + "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", + "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", + "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", + "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", + "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", + "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", + "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", + "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", + "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", + "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", + "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", + "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", + "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", + "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", + "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", + "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", + "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", + "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", + "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", + "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", + "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", + "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", + "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", + "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", + "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", + "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", + "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" + ], + "root": "0x42d3339fe8eb57770953423f20a029e778a707e8d58aaf110b40d5eb4dd25721" + } +] \ No newline at end of file diff --git a/bridgesync/testvectors/leaf-vectors.json b/bridgesync/testvectors/leaf-vectors.json new file mode 100644 index 00000000..a0be0895 --- /dev/null +++ b/bridgesync/testvectors/leaf-vectors.json @@ -0,0 +1,38 @@ +[ + { + "originNetwork": 0, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x", + "leafValue": "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5" + }, + { + "originNetwork": 1, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 0, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345670", + "leafValue": "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f" + }, + { + "originNetwork": 0, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345678", + "leafValue": "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d" + }, + { + "originNetwork": 10, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x01", + "destinationNetwork": 4, + "destinationAddress": "0x0000000000000000000000000000000000000000", + "metadata": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922661234e51aad88F6F4ce6aB8827279cffFb92266", + "leafValue": "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242" + } +] \ No newline at end of file diff --git a/bridgesync/testvectors/root-vectors.json b/bridgesync/testvectors/root-vectors.json new file mode 100644 index 00000000..b1c6929c --- /dev/null +++ b/bridgesync/testvectors/root-vectors.json @@ -0,0 +1,67 @@ +[ + { + "previousLeafsValues": [], + "currentRoot": "0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757", + "newLeaf": { + "originNetwork": 0, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "currentLeafValue": "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5", + "metadata": "0x" + }, + "newRoot": "0xbf7ddbb59aa018a4c74e061f5172973ff09e4cb7f58405af117fc521f1ca46aa" + }, + { + "previousLeafsValues": [ + "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5" + ], + "currentRoot": "0xbf7ddbb59aa018a4c74e061f5172973ff09e4cb7f58405af117fc521f1ca46aa", + "newLeaf": { + "originNetwork": 1, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 0, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "currentLeafValue": "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f", + "metadata": "0x12345670" + }, + "newRoot": "0xa7042a3ce14f384bbff63f1cee6ee5579193c2d7002e0034854963322cda6128" + }, + { + "previousLeafsValues": [ + "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5", + "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f" + ], + "currentRoot": "0xa7042a3ce14f384bbff63f1cee6ee5579193c2d7002e0034854963322cda6128", + "newLeaf": { + "originNetwork": 0, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "currentLeafValue": "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d", + "metadata": "0x12345678" + }, + "newRoot": "0x88e652896cb1de5962a0173a222059f51e6b943a2ba6dfc9acbff051ceb1abb5" + }, + { + "previousLeafsValues": [ + "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5", + "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f", + "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d" + ], + "currentRoot": "0x88e652896cb1de5962a0173a222059f51e6b943a2ba6dfc9acbff051ceb1abb5", + "newLeaf": { + "originNetwork": 10, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x01", + "destinationNetwork": 4, + "destinationAddress": "0x0000000000000000000000000000000000000000", + "currentLeafValue": "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242", + "metadata": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922661234e51aad88F6F4ce6aB8827279cffFb92266" + }, + "newRoot": "0x42d3339fe8eb57770953423f20a029e778a707e8d58aaf110b40d5eb4dd25721" + } +] \ No newline at end of file diff --git a/bridgesync/tree.go b/bridgesync/tree.go index 0dbf7d84..0e6e9620 100644 --- a/bridgesync/tree.go +++ b/bridgesync/tree.go @@ -1,19 +1,28 @@ package bridgesync import ( - "errors" + "context" "fmt" + "math" - "github.com/ledgerwatch/erigon-lib/common" + dbCommon "github.com/0xPolygon/cdk/common" + "github.com/ethereum/go-ethereum/common" "github.com/ledgerwatch/erigon-lib/kv" "golang.org/x/crypto/sha3" ) +const ( + defaultHeight uint8 = 32 +) + type tree struct { - db kv.RwDB - lastIndex int64 - height uint8 - rightPathCache []common.Hash + db kv.RwDB + rhtTable string + rootTable string + height uint8 + lastDepositCount int64 + lastLeftCache []common.Hash + zeroHashes []common.Hash } type treeNode struct { @@ -30,42 +39,122 @@ func (n *treeNode) hash() common.Hash { return hash } -func newTree() (*tree, error) { - // TODO: init lastIndex & rightPathCache - return &tree{}, errors.New("not implemented") +func (n *treeNode) MarshalBinary() ([]byte, error) { + return append(n.left[:], n.right[:]...), nil +} + +func (n *treeNode) UnmarshalBinary(data []byte) error { + if len(data) != 64 { + return fmt.Errorf("expected len %d, actual len %d", 64, len(data)) + } + n.left = common.Hash(data[:32]) + n.right = common.Hash(data[32:]) + return nil } -func (t *tree) addLeaf(index uint, hash common.Hash) error { - if int64(index) != t.lastIndex+1 { - return fmt.Errorf("mismatched index. Expected: %d, actual: %d", t.lastIndex+1, index) +func newTree(ctx context.Context, rhtTable, rootTable string, db kv.RwDB) (*tree, error) { + t := &tree{ + rhtTable: rhtTable, + rootTable: rootTable, + db: db, + height: defaultHeight, + zeroHashes: generateZeroHashes(defaultHeight), + } + + if err := t.initLastLeftCacheAndLastDepositCount(ctx); err != nil { + return nil, err + } + + return t, nil +} + +// getProof returns the merkle proof for a given deposit count and root. +func (t *tree) getProof(ctx context.Context, depositCount uint32, root common.Hash) ([]common.Hash, error) { + tx, err := t.db.BeginRw(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + siblings := make([]common.Hash, int(t.height)) + + currentNodeHash := root + // It starts in height-1 because 0 is the level of the leafs + for h := int(t.height - 1); h >= 0; h-- { + currentNode, err := t.getRHTNode(tx, currentNodeHash) + if err != nil { + return nil, fmt.Errorf( + "height: %d, currentNode: %s, error: %v", + h, currentNodeHash.Hex(), err, + ) + } + /* + * Root (level h=3 => height=4) + * / \ + * O5 O6 (level h=2) + * / \ / \ + * O1 O2 O3 O4 (level h=1) + * /\ /\ /\ /\ + * 0 1 2 3 4 5 6 7 Leafs (level h=0) + * Example 1: + * Choose index = 3 => 011 binary + * Assuming we are in level 1 => h=1; 1< 011&010=010 which is higher than 0 so we need the left sibling (O1) + * Example 2: + * Choose index = 4 => 100 binary + * Assuming we are in level 1 => h=1; 1< 100&010=000 which is not higher than 0 so we need the right sibling (O4) + * Example 3: + * Choose index = 4 => 100 binary + * Assuming we are in level 2 => h=2; 1< 100&100=100 which is higher than 0 so we need the left sibling (O5) + */ + if depositCount&(1< 0 { + siblings = append(siblings, currentNode.left) + currentNodeHash = currentNode.right + } else { + siblings = append(siblings, currentNode.right) + currentNodeHash = currentNode.left + } + } + + // Reverse siblings to go from leafs to root + for i, j := 0, len(siblings)-1; i < j; i, j = i+1, j-1 { + siblings[i], siblings[j] = siblings[j], siblings[i] } + return siblings, nil +} + +func (t *tree) addLeaf(tx kv.RwTx, depositCount uint32, hash common.Hash) error { + // Sanity check + if int64(depositCount) != t.lastDepositCount+1 { + return fmt.Errorf( + "mismatched index. Expected: %d, actual: %d", + t.lastDepositCount+1, depositCount, + ) + } + + // Calculate new tree nodes currentChildHash := hash - leftIsFilled := true newNodes := []treeNode{} for h := uint8(0); h < t.height; h++ { var parent treeNode - if index&(1< 0 { + if depositCount&(1< 0 { // Add child to the right - var child common.Hash - copy(child[:], currentChildHash[:]) parent = treeNode{ - left: t.rightPathCache[h], - right: child, + left: t.lastLeftCache[h], + right: currentChildHash, } } else { // Add child to the left - if leftIsFilled { - // if at this level the left is filled, it means that the new node will be in the right path - copy(t.rightPathCache[h][:], currentChildHash[:]) - leftIsFilled = false - } - var child common.Hash - copy(child[:], currentChildHash[:]) parent = treeNode{ - left: child, - right: common.Hash{}, + left: currentChildHash, + right: t.zeroHashes[h], } + // Update cache + // TODO: review this part of the logic, skipping ?optimizaton? + // from OG implementation + t.lastLeftCache[h] = currentChildHash } currentChildHash = parent.hash() newNodes = append(newNodes, parent) @@ -73,10 +162,155 @@ func (t *tree) addLeaf(index uint, hash common.Hash) error { // store root root := currentChildHash + if err := tx.Put(t.rootTable, dbCommon.Uint32ToBytes(depositCount), root[:]); err != nil { + return err + } + // store nodes + for _, node := range newNodes { + value, err := node.MarshalBinary() + if err != nil { + return err + } + if err := tx.Put(t.rhtTable, node.hash().Bytes(), value); err != nil { + return err + } + } - t.lastIndex++ + t.lastDepositCount++ return nil } -// TODO: handle rerog: lastIndex & rightPathCache +func (t *tree) initLastLeftCacheAndLastDepositCount(ctx context.Context) error { + tx, err := t.db.BeginRw(ctx) + if err != nil { + return err + } + defer tx.Rollback() + + root, err := t.initLastDepositCount(tx) + if err != nil { + return err + } + return t.initLastLeftCache(tx, t.lastDepositCount, root) +} + +// getLastDepositCountAndRoot return the deposit count and the root associated to the last deposit. +// If deposit count == -1, it means no deposit added yet +func (t *tree) getLastDepositCountAndRoot(tx kv.Tx) (int64, common.Hash, error) { + iter, err := tx.RangeDescend( + t.rootTable, + dbCommon.Uint32ToBytes(math.MaxUint32), + dbCommon.Uint32ToBytes(0), + 1, + ) + if err != nil { + return 0, common.Hash{}, err + } + + lastDepositCountBytes, rootBytes, err := iter.Next() + if err != nil { + return 0, common.Hash{}, err + } + if lastDepositCountBytes == nil { + return -1, common.Hash{}, nil + } + return int64(dbCommon.BytesToUint32(lastDepositCountBytes)), common.Hash(rootBytes), nil +} + +func (t *tree) initLastDepositCount(tx kv.Tx) (common.Hash, error) { + ldc, root, err := t.getLastDepositCountAndRoot(tx) + if err != nil { + return common.Hash{}, err + } + t.lastDepositCount = ldc + return root, nil +} + +func (t *tree) initLastLeftCache(tx kv.Tx, lastDepositCount int64, lastRoot common.Hash) error { + siblings := make([]common.Hash, t.height, t.height) + if lastDepositCount == -1 { + t.lastLeftCache = siblings + return nil + } + index := t.lastDepositCount + + currentNodeHash := lastRoot + // It starts in height-1 because 0 is the level of the leafs + for h := int(t.height - 1); h >= 0; h-- { + currentNode, err := t.getRHTNode(tx, currentNodeHash) + if err != nil { + return fmt.Errorf( + "error getting node %s from the RHT at height %d with root %s: %v", + currentNodeHash.Hex(), h, lastRoot.Hex(), err, + ) + } + siblings = append(siblings, currentNode.left) + if index&(1< 0 { + currentNodeHash = currentNode.right + } else { + currentNodeHash = currentNode.left + } + } + + // Reverse the siblings to go from leafs to root + for i, j := 0, len(siblings)-1; i < j; i, j = i+1, j-1 { + siblings[i], siblings[j] = siblings[j], siblings[i] + } + + t.lastLeftCache = siblings + return nil +} + +func (t *tree) getRHTNode(tx kv.Tx, nodeHash common.Hash) (*treeNode, error) { + nodeBytes, err := tx.GetOne(t.rhtTable, nodeHash[:]) + if err != nil { + return nil, err + } + if nodeBytes == nil { + return nil, ErrNotFound + } + node := &treeNode{} + err = node.UnmarshalBinary(nodeBytes) + return node, err +} + +func (t *tree) reorg(tx kv.RwTx, lastValidDepositCount uint32) error { + // Clean root table + for i := lastValidDepositCount + 1; i <= uint32(t.lastDepositCount); i++ { + if err := tx.Delete(t.rootTable, dbCommon.Uint32ToBytes(i)); err != nil { + return err + } + } + + // Reset cache + rootBytes, err := tx.GetOne(t.rootTable, dbCommon.Uint32ToBytes(lastValidDepositCount)) + if err != nil { + return err + } + err = t.initLastLeftCache(tx, int64(lastValidDepositCount), common.Hash(rootBytes)) + if err != nil { + return err + } + + // Note: not cleaning RHT, not worth it + t.lastDepositCount = int64(lastValidDepositCount) + return nil +} + +func generateZeroHashes(height uint8) []common.Hash { + var zeroHashes = []common.Hash{ + {}, + } + // This generates a leaf = HashZero in position 0. In the rest of the positions that are equivalent to the ascending levels, + // we set the hashes of the nodes. So all nodes from level i=5 will have the same value and same children nodes. + for i := 1; i <= int(height); i++ { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(zeroHashes[i-1][:]) + hasher.Write(zeroHashes[i-1][:]) + thisHeightHash := common.Hash{} + copy(thisHeightHash[:], hasher.Sum(nil)) + zeroHashes = append(zeroHashes, thisHeightHash) + } + return zeroHashes +} diff --git a/bridgesync/tree_test.go b/bridgesync/tree_test.go new file mode 100644 index 00000000..5624ad47 --- /dev/null +++ b/bridgesync/tree_test.go @@ -0,0 +1,141 @@ +package bridgesync + +import ( + "context" + "encoding/json" + "fmt" + "math/big" + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +// MTRootVectorRaw represents the root of Merkle Tree +type MTRootVectorRaw struct { + ExistingLeaves []string `json:"previousLeafsValues"` + CurrentRoot string `json:"currentRoot"` + NewLeaf DepositVectorRaw `json:"newLeaf"` + NewRoot string `json:"newRoot"` +} + +func TestMTAddLeaf(t *testing.T) { + data, err := os.ReadFile("testvectors/root-vectors.json") + require.NoError(t, err) + + var mtTestVectors []MTRootVectorRaw + err = json.Unmarshal(data, &mtTestVectors) + require.NoError(t, err) + ctx := context.Background() + + for ti, testVector := range mtTestVectors { + t.Run(fmt.Sprintf("Test vector %d", ti), func(t *testing.T) { + + path := t.TempDir() + p, err := newProcessor(context.Background(), path, "foo") + require.NoError(t, err) + + // Add exisiting leaves + for i, leaf := range testVector.ExistingLeaves { + tx, err := p.db.BeginRw(ctx) + require.NoError(t, err) + err = p.tree.addLeaf(tx, uint32(i), common.HexToHash(leaf)) + require.NoError(t, err) + err = tx.Commit() + require.NoError(t, err) + } + if len(testVector.ExistingLeaves) > 0 { + txRo, err := p.db.BeginRo(ctx) + require.NoError(t, err) + _, actualRoot, err := p.tree.getLastDepositCountAndRoot(txRo) + txRo.Rollback() + require.NoError(t, err) + require.Equal(t, common.HexToHash(testVector.CurrentRoot), actualRoot) + } + + // Add new bridge + amount, result := big.NewInt(0).SetString(testVector.NewLeaf.Amount, 0) + require.True(t, result) + bridge := Bridge{ + OriginNetwork: testVector.NewLeaf.OriginalNetwork, + OriginAddress: common.HexToAddress(testVector.NewLeaf.TokenAddress), + Amount: amount, + DestinationNetwork: testVector.NewLeaf.DestinationNetwork, + DestinationAddress: common.HexToAddress(testVector.NewLeaf.DestinationAddress), + DepositCount: uint32(len(testVector.ExistingLeaves)), + Metadata: common.FromHex(testVector.NewLeaf.Metadata), + } + tx, err := p.db.BeginRw(ctx) + require.NoError(t, err) + require.Equal(t, common.HexToHash(testVector.NewLeaf.CurrentHash), bridge.Hash()) + err = p.tree.addLeaf(tx, bridge.DepositCount, bridge.Hash()) + require.NoError(t, err) + err = tx.Commit() + txRo, err := p.db.BeginRo(ctx) + require.NoError(t, err) + _, actualRoot, err := p.tree.getLastDepositCountAndRoot(txRo) + txRo.Rollback() + require.NoError(t, err) + require.Equal(t, common.HexToHash(testVector.NewRoot), actualRoot) + }) + } +} + +// MTClaimVectorRaw represents the merkle proof +type MTClaimVectorRaw struct { + Deposits []DepositVectorRaw `json:"leafs"` + Index uint32 `json:"index"` + MerkleProof []string `json:"proof"` + ExpectedRoot string `json:"root"` +} + +func TestMTGetProof(t *testing.T) { + data, err := os.ReadFile("testvectors/claim-vectors.json") + require.NoError(t, err) + + var mtTestVectors []MTClaimVectorRaw + err = json.Unmarshal(data, &mtTestVectors) + require.NoError(t, err) + ctx := context.Background() + + for ti, testVector := range mtTestVectors { + t.Run(fmt.Sprintf("Test vector %d", ti), func(t *testing.T) { + path := t.TempDir() + p, err := newProcessor(context.Background(), path, "foo") + require.NoError(t, err) + + for li, leaf := range testVector.Deposits { + amount, result := big.NewInt(0).SetString(leaf.Amount, 0) + require.True(t, result) + bridge := &Bridge{ + OriginNetwork: leaf.OriginalNetwork, + OriginAddress: common.HexToAddress(leaf.TokenAddress), + Amount: amount, + DestinationNetwork: leaf.DestinationNetwork, + DestinationAddress: common.HexToAddress(leaf.DestinationAddress), + DepositCount: uint32(li), + Metadata: common.FromHex(leaf.Metadata), + } + tx, err := p.db.BeginRw(ctx) + require.NoError(t, err) + err = p.tree.addLeaf(tx, bridge.DepositCount, bridge.Hash()) + require.NoError(t, err) + err = tx.Commit() + require.NoError(t, err) + } + txRo, err := p.db.BeginRo(ctx) + require.NoError(t, err) + _, actualRoot, err := p.tree.getLastDepositCountAndRoot(txRo) + txRo.Rollback() + expectedRoot := common.HexToHash(testVector.ExpectedRoot) + require.Equal(t, expectedRoot, actualRoot) + + proof, err := p.tree.getProof(ctx, testVector.Index, expectedRoot) + require.NoError(t, err) + for i, sibling := range testVector.MerkleProof { + require.Equal(t, common.HexToHash(sibling), proof[i]) + } + }) + } +} From bfdc806426ae9e7ad52f82fa5d355d3bc6c47a6e Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 30 Jul 2024 17:06:04 +0200 Subject: [PATCH 18/49] fix UT --- bridgesync/processor.go | 10 +++++++++- bridgesync/processor_test.go | 6 +++--- bridgesync/tree.go | 13 +++++++++++-- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/bridgesync/processor.go b/bridgesync/processor.go index 49c1c1b4..36d34213 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -49,6 +49,9 @@ func (b *Bridge) Hash() common.Hash { metaHash := keccak256.Hash(b.Metadata) hash := common.Hash{} var buf [32]byte //nolint:gomnd + if b.Amount == nil { + b.Amount = big.NewInt(0) + } copy( hash[:], keccak256.Hash( @@ -218,7 +221,12 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { return err } if firstDepositCountReorged != -1 { - lastValidDepositCount := uint32(firstDepositCountReorged) - 1 + var lastValidDepositCount uint32 + if firstDepositCountReorged == 0 { + lastValidDepositCount = 0 + } else { + lastValidDepositCount = uint32(firstDepositCountReorged) - 1 + } if err := p.tree.reorg(tx, lastValidDepositCount); err != nil { tx.Rollback() return err diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index 24e65e5c..c1535d70 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -254,7 +254,7 @@ var ( DestinationAddress: common.HexToAddress("01"), Amount: big.NewInt(1), Metadata: common.Hex2Bytes("01"), - DepositCount: 1, + DepositCount: 0, }}, Event{Claim: &Claim{ GlobalIndex: big.NewInt(1), @@ -276,7 +276,7 @@ var ( DestinationAddress: common.HexToAddress("02"), Amount: big.NewInt(2), Metadata: common.Hex2Bytes("02"), - DepositCount: 2, + DepositCount: 1, }}, Event{Bridge: &Bridge{ LeafType: 3, @@ -286,7 +286,7 @@ var ( DestinationAddress: common.HexToAddress("03"), Amount: nil, Metadata: common.Hex2Bytes("03"), - DepositCount: 3, + DepositCount: 2, }}, }, } diff --git a/bridgesync/tree.go b/bridgesync/tree.go index 0e6e9620..9972af64 100644 --- a/bridgesync/tree.go +++ b/bridgesync/tree.go @@ -233,7 +233,7 @@ func (t *tree) initLastLeftCache(tx kv.Tx, lastDepositCount int64, lastRoot comm t.lastLeftCache = siblings return nil } - index := t.lastDepositCount + index := lastDepositCount currentNodeHash := lastRoot // It starts in height-1 because 0 is the level of the leafs @@ -245,6 +245,9 @@ func (t *tree) initLastLeftCache(tx kv.Tx, lastDepositCount int64, lastRoot comm currentNodeHash.Hex(), h, lastRoot.Hex(), err, ) } + if currentNode == nil { + return ErrNotFound + } siblings = append(siblings, currentNode.left) if index&(1< 0 { currentNodeHash = currentNode.right @@ -276,6 +279,9 @@ func (t *tree) getRHTNode(tx kv.Tx, nodeHash common.Hash) (*treeNode, error) { } func (t *tree) reorg(tx kv.RwTx, lastValidDepositCount uint32) error { + if t.lastDepositCount == -1 { + return nil + } // Clean root table for i := lastValidDepositCount + 1; i <= uint32(t.lastDepositCount); i++ { if err := tx.Delete(t.rootTable, dbCommon.Uint32ToBytes(i)); err != nil { @@ -288,7 +294,10 @@ func (t *tree) reorg(tx kv.RwTx, lastValidDepositCount uint32) error { if err != nil { return err } - err = t.initLastLeftCache(tx, int64(lastValidDepositCount), common.Hash(rootBytes)) + if rootBytes == nil { + return ErrNotFound + } + err = t.initLastLeftCache(tx, int64(lastValidDepositCount), common.Hash(rootBytes)) // 0x619a9fedbe029225288d32e39e06fb868ed0d8f20db26047cf0ef8d3582b5f6e if err != nil { return err } From 69d9139f5c6502f648186e6697522910611fcb2e Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 30 Jul 2024 17:37:19 +0200 Subject: [PATCH 19/49] Add PR review suggestions from Stefan-Ethernal --- Dockerfile | 2 +- aggoracle/chaingersender/evm.go | 6 +-- aggoracle/oracle.go | 4 +- common/common.go | 10 ++--- l1infotreesync/processor.go | 69 +++++++++++++++++++-------------- sync/evmdownloader.go | 27 ++++++++----- 6 files changed, 68 insertions(+), 50 deletions(-) diff --git a/Dockerfile b/Dockerfile index 18224428..719868f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM golang:1.22.5-alpine3.20 AS build WORKDIR $GOPATH/src/github.com/0xPolygon/cdk -RUN apk update && apk add --no-cache make build-base git +RUN apk update && apk add --no-cache make build-base git # INSTALL DEPENDENCIES COPY go.mod go.sum /src/ RUN cd /src && go mod download diff --git a/aggoracle/chaingersender/evm.go b/aggoracle/chaingersender/evm.go index 42cd49a4..3a4060a4 100644 --- a/aggoracle/chaingersender/evm.go +++ b/aggoracle/chaingersender/evm.go @@ -86,6 +86,9 @@ func (c *EVMChainGERSender) UpdateGERWaitUntilMined(ctx context.Context, ger com return err } data, err := abi.Pack("updateGlobalExitRoot", ger) + if err != nil { + return err + } id, err := c.ethTxMan.Add(ctx, &c.gerAddr, nil, big.NewInt(0), data, c.gasOffset, nil) if err != nil { return err @@ -98,15 +101,12 @@ func (c *EVMChainGERSender) UpdateGERWaitUntilMined(ctx context.Context, ger com } switch res.Status { case ethtxmanager.MonitoredTxStatusCreated: - continue case ethtxmanager.MonitoredTxStatusSent: continue case ethtxmanager.MonitoredTxStatusFailed: return fmt.Errorf("tx %s failed", res.ID) case ethtxmanager.MonitoredTxStatusMined: - return nil case ethtxmanager.MonitoredTxStatusSafe: - return nil case ethtxmanager.MonitoredTxStatusFinalized: return nil default: diff --git a/aggoracle/oracle.go b/aggoracle/oracle.go index 291950c5..175801a6 100644 --- a/aggoracle/oracle.go +++ b/aggoracle/oracle.go @@ -71,10 +71,10 @@ func (a *AggOracle) Start(ctx context.Context) { } continue } - if alreadyInjectd, err := a.chainSender.IsGERAlreadyInjected(gerToInject); err != nil { + if alreadyInjected, err := a.chainSender.IsGERAlreadyInjected(gerToInject); err != nil { log.Error("error calling isGERAlreadyInjected: ", err) continue - } else if alreadyInjectd { + } else if alreadyInjected { log.Debugf("GER %s already injected", gerToInject.Hex()) continue } diff --git a/common/common.go b/common/common.go index d2f440d1..ebbafd69 100644 --- a/common/common.go +++ b/common/common.go @@ -9,27 +9,27 @@ import ( "github.com/iden3/go-iden3-crypto/keccak256" ) -// Uint64To2Bytes converts a block number to a byte slice -func Uint64To2Bytes(num uint64) []byte { +// Uint64ToBytes converts a uint64 to a byte slice +func Uint64ToBytes(num uint64) []byte { bytes := make([]byte, 8) binary.LittleEndian.PutUint64(bytes, num) return bytes } -// BytesToUint64 converts a byte slice to a block number +// BytesToUint64 converts a byte slice to a uint64 func BytesToUint64(bytes []byte) uint64 { return binary.LittleEndian.Uint64(bytes) } -// Uint32To2Bytes converts a block number to a byte slice +// Uint32To2Bytes converts a uint32 to a byte slice func Uint32ToBytes(num uint32) []byte { key := make([]byte, 4) binary.LittleEndian.PutUint32(key, num) return key } -// BytesToUint32 converts a byte slice to a block number +// BytesToUint32 converts a byte slice to a uint32 func BytesToUint32(bytes []byte) uint32 { return binary.LittleEndian.Uint32(bytes) } diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 2e6116c4..73c48d86 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -2,7 +2,6 @@ package l1infotreesync import ( "context" - "encoding/binary" "encoding/json" "errors" @@ -17,10 +16,29 @@ import ( ) const ( - rootTable = "l1infotreesync-root" - indexTable = "l1infotreesync-index" - infoTable = "l1infotreesync-info" - blockTable = "l1infotreesync-block" + // rootTable stores the L1 info tree roots + // Key: root (common.Hash) + // Value: hash of the leaf that caused the update (common.Hash) + rootTable = "l1infotreesync-root" + // indexTable stores the L1 info tree indexes + // Key: index (uint32 converted to bytes) + // Value: hash of the leaf that caused the update (common.Hash) + indexTable = "l1infotreesync-index" + // infoTable stores the information of the tree (the leaves). Note that the value + // of rootTable and indexTable references the key of the infoTable + // Key: hash of the leaf that caused the update (common.Hash) + // Value: JSON of storeLeaf struct + infoTable = "l1infotreesync-info" + // blockTable stores the first and last index of L1 Info Tree that have been updated on + // a block. This is useful in case there are blocks with multiple updates and a reorg is needed. + // Or for when querying by block number + // Key: block number (uint64 converted to bytes) + // Value: JSON of blockWithLeafs + blockTable = "l1infotreesync-block" + // lastBlockTable used to store the last block processed. This is needed to know the last processed blcok + // when it doesn't have events that make other tables get populated + // Key: it's always lastBlockKey + // Value: block number (uint64 converted to bytes) lastBlockTable = "l1infotreesync-lastBlock" treeHeight uint8 = 32 @@ -29,7 +47,7 @@ const ( var ( ErrBlockNotProcessed = errors.New("given block(s) have not been processed yet") ErrNotFound = errors.New("not found") - lastBlokcKey = []byte("lb") + lastBlockKey = []byte("lb") ) type processor struct { @@ -130,7 +148,7 @@ func (p *processor) getAllLeavesHashed(ctx context.Context) ([][32]byte, error) return nil, err } - return p.getHasedLeaves(tx, index) + return p.getHashedLeaves(tx, index) } func (p *processor) ComputeMerkleProofByIndex(ctx context.Context, index uint32) ([][32]byte, ethCommon.Hash, error) { @@ -143,14 +161,14 @@ func (p *processor) ComputeMerkleProofByIndex(ctx context.Context, index uint32) } defer tx.Rollback() - leaves, err := p.getHasedLeaves(tx, index) + leaves, err := p.getHashedLeaves(tx, index) if err != nil { return nil, ethCommon.Hash{}, err } return p.tree.ComputeMerkleProof(index, leaves) } -func (p *processor) getHasedLeaves(tx kv.Tx, untilIndex uint32) ([][32]byte, error) { +func (p *processor) getHashedLeaves(tx kv.Tx, untilIndex uint32) ([][32]byte, error) { leaves := [][32]byte{} for i := uint32(0); i <= untilIndex; i++ { info, err := p.getInfoByIndexWithTx(tx, i) @@ -187,7 +205,7 @@ func (p *processor) GetInfoByRoot(ctx context.Context, root ethCommon.Hash) (*L1 return p.getInfoByHashWithTx(tx, hash) } -// GetLatestInfoUntilBlock returns the most recent L1InfoTreeLeaf that occured before or at blockNum. +// 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 func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { tx, err := p.db.BeginRo(ctx) @@ -196,10 +214,13 @@ func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64 } defer tx.Rollback() lpb, err := p.getLastProcessedBlockWithTx(tx) + if err != nil { + return nil, err + } if lpb < blockNum { return nil, ErrBlockNotProcessed } - iter, err := tx.RangeDescend(blockTable, uint64ToBytes(blockNum), uint64ToBytes(0), 1) + iter, err := tx.RangeDescend(blockTable, common.Uint64ToBytes(blockNum), common.Uint64ToBytes(0), 1) if err != nil { return nil, err } @@ -285,13 +306,13 @@ func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { } func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { - if blockNumBytes, err := tx.GetOne(lastBlockTable, lastBlokcKey); err != nil { + blockNumBytes, err := tx.GetOne(lastBlockTable, lastBlockKey) + if err != nil { return 0, err } else if blockNumBytes == nil { return 0, nil - } else { - return bytes2Uint64(blockNumBytes), nil } + return common.BytesToUint64(blockNumBytes), nil } func (p *processor) Reorg(firstReorgedBlock uint64) error { @@ -305,7 +326,7 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { return err } defer c.Close() - firstKey := uint64ToBytes(firstReorgedBlock) + firstKey := common.Uint64ToBytes(firstReorgedBlock) for blkKey, blkValue, err := c.Seek(firstKey); blkKey != nil; blkKey, blkValue, err = c.Next() { if err != nil { tx.Rollback() @@ -411,7 +432,7 @@ func (p *processor) ProcessBlock(b sync.Block) error { tx.Rollback() return err } - if err := tx.Put(blockTable, uint64ToBytes(b.Num), blockValue); err != nil { + if err := tx.Put(blockTable, common.Uint64ToBytes(b.Num), blockValue); err != nil { tx.Rollback() return err } @@ -432,7 +453,7 @@ func (p *processor) getLastIndex(tx kv.Tx) (uint32, error) { if bNum == 0 { return 0, nil } - iter, err := tx.RangeDescend(blockTable, uint64ToBytes(bNum), uint64ToBytes(0), 1) + iter, err := tx.RangeDescend(blockTable, common.Uint64ToBytes(bNum), common.Uint64ToBytes(0), 1) if err != nil { return 0, err } @@ -478,16 +499,6 @@ func (p *processor) addLeaf(tx kv.RwTx, leaf storeLeaf) error { } func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error { - blockNumBytes := uint64ToBytes(blockNum) - return tx.Put(lastBlockTable, lastBlokcKey, blockNumBytes) -} - -func uint64ToBytes(num uint64) []byte { - key := make([]byte, 8) - binary.LittleEndian.PutUint64(key, num) - return key -} - -func bytes2Uint64(key []byte) uint64 { - return binary.LittleEndian.Uint64(key) + blockNumBytes := common.Uint64ToBytes(blockNum) + return tx.Put(lastBlockTable, lastBlockKey, blockNumBytes) } diff --git a/sync/evmdownloader.go b/sync/evmdownloader.go index e6d9dc8d..ebdde880 100644 --- a/sync/evmdownloader.go +++ b/sync/evmdownloader.go @@ -110,18 +110,25 @@ type downloaderImplementation struct { func (d *downloaderImplementation) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) { attempts := 0 + ticker := time.NewTicker(d.waitForNewBlocksPeriod) + defer ticker.Stop() for { - header, err := d.ethClient.HeaderByNumber(ctx, d.blockFinality) - if err != nil { - attempts++ - log.Error("error geting last block num from eth client: ", err) - RetryHandler("waitForNewBlocks", attempts) - continue - } - if header.Number.Uint64() > lastBlockSeen { - return header.Number.Uint64() + select { + case <-ctx.Done(): + log.Info("context cancelled") + return lastBlockSeen + case <-ticker.C: + header, err := d.ethClient.HeaderByNumber(ctx, d.blockFinality) + if err != nil { + attempts++ + log.Error("error getting last block num from eth client: ", err) + RetryHandler("waitForNewBlocks", attempts) + continue + } + if header.Number.Uint64() > lastBlockSeen { + return header.Number.Uint64() + } } - time.Sleep(d.waitForNewBlocksPeriod) } } From b1a399b254e390efdbf1c94b3324080f894a305d Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 30 Jul 2024 18:34:14 +0200 Subject: [PATCH 20/49] fix UTs --- aggoracle/chaingersender/evm.go | 13 +++++---- aggoracle/e2e_test.go | 3 +- aggoracle/oracle.go | 49 +++++++++++++++++++-------------- l1infotreesync/processor.go | 17 ++++++++---- localbridgesync/processor.go | 8 +++--- 5 files changed, 53 insertions(+), 37 deletions(-) diff --git a/aggoracle/chaingersender/evm.go b/aggoracle/chaingersender/evm.go index 3a4060a4..93ce347c 100644 --- a/aggoracle/chaingersender/evm.go +++ b/aggoracle/chaingersender/evm.go @@ -75,7 +75,7 @@ func NewEVMChainGERSender( func (c *EVMChainGERSender) IsGERAlreadyInjected(ger common.Hash) (bool, error) { timestamp, err := c.gerContract.GlobalExitRootMap(&bind.CallOpts{Pending: false}, ger) if err != nil { - return false, err + return false, fmt.Errorf("error calling gerContract.GlobalExitRootMap: %w", err) } return timestamp.Cmp(big.NewInt(0)) != 0, nil } @@ -95,19 +95,20 @@ func (c *EVMChainGERSender) UpdateGERWaitUntilMined(ctx context.Context, ger com } for { time.Sleep(c.waitPeriodMonitorTx) + log.Debugf("waiting for tx %s to be mined", id.Hex()) res, err := c.ethTxMan.Result(ctx, id) if err != nil { log.Error("error calling ethTxMan.Result: ", err) } switch res.Status { - case ethtxmanager.MonitoredTxStatusCreated: - case ethtxmanager.MonitoredTxStatusSent: + case ethtxmanager.MonitoredTxStatusCreated, + ethtxmanager.MonitoredTxStatusSent: continue case ethtxmanager.MonitoredTxStatusFailed: return fmt.Errorf("tx %s failed", res.ID) - case ethtxmanager.MonitoredTxStatusMined: - case ethtxmanager.MonitoredTxStatusSafe: - case ethtxmanager.MonitoredTxStatusFinalized: + case ethtxmanager.MonitoredTxStatusMined, + ethtxmanager.MonitoredTxStatusSafe, + ethtxmanager.MonitoredTxStatusFinalized: return nil default: log.Error("unexpected tx status: ", res.Status) diff --git a/aggoracle/e2e_test.go b/aggoracle/e2e_test.go index ad1ff9c3..3e09c906 100644 --- a/aggoracle/e2e_test.go +++ b/aggoracle/e2e_test.go @@ -3,6 +3,7 @@ package aggoracle_test import ( "context" "errors" + "fmt" "math/big" "strconv" "testing" @@ -208,6 +209,6 @@ func runTest( require.NoError(t, err) isInjected, err := sender.IsGERAlreadyInjected(expectedGER) require.NoError(t, err) - require.True(t, isInjected) + require.True(t, isInjected, fmt.Sprintf("iteration %d, GER: %s", i, common.Bytes2Hex(expectedGER[:]))) } } diff --git a/aggoracle/oracle.go b/aggoracle/oracle.go index 175801a6..49d14b7e 100644 --- a/aggoracle/oracle.go +++ b/aggoracle/oracle.go @@ -9,17 +9,9 @@ import ( "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" ) -type EthClienter interface { - ethereum.LogFilterer - ethereum.BlockNumberReader - ethereum.ChainReader - bind.ContractBackend -} - type L1InfoTreer interface { GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*l1infotreesync.L1InfoTreeLeaf, error) } @@ -31,7 +23,7 @@ type ChainSender interface { type AggOracle struct { ticker *time.Ticker - l1Client EthClienter + l1Client ethereum.ChainReader l1Info L1InfoTreer chainSender ChainSender blockFinality *big.Int @@ -39,7 +31,7 @@ type AggOracle struct { func New( chainSender ChainSender, - l1Client EthClienter, + l1Client ethereum.ChainReader, l1InfoTreeSyncer L1InfoTreer, blockFinalityType etherman.BlockNumberFinality, waitPeriodNextGER time.Duration, @@ -59,15 +51,23 @@ func New( } func (a *AggOracle) Start(ctx context.Context) { + var ( + blockNumToFetch uint64 + gerToInject common.Hash + err error + ) for { select { case <-a.ticker.C: - gerToInject, err := a.getLastFinalisedGER(ctx) + blockNumToFetch, gerToInject, err = a.getLastFinalisedGER(ctx, blockNumToFetch) if err != nil { - if err == l1infotreesync.ErrBlockNotProcessed || err == l1infotreesync.ErrNotFound { - log.Debugf("syncer is not ready: %v", err) + if err == l1infotreesync.ErrBlockNotProcessed { + log.Debugf("syncer is not ready for the block %d", blockNumToFetch) + } else if err == l1infotreesync.ErrNotFound { + blockNumToFetch = 0 + log.Debugf("syncer has not found any GER until block %d", blockNumToFetch) } else { - log.Error("error calling isGERAlreadyInjected: ", err) + log.Error("error calling getLastFinalisedGER: ", err) } continue } @@ -90,14 +90,21 @@ func (a *AggOracle) Start(ctx context.Context) { } } -func (a *AggOracle) getLastFinalisedGER(ctx context.Context) (common.Hash, error) { - header, err := a.l1Client.HeaderByNumber(ctx, a.blockFinality) - if err != nil { - return common.Hash{}, err +// getLastFinalisedGER tries to return a finalised GER: +// If blockNumToFetch != 0: it will try to fetch it until the given block +// Else it will ask the L1 client for the latest finalised block and use that +// If it fails to get the GER from the syncer, it will retunr the block number that used to query +func (a *AggOracle) getLastFinalisedGER(ctx context.Context, blockNumToFetch uint64) (uint64, common.Hash, error) { + if blockNumToFetch == 0 { + header, err := a.l1Client.HeaderByNumber(ctx, a.blockFinality) + if err != nil { + return 0, common.Hash{}, err + } + blockNumToFetch = header.Number.Uint64() } - info, err := a.l1Info.GetLatestInfoUntilBlock(ctx, header.Number.Uint64()) + info, err := a.l1Info.GetLatestInfoUntilBlock(ctx, blockNumToFetch) if err != nil { - return common.Hash{}, err + return blockNumToFetch, common.Hash{}, err } - return info.GlobalExitRoot, nil + return 0, info.GlobalExitRoot, nil } diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 73c48d86..9b444d43 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/l1infotree" @@ -47,6 +48,7 @@ const ( var ( ErrBlockNotProcessed = errors.New("given block(s) have not been processed yet") ErrNotFound = errors.New("not found") + ErrNoBlock0 = errors.New("blockNum must be greater than 0") lastBlockKey = []byte("lb") ) @@ -208,6 +210,9 @@ func (p *processor) GetInfoByRoot(ctx context.Context, root ethCommon.Hash) (*L1 // 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 func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { + if blockNum == 0 { + return nil, ErrNoBlock0 + } tx, err := p.db.BeginRo(ctx) if err != nil { return nil, err @@ -222,15 +227,17 @@ func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64 } iter, err := tx.RangeDescend(blockTable, common.Uint64ToBytes(blockNum), common.Uint64ToBytes(0), 1) if err != nil { - return nil, err - } - if !iter.HasNext() { - return nil, ErrNotFound + return nil, fmt.Errorf( + "error calling RangeDescend(blockTable, %d, 0, 1): %w", blockNum, err, + ) } - _, v, err := iter.Next() + k, v, err := iter.Next() if err != nil { return nil, err } + if k == nil { + return nil, ErrNotFound + } blk := blockWithLeafs{} if err := json.Unmarshal(v, &blk); err != nil { return nil, err diff --git a/localbridgesync/processor.go b/localbridgesync/processor.go index 4a45c663..5d644a9a 100644 --- a/localbridgesync/processor.go +++ b/localbridgesync/processor.go @@ -96,7 +96,7 @@ func (p *processor) GetClaimsAndBridges( } defer c.Close() - for k, v, err := c.Seek(common.Uint64To2Bytes(fromBlock)); k != nil; k, v, err = c.Next() { + for k, v, err := c.Seek(common.Uint64ToBytes(fromBlock)); k != nil; k, v, err = c.Next() { if err != nil { return nil, err } @@ -143,7 +143,7 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { return err } defer c.Close() - firstKey := common.Uint64To2Bytes(firstReorgedBlock) + firstKey := common.Uint64ToBytes(firstReorgedBlock) for k, _, err := c.Seek(firstKey); k != nil; k, _, err = c.Next() { if err != nil { tx.Rollback() @@ -176,7 +176,7 @@ func (p *processor) ProcessBlock(block sync.Block) error { tx.Rollback() return err } - if err := tx.Put(eventsTable, common.Uint64To2Bytes(block.Num), value); err != nil { + if err := tx.Put(eventsTable, common.Uint64ToBytes(block.Num), value); err != nil { tx.Rollback() return err } @@ -189,6 +189,6 @@ func (p *processor) ProcessBlock(block sync.Block) error { } func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error { - blockNumBytes := common.Uint64To2Bytes(blockNum) + blockNumBytes := common.Uint64ToBytes(blockNum) return tx.Put(lastBlockTable, lastBlokcKey, blockNumBytes) } From 78a5cc8accb67cecc7e14ab79b590681ff5385a0 Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 30 Jul 2024 17:37:19 +0200 Subject: [PATCH 21/49] Add PR review suggestions from Stefan-Ethernal --- Dockerfile | 2 +- aggoracle/chaingersender/evm.go | 6 +-- aggoracle/oracle.go | 4 +- common/common.go | 10 ++--- l1infotreesync/processor.go | 69 +++++++++++++++++++-------------- sync/evmdownloader.go | 27 ++++++++----- 6 files changed, 68 insertions(+), 50 deletions(-) diff --git a/Dockerfile b/Dockerfile index 18224428..719868f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM golang:1.22.5-alpine3.20 AS build WORKDIR $GOPATH/src/github.com/0xPolygon/cdk -RUN apk update && apk add --no-cache make build-base git +RUN apk update && apk add --no-cache make build-base git # INSTALL DEPENDENCIES COPY go.mod go.sum /src/ RUN cd /src && go mod download diff --git a/aggoracle/chaingersender/evm.go b/aggoracle/chaingersender/evm.go index 42cd49a4..3a4060a4 100644 --- a/aggoracle/chaingersender/evm.go +++ b/aggoracle/chaingersender/evm.go @@ -86,6 +86,9 @@ func (c *EVMChainGERSender) UpdateGERWaitUntilMined(ctx context.Context, ger com return err } data, err := abi.Pack("updateGlobalExitRoot", ger) + if err != nil { + return err + } id, err := c.ethTxMan.Add(ctx, &c.gerAddr, nil, big.NewInt(0), data, c.gasOffset, nil) if err != nil { return err @@ -98,15 +101,12 @@ func (c *EVMChainGERSender) UpdateGERWaitUntilMined(ctx context.Context, ger com } switch res.Status { case ethtxmanager.MonitoredTxStatusCreated: - continue case ethtxmanager.MonitoredTxStatusSent: continue case ethtxmanager.MonitoredTxStatusFailed: return fmt.Errorf("tx %s failed", res.ID) case ethtxmanager.MonitoredTxStatusMined: - return nil case ethtxmanager.MonitoredTxStatusSafe: - return nil case ethtxmanager.MonitoredTxStatusFinalized: return nil default: diff --git a/aggoracle/oracle.go b/aggoracle/oracle.go index 291950c5..175801a6 100644 --- a/aggoracle/oracle.go +++ b/aggoracle/oracle.go @@ -71,10 +71,10 @@ func (a *AggOracle) Start(ctx context.Context) { } continue } - if alreadyInjectd, err := a.chainSender.IsGERAlreadyInjected(gerToInject); err != nil { + if alreadyInjected, err := a.chainSender.IsGERAlreadyInjected(gerToInject); err != nil { log.Error("error calling isGERAlreadyInjected: ", err) continue - } else if alreadyInjectd { + } else if alreadyInjected { log.Debugf("GER %s already injected", gerToInject.Hex()) continue } diff --git a/common/common.go b/common/common.go index d2f440d1..ebbafd69 100644 --- a/common/common.go +++ b/common/common.go @@ -9,27 +9,27 @@ import ( "github.com/iden3/go-iden3-crypto/keccak256" ) -// Uint64To2Bytes converts a block number to a byte slice -func Uint64To2Bytes(num uint64) []byte { +// Uint64ToBytes converts a uint64 to a byte slice +func Uint64ToBytes(num uint64) []byte { bytes := make([]byte, 8) binary.LittleEndian.PutUint64(bytes, num) return bytes } -// BytesToUint64 converts a byte slice to a block number +// BytesToUint64 converts a byte slice to a uint64 func BytesToUint64(bytes []byte) uint64 { return binary.LittleEndian.Uint64(bytes) } -// Uint32To2Bytes converts a block number to a byte slice +// Uint32To2Bytes converts a uint32 to a byte slice func Uint32ToBytes(num uint32) []byte { key := make([]byte, 4) binary.LittleEndian.PutUint32(key, num) return key } -// BytesToUint32 converts a byte slice to a block number +// BytesToUint32 converts a byte slice to a uint32 func BytesToUint32(bytes []byte) uint32 { return binary.LittleEndian.Uint32(bytes) } diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 2e6116c4..73c48d86 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -2,7 +2,6 @@ package l1infotreesync import ( "context" - "encoding/binary" "encoding/json" "errors" @@ -17,10 +16,29 @@ import ( ) const ( - rootTable = "l1infotreesync-root" - indexTable = "l1infotreesync-index" - infoTable = "l1infotreesync-info" - blockTable = "l1infotreesync-block" + // rootTable stores the L1 info tree roots + // Key: root (common.Hash) + // Value: hash of the leaf that caused the update (common.Hash) + rootTable = "l1infotreesync-root" + // indexTable stores the L1 info tree indexes + // Key: index (uint32 converted to bytes) + // Value: hash of the leaf that caused the update (common.Hash) + indexTable = "l1infotreesync-index" + // infoTable stores the information of the tree (the leaves). Note that the value + // of rootTable and indexTable references the key of the infoTable + // Key: hash of the leaf that caused the update (common.Hash) + // Value: JSON of storeLeaf struct + infoTable = "l1infotreesync-info" + // blockTable stores the first and last index of L1 Info Tree that have been updated on + // a block. This is useful in case there are blocks with multiple updates and a reorg is needed. + // Or for when querying by block number + // Key: block number (uint64 converted to bytes) + // Value: JSON of blockWithLeafs + blockTable = "l1infotreesync-block" + // lastBlockTable used to store the last block processed. This is needed to know the last processed blcok + // when it doesn't have events that make other tables get populated + // Key: it's always lastBlockKey + // Value: block number (uint64 converted to bytes) lastBlockTable = "l1infotreesync-lastBlock" treeHeight uint8 = 32 @@ -29,7 +47,7 @@ const ( var ( ErrBlockNotProcessed = errors.New("given block(s) have not been processed yet") ErrNotFound = errors.New("not found") - lastBlokcKey = []byte("lb") + lastBlockKey = []byte("lb") ) type processor struct { @@ -130,7 +148,7 @@ func (p *processor) getAllLeavesHashed(ctx context.Context) ([][32]byte, error) return nil, err } - return p.getHasedLeaves(tx, index) + return p.getHashedLeaves(tx, index) } func (p *processor) ComputeMerkleProofByIndex(ctx context.Context, index uint32) ([][32]byte, ethCommon.Hash, error) { @@ -143,14 +161,14 @@ func (p *processor) ComputeMerkleProofByIndex(ctx context.Context, index uint32) } defer tx.Rollback() - leaves, err := p.getHasedLeaves(tx, index) + leaves, err := p.getHashedLeaves(tx, index) if err != nil { return nil, ethCommon.Hash{}, err } return p.tree.ComputeMerkleProof(index, leaves) } -func (p *processor) getHasedLeaves(tx kv.Tx, untilIndex uint32) ([][32]byte, error) { +func (p *processor) getHashedLeaves(tx kv.Tx, untilIndex uint32) ([][32]byte, error) { leaves := [][32]byte{} for i := uint32(0); i <= untilIndex; i++ { info, err := p.getInfoByIndexWithTx(tx, i) @@ -187,7 +205,7 @@ func (p *processor) GetInfoByRoot(ctx context.Context, root ethCommon.Hash) (*L1 return p.getInfoByHashWithTx(tx, hash) } -// GetLatestInfoUntilBlock returns the most recent L1InfoTreeLeaf that occured before or at blockNum. +// 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 func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { tx, err := p.db.BeginRo(ctx) @@ -196,10 +214,13 @@ func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64 } defer tx.Rollback() lpb, err := p.getLastProcessedBlockWithTx(tx) + if err != nil { + return nil, err + } if lpb < blockNum { return nil, ErrBlockNotProcessed } - iter, err := tx.RangeDescend(blockTable, uint64ToBytes(blockNum), uint64ToBytes(0), 1) + iter, err := tx.RangeDescend(blockTable, common.Uint64ToBytes(blockNum), common.Uint64ToBytes(0), 1) if err != nil { return nil, err } @@ -285,13 +306,13 @@ func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { } func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { - if blockNumBytes, err := tx.GetOne(lastBlockTable, lastBlokcKey); err != nil { + blockNumBytes, err := tx.GetOne(lastBlockTable, lastBlockKey) + if err != nil { return 0, err } else if blockNumBytes == nil { return 0, nil - } else { - return bytes2Uint64(blockNumBytes), nil } + return common.BytesToUint64(blockNumBytes), nil } func (p *processor) Reorg(firstReorgedBlock uint64) error { @@ -305,7 +326,7 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { return err } defer c.Close() - firstKey := uint64ToBytes(firstReorgedBlock) + firstKey := common.Uint64ToBytes(firstReorgedBlock) for blkKey, blkValue, err := c.Seek(firstKey); blkKey != nil; blkKey, blkValue, err = c.Next() { if err != nil { tx.Rollback() @@ -411,7 +432,7 @@ func (p *processor) ProcessBlock(b sync.Block) error { tx.Rollback() return err } - if err := tx.Put(blockTable, uint64ToBytes(b.Num), blockValue); err != nil { + if err := tx.Put(blockTable, common.Uint64ToBytes(b.Num), blockValue); err != nil { tx.Rollback() return err } @@ -432,7 +453,7 @@ func (p *processor) getLastIndex(tx kv.Tx) (uint32, error) { if bNum == 0 { return 0, nil } - iter, err := tx.RangeDescend(blockTable, uint64ToBytes(bNum), uint64ToBytes(0), 1) + iter, err := tx.RangeDescend(blockTable, common.Uint64ToBytes(bNum), common.Uint64ToBytes(0), 1) if err != nil { return 0, err } @@ -478,16 +499,6 @@ func (p *processor) addLeaf(tx kv.RwTx, leaf storeLeaf) error { } func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error { - blockNumBytes := uint64ToBytes(blockNum) - return tx.Put(lastBlockTable, lastBlokcKey, blockNumBytes) -} - -func uint64ToBytes(num uint64) []byte { - key := make([]byte, 8) - binary.LittleEndian.PutUint64(key, num) - return key -} - -func bytes2Uint64(key []byte) uint64 { - return binary.LittleEndian.Uint64(key) + blockNumBytes := common.Uint64ToBytes(blockNum) + return tx.Put(lastBlockTable, lastBlockKey, blockNumBytes) } diff --git a/sync/evmdownloader.go b/sync/evmdownloader.go index e6d9dc8d..ebdde880 100644 --- a/sync/evmdownloader.go +++ b/sync/evmdownloader.go @@ -110,18 +110,25 @@ type downloaderImplementation struct { func (d *downloaderImplementation) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) { attempts := 0 + ticker := time.NewTicker(d.waitForNewBlocksPeriod) + defer ticker.Stop() for { - header, err := d.ethClient.HeaderByNumber(ctx, d.blockFinality) - if err != nil { - attempts++ - log.Error("error geting last block num from eth client: ", err) - RetryHandler("waitForNewBlocks", attempts) - continue - } - if header.Number.Uint64() > lastBlockSeen { - return header.Number.Uint64() + select { + case <-ctx.Done(): + log.Info("context cancelled") + return lastBlockSeen + case <-ticker.C: + header, err := d.ethClient.HeaderByNumber(ctx, d.blockFinality) + if err != nil { + attempts++ + log.Error("error getting last block num from eth client: ", err) + RetryHandler("waitForNewBlocks", attempts) + continue + } + if header.Number.Uint64() > lastBlockSeen { + return header.Number.Uint64() + } } - time.Sleep(d.waitForNewBlocksPeriod) } } From 024a57f4c5e23f2ed6c74c02131fc8b086378a1a Mon Sep 17 00:00:00 2001 From: joanestebanr <129153821+joanestebanr@users.noreply.github.com> Date: Mon, 29 Jul 2024 10:03:32 +0200 Subject: [PATCH 22/49] fix datacommittee_test deploying proxy contract --- .../datacommittee/datacommittee_test.go | 55 +- go.mod | 2 +- go.sum | 4 +- test/contracts/abi/erc1967proxy.abi | 71 ++ test/contracts/bin/erc1967proxy.bin | 1 + test/contracts/erc1967proxy/erc1967proxy.go | 668 ++++++++++++++++++ 6 files changed, 795 insertions(+), 6 deletions(-) create mode 100644 test/contracts/abi/erc1967proxy.abi create mode 100644 test/contracts/bin/erc1967proxy.bin create mode 100644 test/contracts/erc1967proxy/erc1967proxy.go diff --git a/dataavailability/datacommittee/datacommittee_test.go b/dataavailability/datacommittee/datacommittee_test.go index 4673a4b5..2d9d7589 100644 --- a/dataavailability/datacommittee/datacommittee_test.go +++ b/dataavailability/datacommittee/datacommittee_test.go @@ -1,11 +1,15 @@ package datacommittee import ( + "errors" + "fmt" "math/big" "testing" + smcparis "github.com/0xPolygon/cdk-contracts-tooling/contracts/banana-paris/polygondatacommittee" "github.com/0xPolygon/cdk-contracts-tooling/contracts/banana/polygondatacommittee" "github.com/0xPolygon/cdk/log" + erc1967proxy "github.com/0xPolygon/cdk/test/contracts/erc1967proxy" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -16,7 +20,7 @@ import ( ) func TestUpdateDataCommitteeEvent(t *testing.T) { - t.Skip("This test is not working because the simulated backend doesnt accept PUSH0, check: https://github.com/ethereum/go-ethereum/issues/28144#issuecomment-2247124776") + //t.Skip("This test is not working because the simulated backend doesnt accept PUSH0, check: https://github.com/ethereum/go-ethereum/issues/28144#issuecomment-2247124776") // Set up testing environment dac, ethBackend, auth, da := newTestingEnv(t) @@ -109,16 +113,22 @@ func newSimulatedDacman(t *testing.T, auth *bind.TransactOpts) ( client := simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) // DAC Setup - _, _, da, err = polygondatacommittee.DeployPolygondatacommittee(auth, client.Client()) + addr, _, _, err := smcparis.DeployPolygondatacommittee(auth, client.Client()) if err != nil { return &Backend{}, nil, nil, err } client.Commit() - _, err = da.Initialize(auth) + proxyAddr, err := deployDACProxy(auth, client.Client(), addr) if err != nil { return &Backend{}, nil, nil, err } + client.Commit() + da, err = polygondatacommittee.NewPolygondatacommittee(proxyAddr, client.Client()) + if err != nil { + return &Backend{}, nil, nil, err + } + _, err = da.SetupCommittee(auth, big.NewInt(0), []string{}, []byte{}) if err != nil { return &Backend{}, nil, nil, err @@ -130,3 +140,42 @@ func newSimulatedDacman(t *testing.T, auth *bind.TransactOpts) ( } return c, client, da, nil } + +func deployDACProxy(auth *bind.TransactOpts, client bind.ContractBackend, dacImpl common.Address) (common.Address, error) { + // Deploy proxy + dacABI, err := polygondatacommittee.PolygondatacommitteeMetaData.GetAbi() + if err != nil { + return common.Address{}, err + } + if dacABI == nil { + return common.Address{}, errors.New("GetABI returned nil") + } + initializeCallData, err := dacABI.Pack("initialize") + if err != nil { + return common.Address{}, err + } + proxyAddr, err := deployProxy( + auth, + client, + dacImpl, + initializeCallData, + ) + if err != nil { + return common.Address{}, err + } + fmt.Println("DAC proxy deployed at", proxyAddr) + return proxyAddr, nil +} + +func deployProxy(auth *bind.TransactOpts, + client bind.ContractBackend, + implementationAddr common.Address, + initializeParams []byte) (common.Address, error) { + addr, _, _, err := erc1967proxy.DeployErc1967proxy( + auth, + client, + implementationAddr, + initializeParams, + ) + return addr, err +} diff --git a/go.mod b/go.mod index d40eaa56..5b2fe026 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/0xPolygon/cdk go 1.22.4 require ( - github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884 + github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726125827-301fa4c59245 github.com/0xPolygon/cdk-data-availability v0.0.8 github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-RC4 diff --git a/go.sum b/go.sum index 6b5b219a..a1c78e52 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884 h1:oXUct6UWuGs15WyCEKipY0Kc0BsCnMzniAz0EIFoPxs= -github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726101945-d05a885ae884/go.mod h1:mFlcEjsm2YBBsu8atHJ3zyVnwM+Z/fMXpVmIJge+WVU= +github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726125827-301fa4c59245 h1:BBmVd50JQID9UyUR3vWFMKr2pMHD3mrqjpuB9DDepBw= +github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726125827-301fa4c59245/go.mod h1:mFlcEjsm2YBBsu8atHJ3zyVnwM+Z/fMXpVmIJge+WVU= github.com/0xPolygon/cdk-data-availability v0.0.8 h1:bMmOYZ7Ei683y80ric3KzMPXtRGmchAmfjIRzghaHb4= github.com/0xPolygon/cdk-data-availability v0.0.8/go.mod h1:3XkZ0zn0GsvAT01MPQMmukF534CVSFmtrcoK3F/BK6Q= github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d h1:sxh6hZ2jF/sxxj2jd5o1vuNNCZjYmn4aRG9SRlVaEFs= diff --git a/test/contracts/abi/erc1967proxy.abi b/test/contracts/abi/erc1967proxy.abi new file mode 100644 index 00000000..f676814a --- /dev/null +++ b/test/contracts/abi/erc1967proxy.abi @@ -0,0 +1,71 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] \ No newline at end of file diff --git a/test/contracts/bin/erc1967proxy.bin b/test/contracts/bin/erc1967proxy.bin new file mode 100644 index 00000000..d81a2e24 --- /dev/null +++ b/test/contracts/bin/erc1967proxy.bin @@ -0,0 +1 @@ +60806040526040516104ee3803806104ee833981016040819052610022916102de565b61002e82826000610035565b50506103fb565b61003e83610061565b60008251118061004b5750805b1561005c5761005a83836100a1565b505b505050565b61006a816100cd565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60606100c683836040518060600160405280602781526020016104c760279139610180565b9392505050565b6001600160a01b0381163b61013f5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b6060600080856001600160a01b03168560405161019d91906103ac565b600060405180830381855af49150503d80600081146101d8576040519150601f19603f3d011682016040523d82523d6000602084013e6101dd565b606091505b5090925090506101ef868383876101f9565b9695505050505050565b60608315610268578251600003610261576001600160a01b0385163b6102615760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610136565b5081610272565b610272838361027a565b949350505050565b81511561028a5781518083602001fd5b8060405162461bcd60e51b815260040161013691906103c8565b634e487b7160e01b600052604160045260246000fd5b60005b838110156102d55781810151838201526020016102bd565b50506000910152565b600080604083850312156102f157600080fd5b82516001600160a01b038116811461030857600080fd5b60208401519092506001600160401b038082111561032557600080fd5b818501915085601f83011261033957600080fd5b81518181111561034b5761034b6102a4565b604051601f8201601f19908116603f01168101908382118183101715610373576103736102a4565b8160405282815288602084870101111561038c57600080fd5b61039d8360208301602088016102ba565b80955050505050509250929050565b600082516103be8184602087016102ba565b9190910192915050565b60208152600082518060208401526103e78160408501602087016102ba565b601f01601f19169190910160400192915050565b60be806104096000396000f3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6065565b565b600060607f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5473ffffffffffffffffffffffffffffffffffffffff1690565b905090565b3660008037600080366000845af43d6000803e8080156083573d6000f35b3d6000fdfea2646970667358221220ffbfbaa210c1b5f5ca62a5eba67b7d993e0bdf919f51500f790fb7acf2fd784c64736f6c63430008140033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564 \ No newline at end of file diff --git a/test/contracts/erc1967proxy/erc1967proxy.go b/test/contracts/erc1967proxy/erc1967proxy.go new file mode 100644 index 00000000..f4994e0d --- /dev/null +++ b/test/contracts/erc1967proxy/erc1967proxy.go @@ -0,0 +1,668 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package erc1967proxy + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// Erc1967proxyMetaData contains all meta data concerning the Erc1967proxy contract. +var Erc1967proxyMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_logic\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"previousAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"AdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"beacon\",\"type\":\"address\"}],\"name\":\"BeaconUpgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", + Bin: "0x60806040526040516104ee3803806104ee833981016040819052610022916102de565b61002e82826000610035565b50506103fb565b61003e83610061565b60008251118061004b5750805b1561005c5761005a83836100a1565b505b505050565b61006a816100cd565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60606100c683836040518060600160405280602781526020016104c760279139610180565b9392505050565b6001600160a01b0381163b61013f5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b6060600080856001600160a01b03168560405161019d91906103ac565b600060405180830381855af49150503d80600081146101d8576040519150601f19603f3d011682016040523d82523d6000602084013e6101dd565b606091505b5090925090506101ef868383876101f9565b9695505050505050565b60608315610268578251600003610261576001600160a01b0385163b6102615760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610136565b5081610272565b610272838361027a565b949350505050565b81511561028a5781518083602001fd5b8060405162461bcd60e51b815260040161013691906103c8565b634e487b7160e01b600052604160045260246000fd5b60005b838110156102d55781810151838201526020016102bd565b50506000910152565b600080604083850312156102f157600080fd5b82516001600160a01b038116811461030857600080fd5b60208401519092506001600160401b038082111561032557600080fd5b818501915085601f83011261033957600080fd5b81518181111561034b5761034b6102a4565b604051601f8201601f19908116603f01168101908382118183101715610373576103736102a4565b8160405282815288602084870101111561038c57600080fd5b61039d8360208301602088016102ba565b80955050505050509250929050565b600082516103be8184602087016102ba565b9190910192915050565b60208152600082518060208401526103e78160408501602087016102ba565b601f01601f19169190910160400192915050565b60be806104096000396000f3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6065565b565b600060607f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5473ffffffffffffffffffffffffffffffffffffffff1690565b905090565b3660008037600080366000845af43d6000803e8080156083573d6000f35b3d6000fdfea2646970667358221220ffbfbaa210c1b5f5ca62a5eba67b7d993e0bdf919f51500f790fb7acf2fd784c64736f6c63430008140033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564", +} + +// Erc1967proxyABI is the input ABI used to generate the binding from. +// Deprecated: Use Erc1967proxyMetaData.ABI instead. +var Erc1967proxyABI = Erc1967proxyMetaData.ABI + +// Erc1967proxyBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use Erc1967proxyMetaData.Bin instead. +var Erc1967proxyBin = Erc1967proxyMetaData.Bin + +// DeployErc1967proxy deploys a new Ethereum contract, binding an instance of Erc1967proxy to it. +func DeployErc1967proxy(auth *bind.TransactOpts, backend bind.ContractBackend, _logic common.Address, _data []byte) (common.Address, *types.Transaction, *Erc1967proxy, error) { + parsed, err := Erc1967proxyMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(Erc1967proxyBin), backend, _logic, _data) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Erc1967proxy{Erc1967proxyCaller: Erc1967proxyCaller{contract: contract}, Erc1967proxyTransactor: Erc1967proxyTransactor{contract: contract}, Erc1967proxyFilterer: Erc1967proxyFilterer{contract: contract}}, nil +} + +// Erc1967proxy is an auto generated Go binding around an Ethereum contract. +type Erc1967proxy struct { + Erc1967proxyCaller // Read-only binding to the contract + Erc1967proxyTransactor // Write-only binding to the contract + Erc1967proxyFilterer // Log filterer for contract events +} + +// Erc1967proxyCaller is an auto generated read-only Go binding around an Ethereum contract. +type Erc1967proxyCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// Erc1967proxyTransactor is an auto generated write-only Go binding around an Ethereum contract. +type Erc1967proxyTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// Erc1967proxyFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type Erc1967proxyFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// Erc1967proxySession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type Erc1967proxySession struct { + Contract *Erc1967proxy // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// Erc1967proxyCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type Erc1967proxyCallerSession struct { + Contract *Erc1967proxyCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// Erc1967proxyTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type Erc1967proxyTransactorSession struct { + Contract *Erc1967proxyTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// Erc1967proxyRaw is an auto generated low-level Go binding around an Ethereum contract. +type Erc1967proxyRaw struct { + Contract *Erc1967proxy // Generic contract binding to access the raw methods on +} + +// Erc1967proxyCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type Erc1967proxyCallerRaw struct { + Contract *Erc1967proxyCaller // Generic read-only contract binding to access the raw methods on +} + +// Erc1967proxyTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type Erc1967proxyTransactorRaw struct { + Contract *Erc1967proxyTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewErc1967proxy creates a new instance of Erc1967proxy, bound to a specific deployed contract. +func NewErc1967proxy(address common.Address, backend bind.ContractBackend) (*Erc1967proxy, error) { + contract, err := bindErc1967proxy(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Erc1967proxy{Erc1967proxyCaller: Erc1967proxyCaller{contract: contract}, Erc1967proxyTransactor: Erc1967proxyTransactor{contract: contract}, Erc1967proxyFilterer: Erc1967proxyFilterer{contract: contract}}, nil +} + +// NewErc1967proxyCaller creates a new read-only instance of Erc1967proxy, bound to a specific deployed contract. +func NewErc1967proxyCaller(address common.Address, caller bind.ContractCaller) (*Erc1967proxyCaller, error) { + contract, err := bindErc1967proxy(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &Erc1967proxyCaller{contract: contract}, nil +} + +// NewErc1967proxyTransactor creates a new write-only instance of Erc1967proxy, bound to a specific deployed contract. +func NewErc1967proxyTransactor(address common.Address, transactor bind.ContractTransactor) (*Erc1967proxyTransactor, error) { + contract, err := bindErc1967proxy(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &Erc1967proxyTransactor{contract: contract}, nil +} + +// NewErc1967proxyFilterer creates a new log filterer instance of Erc1967proxy, bound to a specific deployed contract. +func NewErc1967proxyFilterer(address common.Address, filterer bind.ContractFilterer) (*Erc1967proxyFilterer, error) { + contract, err := bindErc1967proxy(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &Erc1967proxyFilterer{contract: contract}, nil +} + +// bindErc1967proxy binds a generic wrapper to an already deployed contract. +func bindErc1967proxy(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := Erc1967proxyMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Erc1967proxy *Erc1967proxyRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Erc1967proxy.Contract.Erc1967proxyCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Erc1967proxy *Erc1967proxyRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Erc1967proxy.Contract.Erc1967proxyTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Erc1967proxy *Erc1967proxyRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Erc1967proxy.Contract.Erc1967proxyTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Erc1967proxy *Erc1967proxyCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Erc1967proxy.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Erc1967proxy *Erc1967proxyTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Erc1967proxy.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Erc1967proxy *Erc1967proxyTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Erc1967proxy.Contract.contract.Transact(opts, method, params...) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_Erc1967proxy *Erc1967proxyTransactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { + return _Erc1967proxy.contract.RawTransact(opts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_Erc1967proxy *Erc1967proxySession) Fallback(calldata []byte) (*types.Transaction, error) { + return _Erc1967proxy.Contract.Fallback(&_Erc1967proxy.TransactOpts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_Erc1967proxy *Erc1967proxyTransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _Erc1967proxy.Contract.Fallback(&_Erc1967proxy.TransactOpts, calldata) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_Erc1967proxy *Erc1967proxyTransactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Erc1967proxy.contract.RawTransact(opts, nil) // calldata is disallowed for receive function +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_Erc1967proxy *Erc1967proxySession) Receive() (*types.Transaction, error) { + return _Erc1967proxy.Contract.Receive(&_Erc1967proxy.TransactOpts) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_Erc1967proxy *Erc1967proxyTransactorSession) Receive() (*types.Transaction, error) { + return _Erc1967proxy.Contract.Receive(&_Erc1967proxy.TransactOpts) +} + +// Erc1967proxyAdminChangedIterator is returned from FilterAdminChanged and is used to iterate over the raw logs and unpacked data for AdminChanged events raised by the Erc1967proxy contract. +type Erc1967proxyAdminChangedIterator struct { + Event *Erc1967proxyAdminChanged // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *Erc1967proxyAdminChangedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(Erc1967proxyAdminChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(Erc1967proxyAdminChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *Erc1967proxyAdminChangedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *Erc1967proxyAdminChangedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// Erc1967proxyAdminChanged represents a AdminChanged event raised by the Erc1967proxy contract. +type Erc1967proxyAdminChanged struct { + PreviousAdmin common.Address + NewAdmin common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAdminChanged is a free log retrieval operation binding the contract event 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f. +// +// Solidity: event AdminChanged(address previousAdmin, address newAdmin) +func (_Erc1967proxy *Erc1967proxyFilterer) FilterAdminChanged(opts *bind.FilterOpts) (*Erc1967proxyAdminChangedIterator, error) { + + logs, sub, err := _Erc1967proxy.contract.FilterLogs(opts, "AdminChanged") + if err != nil { + return nil, err + } + return &Erc1967proxyAdminChangedIterator{contract: _Erc1967proxy.contract, event: "AdminChanged", logs: logs, sub: sub}, nil +} + +// WatchAdminChanged is a free log subscription operation binding the contract event 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f. +// +// Solidity: event AdminChanged(address previousAdmin, address newAdmin) +func (_Erc1967proxy *Erc1967proxyFilterer) WatchAdminChanged(opts *bind.WatchOpts, sink chan<- *Erc1967proxyAdminChanged) (event.Subscription, error) { + + logs, sub, err := _Erc1967proxy.contract.WatchLogs(opts, "AdminChanged") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(Erc1967proxyAdminChanged) + if err := _Erc1967proxy.contract.UnpackLog(event, "AdminChanged", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAdminChanged is a log parse operation binding the contract event 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f. +// +// Solidity: event AdminChanged(address previousAdmin, address newAdmin) +func (_Erc1967proxy *Erc1967proxyFilterer) ParseAdminChanged(log types.Log) (*Erc1967proxyAdminChanged, error) { + event := new(Erc1967proxyAdminChanged) + if err := _Erc1967proxy.contract.UnpackLog(event, "AdminChanged", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// Erc1967proxyBeaconUpgradedIterator is returned from FilterBeaconUpgraded and is used to iterate over the raw logs and unpacked data for BeaconUpgraded events raised by the Erc1967proxy contract. +type Erc1967proxyBeaconUpgradedIterator struct { + Event *Erc1967proxyBeaconUpgraded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *Erc1967proxyBeaconUpgradedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(Erc1967proxyBeaconUpgraded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(Erc1967proxyBeaconUpgraded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *Erc1967proxyBeaconUpgradedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *Erc1967proxyBeaconUpgradedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// Erc1967proxyBeaconUpgraded represents a BeaconUpgraded event raised by the Erc1967proxy contract. +type Erc1967proxyBeaconUpgraded struct { + Beacon common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterBeaconUpgraded is a free log retrieval operation binding the contract event 0x1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e. +// +// Solidity: event BeaconUpgraded(address indexed beacon) +func (_Erc1967proxy *Erc1967proxyFilterer) FilterBeaconUpgraded(opts *bind.FilterOpts, beacon []common.Address) (*Erc1967proxyBeaconUpgradedIterator, error) { + + var beaconRule []interface{} + for _, beaconItem := range beacon { + beaconRule = append(beaconRule, beaconItem) + } + + logs, sub, err := _Erc1967proxy.contract.FilterLogs(opts, "BeaconUpgraded", beaconRule) + if err != nil { + return nil, err + } + return &Erc1967proxyBeaconUpgradedIterator{contract: _Erc1967proxy.contract, event: "BeaconUpgraded", logs: logs, sub: sub}, nil +} + +// WatchBeaconUpgraded is a free log subscription operation binding the contract event 0x1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e. +// +// Solidity: event BeaconUpgraded(address indexed beacon) +func (_Erc1967proxy *Erc1967proxyFilterer) WatchBeaconUpgraded(opts *bind.WatchOpts, sink chan<- *Erc1967proxyBeaconUpgraded, beacon []common.Address) (event.Subscription, error) { + + var beaconRule []interface{} + for _, beaconItem := range beacon { + beaconRule = append(beaconRule, beaconItem) + } + + logs, sub, err := _Erc1967proxy.contract.WatchLogs(opts, "BeaconUpgraded", beaconRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(Erc1967proxyBeaconUpgraded) + if err := _Erc1967proxy.contract.UnpackLog(event, "BeaconUpgraded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseBeaconUpgraded is a log parse operation binding the contract event 0x1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e. +// +// Solidity: event BeaconUpgraded(address indexed beacon) +func (_Erc1967proxy *Erc1967proxyFilterer) ParseBeaconUpgraded(log types.Log) (*Erc1967proxyBeaconUpgraded, error) { + event := new(Erc1967proxyBeaconUpgraded) + if err := _Erc1967proxy.contract.UnpackLog(event, "BeaconUpgraded", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// Erc1967proxyUpgradedIterator is returned from FilterUpgraded and is used to iterate over the raw logs and unpacked data for Upgraded events raised by the Erc1967proxy contract. +type Erc1967proxyUpgradedIterator struct { + Event *Erc1967proxyUpgraded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *Erc1967proxyUpgradedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(Erc1967proxyUpgraded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(Erc1967proxyUpgraded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *Erc1967proxyUpgradedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *Erc1967proxyUpgradedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// Erc1967proxyUpgraded represents a Upgraded event raised by the Erc1967proxy contract. +type Erc1967proxyUpgraded struct { + Implementation common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterUpgraded is a free log retrieval operation binding the contract event 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b. +// +// Solidity: event Upgraded(address indexed implementation) +func (_Erc1967proxy *Erc1967proxyFilterer) FilterUpgraded(opts *bind.FilterOpts, implementation []common.Address) (*Erc1967proxyUpgradedIterator, error) { + + var implementationRule []interface{} + for _, implementationItem := range implementation { + implementationRule = append(implementationRule, implementationItem) + } + + logs, sub, err := _Erc1967proxy.contract.FilterLogs(opts, "Upgraded", implementationRule) + if err != nil { + return nil, err + } + return &Erc1967proxyUpgradedIterator{contract: _Erc1967proxy.contract, event: "Upgraded", logs: logs, sub: sub}, nil +} + +// WatchUpgraded is a free log subscription operation binding the contract event 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b. +// +// Solidity: event Upgraded(address indexed implementation) +func (_Erc1967proxy *Erc1967proxyFilterer) WatchUpgraded(opts *bind.WatchOpts, sink chan<- *Erc1967proxyUpgraded, implementation []common.Address) (event.Subscription, error) { + + var implementationRule []interface{} + for _, implementationItem := range implementation { + implementationRule = append(implementationRule, implementationItem) + } + + logs, sub, err := _Erc1967proxy.contract.WatchLogs(opts, "Upgraded", implementationRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(Erc1967proxyUpgraded) + if err := _Erc1967proxy.contract.UnpackLog(event, "Upgraded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseUpgraded is a log parse operation binding the contract event 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b. +// +// Solidity: event Upgraded(address indexed implementation) +func (_Erc1967proxy *Erc1967proxyFilterer) ParseUpgraded(log types.Log) (*Erc1967proxyUpgraded, error) { + event := new(Erc1967proxyUpgraded) + if err := _Erc1967proxy.contract.UnpackLog(event, "Upgraded", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} From 9ffbd572ca7ca00e84e304ac7a350c75d3597b7b Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 30 Jul 2024 18:34:14 +0200 Subject: [PATCH 23/49] fix UTs --- aggoracle/chaingersender/evm.go | 13 +++++---- aggoracle/e2e_test.go | 3 +- aggoracle/oracle.go | 49 +++++++++++++++++++-------------- l1infotreesync/processor.go | 17 ++++++++---- 4 files changed, 49 insertions(+), 33 deletions(-) diff --git a/aggoracle/chaingersender/evm.go b/aggoracle/chaingersender/evm.go index 3a4060a4..93ce347c 100644 --- a/aggoracle/chaingersender/evm.go +++ b/aggoracle/chaingersender/evm.go @@ -75,7 +75,7 @@ func NewEVMChainGERSender( func (c *EVMChainGERSender) IsGERAlreadyInjected(ger common.Hash) (bool, error) { timestamp, err := c.gerContract.GlobalExitRootMap(&bind.CallOpts{Pending: false}, ger) if err != nil { - return false, err + return false, fmt.Errorf("error calling gerContract.GlobalExitRootMap: %w", err) } return timestamp.Cmp(big.NewInt(0)) != 0, nil } @@ -95,19 +95,20 @@ func (c *EVMChainGERSender) UpdateGERWaitUntilMined(ctx context.Context, ger com } for { time.Sleep(c.waitPeriodMonitorTx) + log.Debugf("waiting for tx %s to be mined", id.Hex()) res, err := c.ethTxMan.Result(ctx, id) if err != nil { log.Error("error calling ethTxMan.Result: ", err) } switch res.Status { - case ethtxmanager.MonitoredTxStatusCreated: - case ethtxmanager.MonitoredTxStatusSent: + case ethtxmanager.MonitoredTxStatusCreated, + ethtxmanager.MonitoredTxStatusSent: continue case ethtxmanager.MonitoredTxStatusFailed: return fmt.Errorf("tx %s failed", res.ID) - case ethtxmanager.MonitoredTxStatusMined: - case ethtxmanager.MonitoredTxStatusSafe: - case ethtxmanager.MonitoredTxStatusFinalized: + case ethtxmanager.MonitoredTxStatusMined, + ethtxmanager.MonitoredTxStatusSafe, + ethtxmanager.MonitoredTxStatusFinalized: return nil default: log.Error("unexpected tx status: ", res.Status) diff --git a/aggoracle/e2e_test.go b/aggoracle/e2e_test.go index ad1ff9c3..3e09c906 100644 --- a/aggoracle/e2e_test.go +++ b/aggoracle/e2e_test.go @@ -3,6 +3,7 @@ package aggoracle_test import ( "context" "errors" + "fmt" "math/big" "strconv" "testing" @@ -208,6 +209,6 @@ func runTest( require.NoError(t, err) isInjected, err := sender.IsGERAlreadyInjected(expectedGER) require.NoError(t, err) - require.True(t, isInjected) + require.True(t, isInjected, fmt.Sprintf("iteration %d, GER: %s", i, common.Bytes2Hex(expectedGER[:]))) } } diff --git a/aggoracle/oracle.go b/aggoracle/oracle.go index 175801a6..49d14b7e 100644 --- a/aggoracle/oracle.go +++ b/aggoracle/oracle.go @@ -9,17 +9,9 @@ import ( "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" ) -type EthClienter interface { - ethereum.LogFilterer - ethereum.BlockNumberReader - ethereum.ChainReader - bind.ContractBackend -} - type L1InfoTreer interface { GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*l1infotreesync.L1InfoTreeLeaf, error) } @@ -31,7 +23,7 @@ type ChainSender interface { type AggOracle struct { ticker *time.Ticker - l1Client EthClienter + l1Client ethereum.ChainReader l1Info L1InfoTreer chainSender ChainSender blockFinality *big.Int @@ -39,7 +31,7 @@ type AggOracle struct { func New( chainSender ChainSender, - l1Client EthClienter, + l1Client ethereum.ChainReader, l1InfoTreeSyncer L1InfoTreer, blockFinalityType etherman.BlockNumberFinality, waitPeriodNextGER time.Duration, @@ -59,15 +51,23 @@ func New( } func (a *AggOracle) Start(ctx context.Context) { + var ( + blockNumToFetch uint64 + gerToInject common.Hash + err error + ) for { select { case <-a.ticker.C: - gerToInject, err := a.getLastFinalisedGER(ctx) + blockNumToFetch, gerToInject, err = a.getLastFinalisedGER(ctx, blockNumToFetch) if err != nil { - if err == l1infotreesync.ErrBlockNotProcessed || err == l1infotreesync.ErrNotFound { - log.Debugf("syncer is not ready: %v", err) + if err == l1infotreesync.ErrBlockNotProcessed { + log.Debugf("syncer is not ready for the block %d", blockNumToFetch) + } else if err == l1infotreesync.ErrNotFound { + blockNumToFetch = 0 + log.Debugf("syncer has not found any GER until block %d", blockNumToFetch) } else { - log.Error("error calling isGERAlreadyInjected: ", err) + log.Error("error calling getLastFinalisedGER: ", err) } continue } @@ -90,14 +90,21 @@ func (a *AggOracle) Start(ctx context.Context) { } } -func (a *AggOracle) getLastFinalisedGER(ctx context.Context) (common.Hash, error) { - header, err := a.l1Client.HeaderByNumber(ctx, a.blockFinality) - if err != nil { - return common.Hash{}, err +// getLastFinalisedGER tries to return a finalised GER: +// If blockNumToFetch != 0: it will try to fetch it until the given block +// Else it will ask the L1 client for the latest finalised block and use that +// If it fails to get the GER from the syncer, it will retunr the block number that used to query +func (a *AggOracle) getLastFinalisedGER(ctx context.Context, blockNumToFetch uint64) (uint64, common.Hash, error) { + if blockNumToFetch == 0 { + header, err := a.l1Client.HeaderByNumber(ctx, a.blockFinality) + if err != nil { + return 0, common.Hash{}, err + } + blockNumToFetch = header.Number.Uint64() } - info, err := a.l1Info.GetLatestInfoUntilBlock(ctx, header.Number.Uint64()) + info, err := a.l1Info.GetLatestInfoUntilBlock(ctx, blockNumToFetch) if err != nil { - return common.Hash{}, err + return blockNumToFetch, common.Hash{}, err } - return info.GlobalExitRoot, nil + return 0, info.GlobalExitRoot, nil } diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 73c48d86..9b444d43 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/l1infotree" @@ -47,6 +48,7 @@ const ( var ( ErrBlockNotProcessed = errors.New("given block(s) have not been processed yet") ErrNotFound = errors.New("not found") + ErrNoBlock0 = errors.New("blockNum must be greater than 0") lastBlockKey = []byte("lb") ) @@ -208,6 +210,9 @@ func (p *processor) GetInfoByRoot(ctx context.Context, root ethCommon.Hash) (*L1 // 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 func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { + if blockNum == 0 { + return nil, ErrNoBlock0 + } tx, err := p.db.BeginRo(ctx) if err != nil { return nil, err @@ -222,15 +227,17 @@ func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64 } iter, err := tx.RangeDescend(blockTable, common.Uint64ToBytes(blockNum), common.Uint64ToBytes(0), 1) if err != nil { - return nil, err - } - if !iter.HasNext() { - return nil, ErrNotFound + return nil, fmt.Errorf( + "error calling RangeDescend(blockTable, %d, 0, 1): %w", blockNum, err, + ) } - _, v, err := iter.Next() + k, v, err := iter.Next() if err != nil { return nil, err } + if k == nil { + return nil, ErrNotFound + } blk := blockWithLeafs{} if err := json.Unmarshal(v, &blk); err != nil { return nil, err From d5f517f96113437017c71c89ac266dec11c54302 Mon Sep 17 00:00:00 2001 From: Arnau Date: Wed, 31 Jul 2024 14:06:22 +0200 Subject: [PATCH 24/49] WIP --- aggoracle/e2e_test.go | 2 +- cmd/run.go | 1 + l1infotreesync/downloader.go | 37 ++++++++-- l1infotreesync/e2e_test.go | 2 +- l1infotreesync/l1infotreesync.go | 5 +- l1infotreesync/processor.go | 68 +++++++++++++----- l1infotreesync/rollupexittree.go | 119 +++++++++++++++++++++++++++++++ 7 files changed, 207 insertions(+), 27 deletions(-) create mode 100644 l1infotreesync/rollupexittree.go diff --git a/aggoracle/e2e_test.go b/aggoracle/e2e_test.go index 3e09c906..21ad642e 100644 --- a/aggoracle/e2e_test.go +++ b/aggoracle/e2e_test.go @@ -60,7 +60,7 @@ func commonSetup(t *testing.T) ( require.NoError(t, err) // Syncer dbPathSyncer := t.TempDir() - syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), time.Millisecond, 0) + syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, common.Address{}, 10, etherman.LatestBlock, reorg, l1Client.Client(), time.Millisecond, 0) require.NoError(t, err) go syncer.Start(ctx) diff --git a/cmd/run.go b/cmd/run.go index 4bb24fc4..ef228f5e 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -382,6 +382,7 @@ func newL1InfoTreeSyncer( ctx, cfg.L1InfoTreeSync.DBPath, cfg.L1InfoTreeSync.GlobalExitRootAddr, + cfg.L1InfoTreeSync.RollupManagerAddr, cfg.L1InfoTreeSync.SyncBlockChunkSize, etherman.BlockNumberFinality(cfg.L1InfoTreeSync.BlockFinality), reorgDetector, diff --git a/l1infotreesync/downloader.go b/l1infotreesync/downloader.go index 255395dd..02f4f14d 100644 --- a/l1infotreesync/downloader.go +++ b/l1infotreesync/downloader.go @@ -3,6 +3,7 @@ package l1infotreesync import ( "fmt" + "github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry/polygonrollupmanager" "github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry/polygonzkevmglobalexitrootv2" "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum" @@ -13,7 +14,9 @@ import ( ) var ( - updateL1InfoTreeSignature = crypto.Keccak256Hash([]byte("UpdateL1InfoTree(bytes32,bytes32)")) + updateL1InfoTreeSignature = crypto.Keccak256Hash([]byte("UpdateL1InfoTree(bytes32,bytes32)")) + verifyBatchesSignature = crypto.Keccak256Hash([]byte("VerifyBatches(uint32,uint64,bytes32,bytes32,address)")) + verifyBatchesTrustedAggregatorSignature = crypto.Keccak256Hash([]byte("VerifyBatchesTrustedAggregator(uint32,uint64,bytes32,bytes32,address)")) ) type EthClienter interface { @@ -23,27 +26,47 @@ type EthClienter interface { bind.ContractBackend } -func buildAppender(client EthClienter, globalExitRoot common.Address) (sync.LogAppenderMap, error) { - contract, err := polygonzkevmglobalexitrootv2.NewPolygonzkevmglobalexitrootv2(globalExitRoot, client) +func buildAppender(client EthClienter, globalExitRoot, rollupManager common.Address) (sync.LogAppenderMap, error) { + ger, err := polygonzkevmglobalexitrootv2.NewPolygonzkevmglobalexitrootv2(globalExitRoot, client) + rm, err := polygonrollupmanager.NewPolygonrollupmanager(rollupManager, client) if err != nil { return nil, err } appender := make(sync.LogAppenderMap) appender[updateL1InfoTreeSignature] = func(b *sync.EVMBlock, l types.Log) error { - l1InfoTreeUpdate, err := contract.ParseUpdateL1InfoTree(l) + l1InfoTreeUpdate, err := ger.ParseUpdateL1InfoTree(l) if err != nil { return fmt.Errorf( - "error parsing log %+v using contract.ParseUpdateL1InfoTree: %v", + "error parsing log %+v using ger.ParseUpdateL1InfoTree: %v", l, err, ) } - b.Events = append(b.Events, Event{ + b.Events = append(b.Events, Event{UpdateL1InfoTree: &UpdateL1InfoTree{ MainnetExitRoot: l1InfoTreeUpdate.MainnetExitRoot, RollupExitRoot: l1InfoTreeUpdate.RollupExitRoot, ParentHash: b.ParentHash, Timestamp: b.Timestamp, - }) + }}) return nil } + appender[verifyBatchesSignature] = func(b *sync.EVMBlock, l types.Log) error { + verifyBatches, err := rm.ParseVerifyBatches(l) + if err != nil { + return fmt.Errorf( + "error parsing log %+v using rm.ParseVerifyBatches: %v", + l, err, + ) + } + fmt.Println(verifyBatches) + b.Events = append(b.Events, Event{VerifyBatches: &VerifyBatches{ + RollupID: verifyBatches.RollupID, + NumBatch: verifyBatches.NumBatch, + StateRoot: verifyBatches.StateRoot, + ExitRoot: verifyBatches.ExitRoot, + Aggregator: verifyBatches.Aggregator, + }}) + return nil + } + return appender, nil } diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index 35958f08..92762150 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -54,7 +54,7 @@ func TestE2E(t *testing.T) { rdm.On("AddBlockToTrack", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) client, gerAddr, gerSc, err := newSimulatedClient(auth) require.NoError(t, err) - syncer, err := New(ctx, dbPath, gerAddr, 10, etherman.LatestBlock, rdm, client.Client(), time.Millisecond, 0) + syncer, err := New(ctx, dbPath, gerAddr, common.Address{}, 10, etherman.LatestBlock, rdm, client.Client(), time.Millisecond, 0) require.NoError(t, err) go syncer.Start(ctx) diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index ed9056bc..7c45356c 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -23,6 +23,7 @@ var ( type Config struct { DBPath string `mapstructure:"DBPath"` GlobalExitRootAddr common.Address `mapstructure:"GlobalExitRootAddr"` + RollupManagerAddr common.Address `mapstructure:"RollupManagerAddr"` SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"` // TODO: BlockFinality doesnt work as per the jsonschema BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` @@ -39,7 +40,7 @@ type L1InfoTreeSync struct { func New( ctx context.Context, dbPath string, - globalExitRoot common.Address, + globalExitRoot, rollupManager common.Address, syncBlockChunkSize uint64, blockFinalityType etherman.BlockNumberFinality, rd sync.ReorgDetector, @@ -65,7 +66,7 @@ func New( } } - appender, err := buildAppender(l1Client, globalExitRoot) + appender, err := buildAppender(l1Client, globalExitRoot, rollupManager) if err != nil { return nil, err } diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 9b444d43..4821e151 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -53,17 +53,31 @@ var ( ) type processor struct { - db kv.RwDB - tree *l1infotree.L1InfoTree + db kv.RwDB + l1InfoTree *l1infotree.L1InfoTree + rollupExitTree *rollupExitTree } -type Event struct { +type UpdateL1InfoTree struct { MainnetExitRoot ethCommon.Hash RollupExitRoot ethCommon.Hash ParentHash ethCommon.Hash Timestamp uint64 } +type VerifyBatches struct { + RollupID uint32 + NumBatch uint64 + StateRoot ethCommon.Hash + ExitRoot ethCommon.Hash + Aggregator ethCommon.Address +} + +type Event struct { + UpdateL1InfoTree *UpdateL1InfoTree + VerifyBatches *VerifyBatches +} + type L1InfoTreeLeaf struct { L1InfoTreeRoot ethCommon.Hash L1InfoTreeIndex uint32 @@ -130,7 +144,9 @@ func newProcessor(ctx context.Context, dbPath string) (*processor, error) { if err != nil { return nil, err } - p.tree = tree + p.l1InfoTree = tree + rollupExitTree := newRollupExitTree() + p.rollupExitTree = rollupExitTree return p, nil } @@ -167,7 +183,7 @@ func (p *processor) ComputeMerkleProofByIndex(ctx context.Context, index uint32) if err != nil { return nil, ethCommon.Hash{}, err } - return p.tree.ComputeMerkleProof(index, leaves) + return p.l1InfoTree.ComputeMerkleProof(index, leaves) } func (p *processor) getHashedLeaves(tx kv.Tx, untilIndex uint32) ([][32]byte, error) { @@ -415,19 +431,39 @@ func (p *processor) ProcessBlock(b sync.Block) error { } else { initialIndex = lastIndex + 1 } + var nextExpectedRollupExitTreeRoot *ethCommon.Hash for i, e := range b.Events { event := e.(Event) - leafToStore := storeLeaf{ - Index: initialIndex + uint32(i), - MainnetExitRoot: event.MainnetExitRoot, - RollupExitRoot: event.RollupExitRoot, - ParentHash: event.ParentHash, - Timestamp: event.Timestamp, - BlockNumber: b.Num, + if event.UpdateL1InfoTree != nil { + leafToStore := storeLeaf{ + Index: initialIndex + uint32(i), + MainnetExitRoot: event.UpdateL1InfoTree.MainnetExitRoot, + RollupExitRoot: event.UpdateL1InfoTree.RollupExitRoot, + ParentHash: event.UpdateL1InfoTree.ParentHash, + Timestamp: event.UpdateL1InfoTree.Timestamp, + BlockNumber: b.Num, + } + if err := p.addLeaf(tx, leafToStore); err != nil { + tx.Rollback() + return err + } + nextExpectedRollupExitTreeRoot = &leafToStore.RollupExitRoot } - if err := p.addLeaf(tx, leafToStore); err != nil { - tx.Rollback() - return err + + if event.VerifyBatches != nil { + // before the verify batches event happens, the updateExitRoot event is emitted. + // Since the previous event include the rollup exit root, this can use it to assert + // that the computation of the tree is correct. However, there are some execution paths + // on the contract that don't follow this (verifyBatches + pendingStateTimeout != 0) + if err := p.rollupExitTree.addLeaf( + tx, event.VerifyBatches.RollupID, + event.VerifyBatches.ExitRoot, + nextExpectedRollupExitTreeRoot, + ); err != nil { + tx.Rollback() + return err + } + nextExpectedRollupExitTreeRoot = nil } } bwl := blockWithLeafs{ @@ -481,7 +517,7 @@ func (p *processor) getLastIndex(tx kv.Tx) (uint32, error) { func (p *processor) addLeaf(tx kv.RwTx, leaf storeLeaf) error { // Update tree hash := l1infotree.HashLeafData(leaf.GlobalExitRoot(), leaf.ParentHash, leaf.Timestamp) - root, err := p.tree.AddLeaf(leaf.Index, hash) + root, err := p.l1InfoTree.AddLeaf(leaf.Index, hash) if err != nil { return err } diff --git a/l1infotreesync/rollupexittree.go b/l1infotreesync/rollupexittree.go new file mode 100644 index 00000000..2004fcdd --- /dev/null +++ b/l1infotreesync/rollupexittree.go @@ -0,0 +1,119 @@ +package l1infotreesync + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ledgerwatch/erigon-lib/kv" + "golang.org/x/crypto/sha3" +) + +type treeNode struct { + left common.Hash + right common.Hash +} + +func (n *treeNode) hash() common.Hash { + var hash common.Hash + hasher := sha3.NewLegacyKeccak256() + hasher.Write(n.left[:]) + hasher.Write(n.right[:]) + copy(hash[:], hasher.Sum(nil)) + return hash +} + +func (n *treeNode) MarshalBinary() ([]byte, error) { + return append(n.left[:], n.right[:]...), nil +} + +func (n *treeNode) UnmarshalBinary(data []byte) error { + if len(data) != 64 { + return fmt.Errorf("expected len %d, actual len %d", 64, len(data)) + } + n.left = common.Hash(data[:32]) + n.right = common.Hash(data[32:]) + return nil +} + +type rollupExitTree struct { + height uint8 + rhtTable string + lastExitTreeRoot common.Hash +} + +func newRollupExitTree() *rollupExitTree { + return &rollupExitTree{} +} + +func (t *rollupExitTree) addLeaf( + tx kv.RwTx, + rollupID uint32, + rollupExitRoot common.Hash, + expectedRollupExitRoot *common.Hash, +) error { + siblings, err := t.getProof(tx, rollupID, t.lastExitTreeRoot) + if err != nil { + return err + } + if expectedRollupExitRoot != nil && *expectedRollupExitRoot != t.lastExitTreeRoot { + return fmt.Errorf( + "expectedRollupExitRoot: %s, actual: %s", + expectedRollupExitRoot.Hex(), t.lastExitTreeRoot.Hex(), + ) + } + return nil +} + +func (t *rollupExitTree) getSiblings(tx kv.RwTx, rollupID uint32, root common.Hash) (bool, []common.Hash, error) { + siblings := make([]common.Hash, int(t.height)) + + currentNodeHash := root + // It starts in height-1 because 0 is the level of the leafs + for h := int(t.height - 1); h >= 0; h-- { + currentNode, err := t.getRHTNode(tx, currentNodeHash) + if err != nil { + // handle not found for inserts and shit + return false, nil, fmt.Errorf( + "height: %d, currentNode: %s, error: %v", + h, currentNodeHash.Hex(), err, + ) + } + if rollupID&(1< 0 { + siblings = append(siblings, currentNode.left) + currentNodeHash = currentNode.right + } else { + siblings = append(siblings, currentNode.right) + currentNodeHash = currentNode.left + } + } + + // Reverse siblings to go from leafs to root + for i, j := 0, len(siblings)-1; i < j; i, j = i+1, j-1 { + siblings[i], siblings[j] = siblings[j], siblings[i] + } + + return false, siblings, nil + +} + +// getProof returns the merkle proof for a given deposit count and root. +func (t *rollupExitTree) getProof(tx kv.RwTx, rollupID uint32, root common.Hash) ([]common.Hash, error) { + usedEmptyTree, siblings, err := t.getSiblings(tx, rollupID, root) + if usedEmptyTree { + return nil, ErrNotFound + } + return siblings, err +} + +func (t *rollupExitTree) getRHTNode(tx kv.Tx, nodeHash common.Hash) (*treeNode, error) { + nodeBytes, err := tx.GetOne(t.rhtTable, nodeHash[:]) + if err != nil { + return nil, err + } + if nodeBytes == nil { + return nil, ErrNotFound + } + node := &treeNode{} + err = node.UnmarshalBinary(nodeBytes) + return node, err +} From c90f2d031654206cfd13413c8e74453e84f07fee Mon Sep 17 00:00:00 2001 From: Arnau Date: Wed, 31 Jul 2024 14:12:56 +0200 Subject: [PATCH 25/49] WIP --- bridgesync/processor.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bridgesync/processor.go b/bridgesync/processor.go index 36d34213..a0db15a5 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -144,7 +144,7 @@ func (p *processor) GetClaimsAndBridges( } defer c.Close() - for k, v, err := c.Seek(dbCommon.Uint64To2Bytes(fromBlock)); k != nil; k, v, err = c.Next() { + for k, v, err := c.Seek(dbCommon.Uint64ToBytes(fromBlock)); k != nil; k, v, err = c.Next() { if err != nil { return nil, err } @@ -191,7 +191,7 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { return err } defer c.Close() - firstKey := dbCommon.Uint64To2Bytes(firstReorgedBlock) + firstKey := dbCommon.Uint64ToBytes(firstReorgedBlock) firstDepositCountReorged := int64(-1) for k, v, err := c.Seek(firstKey); k != nil; k, _, err = c.Next() { if err != nil { @@ -256,7 +256,7 @@ func (p *processor) ProcessBlock(block sync.Block) error { tx.Rollback() return err } - if err := tx.Put(p.eventsTable, dbCommon.Uint64To2Bytes(block.Num), value); err != nil { + if err := tx.Put(p.eventsTable, dbCommon.Uint64ToBytes(block.Num), value); err != nil { tx.Rollback() return err } @@ -285,6 +285,6 @@ func (p *processor) ProcessBlock(block sync.Block) error { } func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error { - blockNumBytes := dbCommon.Uint64To2Bytes(blockNum) + blockNumBytes := dbCommon.Uint64ToBytes(blockNum) return tx.Put(p.lastBlockTable, lastBlokcKey, blockNumBytes) } From 486909499773e44c34867ffbae4482c1e169e5eb Mon Sep 17 00:00:00 2001 From: Arnau Date: Wed, 31 Jul 2024 17:28:27 +0200 Subject: [PATCH 26/49] abstract tree --- bridgesync/bridgesync.go | 2 +- bridgesync/processor.go | 55 ++- bridgesync/processor_test.go | 21 +- bridgesync/tree.go | 325 ------------------ bridgesync/tree_test.go | 141 -------- l1infotreesync/downloader.go | 23 +- l1infotreesync/l1infotreesync.go | 4 +- l1infotreesync/processor.go | 40 ++- l1infotreesync/rollupexittree.go | 119 ------- sync/evmdownloader.go | 8 +- sync/evmdriver.go | 12 +- sync/evmdriver_test.go | 32 +- sync/mock_processor_test.go | 20 +- tree/appendonlytree.go | 243 +++++++++++++ .../testvectors/claim-vectors.json | 0 .../testvectors/leaf-vectors.json | 0 .../testvectors/root-vectors.json | 0 tree/testvectors/types.go | 64 ++++ tree/tree.go | 175 ++++++++++ tree/tree_test.go | 103 ++++++ tree/updatabletree.go | 25 ++ 21 files changed, 719 insertions(+), 693 deletions(-) delete mode 100644 bridgesync/tree.go delete mode 100644 bridgesync/tree_test.go delete mode 100644 l1infotreesync/rollupexittree.go create mode 100644 tree/appendonlytree.go rename {bridgesync => tree}/testvectors/claim-vectors.json (100%) rename {bridgesync => tree}/testvectors/leaf-vectors.json (100%) rename {bridgesync => tree}/testvectors/root-vectors.json (100%) create mode 100644 tree/testvectors/types.go create mode 100644 tree/tree.go create mode 100644 tree/tree_test.go create mode 100644 tree/updatabletree.go diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index c9dceb77..10d79dbc 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -95,7 +95,7 @@ func new( return nil, err } if lastProcessedBlock < initialBlock { - err = processor.ProcessBlock(sync.Block{ + err = processor.ProcessBlock(ctx, sync.Block{ Num: initialBlock, }) if err != nil { diff --git a/bridgesync/processor.go b/bridgesync/processor.go index a0db15a5..72fc5a01 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -5,11 +5,12 @@ import ( "encoding/binary" "encoding/json" "errors" - "log" "math/big" + "path" dbCommon "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/sync" + "github.com/0xPolygon/cdk/tree" "github.com/ethereum/go-ethereum/common" "github.com/iden3/go-iden3-crypto/keccak256" "github.com/ledgerwatch/erigon-lib/kv" @@ -19,8 +20,6 @@ import ( const ( eventsTableSufix = "-events" lastBlockTableSufix = "-lastBlock" - rootTableSufix = "-root" - rhtTableSufix = "-rht" ) var ( @@ -84,30 +83,26 @@ type processor struct { db kv.RwDB eventsTable string lastBlockTable string - tree *tree + exitTree *tree.AppendOnlyTree } func newProcessor(ctx context.Context, dbPath, dbPrefix string) (*processor, error) { eventsTable := dbPrefix + eventsTableSufix lastBlockTable := dbPrefix + lastBlockTableSufix - rootTable := dbPrefix + rootTableSufix - rhtTable := dbPrefix + rhtTableSufix db, err := mdbx.NewMDBX(nil). Path(dbPath). WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { return kv.TableCfg{ eventsTable: {}, lastBlockTable: {}, - rootTable: {}, - rhtTable: {}, } }). Open() if err != nil { return nil, err } - - tree, err := newTree(ctx, rhtTable, rootTable, db) + exitTreeDBPath := path.Join(dbPath, "exittree") + exitTree, err := tree.NewAppendOnly(ctx, exitTreeDBPath, dbPrefix) if err != nil { return nil, err } @@ -115,7 +110,7 @@ func newProcessor(ctx context.Context, dbPath, dbPrefix string) (*processor, err db: db, eventsTable: eventsTable, lastBlockTable: lastBlockTable, - tree: tree, + exitTree: exitTree, }, nil } @@ -181,8 +176,8 @@ func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { } } -func (p *processor) Reorg(firstReorgedBlock uint64) error { - tx, err := p.db.BeginRw(context.Background()) +func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { + tx, err := p.db.BeginRw(ctx) if err != nil { return err } @@ -221,13 +216,7 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { return err } if firstDepositCountReorged != -1 { - var lastValidDepositCount uint32 - if firstDepositCountReorged == 0 { - lastValidDepositCount = 0 - } else { - lastValidDepositCount = uint32(firstDepositCountReorged) - 1 - } - if err := p.tree.reorg(tx, lastValidDepositCount); err != nil { + if err := p.exitTree.Reorg(ctx, uint32(firstDepositCountReorged)); err != nil { tx.Rollback() return err } @@ -235,8 +224,7 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { return tx.Commit() } -func (p *processor) ProcessBlock(block sync.Block) error { - ctx := context.Background() +func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { tx, err := p.db.BeginRw(ctx) if err != nil { return err @@ -267,19 +255,16 @@ func (p *processor) ProcessBlock(block sync.Block) error { return err } - for i, bridge := range bridges { - if err := p.tree.addLeaf(tx, bridge.DepositCount, bridge.Hash()); err != nil { - if i != 0 { - tx.Rollback() - if err2 := p.tree.initLastLeftCacheAndLastDepositCount(ctx); err2 != nil { - log.Fatalf( - "after failing to add a leaf to the tree with error: %v, error initializing the cache with error: %v", - err, err2, - ) - } - return err - } - } + leaves := []tree.Leaf{} + for _, bridge := range bridges { + leaves = append(leaves, tree.Leaf{ + Index: bridge.DepositCount, + Hash: bridge.Hash(), + }) + } + if err := p.exitTree.AddLeaves(ctx, leaves); err != nil { + tx.Rollback() + return err } return tx.Commit() } diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index c1535d70..7d337d08 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/0xPolygon/cdk/sync" + "github.com/0xPolygon/cdk/tree/testvectors" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -391,7 +392,7 @@ func (a *reorgAction) desc() string { } func (a *reorgAction) execute(t *testing.T) { - actualErr := a.p.Reorg(a.firstReorgedBlock) + actualErr := a.p.Reorg(context.Background(), a.firstReorgedBlock) require.Equal(t, a.expectedErr, actualErr) } @@ -413,7 +414,7 @@ func (a *processBlockAction) desc() string { } func (a *processBlockAction) execute(t *testing.T) { - actualErr := a.p.ProcessBlock(a.block) + actualErr := a.p.ProcessBlock(context.Background(), a.block) require.Equal(t, a.expectedErr, actualErr) } @@ -425,23 +426,11 @@ func eventsToBridgeEvents(events []interface{}) []Event { return bridgeEvents } -// DepositVectorRaw represents the deposit vector -type DepositVectorRaw struct { - OriginalNetwork uint32 `json:"originNetwork"` - TokenAddress string `json:"tokenAddress"` - Amount string `json:"amount"` - DestinationNetwork uint32 `json:"destinationNetwork"` - DestinationAddress string `json:"destinationAddress"` - ExpectedHash string `json:"leafValue"` - CurrentHash string `json:"currentLeafValue"` - Metadata string `json:"metadata"` -} - func TestHashBridge(t *testing.T) { - data, err := os.ReadFile("testvectors/leaf-vectors.json") + data, err := os.ReadFile("../tree/testvectors/leaf-vectors.json") require.NoError(t, err) - var leafVectors []DepositVectorRaw + var leafVectors []testvectors.DepositVectorRaw err = json.Unmarshal(data, &leafVectors) require.NoError(t, err) diff --git a/bridgesync/tree.go b/bridgesync/tree.go deleted file mode 100644 index 9972af64..00000000 --- a/bridgesync/tree.go +++ /dev/null @@ -1,325 +0,0 @@ -package bridgesync - -import ( - "context" - "fmt" - "math" - - dbCommon "github.com/0xPolygon/cdk/common" - "github.com/ethereum/go-ethereum/common" - "github.com/ledgerwatch/erigon-lib/kv" - "golang.org/x/crypto/sha3" -) - -const ( - defaultHeight uint8 = 32 -) - -type tree struct { - db kv.RwDB - rhtTable string - rootTable string - height uint8 - lastDepositCount int64 - lastLeftCache []common.Hash - zeroHashes []common.Hash -} - -type treeNode struct { - left common.Hash - right common.Hash -} - -func (n *treeNode) hash() common.Hash { - var hash common.Hash - hasher := sha3.NewLegacyKeccak256() - hasher.Write(n.left[:]) - hasher.Write(n.right[:]) - copy(hash[:], hasher.Sum(nil)) - return hash -} - -func (n *treeNode) MarshalBinary() ([]byte, error) { - return append(n.left[:], n.right[:]...), nil -} - -func (n *treeNode) UnmarshalBinary(data []byte) error { - if len(data) != 64 { - return fmt.Errorf("expected len %d, actual len %d", 64, len(data)) - } - n.left = common.Hash(data[:32]) - n.right = common.Hash(data[32:]) - return nil -} - -func newTree(ctx context.Context, rhtTable, rootTable string, db kv.RwDB) (*tree, error) { - t := &tree{ - rhtTable: rhtTable, - rootTable: rootTable, - db: db, - height: defaultHeight, - zeroHashes: generateZeroHashes(defaultHeight), - } - - if err := t.initLastLeftCacheAndLastDepositCount(ctx); err != nil { - return nil, err - } - - return t, nil -} - -// getProof returns the merkle proof for a given deposit count and root. -func (t *tree) getProof(ctx context.Context, depositCount uint32, root common.Hash) ([]common.Hash, error) { - tx, err := t.db.BeginRw(ctx) - if err != nil { - return nil, err - } - defer tx.Rollback() - siblings := make([]common.Hash, int(t.height)) - - currentNodeHash := root - // It starts in height-1 because 0 is the level of the leafs - for h := int(t.height - 1); h >= 0; h-- { - currentNode, err := t.getRHTNode(tx, currentNodeHash) - if err != nil { - return nil, fmt.Errorf( - "height: %d, currentNode: %s, error: %v", - h, currentNodeHash.Hex(), err, - ) - } - /* - * Root (level h=3 => height=4) - * / \ - * O5 O6 (level h=2) - * / \ / \ - * O1 O2 O3 O4 (level h=1) - * /\ /\ /\ /\ - * 0 1 2 3 4 5 6 7 Leafs (level h=0) - * Example 1: - * Choose index = 3 => 011 binary - * Assuming we are in level 1 => h=1; 1< 011&010=010 which is higher than 0 so we need the left sibling (O1) - * Example 2: - * Choose index = 4 => 100 binary - * Assuming we are in level 1 => h=1; 1< 100&010=000 which is not higher than 0 so we need the right sibling (O4) - * Example 3: - * Choose index = 4 => 100 binary - * Assuming we are in level 2 => h=2; 1< 100&100=100 which is higher than 0 so we need the left sibling (O5) - */ - if depositCount&(1< 0 { - siblings = append(siblings, currentNode.left) - currentNodeHash = currentNode.right - } else { - siblings = append(siblings, currentNode.right) - currentNodeHash = currentNode.left - } - } - - // Reverse siblings to go from leafs to root - for i, j := 0, len(siblings)-1; i < j; i, j = i+1, j-1 { - siblings[i], siblings[j] = siblings[j], siblings[i] - } - - return siblings, nil -} - -func (t *tree) addLeaf(tx kv.RwTx, depositCount uint32, hash common.Hash) error { - // Sanity check - if int64(depositCount) != t.lastDepositCount+1 { - return fmt.Errorf( - "mismatched index. Expected: %d, actual: %d", - t.lastDepositCount+1, depositCount, - ) - } - - // Calculate new tree nodes - currentChildHash := hash - newNodes := []treeNode{} - for h := uint8(0); h < t.height; h++ { - var parent treeNode - if depositCount&(1< 0 { - // Add child to the right - parent = treeNode{ - left: t.lastLeftCache[h], - right: currentChildHash, - } - } else { - // Add child to the left - parent = treeNode{ - left: currentChildHash, - right: t.zeroHashes[h], - } - // Update cache - // TODO: review this part of the logic, skipping ?optimizaton? - // from OG implementation - t.lastLeftCache[h] = currentChildHash - } - currentChildHash = parent.hash() - newNodes = append(newNodes, parent) - } - - // store root - root := currentChildHash - if err := tx.Put(t.rootTable, dbCommon.Uint32ToBytes(depositCount), root[:]); err != nil { - return err - } - - // store nodes - for _, node := range newNodes { - value, err := node.MarshalBinary() - if err != nil { - return err - } - if err := tx.Put(t.rhtTable, node.hash().Bytes(), value); err != nil { - return err - } - } - - t.lastDepositCount++ - return nil -} - -func (t *tree) initLastLeftCacheAndLastDepositCount(ctx context.Context) error { - tx, err := t.db.BeginRw(ctx) - if err != nil { - return err - } - defer tx.Rollback() - - root, err := t.initLastDepositCount(tx) - if err != nil { - return err - } - return t.initLastLeftCache(tx, t.lastDepositCount, root) -} - -// getLastDepositCountAndRoot return the deposit count and the root associated to the last deposit. -// If deposit count == -1, it means no deposit added yet -func (t *tree) getLastDepositCountAndRoot(tx kv.Tx) (int64, common.Hash, error) { - iter, err := tx.RangeDescend( - t.rootTable, - dbCommon.Uint32ToBytes(math.MaxUint32), - dbCommon.Uint32ToBytes(0), - 1, - ) - if err != nil { - return 0, common.Hash{}, err - } - - lastDepositCountBytes, rootBytes, err := iter.Next() - if err != nil { - return 0, common.Hash{}, err - } - if lastDepositCountBytes == nil { - return -1, common.Hash{}, nil - } - return int64(dbCommon.BytesToUint32(lastDepositCountBytes)), common.Hash(rootBytes), nil -} - -func (t *tree) initLastDepositCount(tx kv.Tx) (common.Hash, error) { - ldc, root, err := t.getLastDepositCountAndRoot(tx) - if err != nil { - return common.Hash{}, err - } - t.lastDepositCount = ldc - return root, nil -} - -func (t *tree) initLastLeftCache(tx kv.Tx, lastDepositCount int64, lastRoot common.Hash) error { - siblings := make([]common.Hash, t.height, t.height) - if lastDepositCount == -1 { - t.lastLeftCache = siblings - return nil - } - index := lastDepositCount - - currentNodeHash := lastRoot - // It starts in height-1 because 0 is the level of the leafs - for h := int(t.height - 1); h >= 0; h-- { - currentNode, err := t.getRHTNode(tx, currentNodeHash) - if err != nil { - return fmt.Errorf( - "error getting node %s from the RHT at height %d with root %s: %v", - currentNodeHash.Hex(), h, lastRoot.Hex(), err, - ) - } - if currentNode == nil { - return ErrNotFound - } - siblings = append(siblings, currentNode.left) - if index&(1< 0 { - currentNodeHash = currentNode.right - } else { - currentNodeHash = currentNode.left - } - } - - // Reverse the siblings to go from leafs to root - for i, j := 0, len(siblings)-1; i < j; i, j = i+1, j-1 { - siblings[i], siblings[j] = siblings[j], siblings[i] - } - - t.lastLeftCache = siblings - return nil -} - -func (t *tree) getRHTNode(tx kv.Tx, nodeHash common.Hash) (*treeNode, error) { - nodeBytes, err := tx.GetOne(t.rhtTable, nodeHash[:]) - if err != nil { - return nil, err - } - if nodeBytes == nil { - return nil, ErrNotFound - } - node := &treeNode{} - err = node.UnmarshalBinary(nodeBytes) - return node, err -} - -func (t *tree) reorg(tx kv.RwTx, lastValidDepositCount uint32) error { - if t.lastDepositCount == -1 { - return nil - } - // Clean root table - for i := lastValidDepositCount + 1; i <= uint32(t.lastDepositCount); i++ { - if err := tx.Delete(t.rootTable, dbCommon.Uint32ToBytes(i)); err != nil { - return err - } - } - - // Reset cache - rootBytes, err := tx.GetOne(t.rootTable, dbCommon.Uint32ToBytes(lastValidDepositCount)) - if err != nil { - return err - } - if rootBytes == nil { - return ErrNotFound - } - err = t.initLastLeftCache(tx, int64(lastValidDepositCount), common.Hash(rootBytes)) // 0x619a9fedbe029225288d32e39e06fb868ed0d8f20db26047cf0ef8d3582b5f6e - if err != nil { - return err - } - - // Note: not cleaning RHT, not worth it - t.lastDepositCount = int64(lastValidDepositCount) - return nil -} - -func generateZeroHashes(height uint8) []common.Hash { - var zeroHashes = []common.Hash{ - {}, - } - // This generates a leaf = HashZero in position 0. In the rest of the positions that are equivalent to the ascending levels, - // we set the hashes of the nodes. So all nodes from level i=5 will have the same value and same children nodes. - for i := 1; i <= int(height); i++ { - hasher := sha3.NewLegacyKeccak256() - hasher.Write(zeroHashes[i-1][:]) - hasher.Write(zeroHashes[i-1][:]) - thisHeightHash := common.Hash{} - copy(thisHeightHash[:], hasher.Sum(nil)) - zeroHashes = append(zeroHashes, thisHeightHash) - } - return zeroHashes -} diff --git a/bridgesync/tree_test.go b/bridgesync/tree_test.go deleted file mode 100644 index 5624ad47..00000000 --- a/bridgesync/tree_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package bridgesync - -import ( - "context" - "encoding/json" - "fmt" - "math/big" - "os" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" -) - -// MTRootVectorRaw represents the root of Merkle Tree -type MTRootVectorRaw struct { - ExistingLeaves []string `json:"previousLeafsValues"` - CurrentRoot string `json:"currentRoot"` - NewLeaf DepositVectorRaw `json:"newLeaf"` - NewRoot string `json:"newRoot"` -} - -func TestMTAddLeaf(t *testing.T) { - data, err := os.ReadFile("testvectors/root-vectors.json") - require.NoError(t, err) - - var mtTestVectors []MTRootVectorRaw - err = json.Unmarshal(data, &mtTestVectors) - require.NoError(t, err) - ctx := context.Background() - - for ti, testVector := range mtTestVectors { - t.Run(fmt.Sprintf("Test vector %d", ti), func(t *testing.T) { - - path := t.TempDir() - p, err := newProcessor(context.Background(), path, "foo") - require.NoError(t, err) - - // Add exisiting leaves - for i, leaf := range testVector.ExistingLeaves { - tx, err := p.db.BeginRw(ctx) - require.NoError(t, err) - err = p.tree.addLeaf(tx, uint32(i), common.HexToHash(leaf)) - require.NoError(t, err) - err = tx.Commit() - require.NoError(t, err) - } - if len(testVector.ExistingLeaves) > 0 { - txRo, err := p.db.BeginRo(ctx) - require.NoError(t, err) - _, actualRoot, err := p.tree.getLastDepositCountAndRoot(txRo) - txRo.Rollback() - require.NoError(t, err) - require.Equal(t, common.HexToHash(testVector.CurrentRoot), actualRoot) - } - - // Add new bridge - amount, result := big.NewInt(0).SetString(testVector.NewLeaf.Amount, 0) - require.True(t, result) - bridge := Bridge{ - OriginNetwork: testVector.NewLeaf.OriginalNetwork, - OriginAddress: common.HexToAddress(testVector.NewLeaf.TokenAddress), - Amount: amount, - DestinationNetwork: testVector.NewLeaf.DestinationNetwork, - DestinationAddress: common.HexToAddress(testVector.NewLeaf.DestinationAddress), - DepositCount: uint32(len(testVector.ExistingLeaves)), - Metadata: common.FromHex(testVector.NewLeaf.Metadata), - } - tx, err := p.db.BeginRw(ctx) - require.NoError(t, err) - require.Equal(t, common.HexToHash(testVector.NewLeaf.CurrentHash), bridge.Hash()) - err = p.tree.addLeaf(tx, bridge.DepositCount, bridge.Hash()) - require.NoError(t, err) - err = tx.Commit() - txRo, err := p.db.BeginRo(ctx) - require.NoError(t, err) - _, actualRoot, err := p.tree.getLastDepositCountAndRoot(txRo) - txRo.Rollback() - require.NoError(t, err) - require.Equal(t, common.HexToHash(testVector.NewRoot), actualRoot) - }) - } -} - -// MTClaimVectorRaw represents the merkle proof -type MTClaimVectorRaw struct { - Deposits []DepositVectorRaw `json:"leafs"` - Index uint32 `json:"index"` - MerkleProof []string `json:"proof"` - ExpectedRoot string `json:"root"` -} - -func TestMTGetProof(t *testing.T) { - data, err := os.ReadFile("testvectors/claim-vectors.json") - require.NoError(t, err) - - var mtTestVectors []MTClaimVectorRaw - err = json.Unmarshal(data, &mtTestVectors) - require.NoError(t, err) - ctx := context.Background() - - for ti, testVector := range mtTestVectors { - t.Run(fmt.Sprintf("Test vector %d", ti), func(t *testing.T) { - path := t.TempDir() - p, err := newProcessor(context.Background(), path, "foo") - require.NoError(t, err) - - for li, leaf := range testVector.Deposits { - amount, result := big.NewInt(0).SetString(leaf.Amount, 0) - require.True(t, result) - bridge := &Bridge{ - OriginNetwork: leaf.OriginalNetwork, - OriginAddress: common.HexToAddress(leaf.TokenAddress), - Amount: amount, - DestinationNetwork: leaf.DestinationNetwork, - DestinationAddress: common.HexToAddress(leaf.DestinationAddress), - DepositCount: uint32(li), - Metadata: common.FromHex(leaf.Metadata), - } - tx, err := p.db.BeginRw(ctx) - require.NoError(t, err) - err = p.tree.addLeaf(tx, bridge.DepositCount, bridge.Hash()) - require.NoError(t, err) - err = tx.Commit() - require.NoError(t, err) - } - txRo, err := p.db.BeginRo(ctx) - require.NoError(t, err) - _, actualRoot, err := p.tree.getLastDepositCountAndRoot(txRo) - txRo.Rollback() - expectedRoot := common.HexToHash(testVector.ExpectedRoot) - require.Equal(t, expectedRoot, actualRoot) - - proof, err := p.tree.getProof(ctx, testVector.Index, expectedRoot) - require.NoError(t, err) - for i, sibling := range testVector.MerkleProof { - require.Equal(t, common.HexToHash(sibling), proof[i]) - } - }) - } -} diff --git a/l1infotreesync/downloader.go b/l1infotreesync/downloader.go index 02f4f14d..dbe6950a 100644 --- a/l1infotreesync/downloader.go +++ b/l1infotreesync/downloader.go @@ -3,8 +3,8 @@ package l1infotreesync import ( "fmt" - "github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry/polygonrollupmanager" "github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry/polygonzkevmglobalexitrootv2" + "github.com/0xPolygon/cdk-contracts-tooling/contracts/etrog/polygonrollupmanager" "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -28,6 +28,9 @@ type EthClienter interface { func buildAppender(client EthClienter, globalExitRoot, rollupManager common.Address) (sync.LogAppenderMap, error) { ger, err := polygonzkevmglobalexitrootv2.NewPolygonzkevmglobalexitrootv2(globalExitRoot, client) + if err != nil { + return nil, err + } rm, err := polygonrollupmanager.NewPolygonrollupmanager(rollupManager, client) if err != nil { return nil, err @@ -67,6 +70,24 @@ func buildAppender(client EthClienter, globalExitRoot, rollupManager common.Addr }}) return nil } + appender[verifyBatchesTrustedAggregatorSignature] = func(b *sync.EVMBlock, l types.Log) error { + verifyBatches, err := rm.ParseVerifyBatchesTrustedAggregator(l) + if err != nil { + return fmt.Errorf( + "error parsing log %+v using rm.ParseVerifyBatches: %v", + l, err, + ) + } + fmt.Println(verifyBatches) + b.Events = append(b.Events, Event{VerifyBatches: &VerifyBatches{ + RollupID: verifyBatches.RollupID, + NumBatch: verifyBatches.NumBatch, + StateRoot: verifyBatches.StateRoot, + ExitRoot: verifyBatches.ExitRoot, + Aggregator: verifyBatches.Aggregator, + }}) + return nil + } return appender, nil } diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index 7c45356c..67342613 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -58,7 +58,7 @@ func New( return nil, err } if lastProcessedBlock < initialBlock { - err = processor.ProcessBlock(sync.Block{ + err = processor.ProcessBlock(ctx, sync.Block{ Num: initialBlock, }) if err != nil { @@ -76,7 +76,7 @@ func New( blockFinalityType, waitForNewBlocksPeriod, appender, - []common.Address{globalExitRoot}, + []common.Address{globalExitRoot, rollupManager}, ) if err != nil { return nil, err diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 4821e151..927eff82 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -5,11 +5,12 @@ import ( "encoding/json" "errors" "fmt" + "path" "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/l1infotree" - "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/sync" + "github.com/0xPolygon/cdk/tree" ethCommon "github.com/ethereum/go-ethereum/common" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/kv/mdbx" @@ -17,30 +18,31 @@ import ( ) const ( + dbPrefix = "l1infotreesync" // rootTable stores the L1 info tree roots // Key: root (common.Hash) // Value: hash of the leaf that caused the update (common.Hash) - rootTable = "l1infotreesync-root" + rootTable = dbPrefix + "-root" // indexTable stores the L1 info tree indexes // Key: index (uint32 converted to bytes) // Value: hash of the leaf that caused the update (common.Hash) - indexTable = "l1infotreesync-index" + indexTable = dbPrefix + "-index" // infoTable stores the information of the tree (the leaves). Note that the value // of rootTable and indexTable references the key of the infoTable // Key: hash of the leaf that caused the update (common.Hash) // Value: JSON of storeLeaf struct - infoTable = "l1infotreesync-info" + infoTable = dbPrefix + "-info" // blockTable stores the first and last index of L1 Info Tree that have been updated on // a block. This is useful in case there are blocks with multiple updates and a reorg is needed. // Or for when querying by block number // Key: block number (uint64 converted to bytes) // Value: JSON of blockWithLeafs - blockTable = "l1infotreesync-block" + blockTable = dbPrefix + "-block" // lastBlockTable used to store the last block processed. This is needed to know the last processed blcok // when it doesn't have events that make other tables get populated // Key: it's always lastBlockKey // Value: block number (uint64 converted to bytes) - lastBlockTable = "l1infotreesync-lastBlock" + lastBlockTable = dbPrefix + "-lastBlock" treeHeight uint8 = 32 ) @@ -55,7 +57,7 @@ var ( type processor struct { db kv.RwDB l1InfoTree *l1infotree.L1InfoTree - rollupExitTree *rollupExitTree + rollupExitTree *tree.UpdatableTree } type UpdateL1InfoTree struct { @@ -140,12 +142,16 @@ func newProcessor(ctx context.Context, dbPath string) (*processor, error) { if err != nil { return nil, err } - tree, err := l1infotree.NewL1InfoTree(treeHeight, leaves) + l1InfoTree, err := l1infotree.NewL1InfoTree(treeHeight, leaves) + if err != nil { + return nil, err + } + p.l1InfoTree = l1InfoTree + rollupExitTreeDBPath := path.Join(dbPath, "rollupExitTree") + rollupExitTree, err := tree.NewUpdatable(ctx, rollupExitTreeDBPath, dbPrefix) if err != nil { return nil, err } - p.l1InfoTree = tree - rollupExitTree := newRollupExitTree() p.rollupExitTree = rollupExitTree return p, nil } @@ -338,9 +344,9 @@ func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { return common.BytesToUint64(blockNumBytes), nil } -func (p *processor) Reorg(firstReorgedBlock uint64) error { +func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { // TODO: Does tree need to be reorged? - tx, err := p.db.BeginRw(context.Background()) + tx, err := p.db.BeginRw(ctx) if err != nil { return err } @@ -415,8 +421,8 @@ func (p *processor) deleteLeaf(tx kv.RwTx, index uint32) error { // ProcessBlock process the leafs of the L1 info tree found on a block // this function can be called without leafs with the intention to track the last processed block -func (p *processor) ProcessBlock(b sync.Block) error { - tx, err := p.db.BeginRw(context.Background()) +func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { + tx, err := p.db.BeginRw(ctx) if err != nil { return err } @@ -455,8 +461,9 @@ func (p *processor) ProcessBlock(b sync.Block) error { // Since the previous event include the rollup exit root, this can use it to assert // that the computation of the tree is correct. However, there are some execution paths // on the contract that don't follow this (verifyBatches + pendingStateTimeout != 0) - if err := p.rollupExitTree.addLeaf( - tx, event.VerifyBatches.RollupID, + if err := p.rollupExitTree.UpsertLeaf( + ctx, + event.VerifyBatches.RollupID, event.VerifyBatches.ExitRoot, nextExpectedRollupExitTreeRoot, ); err != nil { @@ -484,7 +491,6 @@ func (p *processor) ProcessBlock(b sync.Block) error { tx.Rollback() return err } - log.Debugf("block %d processed with events: %+v", b.Num, b.Events) return tx.Commit() } diff --git a/l1infotreesync/rollupexittree.go b/l1infotreesync/rollupexittree.go deleted file mode 100644 index 2004fcdd..00000000 --- a/l1infotreesync/rollupexittree.go +++ /dev/null @@ -1,119 +0,0 @@ -package l1infotreesync - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ledgerwatch/erigon-lib/kv" - "golang.org/x/crypto/sha3" -) - -type treeNode struct { - left common.Hash - right common.Hash -} - -func (n *treeNode) hash() common.Hash { - var hash common.Hash - hasher := sha3.NewLegacyKeccak256() - hasher.Write(n.left[:]) - hasher.Write(n.right[:]) - copy(hash[:], hasher.Sum(nil)) - return hash -} - -func (n *treeNode) MarshalBinary() ([]byte, error) { - return append(n.left[:], n.right[:]...), nil -} - -func (n *treeNode) UnmarshalBinary(data []byte) error { - if len(data) != 64 { - return fmt.Errorf("expected len %d, actual len %d", 64, len(data)) - } - n.left = common.Hash(data[:32]) - n.right = common.Hash(data[32:]) - return nil -} - -type rollupExitTree struct { - height uint8 - rhtTable string - lastExitTreeRoot common.Hash -} - -func newRollupExitTree() *rollupExitTree { - return &rollupExitTree{} -} - -func (t *rollupExitTree) addLeaf( - tx kv.RwTx, - rollupID uint32, - rollupExitRoot common.Hash, - expectedRollupExitRoot *common.Hash, -) error { - siblings, err := t.getProof(tx, rollupID, t.lastExitTreeRoot) - if err != nil { - return err - } - if expectedRollupExitRoot != nil && *expectedRollupExitRoot != t.lastExitTreeRoot { - return fmt.Errorf( - "expectedRollupExitRoot: %s, actual: %s", - expectedRollupExitRoot.Hex(), t.lastExitTreeRoot.Hex(), - ) - } - return nil -} - -func (t *rollupExitTree) getSiblings(tx kv.RwTx, rollupID uint32, root common.Hash) (bool, []common.Hash, error) { - siblings := make([]common.Hash, int(t.height)) - - currentNodeHash := root - // It starts in height-1 because 0 is the level of the leafs - for h := int(t.height - 1); h >= 0; h-- { - currentNode, err := t.getRHTNode(tx, currentNodeHash) - if err != nil { - // handle not found for inserts and shit - return false, nil, fmt.Errorf( - "height: %d, currentNode: %s, error: %v", - h, currentNodeHash.Hex(), err, - ) - } - if rollupID&(1< 0 { - siblings = append(siblings, currentNode.left) - currentNodeHash = currentNode.right - } else { - siblings = append(siblings, currentNode.right) - currentNodeHash = currentNode.left - } - } - - // Reverse siblings to go from leafs to root - for i, j := 0, len(siblings)-1; i < j; i, j = i+1, j-1 { - siblings[i], siblings[j] = siblings[j], siblings[i] - } - - return false, siblings, nil - -} - -// getProof returns the merkle proof for a given deposit count and root. -func (t *rollupExitTree) getProof(tx kv.RwTx, rollupID uint32, root common.Hash) ([]common.Hash, error) { - usedEmptyTree, siblings, err := t.getSiblings(tx, rollupID, root) - if usedEmptyTree { - return nil, ErrNotFound - } - return siblings, err -} - -func (t *rollupExitTree) getRHTNode(tx kv.Tx, nodeHash common.Hash) (*treeNode, error) { - nodeBytes, err := tx.GetOne(t.rhtTable, nodeHash[:]) - if err != nil { - return nil, err - } - if nodeBytes == nil { - return nil, ErrNotFound - } - node := &treeNode{} - err = node.UnmarshalBinary(nodeBytes) - return node, err -} diff --git a/sync/evmdownloader.go b/sync/evmdownloader.go index ebdde880..9a5efd5f 100644 --- a/sync/evmdownloader.go +++ b/sync/evmdownloader.go @@ -46,9 +46,9 @@ func NewEVMDownloader( if err != nil { return nil, err } - topicsToQuery := [][]common.Hash{} + topicsToQuery := []common.Hash{} for topic := range appender { - topicsToQuery = append(topicsToQuery, []common.Hash{topic}) + topicsToQuery = append(topicsToQuery, topic) } return &EVMDownloader{ syncBlockChunkSize: syncBlockChunkSize, @@ -57,7 +57,7 @@ func NewEVMDownloader( blockFinality: finality, waitForNewBlocksPeriod: waitForNewBlocksPeriod, appender: appender, - topicsToQuery: topicsToQuery, + topicsToQuery: [][]common.Hash{topicsToQuery}, adressessToQuery: adressessToQuery, }, }, nil @@ -90,7 +90,7 @@ func (d *EVMDownloader) download(ctx context.Context, fromBlock uint64, download } if len(blocks) == 0 || blocks[len(blocks)-1].Num < toBlock { // Indicate the last downloaded block if there are not events on it - log.Debugf("sending block %d to the driver (without evvents)", toBlock) + log.Debugf("sending block %d to the driver (without events)", toBlock) downloadedCh <- EVMBlock{ EVMBlockHeader: d.getBlockHeader(ctx, toBlock), } diff --git a/sync/evmdriver.go b/sync/evmdriver.go index a30b96d6..9eabe644 100644 --- a/sync/evmdriver.go +++ b/sync/evmdriver.go @@ -24,8 +24,8 @@ type EVMDriver struct { type processorInterface interface { GetLastProcessedBlock(ctx context.Context) (uint64, error) - ProcessBlock(block Block) error - Reorg(firstReorgedBlock uint64) error + ProcessBlock(ctx context.Context, block Block) error + Reorg(ctx context.Context, firstReorgedBlock uint64) error } type ReorgDetector interface { @@ -85,7 +85,7 @@ reset: d.handleNewBlock(ctx, b) case firstReorgedBlock := <-d.reorgSub.FirstReorgedBlock: log.Debug("handleReorg") - d.handleReorg(cancel, downloadCh, firstReorgedBlock) + d.handleReorg(ctx, cancel, downloadCh, firstReorgedBlock) goto reset } } @@ -109,7 +109,7 @@ func (d *EVMDriver) handleNewBlock(ctx context.Context, b EVMBlock) { Num: b.Num, Events: b.Events, } - err := d.processor.ProcessBlock(blockToProcess) + err := d.processor.ProcessBlock(ctx, blockToProcess) if err != nil { attempts++ log.Errorf("error processing events for blcok %d, err: ", b.Num, err) @@ -121,7 +121,7 @@ func (d *EVMDriver) handleNewBlock(ctx context.Context, b EVMBlock) { } func (d *EVMDriver) handleReorg( - cancel context.CancelFunc, downloadCh chan EVMBlock, firstReorgedBlock uint64, + ctx context.Context, cancel context.CancelFunc, downloadCh chan EVMBlock, firstReorgedBlock uint64, ) { // stop downloader cancel() @@ -132,7 +132,7 @@ func (d *EVMDriver) handleReorg( // handle reorg attempts := 0 for { - err := d.processor.Reorg(firstReorgedBlock) + err := d.processor.Reorg(ctx, firstReorgedBlock) if err != nil { attempts++ log.Errorf( diff --git a/sync/evmdriver_test.go b/sync/evmdriver_test.go index 502722f6..a09b94a8 100644 --- a/sync/evmdriver_test.go +++ b/sync/evmdriver_test.go @@ -78,18 +78,18 @@ func TestSync(t *testing.T) { Return(uint64(3), nil) rdm.On("AddBlockToTrack", ctx, reorgDetectorID, expectedBlock1.Num, expectedBlock1.Hash). Return(nil) - pm.On("ProcessBlock", Block{Num: expectedBlock1.Num, Events: expectedBlock1.Events}). + pm.On("ProcessBlock", ctx, Block{Num: expectedBlock1.Num, Events: expectedBlock1.Events}). Return(nil) rdm.On("AddBlockToTrack", ctx, reorgDetectorID, expectedBlock2.Num, expectedBlock2.Hash). Return(nil) - pm.On("ProcessBlock", Block{Num: expectedBlock2.Num, Events: expectedBlock2.Events}). + pm.On("ProcessBlock", ctx, Block{Num: expectedBlock2.Num, Events: expectedBlock2.Events}). Return(nil) go driver.Sync(ctx) time.Sleep(time.Millisecond * 200) // time to download expectedBlock1 // Trigger reorg 1 reorgedBlock1 := uint64(5) - pm.On("Reorg", reorgedBlock1).Return(nil) + pm.On("Reorg", ctx, reorgedBlock1).Return(nil) firstReorgedBlock <- reorgedBlock1 ok := <-reorgProcessed require.True(t, ok) @@ -100,7 +100,7 @@ func TestSync(t *testing.T) { // Trigger reorg 2: syncer restarts the porcess reorgedBlock2 := uint64(7) - pm.On("Reorg", reorgedBlock2).Return(nil) + pm.On("Reorg", ctx, reorgedBlock2).Return(nil) firstReorgedBlock <- reorgedBlock2 ok = <-reorgProcessed require.True(t, ok) @@ -126,7 +126,7 @@ func TestHandleNewBlock(t *testing.T) { rdm. On("AddBlockToTrack", ctx, reorgDetectorID, b1.Num, b1.Hash). Return(nil) - pm.On("ProcessBlock", Block{Num: b1.Num, Events: b1.Events}). + pm.On("ProcessBlock", ctx, Block{Num: b1.Num, Events: b1.Events}). Return(nil) driver.handleNewBlock(ctx, b1) @@ -143,7 +143,7 @@ func TestHandleNewBlock(t *testing.T) { rdm. On("AddBlockToTrack", ctx, reorgDetectorID, b2.Num, b2.Hash). Return(nil).Once() - pm.On("ProcessBlock", Block{Num: b2.Num, Events: b2.Events}). + pm.On("ProcessBlock", ctx, Block{Num: b2.Num, Events: b2.Events}). Return(nil) driver.handleNewBlock(ctx, b2) @@ -157,9 +157,9 @@ func TestHandleNewBlock(t *testing.T) { rdm. On("AddBlockToTrack", ctx, reorgDetectorID, b3.Num, b3.Hash). Return(nil) - pm.On("ProcessBlock", Block{Num: b3.Num, Events: b3.Events}). + pm.On("ProcessBlock", ctx, Block{Num: b3.Num, Events: b3.Events}). Return(errors.New("foo")).Once() - pm.On("ProcessBlock", Block{Num: b3.Num, Events: b3.Events}). + pm.On("ProcessBlock", ctx, Block{Num: b3.Num, Events: b3.Events}). Return(nil).Once() driver.handleNewBlock(ctx, b3) @@ -182,8 +182,8 @@ func TestHandleReorg(t *testing.T) { _, cancel := context.WithCancel(ctx) downloadCh := make(chan EVMBlock) firstReorgedBlock := uint64(5) - pm.On("Reorg", firstReorgedBlock).Return(nil) - go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) + pm.On("Reorg", ctx, firstReorgedBlock).Return(nil) + go driver.handleReorg(ctx, cancel, downloadCh, firstReorgedBlock) close(downloadCh) done := <-reorgProcessed require.True(t, done) @@ -192,8 +192,8 @@ func TestHandleReorg(t *testing.T) { _, cancel = context.WithCancel(ctx) downloadCh = make(chan EVMBlock) firstReorgedBlock = uint64(6) - pm.On("Reorg", firstReorgedBlock).Return(nil) - go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) + pm.On("Reorg", ctx, firstReorgedBlock).Return(nil) + go driver.handleReorg(ctx, cancel, downloadCh, firstReorgedBlock) downloadCh <- EVMBlock{} downloadCh <- EVMBlock{} downloadCh <- EVMBlock{} @@ -205,10 +205,10 @@ func TestHandleReorg(t *testing.T) { _, cancel = context.WithCancel(ctx) downloadCh = make(chan EVMBlock) firstReorgedBlock = uint64(7) - pm.On("Reorg", firstReorgedBlock).Return(errors.New("foo")).Once() - pm.On("Reorg", firstReorgedBlock).Return(errors.New("foo")).Once() - pm.On("Reorg", firstReorgedBlock).Return(nil).Once() - go driver.handleReorg(cancel, downloadCh, firstReorgedBlock) + pm.On("Reorg", ctx, firstReorgedBlock).Return(errors.New("foo")).Once() + pm.On("Reorg", ctx, firstReorgedBlock).Return(errors.New("foo")).Once() + pm.On("Reorg", ctx, firstReorgedBlock).Return(nil).Once() + go driver.handleReorg(ctx, cancel, downloadCh, firstReorgedBlock) close(downloadCh) done = <-reorgProcessed require.True(t, done) diff --git a/sync/mock_processor_test.go b/sync/mock_processor_test.go index d2c3e299..19738ef5 100644 --- a/sync/mock_processor_test.go +++ b/sync/mock_processor_test.go @@ -37,13 +37,13 @@ func (_m *ProcessorMock) GetLastProcessedBlock(ctx context.Context) (uint64, err return r0, r1 } -// ProcessBlock provides a mock function with given fields: block -func (_m *ProcessorMock) ProcessBlock(block Block) error { - ret := _m.Called(block) +// ProcessBlock provides a mock function with given fields: ctx, block +func (_m *ProcessorMock) ProcessBlock(ctx context.Context, block Block) error { + ret := _m.Called(ctx, block) var r0 error - if rf, ok := ret.Get(0).(func(Block) error); ok { - r0 = rf(block) + if rf, ok := ret.Get(0).(func(context.Context, Block) error); ok { + r0 = rf(ctx, block) } else { r0 = ret.Error(0) } @@ -51,13 +51,13 @@ func (_m *ProcessorMock) ProcessBlock(block Block) error { return r0 } -// Reorg provides a mock function with given fields: firstReorgedBlock -func (_m *ProcessorMock) Reorg(firstReorgedBlock uint64) error { - ret := _m.Called(firstReorgedBlock) +// Reorg provides a mock function with given fields: ctx, firstReorgedBlock +func (_m *ProcessorMock) Reorg(ctx context.Context, firstReorgedBlock uint64) error { + ret := _m.Called(ctx, firstReorgedBlock) var r0 error - if rf, ok := ret.Get(0).(func(uint64) error); ok { - r0 = rf(firstReorgedBlock) + if rf, ok := ret.Get(0).(func(context.Context, uint64) error); ok { + r0 = rf(ctx, firstReorgedBlock) } else { r0 = ret.Error(0) } diff --git a/tree/appendonlytree.go b/tree/appendonlytree.go new file mode 100644 index 00000000..39719028 --- /dev/null +++ b/tree/appendonlytree.go @@ -0,0 +1,243 @@ +package tree + +import ( + "context" + "fmt" + "math" + + dbCommon "github.com/0xPolygon/cdk/common" + "github.com/ethereum/go-ethereum/common" + "github.com/ledgerwatch/erigon-lib/kv" +) + +type AppendOnlyTree struct { + *Tree + lastLeftCache []common.Hash + lastIndex int64 +} + +func NewAppendOnly(ctx context.Context, dbPath, dbPrefix string) (*AppendOnlyTree, error) { + t, err := newTree(dbPath, dbPrefix) + if err != nil { + return nil, err + } + at := &AppendOnlyTree{Tree: t} + if err := at.initLastLeftCacheAndLastDepositCount(ctx); err != nil { + return nil, err + } + return at, nil +} + +// AddLeaves adds a list leaves into the tree +func (t *AppendOnlyTree) AddLeaves(ctx context.Context, leaves []Leaf) error { + // Sanity check + if len(leaves) == 0 { + return nil + } + if int64(leaves[0].Index) != t.lastIndex+1 { + return fmt.Errorf( + "mismatched index. Expected: %d, actual: %d", + t.lastIndex+1, leaves[0].Index, + ) + } + tx, err := t.db.BeginRw(ctx) + if err != nil { + return err + } + backupIndx := t.lastIndex + backupCache := make([]common.Hash, len(t.lastLeftCache)) + copy(backupCache, t.lastLeftCache) + + for _, leaf := range leaves { + if err := t.addLeaf(tx, leaf); err != nil { + tx.Rollback() + t.lastIndex = backupIndx + t.lastLeftCache = backupCache + return err + } + } + + if err := tx.Commit(); err != nil { + t.lastIndex = backupIndx + t.lastLeftCache = backupCache + return err + } + return nil +} + +func (t *AppendOnlyTree) addLeaf(tx kv.RwTx, leaf Leaf) error { + // Calculate new tree nodes + currentChildHash := leaf.Hash + newNodes := []treeNode{} + for h := uint8(0); h < t.height; h++ { + var parent treeNode + if leaf.Index&(1< 0 { + // Add child to the right + parent = treeNode{ + left: t.lastLeftCache[h], + right: currentChildHash, + } + } else { + // Add child to the left + parent = treeNode{ + left: currentChildHash, + right: t.zeroHashes[h], + } + // Update cache + // TODO: review this part of the logic, skipping ?optimizaton? + // from OG implementation + t.lastLeftCache[h] = currentChildHash + } + currentChildHash = parent.hash() + newNodes = append(newNodes, parent) + } + + // store root + root := currentChildHash + if err := tx.Put(t.rootTable, dbCommon.Uint32ToBytes(leaf.Index), root[:]); err != nil { + return err + } + + // store nodes + for _, node := range newNodes { + value, err := node.MarshalBinary() + if err != nil { + return err + } + if err := tx.Put(t.rhtTable, node.hash().Bytes(), value); err != nil { + return err + } + } + + t.lastIndex++ + return nil +} + +func (t *AppendOnlyTree) initLastLeftCacheAndLastDepositCount(ctx context.Context) error { + tx, err := t.db.BeginRw(ctx) + if err != nil { + return err + } + defer tx.Rollback() + + root, err := t.initLastIndex(tx) + if err != nil { + return err + } + return t.initLastLeftCache(tx, t.lastIndex, root) +} + +// getLastIndexAndRoot return the index and the root associated to the last leaf inserted. +// If index == -1, it means no leaf added yet +func (t *AppendOnlyTree) getLastIndexAndRoot(tx kv.Tx) (int64, common.Hash, error) { + iter, err := tx.RangeDescend( + t.rootTable, + dbCommon.Uint32ToBytes(math.MaxUint32), + dbCommon.Uint32ToBytes(0), + 1, + ) + if err != nil { + return 0, common.Hash{}, err + } + + lastIndexBytes, rootBytes, err := iter.Next() + if err != nil { + return 0, common.Hash{}, err + } + if lastIndexBytes == nil { + return -1, common.Hash{}, nil + } + return int64(dbCommon.BytesToUint32(lastIndexBytes)), common.Hash(rootBytes), nil +} + +func (t *AppendOnlyTree) initLastIndex(tx kv.Tx) (common.Hash, error) { + ldc, root, err := t.getLastIndexAndRoot(tx) + if err != nil { + return common.Hash{}, err + } + t.lastIndex = ldc + return root, nil +} +func (t *AppendOnlyTree) initLastLeftCache(tx kv.Tx, lastIndex int64, lastRoot common.Hash) error { + siblings := make([]common.Hash, t.height, t.height) + if lastIndex == -1 { + t.lastLeftCache = siblings + return nil + } + index := lastIndex + + currentNodeHash := lastRoot + // It starts in height-1 because 0 is the level of the leafs + for h := int(t.height - 1); h >= 0; h-- { + currentNode, err := t.getRHTNode(tx, currentNodeHash) + if err != nil { + return fmt.Errorf( + "error getting node %s from the RHT at height %d with root %s: %v", + currentNodeHash.Hex(), h, lastRoot.Hex(), err, + ) + } + if currentNode == nil { + return ErrNotFound + } + siblings = append(siblings, currentNode.left) + if index&(1< 0 { + currentNodeHash = currentNode.right + } else { + currentNodeHash = currentNode.left + } + } + + // Reverse the siblings to go from leafs to root + for i, j := 0, len(siblings)-1; i < j; i, j = i+1, j-1 { + siblings[i], siblings[j] = siblings[j], siblings[i] + } + + t.lastLeftCache = siblings + return nil +} + +// Reorg deletes all the data relevant from firstReorgedIndex (includded) and onwards +// and prepares the tree tfor being used as it was at firstReorgedIndex-1 +func (t *AppendOnlyTree) Reorg(ctx context.Context, firstReorgedIndex uint32) error { + if t.lastIndex == -1 { + return nil + } + tx, err := t.db.BeginRw(ctx) + if err != nil { + return err + } + // Clean root table + for i := firstReorgedIndex; i <= uint32(t.lastIndex); i++ { + if err := tx.Delete(t.rootTable, dbCommon.Uint32ToBytes(i)); err != nil { + tx.Rollback() + return err + } + } + + // Reset + root := common.Hash{} + if firstReorgedIndex > 0 { + rootBytes, err := tx.GetOne(t.rootTable, dbCommon.Uint32ToBytes(firstReorgedIndex-1)) + if err != nil { + tx.Rollback() + return err + } + if rootBytes == nil { + tx.Rollback() + return ErrNotFound + } + root = common.Hash(rootBytes) + } + err = t.initLastLeftCache(tx, int64(firstReorgedIndex)-1, root) + if err != nil { + tx.Rollback() + return err + } + + // Note: not cleaning RHT, not worth it + if err := tx.Commit(); err != nil { + return err + } + t.lastIndex = int64(firstReorgedIndex) - 1 + return nil +} diff --git a/bridgesync/testvectors/claim-vectors.json b/tree/testvectors/claim-vectors.json similarity index 100% rename from bridgesync/testvectors/claim-vectors.json rename to tree/testvectors/claim-vectors.json diff --git a/bridgesync/testvectors/leaf-vectors.json b/tree/testvectors/leaf-vectors.json similarity index 100% rename from bridgesync/testvectors/leaf-vectors.json rename to tree/testvectors/leaf-vectors.json diff --git a/bridgesync/testvectors/root-vectors.json b/tree/testvectors/root-vectors.json similarity index 100% rename from bridgesync/testvectors/root-vectors.json rename to tree/testvectors/root-vectors.json diff --git a/tree/testvectors/types.go b/tree/testvectors/types.go new file mode 100644 index 00000000..f005b6ea --- /dev/null +++ b/tree/testvectors/types.go @@ -0,0 +1,64 @@ +package testvectors + +import ( + "encoding/binary" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/iden3/go-iden3-crypto/keccak256" +) + +// DepositVectorRaw represents the deposit vector +type DepositVectorRaw struct { + OriginalNetwork uint32 `json:"originNetwork"` + TokenAddress string `json:"tokenAddress"` + Amount string `json:"amount"` + DestinationNetwork uint32 `json:"destinationNetwork"` + DestinationAddress string `json:"destinationAddress"` + ExpectedHash string `json:"leafValue"` + CurrentHash string `json:"currentLeafValue"` + Metadata string `json:"metadata"` +} + +func (d *DepositVectorRaw) Hash() common.Hash { + origNet := make([]byte, 4) //nolint:gomnd + binary.BigEndian.PutUint32(origNet, uint32(d.OriginalNetwork)) + destNet := make([]byte, 4) //nolint:gomnd + binary.BigEndian.PutUint32(destNet, uint32(d.DestinationNetwork)) + + metaHash := keccak256.Hash(common.FromHex(d.Metadata)) + hash := common.Hash{} + var buf [32]byte //nolint:gomnd + amount, _ := big.NewInt(0).SetString(d.Amount, 0) + origAddrBytes := common.HexToAddress(d.TokenAddress) + destAddrBytes := common.HexToAddress(d.DestinationAddress) + copy( + hash[:], + keccak256.Hash( + []byte{0}, + origNet, + origAddrBytes[:], + destNet, + destAddrBytes[:], + amount.FillBytes(buf[:]), + metaHash, + ), + ) + return hash +} + +// MTClaimVectorRaw represents the merkle proof +type MTClaimVectorRaw struct { + Deposits []DepositVectorRaw `json:"leafs"` + Index uint32 `json:"index"` + MerkleProof []string `json:"proof"` + ExpectedRoot string `json:"root"` +} + +// MTRootVectorRaw represents the root of Merkle Tree +type MTRootVectorRaw struct { + ExistingLeaves []string `json:"previousLeafsValues"` + CurrentRoot string `json:"currentRoot"` + NewLeaf DepositVectorRaw `json:"newLeaf"` + NewRoot string `json:"newRoot"` +} diff --git a/tree/tree.go b/tree/tree.go new file mode 100644 index 00000000..6271f16e --- /dev/null +++ b/tree/tree.go @@ -0,0 +1,175 @@ +package tree + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/kv/mdbx" + "golang.org/x/crypto/sha3" +) + +const ( + defaultHeight uint8 = 32 + rootTableSufix = "-root" + rhtTableSufix = "-rht" +) + +var ( + ErrNotFound = errors.New("not found") +) + +type Leaf struct { + Index uint32 + Hash common.Hash +} + +type Tree struct { + db kv.RwDB + rhtTable string + rootTable string + height uint8 + zeroHashes []common.Hash +} + +type treeNode struct { + left common.Hash + right common.Hash +} + +func (n *treeNode) hash() common.Hash { + var hash common.Hash + hasher := sha3.NewLegacyKeccak256() + hasher.Write(n.left[:]) + hasher.Write(n.right[:]) + copy(hash[:], hasher.Sum(nil)) + return hash +} + +func (n *treeNode) MarshalBinary() ([]byte, error) { + return append(n.left[:], n.right[:]...), nil +} + +func (n *treeNode) UnmarshalBinary(data []byte) error { + if len(data) != 64 { + return fmt.Errorf("expected len %d, actual len %d", 64, len(data)) + } + n.left = common.Hash(data[:32]) + n.right = common.Hash(data[32:]) + return nil +} + +func newTree(dbPath, dbPrefix string) (*Tree, error) { + rootTable := dbPrefix + rootTableSufix + rhtTable := dbPrefix + rhtTableSufix + db, err := mdbx.NewMDBX(nil). + Path(dbPath). + WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { + return kv.TableCfg{ + rootTable: {}, + rhtTable: {}, + } + }). + Open() + if err != nil { + return nil, err + } + t := &Tree{ + rhtTable: rhtTable, + rootTable: rootTable, + db: db, + height: defaultHeight, + zeroHashes: generateZeroHashes(defaultHeight), + } + + return t, nil +} + +// GetProof returns the merkle proof for a given index and root. +func (t *Tree) GetProof(ctx context.Context, index uint32, root common.Hash) ([]common.Hash, error) { + tx, err := t.db.BeginRw(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + siblings := make([]common.Hash, int(t.height)) + + currentNodeHash := root + // It starts in height-1 because 0 is the level of the leafs + for h := int(t.height - 1); h >= 0; h-- { + currentNode, err := t.getRHTNode(tx, currentNodeHash) + if err != nil { + return nil, fmt.Errorf( + "height: %d, currentNode: %s, error: %v", + h, currentNodeHash.Hex(), err, + ) + } + /* + * Root (level h=3 => height=4) + * / \ + * O5 O6 (level h=2) + * / \ / \ + * O1 O2 O3 O4 (level h=1) + * /\ /\ /\ /\ + * 0 1 2 3 4 5 6 7 Leafs (level h=0) + * Example 1: + * Choose index = 3 => 011 binary + * Assuming we are in level 1 => h=1; 1< 011&010=010 which is higher than 0 so we need the left sibling (O1) + * Example 2: + * Choose index = 4 => 100 binary + * Assuming we are in level 1 => h=1; 1< 100&010=000 which is not higher than 0 so we need the right sibling (O4) + * Example 3: + * Choose index = 4 => 100 binary + * Assuming we are in level 2 => h=2; 1< 100&100=100 which is higher than 0 so we need the left sibling (O5) + */ + if index&(1< 0 { + siblings = append(siblings, currentNode.left) + currentNodeHash = currentNode.right + } else { + siblings = append(siblings, currentNode.right) + currentNodeHash = currentNode.left + } + } + + // Reverse siblings to go from leafs to root + for i, j := 0, len(siblings)-1; i < j; i, j = i+1, j-1 { + siblings[i], siblings[j] = siblings[j], siblings[i] + } + + return siblings, nil +} + +func (t *Tree) getRHTNode(tx kv.Tx, nodeHash common.Hash) (*treeNode, error) { + nodeBytes, err := tx.GetOne(t.rhtTable, nodeHash[:]) + if err != nil { + return nil, err + } + if nodeBytes == nil { + return nil, ErrNotFound + } + node := &treeNode{} + err = node.UnmarshalBinary(nodeBytes) + return node, err +} + +func generateZeroHashes(height uint8) []common.Hash { + var zeroHashes = []common.Hash{ + {}, + } + // This generates a leaf = HashZero in position 0. In the rest of the positions that are equivalent to the ascending levels, + // we set the hashes of the nodes. So all nodes from level i=5 will have the same value and same children nodes. + for i := 1; i <= int(height); i++ { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(zeroHashes[i-1][:]) + hasher.Write(zeroHashes[i-1][:]) + thisHeightHash := common.Hash{} + copy(thisHeightHash[:], hasher.Sum(nil)) + zeroHashes = append(zeroHashes, thisHeightHash) + } + return zeroHashes +} diff --git a/tree/tree_test.go b/tree/tree_test.go new file mode 100644 index 00000000..47129aee --- /dev/null +++ b/tree/tree_test.go @@ -0,0 +1,103 @@ +package tree + +import ( + "context" + "encoding/json" + "fmt" + "os" + "testing" + + "github.com/0xPolygon/cdk/tree/testvectors" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestMTAddLeaf(t *testing.T) { + data, err := os.ReadFile("testvectors/root-vectors.json") + require.NoError(t, err) + + var mtTestVectors []testvectors.MTRootVectorRaw + err = json.Unmarshal(data, &mtTestVectors) + require.NoError(t, err) + ctx := context.Background() + + for ti, testVector := range mtTestVectors { + t.Run(fmt.Sprintf("Test vector %d", ti), func(t *testing.T) { + + path := t.TempDir() + tree, err := NewAppendOnly(context.Background(), path, "foo") + require.NoError(t, err) + + // Add exisiting leaves + leaves := []Leaf{} + for i, leaf := range testVector.ExistingLeaves { + leaves = append(leaves, Leaf{ + Index: uint32(i), + Hash: common.HexToHash(leaf), + }) + } + err = tree.AddLeaves(ctx, leaves) + require.NoError(t, err) + if len(testVector.ExistingLeaves) > 0 { + txRo, err := tree.db.BeginRo(ctx) + require.NoError(t, err) + _, actualRoot, err := tree.getLastIndexAndRoot(txRo) + txRo.Rollback() + require.NoError(t, err) + require.Equal(t, common.HexToHash(testVector.CurrentRoot), actualRoot) + } + + // Add new bridge + err = tree.AddLeaves(ctx, []Leaf{{ + Index: uint32(len(testVector.ExistingLeaves)), + Hash: common.HexToHash(testVector.NewLeaf.CurrentHash), + }}) + require.NoError(t, err) + txRo, err := tree.db.BeginRo(ctx) + require.NoError(t, err) + _, actualRoot, err := tree.getLastIndexAndRoot(txRo) + txRo.Rollback() + require.NoError(t, err) + require.Equal(t, common.HexToHash(testVector.NewRoot), actualRoot) + }) + } +} + +func TestMTGetProof(t *testing.T) { + data, err := os.ReadFile("testvectors/claim-vectors.json") + require.NoError(t, err) + + var mtTestVectors []testvectors.MTClaimVectorRaw + err = json.Unmarshal(data, &mtTestVectors) + require.NoError(t, err) + ctx := context.Background() + + for ti, testVector := range mtTestVectors { + t.Run(fmt.Sprintf("Test vector %d", ti), func(t *testing.T) { + path := t.TempDir() + tree, err := NewAppendOnly(context.Background(), path, "foo") + require.NoError(t, err) + leaves := []Leaf{} + for li, leaf := range testVector.Deposits { + leaves = append(leaves, Leaf{ + Index: uint32(li), + Hash: leaf.Hash(), + }) + } + err = tree.AddLeaves(ctx, leaves) + require.NoError(t, err) + txRo, err := tree.db.BeginRo(ctx) + require.NoError(t, err) + _, actualRoot, err := tree.getLastIndexAndRoot(txRo) + txRo.Rollback() + expectedRoot := common.HexToHash(testVector.ExpectedRoot) + require.Equal(t, expectedRoot, actualRoot) + + proof, err := tree.GetProof(ctx, testVector.Index, expectedRoot) + require.NoError(t, err) + for i, sibling := range testVector.MerkleProof { + require.Equal(t, common.HexToHash(sibling), proof[i]) + } + }) + } +} diff --git a/tree/updatabletree.go b/tree/updatabletree.go new file mode 100644 index 00000000..74812b26 --- /dev/null +++ b/tree/updatabletree.go @@ -0,0 +1,25 @@ +package tree + +import ( + "context" + "errors" + + "github.com/ethereum/go-ethereum/common" +) + +type UpdatableTree struct { + *Tree +} + +func NewUpdatable(ctx context.Context, dbPath, dbPrefix string) (*UpdatableTree, error) { + t, err := newTree(dbPath, dbPrefix) + if err != nil { + return nil, err + } + ut := &UpdatableTree{Tree: t} + return ut, nil +} + +func (t *UpdatableTree) UpsertLeaf(ctx context.Context, index uint32, leafHash common.Hash, expectedRoot *common.Hash) error { + return errors.New("not implemented") +} From 99180de710b815ef9c8a1555a6334efcaa3fb259 Mon Sep 17 00:00:00 2001 From: Arnau Date: Wed, 31 Jul 2024 17:49:42 +0200 Subject: [PATCH 27/49] Fix UTs --- l1infotreesync/processor.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 864421be..1de6f879 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -31,8 +31,10 @@ const ( // of rootTable and indexTable references the key of the infoTable // Key: hash of the leaf that caused the update (common.Hash) // Value: JSON of storeLeaf struct + infoTable = dbPrefix + "-info" // blockTable stores the first and last index of L1 Info Tree that have been updated on // Value: JSON of blockWithLeafs + blockTable = dbPrefix + "-block" // lastBlockTable used to store the last block processed. This is needed to know the last processed blcok lastBlockTable = dbPrefix + "-lastBlock" From 5f2c29ffc82273a027c703649b21c216ac214358 Mon Sep 17 00:00:00 2001 From: Arnau Date: Thu, 1 Aug 2024 10:23:09 +0200 Subject: [PATCH 28/49] Add PR review suggestions from joanestebanr --- aggoracle/chaingersender/evm.go | 14 +++++----- aggoracle/e2e_test.go | 2 +- aggoracle/oracle.go | 4 +-- cmd/run.go | 4 ++- config/default.go | 45 ++++++++++++++++++++++++++++++ l1infotreesync/e2e_test.go | 2 +- l1infotreesync/l1infotreesync.go | 28 +++++++++++-------- l1infotreesync/processor.go | 32 ++++++++++++++------- localbridgesync/localbridgesync.go | 7 ++++- sync/common.go | 16 +++++------ sync/evmdownloader.go | 11 +++++--- sync/evmdownloader_test.go | 8 ++++-- sync/evmdriver.go | 11 +++++--- sync/evmdriver_test.go | 21 ++++++++++---- 14 files changed, 145 insertions(+), 60 deletions(-) diff --git a/aggoracle/chaingersender/evm.go b/aggoracle/chaingersender/evm.go index 93ce347c..859f4b8b 100644 --- a/aggoracle/chaingersender/evm.go +++ b/aggoracle/chaingersender/evm.go @@ -41,13 +41,13 @@ type EVMChainGERSender struct { } type EVMConfig struct { - GlobalExitRootL2 common.Address `mapstructure:"GlobalExitRootL2"` - URLRPCL2 string `mapstructure:"URLRPCL2"` - ChainIDL2 uint64 `mapstructure:"ChainIDL2"` - GasOffset uint64 `mapstructure:"GasOffset"` - WaitPeriodMonitorTx cfgTypes.Duration `mapstructure:"WaitPeriodMonitorTx"` - SenderAddr common.Address `mapstructure:"SenderAddr"` - EthTxManager ethtxmanager.Config `mapstructure:"EthTxManager"` + GlobalExitRootL2Addr common.Address `mapstructure:"GlobalExitRootL2"` + URLRPCL2 string `mapstructure:"URLRPCL2"` + ChainIDL2 uint64 `mapstructure:"ChainIDL2"` + GasOffset uint64 `mapstructure:"GasOffset"` + WaitPeriodMonitorTx cfgTypes.Duration `mapstructure:"WaitPeriodMonitorTx"` + SenderAddr common.Address `mapstructure:"SenderAddr"` + EthTxManager ethtxmanager.Config `mapstructure:"EthTxManager"` } func NewEVMChainGERSender( diff --git a/aggoracle/e2e_test.go b/aggoracle/e2e_test.go index 3e09c906..ce081c35 100644 --- a/aggoracle/e2e_test.go +++ b/aggoracle/e2e_test.go @@ -60,7 +60,7 @@ func commonSetup(t *testing.T) ( require.NoError(t, err) // Syncer dbPathSyncer := t.TempDir() - syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), time.Millisecond, 0) + syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), time.Millisecond, 0, 100*time.Millisecond, 3) require.NoError(t, err) go syncer.Start(ctx) diff --git a/aggoracle/oracle.go b/aggoracle/oracle.go index 49d14b7e..f22ee1f0 100644 --- a/aggoracle/oracle.go +++ b/aggoracle/oracle.go @@ -78,12 +78,12 @@ func (a *AggOracle) Start(ctx context.Context) { log.Debugf("GER %s already injected", gerToInject.Hex()) continue } - log.Debugf("injecting new GER: %s", gerToInject.Hex()) + log.Infof("injecting new GER: %s", gerToInject.Hex()) if err := a.chainSender.UpdateGERWaitUntilMined(ctx, gerToInject); err != nil { log.Errorf("error calling updateGERWaitUntilMined, when trying to inject GER %s: %v", gerToInject.Hex(), err) continue } - log.Debugf("GER %s injected", gerToInject.Hex()) + log.Infof("GER %s injected", gerToInject.Hex()) case <-ctx.Done(): return } diff --git a/cmd/run.go b/cmd/run.go index 4bb24fc4..f7d36b74 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -217,7 +217,7 @@ func createAggoracle(cfg config.Config, l1Client *ethclient.Client, syncer *l1in log.Fatal(err) } sender, err = chaingersender.NewEVMChainGERSender( - cfg.AggOracle.EVMSender.GlobalExitRootL2, + cfg.AggOracle.EVMSender.GlobalExitRootL2Addr, cfg.AggOracle.EVMSender.SenderAddr, l2CLient, ethTxManager, @@ -388,6 +388,8 @@ func newL1InfoTreeSyncer( l1Client, cfg.L1InfoTreeSync.WaitForNewBlocksPeriod.Duration, cfg.L1InfoTreeSync.InitialBlock, + cfg.L1InfoTreeSync.RetryAfterErrorPeriod.Duration, + cfg.L1InfoTreeSync.MaxRetryAttemptsAfterError, ) if err != nil { log.Fatal(err) diff --git a/config/default.go b/config/default.go index 7b12e6f3..85dbd1cd 100644 --- a/config/default.go +++ b/config/default.go @@ -118,4 +118,49 @@ SequencerPrivateKey = {} [Aggregator.Synchronizer.Etherman] [Aggregator.Synchronizer.Etherman.Validium] Enabled = false + +[ReorgDetectorL1] +DBPath = "/tmp/reorgdetector" + +[L1InfoTreeSync] +DBPath = "/tmp/L1InfoTreeSync" +GlobalExitRootAddr="0x8464135c8F25Da09e49BC8782676a84730C318bC" +SyncBlockChunkSize=10 +BlockFinality="latest" +URLRPCL1="http://test-aggoracle-l1:8545" +WaitForNewBlocksPeriod="100ms" +InitialBlock=0 + +[AggOracle] +TargetChainType="EVM" +URLRPCL1="http://test-aggoracle-l1:8545" +BlockFinality="latest" +WaitPeriodNextGER="100ms" + [EVMSender] + GlobalExitRootL2="0x8464135c8F25Da09e49BC8782676a84730C318bC" + URLRPCL2="http://test-aggoracle-l2:8545" + ChainIDL2=1337 + GasOffset=0 + WaitPeriodMonitorTx="100ms" + SenderAddr="0x70997970c51812dc3a010c7d01b50e0d17dc79c8" + [SequenceSender.EthTxManager] + FrequencyToMonitorTxs = "1s" + WaitTxToBeMined = "2s" + GetReceiptMaxTime = "250ms" + GetReceiptWaitInterval = "1s" + PrivateKeys = [ + {Path = "/app/keystore/aggoracle.keystore", Password = "testonly"}, + ] + ForcedGas = 0 + GasPriceMarginFactor = 1 + MaxGasPriceLimit = 0 + PersistenceFilename = "/tmp/ethtxmanager.json" + ReadPendingL1Txs = false + SafeStatusL1NumberOfBlocks = 5 + FinalizedStatusL1NumberOfBlocks = 10 + [SequenceSender.EthTxManager.Etherman] + URL = "http://test-aggoracle-l2" + MultiGasProvider = false + L1ChainID = 1337 + HTTPHeaders = [] ` diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index 35958f08..82bef733 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -54,7 +54,7 @@ func TestE2E(t *testing.T) { rdm.On("AddBlockToTrack", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) client, gerAddr, gerSc, err := newSimulatedClient(auth) require.NoError(t, err) - syncer, err := New(ctx, dbPath, gerAddr, 10, etherman.LatestBlock, rdm, client.Client(), time.Millisecond, 0) + syncer, err := New(ctx, dbPath, gerAddr, 10, etherman.LatestBlock, rdm, client.Client(), time.Millisecond, 0, 100*time.Millisecond, 3) require.NoError(t, err) go syncer.Start(ctx) diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index ed9056bc..7b3be753 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -15,20 +15,17 @@ const ( downloadBufferSize = 1000 ) -var ( - retryAfterErrorPeriod = time.Second * 10 - maxRetryAttemptsAfterError = 5 -) - type Config struct { DBPath string `mapstructure:"DBPath"` GlobalExitRootAddr common.Address `mapstructure:"GlobalExitRootAddr"` SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"` // TODO: BlockFinality doesnt work as per the jsonschema - BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` - URLRPCL1 string `mapstructure:"URLRPCL1"` - WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"` - InitialBlock uint64 `mapstructure:"InitialBlock"` + BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` + URLRPCL1 string `mapstructure:"URLRPCL1"` + WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"` + InitialBlock uint64 `mapstructure:"InitialBlock"` + RetryAfterErrorPeriod types.Duration `mapstructure:"RetryAfterErrorPeriod"` + MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` } type L1InfoTreeSync struct { @@ -46,6 +43,8 @@ func New( l1Client EthClienter, waitForNewBlocksPeriod time.Duration, initialBlock uint64, + retryAfterErrorPeriod time.Duration, + maxRetryAttemptsAfterError int, ) (*L1InfoTreeSync, error) { processor, err := newProcessor(ctx, dbPath) if err != nil { @@ -56,14 +55,18 @@ func New( if err != nil { return nil, err } - if lastProcessedBlock < initialBlock { + if initialBlock > 0 && lastProcessedBlock < initialBlock-1 { err = processor.ProcessBlock(sync.Block{ - Num: initialBlock, + Num: initialBlock - 1, }) if err != nil { return nil, err } } + rh := &sync.RetryHandler{ + RetryAfterErrorPeriod: retryAfterErrorPeriod, + MaxRetryAttemptsAfterError: maxRetryAttemptsAfterError, + } appender, err := buildAppender(l1Client, globalExitRoot) if err != nil { @@ -76,12 +79,13 @@ func New( waitForNewBlocksPeriod, appender, []common.Address{globalExitRoot}, + rh, ) if err != nil { return nil, err } - driver, err := sync.NewEVMDriver(rd, processor, downloader, reorgDetectorID, downloadBufferSize) + driver, err := sync.NewEVMDriver(rd, processor, downloader, reorgDetectorID, downloadBufferSize, rh) if err != nil { return nil, err } diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 9b444d43..35191062 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -122,7 +122,13 @@ func newProcessor(ctx context.Context, dbPath string) (*processor, error) { p := &processor{ db: db, } - leaves, err := p.getAllLeavesHashed(ctx) + + tx, err := p.db.BeginRo(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + leaves, err := p.getAllLeavesHashed(tx) if err != nil { return nil, err } @@ -134,14 +140,8 @@ func newProcessor(ctx context.Context, dbPath string) (*processor, error) { return p, nil } -func (p *processor) getAllLeavesHashed(ctx context.Context) ([][32]byte, error) { +func (p *processor) getAllLeavesHashed(tx kv.Tx) ([][32]byte, error) { // TODO: same coment about refactor that appears at ComputeMerkleProofByIndex - tx, err := p.db.BeginRo(ctx) - if err != nil { - return nil, err - } - defer tx.Rollback() - index, err := p.getLastIndex(tx) if err == ErrNotFound || index == 0 { return nil, nil @@ -323,7 +323,6 @@ func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { } func (p *processor) Reorg(firstReorgedBlock uint64) error { - // TODO: Does tree need to be reorged? tx, err := p.db.BeginRw(context.Background()) if err != nil { return err @@ -359,6 +358,17 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { tx.Rollback() return err } + leaves, err := p.getAllLeavesHashed(tx) + if err != nil { + tx.Rollback() + return err + } + tree, err := l1infotree.NewL1InfoTree(treeHeight, leaves) + if err != nil { + tx.Rollback() + return err + } + p.tree = tree return tx.Commit() } @@ -404,6 +414,7 @@ func (p *processor) ProcessBlock(b sync.Block) error { if err != nil { return err } + events := make([]Event, len(b.Events)) if len(b.Events) > 0 { var initialIndex uint32 lastIndex, err := p.getLastIndex(tx) @@ -417,6 +428,7 @@ func (p *processor) ProcessBlock(b sync.Block) error { } for i, e := range b.Events { event := e.(Event) + events = append(events, event) leafToStore := storeLeaf{ Index: initialIndex + uint32(i), MainnetExitRoot: event.MainnetExitRoot, @@ -448,7 +460,7 @@ func (p *processor) ProcessBlock(b sync.Block) error { tx.Rollback() return err } - log.Debugf("block %d processed with events: %+v", b.Num, b.Events) + log.Debugf("block %d processed with events: %+v", b.Num, events) return tx.Commit() } diff --git a/localbridgesync/localbridgesync.go b/localbridgesync/localbridgesync.go index d0617584..94e60b59 100644 --- a/localbridgesync/localbridgesync.go +++ b/localbridgesync/localbridgesync.go @@ -50,6 +50,10 @@ func New( return nil, err } } + rh := &sync.RetryHandler{ + MaxRetryAttemptsAfterError: maxRetryAttemptsAfterError, + RetryAfterErrorPeriod: retryAfterErrorPeriod, + } appender, err := buildAppender(l2Client, bridge) if err != nil { @@ -62,12 +66,13 @@ func New( waitForNewBlocksPeriod, appender, []common.Address{bridge}, + rh, ) if err != nil { return nil, err } - driver, err := sync.NewEVMDriver(rd, processor, downloader, reorgDetectorID, downloadBufferSize) + driver, err := sync.NewEVMDriver(rd, processor, downloader, reorgDetectorID, downloadBufferSize, rh) if err != nil { return nil, err } diff --git a/sync/common.go b/sync/common.go index 6d1011f5..4fe049d5 100644 --- a/sync/common.go +++ b/sync/common.go @@ -5,17 +5,17 @@ import ( "time" ) -var ( - RetryAfterErrorPeriod = time.Second * 10 - MaxRetryAttemptsAfterError = 5 -) +type RetryHandler struct { + RetryAfterErrorPeriod time.Duration + MaxRetryAttemptsAfterError int +} -func RetryHandler(funcName string, attempts int) { - if attempts >= MaxRetryAttemptsAfterError { +func (h *RetryHandler) Handle(funcName string, attempts int) { + if h.MaxRetryAttemptsAfterError > -1 && attempts >= h.MaxRetryAttemptsAfterError { log.Fatalf( "%s failed too many times (%d)", - funcName, MaxRetryAttemptsAfterError, + funcName, h.MaxRetryAttemptsAfterError, ) } - time.Sleep(RetryAfterErrorPeriod) + time.Sleep(h.RetryAfterErrorPeriod) } diff --git a/sync/evmdownloader.go b/sync/evmdownloader.go index ebdde880..ad452856 100644 --- a/sync/evmdownloader.go +++ b/sync/evmdownloader.go @@ -41,6 +41,7 @@ func NewEVMDownloader( waitForNewBlocksPeriod time.Duration, appender LogAppenderMap, adressessToQuery []common.Address, + rh *RetryHandler, ) (*EVMDownloader, error) { finality, err := blockFinalityType.ToBlockNum() if err != nil { @@ -59,6 +60,7 @@ func NewEVMDownloader( appender: appender, topicsToQuery: topicsToQuery, adressessToQuery: adressessToQuery, + rh: rh, }, }, nil } @@ -106,6 +108,7 @@ type downloaderImplementation struct { appender LogAppenderMap topicsToQuery [][]common.Hash adressessToQuery []common.Address + rh *RetryHandler } func (d *downloaderImplementation) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) { @@ -122,7 +125,7 @@ func (d *downloaderImplementation) waitForNewBlocks(ctx context.Context, lastBlo if err != nil { attempts++ log.Error("error getting last block num from eth client: ", err) - RetryHandler("waitForNewBlocks", attempts) + d.rh.Handle("waitForNewBlocks", attempts) continue } if header.Number.Uint64() > lastBlockSeen { @@ -155,7 +158,7 @@ func (d *downloaderImplementation) getEventsByBlockRange(ctx context.Context, fr if err != nil { attempts++ log.Error("error trying to append log: ", err) - RetryHandler("getLogs", attempts) + d.rh.Handle("getLogs", attempts) continue } break @@ -178,7 +181,7 @@ func (d *downloaderImplementation) getLogs(ctx context.Context, fromBlock, toBlo if err != nil { attempts++ log.Error("error calling FilterLogs to eth client: ", err) - RetryHandler("getLogs", attempts) + d.rh.Handle("getLogs", attempts) continue } return logs @@ -192,7 +195,7 @@ func (d *downloaderImplementation) getBlockHeader(ctx context.Context, blockNum if err != nil { attempts++ log.Errorf("error getting block header for block %d, err: %v", blockNum, err) - RetryHandler("getBlockHeader", attempts) + d.rh.Handle("getBlockHeader", attempts) continue } return EVMBlockHeader{ diff --git a/sync/evmdownloader_test.go b/sync/evmdownloader_test.go index c733aabd..2f5a7ee5 100644 --- a/sync/evmdownloader_test.go +++ b/sync/evmdownloader_test.go @@ -320,7 +320,6 @@ func TestDownload(t *testing.T) { } func TestWaitForNewBlocks(t *testing.T) { - RetryAfterErrorPeriod = time.Millisecond * 100 ctx := context.Background() d, clientMock := NewTestDownloader(t) @@ -353,7 +352,6 @@ func TestWaitForNewBlocks(t *testing.T) { } func TestGetBlockHeader(t *testing.T) { - RetryAfterErrorPeriod = time.Millisecond * 100 ctx := context.Background() d, clientMock := NewTestDownloader(t) @@ -389,8 +387,12 @@ func buildAppender() LogAppenderMap { } func NewTestDownloader(t *testing.T) (*EVMDownloader, *L2Mock) { + rh := &RetryHandler{ + MaxRetryAttemptsAfterError: 5, + RetryAfterErrorPeriod: time.Millisecond * 100, + } clientMock := NewL2Mock(t) - d, err := NewEVMDownloader(clientMock, syncBlockChunck, etherman.LatestBlock, time.Millisecond, buildAppender(), []common.Address{contractAddr}) + d, err := NewEVMDownloader(clientMock, syncBlockChunck, etherman.LatestBlock, time.Millisecond, buildAppender(), []common.Address{contractAddr}, rh) require.NoError(t, err) return d, clientMock } diff --git a/sync/evmdriver.go b/sync/evmdriver.go index a30b96d6..0e20731f 100644 --- a/sync/evmdriver.go +++ b/sync/evmdriver.go @@ -20,6 +20,7 @@ type EVMDriver struct { downloader evmDownloaderFull reorgDetectorID string downloadBufferSize int + rh *RetryHandler } type processorInterface interface { @@ -39,6 +40,7 @@ func NewEVMDriver( downloader evmDownloaderFull, reorgDetectorID string, downloadBufferSize int, + rh *RetryHandler, ) (*EVMDriver, error) { reorgSub, err := reorgDetector.Subscribe(reorgDetectorID) if err != nil { @@ -51,6 +53,7 @@ func NewEVMDriver( downloader: downloader, reorgDetectorID: reorgDetectorID, downloadBufferSize: downloadBufferSize, + rh: rh, }, nil } @@ -66,7 +69,7 @@ reset: if err != nil { attempts++ log.Error("error geting last processed block: ", err) - RetryHandler("Sync", attempts) + d.rh.Handle("Sync", attempts) continue } break @@ -98,7 +101,7 @@ func (d *EVMDriver) handleNewBlock(ctx context.Context, b EVMBlock) { if err != nil { attempts++ log.Errorf("error adding block %d to tracker: %v", b.Num, err) - RetryHandler("handleNewBlock", attempts) + d.rh.Handle("handleNewBlock", attempts) continue } break @@ -113,7 +116,7 @@ func (d *EVMDriver) handleNewBlock(ctx context.Context, b EVMBlock) { if err != nil { attempts++ log.Errorf("error processing events for blcok %d, err: ", b.Num, err) - RetryHandler("handleNewBlock", attempts) + d.rh.Handle("handleNewBlock", attempts) continue } break @@ -139,7 +142,7 @@ func (d *EVMDriver) handleReorg( "error processing reorg, last valid Block %d, err: %v", firstReorgedBlock, err, ) - RetryHandler("handleReorg", attempts) + d.rh.Handle("handleReorg", attempts) continue } break diff --git a/sync/evmdriver_test.go b/sync/evmdriver_test.go index 502722f6..853dda81 100644 --- a/sync/evmdriver_test.go +++ b/sync/evmdriver_test.go @@ -19,7 +19,10 @@ var ( ) func TestSync(t *testing.T) { - RetryAfterErrorPeriod = time.Millisecond * 100 + rh := &RetryHandler{ + MaxRetryAttemptsAfterError: 5, + RetryAfterErrorPeriod: time.Millisecond * 100, + } rdm := NewReorgDetectorMock(t) pm := NewProcessorMock(t) dm := NewEVMDownloaderMock(t) @@ -29,7 +32,7 @@ func TestSync(t *testing.T) { FirstReorgedBlock: firstReorgedBlock, ReorgProcessed: reorgProcessed, }, nil) - driver, err := NewEVMDriver(rdm, pm, dm, reorgDetectorID, 10) + driver, err := NewEVMDriver(rdm, pm, dm, reorgDetectorID, 10, rh) require.NoError(t, err) ctx := context.Background() expectedBlock1 := EVMBlock{ @@ -107,12 +110,15 @@ func TestSync(t *testing.T) { } func TestHandleNewBlock(t *testing.T) { - RetryAfterErrorPeriod = time.Millisecond * 100 + rh := &RetryHandler{ + MaxRetryAttemptsAfterError: 5, + RetryAfterErrorPeriod: time.Millisecond * 100, + } rdm := NewReorgDetectorMock(t) pm := NewProcessorMock(t) dm := NewEVMDownloaderMock(t) rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{}, nil) - driver, err := NewEVMDriver(rdm, pm, dm, reorgDetectorID, 10) + driver, err := NewEVMDriver(rdm, pm, dm, reorgDetectorID, 10, rh) require.NoError(t, err) ctx := context.Background() @@ -166,7 +172,10 @@ func TestHandleNewBlock(t *testing.T) { } func TestHandleReorg(t *testing.T) { - RetryAfterErrorPeriod = time.Millisecond * 100 + rh := &RetryHandler{ + MaxRetryAttemptsAfterError: 5, + RetryAfterErrorPeriod: time.Millisecond * 100, + } rdm := NewReorgDetectorMock(t) pm := NewProcessorMock(t) dm := NewEVMDownloaderMock(t) @@ -174,7 +183,7 @@ func TestHandleReorg(t *testing.T) { rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{ ReorgProcessed: reorgProcessed, }, nil) - driver, err := NewEVMDriver(rdm, pm, dm, reorgDetectorID, 10) + driver, err := NewEVMDriver(rdm, pm, dm, reorgDetectorID, 10, rh) require.NoError(t, err) ctx := context.Background() From 5d0c94f27c0f71083ec9652c01280090c4dcc266 Mon Sep 17 00:00:00 2001 From: Arnau Date: Thu, 1 Aug 2024 11:39:06 +0200 Subject: [PATCH 29/49] Fix evmdownloader problems --- l1infotreesync/processor.go | 5 ++++- sync/evmdownloader.go | 32 +++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index d79448cf..b67847c5 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -385,8 +385,11 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { tx.Rollback() return err } + if err := tx.Commit(); err != nil { + return err + } p.l1InfoTree = l1InfoTree - return tx.Commit() + return nil } func (p *processor) deleteLeaf(tx kv.RwTx, index uint32) error { diff --git a/sync/evmdownloader.go b/sync/evmdownloader.go index df393de6..76fe99ab 100644 --- a/sync/evmdownloader.go +++ b/sync/evmdownloader.go @@ -58,7 +58,7 @@ func NewEVMDownloader( blockFinality: finality, waitForNewBlocksPeriod: waitForNewBlocksPeriod, appender: appender, - topicsToQuery: [][]common.Hash{topicsToQuery}, + topicsToQuery: topicsToQuery, adressessToQuery: adressessToQuery, rh: rh, }, @@ -106,7 +106,7 @@ type downloaderImplementation struct { blockFinality *big.Int waitForNewBlocksPeriod time.Duration appender LogAppenderMap - topicsToQuery [][]common.Hash + topicsToQuery []common.Hash adressessToQuery []common.Address rh *RetryHandler } @@ -141,6 +141,10 @@ func (d *downloaderImplementation) getEventsByBlockRange(ctx context.Context, fr for _, l := range logs { if len(blocks) == 0 || blocks[len(blocks)-1].Num < l.BlockNumber { b := d.getBlockHeader(ctx, l.BlockNumber) + if b.Hash != l.BlockHash { + log.Infof("there has been a block hash change between the event query and the block query, retrtying") + return d.getEventsByBlockRange(ctx, fromBlock, toBlock) + } blocks = append(blocks, EVMBlock{ EVMBlockHeader: EVMBlockHeader{ Num: l.BlockNumber, @@ -172,20 +176,38 @@ func (d *downloaderImplementation) getLogs(ctx context.Context, fromBlock, toBlo query := ethereum.FilterQuery{ FromBlock: new(big.Int).SetUint64(fromBlock), Addresses: d.adressessToQuery, - Topics: d.topicsToQuery, ToBlock: new(big.Int).SetUint64(toBlock), } attempts := 0 + var ( + unfilteredLogs []types.Log + err error + ) for { - logs, err := d.ethClient.FilterLogs(ctx, query) + unfilteredLogs, err = d.ethClient.FilterLogs(ctx, query) if err != nil { attempts++ log.Error("error calling FilterLogs to eth client: ", err) d.rh.Handle("getLogs", attempts) continue } - return logs + break + } + logs := []types.Log{} + for _, l := range unfilteredLogs { + found := false + for _, topic := range d.topicsToQuery { + if l.Topics[0] == topic { + logs = append(logs, l) + found = true + break + } + } + if !found { + log.Debugf("ignoring log %+v because it's not under the list of topics to query", l) + } } + return logs } func (d *downloaderImplementation) getBlockHeader(ctx context.Context, blockNum uint64) EVMBlockHeader { From 7665527ed1cc5b7b4165ecb76d1d5b820e973cef Mon Sep 17 00:00:00 2001 From: Arnau Date: Thu, 1 Aug 2024 12:27:51 +0200 Subject: [PATCH 30/49] refactor tree add and rollback to be atomic --- bridgesync/processor.go | 39 +++++++++++++++-------- l1infotreesync/processor.go | 28 +++++++---------- sync/evmdownloader.go | 4 ++- sync/evmdownloader_test.go | 14 +++++---- tree/appendonlytree.go | 63 +++++++++++++------------------------ tree/tree.go | 24 ++++++-------- tree/tree_test.go | 47 +++++++++++++++++++++++---- tree/updatabletree.go | 10 +++--- 8 files changed, 125 insertions(+), 104 deletions(-) diff --git a/bridgesync/processor.go b/bridgesync/processor.go index 72fc5a01..a4ffdd7f 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "math/big" - "path" dbCommon "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/sync" @@ -89,20 +88,22 @@ type processor struct { func newProcessor(ctx context.Context, dbPath, dbPrefix string) (*processor, error) { eventsTable := dbPrefix + eventsTableSufix lastBlockTable := dbPrefix + lastBlockTableSufix + tableCfgFunc := func(defaultBuckets kv.TableCfg) kv.TableCfg { + cfg := kv.TableCfg{ + eventsTable: {}, + lastBlockTable: {}, + } + tree.AddTables(cfg, dbPrefix) + return cfg + } db, err := mdbx.NewMDBX(nil). Path(dbPath). - WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { - return kv.TableCfg{ - eventsTable: {}, - lastBlockTable: {}, - } - }). + WithTableCfg(tableCfgFunc). Open() if err != nil { return nil, err } - exitTreeDBPath := path.Join(dbPath, "exittree") - exitTree, err := tree.NewAppendOnly(ctx, exitTreeDBPath, dbPrefix) + exitTree, err := tree.NewAppendOnly(ctx, db, dbPrefix) if err != nil { return nil, err } @@ -215,13 +216,19 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { tx.Rollback() return err } + exitTreeRollback := func() {} if firstDepositCountReorged != -1 { - if err := p.exitTree.Reorg(ctx, uint32(firstDepositCountReorged)); err != nil { + if exitTreeRollback, err = p.exitTree.Reorg(tx, uint32(firstDepositCountReorged)); err != nil { tx.Rollback() + exitTreeRollback() return err } } - return tx.Commit() + if err := tx.Commit(); err != nil { + exitTreeRollback() + return err + } + return nil } func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { @@ -262,11 +269,17 @@ func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { Hash: bridge.Hash(), }) } - if err := p.exitTree.AddLeaves(ctx, leaves); err != nil { + exitTreeRollback, err := p.exitTree.AddLeaves(tx, leaves) + if err != nil { tx.Rollback() + exitTreeRollback() + return err + } + if err := tx.Commit(); err != nil { + exitTreeRollback() return err } - return tx.Commit() + return nil } func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error { diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index b67847c5..e1599839 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "path" "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/l1infotree" @@ -112,17 +111,18 @@ func (l *storeLeaf) GlobalExitRoot() ethCommon.Hash { return gerBytes } -func tableCfgFunc(defaultBuckets kv.TableCfg) kv.TableCfg { - return kv.TableCfg{ - rootTable: {}, - indexTable: {}, - infoTable: {}, - blockTable: {}, - lastBlockTable: {}, - } -} - func newProcessor(ctx context.Context, dbPath string) (*processor, error) { + tableCfgFunc := func(defaultBuckets kv.TableCfg) kv.TableCfg { + cfg := kv.TableCfg{ + rootTable: {}, + indexTable: {}, + infoTable: {}, + blockTable: {}, + lastBlockTable: {}, + } + tree.AddTables(cfg, dbPrefix) + return cfg + } db, err := mdbx.NewMDBX(nil). Path(dbPath). WithTableCfg(tableCfgFunc). @@ -148,11 +148,7 @@ func newProcessor(ctx context.Context, dbPath string) (*processor, error) { return nil, err } p.l1InfoTree = l1InfoTree - rollupExitTreeDBPath := path.Join(dbPath, "rollupExitTree") - rollupExitTree, err := tree.NewUpdatable(ctx, rollupExitTreeDBPath, dbPrefix) - if err != nil { - return nil, err - } + rollupExitTree := tree.NewUpdatable(ctx, db, dbPrefix) p.rollupExitTree = rollupExitTree return p, nil } diff --git a/sync/evmdownloader.go b/sync/evmdownloader.go index 76fe99ab..47aa7fbe 100644 --- a/sync/evmdownloader.go +++ b/sync/evmdownloader.go @@ -142,7 +142,9 @@ func (d *downloaderImplementation) getEventsByBlockRange(ctx context.Context, fr if len(blocks) == 0 || blocks[len(blocks)-1].Num < l.BlockNumber { b := d.getBlockHeader(ctx, l.BlockNumber) if b.Hash != l.BlockHash { - log.Infof("there has been a block hash change between the event query and the block query, retrtying") + log.Infof( + "there has been a block hash change between the event query and the block query for block %d: %s vs %s. Retrtying.", + l.BlockNumber, b.Hash, l.BlockHash) return d.getEventsByBlockRange(ctx, fromBlock, toBlock) } blocks = append(blocks, EVMBlock{ diff --git a/sync/evmdownloader_test.go b/sync/evmdownloader_test.go index 2f5a7ee5..2c947370 100644 --- a/sync/evmdownloader_test.go +++ b/sync/evmdownloader_test.go @@ -158,10 +158,7 @@ func TestGetEventsByBlockRange(t *testing.T) { query := ethereum.FilterQuery{ FromBlock: new(big.Int).SetUint64(tc.fromBlock), Addresses: []common.Address{contractAddr}, - Topics: [][]common.Hash{ - {eventSignature}, - }, - ToBlock: new(big.Int).SetUint64(tc.toBlock), + ToBlock: new(big.Int).SetUint64(tc.toBlock), } clientMock. On("FilterLogs", mock.Anything, query). @@ -182,15 +179,20 @@ func TestGetEventsByBlockRange(t *testing.T) { func generateEvent(blockNum uint32) (*types.Log, testEvent) { h := common.HexToHash(strconv.Itoa(int(blockNum))) + header := types.Header{ + Number: big.NewInt(int64(blockNum)), + ParentHash: common.HexToHash("foo"), + } + blockHash := header.Hash() log := &types.Log{ Address: contractAddr, BlockNumber: uint64(blockNum), - BlockHash: h, Topics: []common.Hash{ eventSignature, h, }, - Data: nil, + BlockHash: blockHash, + Data: nil, } return log, testEvent(h) } diff --git a/tree/appendonlytree.go b/tree/appendonlytree.go index 39719028..916d70cd 100644 --- a/tree/appendonlytree.go +++ b/tree/appendonlytree.go @@ -16,11 +16,8 @@ type AppendOnlyTree struct { lastIndex int64 } -func NewAppendOnly(ctx context.Context, dbPath, dbPrefix string) (*AppendOnlyTree, error) { - t, err := newTree(dbPath, dbPrefix) - if err != nil { - return nil, err - } +func NewAppendOnly(ctx context.Context, db kv.RwDB, dbPrefix string) (*AppendOnlyTree, error) { + t := newTree(db, dbPrefix) at := &AppendOnlyTree{Tree: t} if err := at.initLastLeftCacheAndLastDepositCount(ctx); err != nil { return nil, err @@ -29,40 +26,32 @@ func NewAppendOnly(ctx context.Context, dbPath, dbPrefix string) (*AppendOnlyTre } // AddLeaves adds a list leaves into the tree -func (t *AppendOnlyTree) AddLeaves(ctx context.Context, leaves []Leaf) error { +func (t *AppendOnlyTree) AddLeaves(tx kv.RwTx, leaves []Leaf) (func(), error) { // Sanity check if len(leaves) == 0 { - return nil + return func() {}, nil } if int64(leaves[0].Index) != t.lastIndex+1 { - return fmt.Errorf( + return func() {}, fmt.Errorf( "mismatched index. Expected: %d, actual: %d", t.lastIndex+1, leaves[0].Index, ) } - tx, err := t.db.BeginRw(ctx) - if err != nil { - return err - } backupIndx := t.lastIndex backupCache := make([]common.Hash, len(t.lastLeftCache)) copy(backupCache, t.lastLeftCache) + rollback := func() { + t.lastIndex = backupIndx + t.lastLeftCache = backupCache + } for _, leaf := range leaves { if err := t.addLeaf(tx, leaf); err != nil { - tx.Rollback() - t.lastIndex = backupIndx - t.lastLeftCache = backupCache - return err + return rollback, err } } - if err := tx.Commit(); err != nil { - t.lastIndex = backupIndx - t.lastLeftCache = backupCache - return err - } - return nil + return rollback, nil } func (t *AppendOnlyTree) addLeaf(tx kv.RwTx, leaf Leaf) error { @@ -198,19 +187,14 @@ func (t *AppendOnlyTree) initLastLeftCache(tx kv.Tx, lastIndex int64, lastRoot c // Reorg deletes all the data relevant from firstReorgedIndex (includded) and onwards // and prepares the tree tfor being used as it was at firstReorgedIndex-1 -func (t *AppendOnlyTree) Reorg(ctx context.Context, firstReorgedIndex uint32) error { +func (t *AppendOnlyTree) Reorg(tx kv.RwTx, firstReorgedIndex uint32) (func(), error) { if t.lastIndex == -1 { - return nil - } - tx, err := t.db.BeginRw(ctx) - if err != nil { - return err + return func() {}, nil } // Clean root table for i := firstReorgedIndex; i <= uint32(t.lastIndex); i++ { if err := tx.Delete(t.rootTable, dbCommon.Uint32ToBytes(i)); err != nil { - tx.Rollback() - return err + return func() {}, err } } @@ -219,25 +203,22 @@ func (t *AppendOnlyTree) Reorg(ctx context.Context, firstReorgedIndex uint32) er if firstReorgedIndex > 0 { rootBytes, err := tx.GetOne(t.rootTable, dbCommon.Uint32ToBytes(firstReorgedIndex-1)) if err != nil { - tx.Rollback() - return err + return func() {}, err } if rootBytes == nil { - tx.Rollback() - return ErrNotFound + return func() {}, ErrNotFound } root = common.Hash(rootBytes) } - err = t.initLastLeftCache(tx, int64(firstReorgedIndex)-1, root) + err := t.initLastLeftCache(tx, int64(firstReorgedIndex)-1, root) if err != nil { - tx.Rollback() - return err + return func() {}, err } // Note: not cleaning RHT, not worth it - if err := tx.Commit(); err != nil { - return err - } + backupLastIndex := t.lastIndex t.lastIndex = int64(firstReorgedIndex) - 1 - return nil + return func() { + t.lastIndex = backupLastIndex + }, nil } diff --git a/tree/tree.go b/tree/tree.go index 6271f16e..b3881af8 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -7,7 +7,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ledgerwatch/erigon-lib/kv" - "github.com/ledgerwatch/erigon-lib/kv/mdbx" "golang.org/x/crypto/sha3" ) @@ -61,21 +60,16 @@ func (n *treeNode) UnmarshalBinary(data []byte) error { return nil } -func newTree(dbPath, dbPrefix string) (*Tree, error) { +func AddTables(tableCfg map[string]kv.TableCfgItem, dbPrefix string) { + rootTable := dbPrefix + rootTableSufix + rhtTable := dbPrefix + rhtTableSufix + tableCfg[rootTable] = kv.TableCfgItem{} + tableCfg[rhtTable] = kv.TableCfgItem{} +} + +func newTree(db kv.RwDB, dbPrefix string) *Tree { rootTable := dbPrefix + rootTableSufix rhtTable := dbPrefix + rhtTableSufix - db, err := mdbx.NewMDBX(nil). - Path(dbPath). - WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { - return kv.TableCfg{ - rootTable: {}, - rhtTable: {}, - } - }). - Open() - if err != nil { - return nil, err - } t := &Tree{ rhtTable: rhtTable, rootTable: rootTable, @@ -84,7 +78,7 @@ func newTree(dbPath, dbPrefix string) (*Tree, error) { zeroHashes: generateZeroHashes(defaultHeight), } - return t, nil + return t } // GetProof returns the merkle proof for a given index and root. diff --git a/tree/tree_test.go b/tree/tree_test.go index 47129aee..cd1348dd 100644 --- a/tree/tree_test.go +++ b/tree/tree_test.go @@ -9,6 +9,8 @@ import ( "github.com/0xPolygon/cdk/tree/testvectors" "github.com/ethereum/go-ethereum/common" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/kv/mdbx" "github.com/stretchr/testify/require" ) @@ -23,9 +25,19 @@ func TestMTAddLeaf(t *testing.T) { for ti, testVector := range mtTestVectors { t.Run(fmt.Sprintf("Test vector %d", ti), func(t *testing.T) { - path := t.TempDir() - tree, err := NewAppendOnly(context.Background(), path, "foo") + dbPrefix := "foo" + tableCfgFunc := func(defaultBuckets kv.TableCfg) kv.TableCfg { + cfg := kv.TableCfg{} + AddTables(cfg, dbPrefix) + return cfg + } + db, err := mdbx.NewMDBX(nil). + Path(path). + WithTableCfg(tableCfgFunc). + Open() + require.NoError(t, err) + tree, err := NewAppendOnly(context.Background(), db, dbPrefix) require.NoError(t, err) // Add exisiting leaves @@ -36,8 +48,11 @@ func TestMTAddLeaf(t *testing.T) { Hash: common.HexToHash(leaf), }) } - err = tree.AddLeaves(ctx, leaves) + tx, err := db.BeginRw(ctx) require.NoError(t, err) + _, err = tree.AddLeaves(tx, leaves) + require.NoError(t, err) + require.NoError(t, tx.Commit()) if len(testVector.ExistingLeaves) > 0 { txRo, err := tree.db.BeginRo(ctx) require.NoError(t, err) @@ -48,11 +63,15 @@ func TestMTAddLeaf(t *testing.T) { } // Add new bridge - err = tree.AddLeaves(ctx, []Leaf{{ + tx, err = db.BeginRw(ctx) + require.NoError(t, err) + _, err = tree.AddLeaves(tx, []Leaf{{ Index: uint32(len(testVector.ExistingLeaves)), Hash: common.HexToHash(testVector.NewLeaf.CurrentHash), }}) require.NoError(t, err) + require.NoError(t, tx.Commit()) + txRo, err := tree.db.BeginRo(ctx) require.NoError(t, err) _, actualRoot, err := tree.getLastIndexAndRoot(txRo) @@ -75,8 +94,20 @@ func TestMTGetProof(t *testing.T) { for ti, testVector := range mtTestVectors { t.Run(fmt.Sprintf("Test vector %d", ti), func(t *testing.T) { path := t.TempDir() - tree, err := NewAppendOnly(context.Background(), path, "foo") + dbPrefix := "foo" + tableCfgFunc := func(defaultBuckets kv.TableCfg) kv.TableCfg { + cfg := kv.TableCfg{} + AddTables(cfg, dbPrefix) + return cfg + } + db, err := mdbx.NewMDBX(nil). + Path(path). + WithTableCfg(tableCfgFunc). + Open() + require.NoError(t, err) + tree, err := NewAppendOnly(context.Background(), db, dbPrefix) require.NoError(t, err) + leaves := []Leaf{} for li, leaf := range testVector.Deposits { leaves = append(leaves, Leaf{ @@ -84,8 +115,12 @@ func TestMTGetProof(t *testing.T) { Hash: leaf.Hash(), }) } - err = tree.AddLeaves(ctx, leaves) + tx, err := db.BeginRw(ctx) require.NoError(t, err) + _, err = tree.AddLeaves(tx, leaves) + require.NoError(t, err) + require.NoError(t, tx.Commit()) + txRo, err := tree.db.BeginRo(ctx) require.NoError(t, err) _, actualRoot, err := tree.getLastIndexAndRoot(txRo) diff --git a/tree/updatabletree.go b/tree/updatabletree.go index 74812b26..1f72832a 100644 --- a/tree/updatabletree.go +++ b/tree/updatabletree.go @@ -5,19 +5,17 @@ import ( "errors" "github.com/ethereum/go-ethereum/common" + "github.com/ledgerwatch/erigon-lib/kv" ) type UpdatableTree struct { *Tree } -func NewUpdatable(ctx context.Context, dbPath, dbPrefix string) (*UpdatableTree, error) { - t, err := newTree(dbPath, dbPrefix) - if err != nil { - return nil, err - } +func NewUpdatable(ctx context.Context, db kv.RwDB, dbPrefix string) *UpdatableTree { + t := newTree(db, dbPrefix) ut := &UpdatableTree{Tree: t} - return ut, nil + return ut } func (t *UpdatableTree) UpsertLeaf(ctx context.Context, index uint32, leafHash common.Hash, expectedRoot *common.Hash) error { From 1453bce459a53399ca42233039931a34ee7ec154 Mon Sep 17 00:00:00 2001 From: Arnau Date: Thu, 1 Aug 2024 14:27:17 +0200 Subject: [PATCH 31/49] simplify l1infotree --- l1infotreesync/e2e_test.go | 16 +- l1infotreesync/l1infotreesync.go | 20 ++- l1infotreesync/processor.go | 260 ++++++++++--------------------- tree/tree.go | 12 ++ tree/updatabletree.go | 8 +- 5 files changed, 115 insertions(+), 201 deletions(-) diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index 42b46455..4ad76198 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -67,16 +67,16 @@ func TestE2E(t *testing.T) { // Let the processor catch up time.Sleep(time.Millisecond * 10) + expectedGER, err := gerSc.GetLastGlobalExitRoot(&bind.CallOpts{Pending: false}) + require.NoError(t, err) + info, err := syncer.GetInfoByIndex(ctx, uint32(i)) + require.NoError(t, err) + require.Equal(t, common.Hash(expectedGER), info.GlobalExitRoot, fmt.Sprintf("index: %d", i)) + expectedRoot, err := gerSc.GetRoot(&bind.CallOpts{Pending: false}) require.NoError(t, err) - expectedGER, err := gerSc.GetLastGlobalExitRoot(&bind.CallOpts{Pending: false}) + actualRoot, err := syncer.GetRootByIndex(ctx, uint32(i)) require.NoError(t, err) - info2, err := syncer.GetInfoByIndex(ctx, uint32(i)) - require.NoError(t, err, fmt.Sprintf("index: %d, root: %s", i, common.Bytes2Hex(expectedRoot[:]))) - require.Equal(t, common.Hash(expectedGER), info2.GlobalExitRoot, fmt.Sprintf("index: %d", i)) - info, err := syncer.GetInfoByRoot(ctx, expectedRoot) - require.NoError(t, err, fmt.Sprintf("index: %d, expected root: %s, actual root: %s", i, common.Bytes2Hex(expectedRoot[:]), info2.L1InfoTreeRoot)) - require.Equal(t, common.Hash(expectedRoot), info.L1InfoTreeRoot) - require.Equal(t, info, info2) + require.Equal(t, common.Hash(expectedRoot), actualRoot) } } diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index b0f2c41f..d526295b 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -100,18 +100,10 @@ func (s *L1InfoTreeSync) Start(ctx context.Context) { s.driver.Sync(ctx) } -func (s *L1InfoTreeSync) ComputeMerkleProofByIndex(ctx context.Context, index uint32) ([][32]byte, common.Hash, error) { +func (s *L1InfoTreeSync) ComputeMerkleProofByIndex(ctx context.Context, index uint32) ([]common.Hash, common.Hash, error) { return s.processor.ComputeMerkleProofByIndex(ctx, index) } -func (s *L1InfoTreeSync) ComputeMerkleProofByRoot(ctx context.Context, root common.Hash) ([][32]byte, common.Hash, error) { - return s.processor.ComputeMerkleProofByRoot(ctx, root) -} - -func (s *L1InfoTreeSync) GetInfoByRoot(ctx context.Context, root common.Hash) (*L1InfoTreeLeaf, error) { - return s.processor.GetInfoByRoot(ctx, root) -} - func (s *L1InfoTreeSync) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { return s.processor.GetLatestInfoUntilBlock(ctx, blockNum) } @@ -120,6 +112,12 @@ func (s *L1InfoTreeSync) GetInfoByIndex(ctx context.Context, index uint32) (*L1I return s.processor.GetInfoByIndex(ctx, index) } -func (s *L1InfoTreeSync) GetInfoByHash(ctx context.Context, hash []byte) (*L1InfoTreeLeaf, error) { - return s.processor.GetInfoByHash(ctx, hash) +func (s *L1InfoTreeSync) GetRootByIndex(ctx context.Context, index uint32) (common.Hash, error) { + tx, err := s.processor.db.BeginRo(ctx) + if err != nil { + return common.Hash{}, err + } + defer tx.Rollback() + + return s.processor.l1InfoTree.GetRootByIndex(tx, index) } diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index e1599839..16af569e 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -2,34 +2,29 @@ package l1infotreesync import ( "context" + "encoding/binary" "encoding/json" "errors" "fmt" "github.com/0xPolygon/cdk/common" - "github.com/0xPolygon/cdk/l1infotree" "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/sync" "github.com/0xPolygon/cdk/tree" ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/iden3/go-iden3-crypto/keccak256" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/kv/mdbx" "golang.org/x/crypto/sha3" ) const ( - dbPrefix = "l1infotreesync" - // rootTable stores the L1 info tree roots - // Key: root (common.Hash) - // Value: hash of the leaf that caused the update (common.Hash) - rootTable = dbPrefix + "-root" - // indexTable stores the L1 info tree indexes + dbPrefix = "l1infotreesync" + l1InfoTreeSuffix = "-l1infotree" + rollupExitTreeSuffix = "-rollupexittree" + + // infoTable stores the information of L1 Info Tree (the leaves) // Key: index (uint32 converted to bytes) - // Value: hash of the leaf that caused the update (common.Hash) - indexTable = dbPrefix + "-index" - // infoTable stores the information of the tree (the leaves). Note that the value - // of rootTable and indexTable references the key of the infoTable - // Key: hash of the leaf that caused the update (common.Hash) // Value: JSON of storeLeaf struct infoTable = dbPrefix + "-info" // blockTable stores the first and last index of L1 Info Tree that have been updated on @@ -50,7 +45,7 @@ var ( type processor struct { db kv.RwDB - l1InfoTree *l1infotree.L1InfoTree + l1InfoTree *tree.AppendOnlyTree rollupExitTree *tree.UpdatableTree } @@ -75,7 +70,6 @@ type Event struct { } type L1InfoTreeLeaf struct { - L1InfoTreeRoot ethCommon.Hash L1InfoTreeIndex uint32 PreviousBlockHash ethCommon.Hash BlockNumber uint64 @@ -86,13 +80,20 @@ type L1InfoTreeLeaf struct { } type storeLeaf struct { + BlockNumber uint64 MainnetExitRoot ethCommon.Hash RollupExitRoot ethCommon.Hash ParentHash ethCommon.Hash - InfoRoot ethCommon.Hash Index uint32 Timestamp uint64 - BlockNumber uint64 +} + +func (l *storeLeaf) Hash() ethCommon.Hash { + var res [32]byte + t := make([]byte, 8) //nolint:gomnd + binary.BigEndian.PutUint64(t, l.Timestamp) + copy(res[:], keccak256.Hash(l.GlobalExitRoot().Bytes(), l.ParentHash.Bytes(), t)) + return res } type blockWithLeafs struct { @@ -114,13 +115,12 @@ func (l *storeLeaf) GlobalExitRoot() ethCommon.Hash { func newProcessor(ctx context.Context, dbPath string) (*processor, error) { tableCfgFunc := func(defaultBuckets kv.TableCfg) kv.TableCfg { cfg := kv.TableCfg{ - rootTable: {}, - indexTable: {}, infoTable: {}, blockTable: {}, lastBlockTable: {}, } - tree.AddTables(cfg, dbPrefix) + tree.AddTables(cfg, dbPrefix+rollupExitTreeSuffix) + tree.AddTables(cfg, dbPrefix+l1InfoTreeSuffix) return cfg } db, err := mdbx.NewMDBX(nil). @@ -134,90 +134,35 @@ func newProcessor(ctx context.Context, dbPath string) (*processor, error) { db: db, } - tx, err := p.db.BeginRo(ctx) - if err != nil { - return nil, err - } - defer tx.Rollback() - leaves, err := p.getAllLeavesHashed(tx) - if err != nil { - return nil, err - } - l1InfoTree, err := l1infotree.NewL1InfoTree(treeHeight, leaves) + l1InfoTree, err := tree.NewAppendOnly(ctx, db, dbPrefix+l1InfoTreeSuffix) if err != nil { return nil, err } p.l1InfoTree = l1InfoTree - rollupExitTree := tree.NewUpdatable(ctx, db, dbPrefix) + rollupExitTree := tree.NewUpdatable(ctx, db, dbPrefix+rollupExitTreeSuffix) p.rollupExitTree = rollupExitTree return p, nil } -func (p *processor) getAllLeavesHashed(tx kv.Tx) ([][32]byte, error) { - // TODO: same coment about refactor that appears at ComputeMerkleProofByIndex - index, err := p.getLastIndex(tx) - if err == ErrNotFound || index == 0 { - return nil, nil - } - if err != nil { - return nil, err - } - - return p.getHashedLeaves(tx, index) -} - -func (p *processor) ComputeMerkleProofByIndex(ctx context.Context, index uint32) ([][32]byte, ethCommon.Hash, error) { - // TODO: refactor the tree to store the nodes so it's not neede to load all the leaves and compute the tree - // every time this function is called. Since it's not a sparse MT, an alternative could be to store the proofs - // as part of the info +func (p *processor) ComputeMerkleProofByIndex(ctx context.Context, index uint32) ([]ethCommon.Hash, ethCommon.Hash, error) { tx, err := p.db.BeginRo(ctx) if err != nil { return nil, ethCommon.Hash{}, err } defer tx.Rollback() - leaves, err := p.getHashedLeaves(tx, index) + root, err := p.l1InfoTree.GetRootByIndex(tx, index) if err != nil { return nil, ethCommon.Hash{}, err } - return p.l1InfoTree.ComputeMerkleProof(index, leaves) -} - -func (p *processor) getHashedLeaves(tx kv.Tx, untilIndex uint32) ([][32]byte, error) { - leaves := [][32]byte{} - for i := uint32(0); i <= untilIndex; i++ { - info, err := p.getInfoByIndexWithTx(tx, i) - if err != nil { - return nil, err - } - h := l1infotree.HashLeafData(info.GlobalExitRoot, info.PreviousBlockHash, info.Timestamp) - leaves = append(leaves, h) - } - return leaves, nil -} -func (p *processor) ComputeMerkleProofByRoot(ctx context.Context, root ethCommon.Hash) ([][32]byte, ethCommon.Hash, error) { - info, err := p.GetInfoByRoot(ctx, root) + proof, err := p.l1InfoTree.GetProof(ctx, index, root) if err != nil { return nil, ethCommon.Hash{}, err } - return p.ComputeMerkleProofByIndex(ctx, info.L1InfoTreeIndex) -} -func (p *processor) GetInfoByRoot(ctx context.Context, root ethCommon.Hash) (*L1InfoTreeLeaf, error) { - tx, err := p.db.BeginRo(ctx) - if err != nil { - return nil, err - } - defer tx.Rollback() - hash, err := tx.GetOne(rootTable, root[:]) - if err != nil { - return nil, err - } - if hash == nil { - return nil, ErrNotFound - } - return p.getInfoByHashWithTx(tx, hash) + // TODO: check if we need to return root or wat + return proof, root, nil } // GetLatestInfoUntilBlock returns the most recent L1InfoTreeLeaf that occurred before or at blockNum. @@ -255,11 +200,7 @@ func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64 if err := json.Unmarshal(v, &blk); err != nil { return nil, err } - hash, err := tx.GetOne(indexTable, common.Uint32ToBytes(blk.LastIndex-1)) - if err != nil { - return nil, err - } - return p.getInfoByHashWithTx(tx, hash) + return p.getInfoByIndexWithTx(tx, blk.LastIndex-1) } func (p *processor) GetInfoByIndex(ctx context.Context, index uint32) (*L1InfoTreeLeaf, error) { @@ -272,40 +213,19 @@ func (p *processor) GetInfoByIndex(ctx context.Context, index uint32) (*L1InfoTr } func (p *processor) getInfoByIndexWithTx(tx kv.Tx, index uint32) (*L1InfoTreeLeaf, error) { - hash, err := tx.GetOne(indexTable, common.Uint32ToBytes(index)) - if err != nil { - return nil, err - } - if hash == nil { - return nil, ErrNotFound - } - return p.getInfoByHashWithTx(tx, hash) -} - -func (p *processor) GetInfoByHash(ctx context.Context, hash []byte) (*L1InfoTreeLeaf, error) { - tx, err := p.db.BeginRo(ctx) - if err != nil { - return nil, err - } - defer tx.Rollback() - return p.getInfoByHashWithTx(tx, hash) -} - -func (p *processor) getInfoByHashWithTx(tx kv.Tx, hash []byte) (*L1InfoTreeLeaf, error) { - infoBytes, err := tx.GetOne(infoTable, hash) + infoBytes, err := tx.GetOne(infoTable, common.Uint32ToBytes(index)) if err != nil { return nil, err } if infoBytes == nil { return nil, ErrNotFound } + var info storeLeaf if err := json.Unmarshal(infoBytes, &info); err != nil { return nil, err } - return &L1InfoTreeLeaf{ - L1InfoTreeRoot: info.InfoRoot, L1InfoTreeIndex: info.Index, PreviousBlockHash: info.ParentHash, BlockNumber: info.BlockNumber, @@ -346,6 +266,7 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { } defer c.Close() firstKey := common.Uint64ToBytes(firstReorgedBlock) + firstReorgedL1InfoTreeIndex := int64(-1) for blkKey, blkValue, err := c.Seek(firstKey); blkKey != nil; blkKey, blkValue, err = c.Next() { if err != nil { tx.Rollback() @@ -357,6 +278,9 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { return err } for i := blk.FirstIndex; i < blk.LastIndex; i++ { + if firstReorgedL1InfoTreeIndex == -1 { + firstReorgedL1InfoTreeIndex = int64(i) + } if err := p.deleteLeaf(tx, i); err != nil { tx.Rollback() return err @@ -371,53 +295,24 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { tx.Rollback() return err } - leaves, err := p.getAllLeavesHashed(tx) - if err != nil { - tx.Rollback() - return err - } - l1InfoTree, err := l1infotree.NewL1InfoTree(treeHeight, leaves) - if err != nil { - tx.Rollback() - return err + var rollbackL1InfoTree func() + if firstReorgedL1InfoTreeIndex != -1 { + rollbackL1InfoTree, err = p.l1InfoTree.Reorg(tx, uint32(firstReorgedL1InfoTreeIndex)) + if err != nil { + tx.Rollback() + rollbackL1InfoTree() + return err + } } if err := tx.Commit(); err != nil { + rollbackL1InfoTree() return err } - p.l1InfoTree = l1InfoTree return nil } func (p *processor) deleteLeaf(tx kv.RwTx, index uint32) error { - // TODO: do we need to do something with p.tree here? - // Get leaf info to delete all relations - hash, err := tx.GetOne(indexTable, common.Uint32ToBytes(index)) - if err != nil { - return err - } - if hash == nil { - return ErrNotFound - } - infoBytes, err := tx.GetOne(infoTable, hash) - if err != nil { - return err - } - if infoBytes == nil { - return ErrNotFound - } - var info storeLeaf - if err := json.Unmarshal(infoBytes, &info); err != nil { - return err - } - - // Delete - if err := tx.Delete(rootTable, info.InfoRoot[:]); err != nil { - return err - } - if err := tx.Delete(indexTable, common.Uint32ToBytes(index)); err != nil { - return err - } - if err := tx.Delete(infoTable, hash); err != nil { + if err := tx.Delete(infoTable, common.Uint32ToBytes(index)); err != nil { return err } return nil @@ -431,13 +326,21 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { return err } events := make([]Event, len(b.Events)) + rollupExitTreeRollback := func() {} + l1InfoTreeRollback := func() {} + rollback := func() { + tx.Rollback() + rollupExitTreeRollback() + l1InfoTreeRollback() + } + l1InfoTreeLeavesToAdd := []tree.Leaf{} if len(b.Events) > 0 { var initialIndex uint32 lastIndex, err := p.getLastIndex(tx) if err == ErrNotFound { initialIndex = 0 } else if err != nil { - tx.Rollback() + rollback() return err } else { initialIndex = lastIndex + 1 @@ -448,17 +351,21 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { events = append(events, event) if event.UpdateL1InfoTree != nil { leafToStore := storeLeaf{ + BlockNumber: b.Num, Index: initialIndex + uint32(i), MainnetExitRoot: event.UpdateL1InfoTree.MainnetExitRoot, RollupExitRoot: event.UpdateL1InfoTree.RollupExitRoot, ParentHash: event.UpdateL1InfoTree.ParentHash, Timestamp: event.UpdateL1InfoTree.Timestamp, - BlockNumber: b.Num, } - if err := p.addLeaf(tx, leafToStore); err != nil { + if err := p.storeLeafInfo(tx, leafToStore); err != nil { tx.Rollback() return err } + l1InfoTreeLeavesToAdd = append(l1InfoTreeLeavesToAdd, tree.Leaf{ + Index: initialIndex + uint32(i), + Hash: leafToStore.Hash(), + }) nextExpectedRollupExitTreeRoot = &leafToStore.RollupExitRoot } @@ -467,13 +374,14 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { // Since the previous event include the rollup exit root, this can use it to assert // that the computation of the tree is correct. However, there are some execution paths // on the contract that don't follow this (verifyBatches + pendingStateTimeout != 0) - if err := p.rollupExitTree.UpsertLeaf( - ctx, + rollupExitTreeRollback, err = p.rollupExitTree.UpsertLeaf( + tx, event.VerifyBatches.RollupID, event.VerifyBatches.ExitRoot, nextExpectedRollupExitTreeRoot, - ); err != nil { - tx.Rollback() + ) + if err != nil { + rollback() return err } nextExpectedRollupExitTreeRoot = nil @@ -485,20 +393,31 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { } blockValue, err := json.Marshal(bwl) if err != nil { - tx.Rollback() + rollback() return err } if err := tx.Put(blockTable, common.Uint64ToBytes(b.Num), blockValue); err != nil { - tx.Rollback() + rollback() return err } } if err := p.updateLastProcessedBlock(tx, b.Num); err != nil { - tx.Rollback() + rollback() + return err + } + + l1InfoTreeRollback, err = p.l1InfoTree.AddLeaves(tx, l1InfoTreeLeavesToAdd) + if err != nil { + rollback() + return err + } + + if err := tx.Commit(); err != nil { + rollback() return err } log.Debugf("block %d processed with events: %+v", b.Num, events) - return tx.Commit() + return nil } func (p *processor) getLastIndex(tx kv.Tx) (uint32, error) { @@ -527,31 +446,12 @@ func (p *processor) getLastIndex(tx kv.Tx) (uint32, error) { return blk.LastIndex - 1, nil } -func (p *processor) addLeaf(tx kv.RwTx, leaf storeLeaf) error { - // Update tree - hash := l1infotree.HashLeafData(leaf.GlobalExitRoot(), leaf.ParentHash, leaf.Timestamp) - root, err := p.l1InfoTree.AddLeaf(leaf.Index, hash) - if err != nil { - return err - } - leaf.InfoRoot = root - // store info +func (p *processor) storeLeafInfo(tx kv.RwTx, leaf storeLeaf) error { leafValue, err := json.Marshal(leaf) if err != nil { return err } - if err := tx.Put(infoTable, hash[:], leafValue); err != nil { - return err - } - // store index relation - if err := tx.Put(indexTable, common.Uint32ToBytes(leaf.Index), hash[:]); err != nil { - return err - } - // store root relation - if err := tx.Put(rootTable, root.Bytes(), hash[:]); err != nil { - return err - } - return nil + return tx.Put(infoTable, common.Uint32ToBytes(leaf.Index), leafValue) } func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error { diff --git a/tree/tree.go b/tree/tree.go index b3881af8..ed75dd89 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + dbCommon "github.com/0xPolygon/cdk/common" "github.com/ethereum/go-ethereum/common" "github.com/ledgerwatch/erigon-lib/kv" "golang.org/x/crypto/sha3" @@ -81,6 +82,17 @@ func newTree(db kv.RwDB, dbPrefix string) *Tree { return t } +func (t *Tree) GetRootByIndex(tx kv.Tx, index uint32) (common.Hash, error) { + rootBytes, err := tx.GetOne(t.rootTable, dbCommon.Uint32ToBytes(index)) + if err != nil { + return common.Hash{}, err + } + if rootBytes == nil { + return common.Hash{}, ErrNotFound + } + return common.BytesToHash(rootBytes), nil +} + // GetProof returns the merkle proof for a given index and root. func (t *Tree) GetProof(ctx context.Context, index uint32, root common.Hash) ([]common.Hash, error) { tx, err := t.db.BeginRw(ctx) diff --git a/tree/updatabletree.go b/tree/updatabletree.go index 1f72832a..35db5243 100644 --- a/tree/updatabletree.go +++ b/tree/updatabletree.go @@ -18,6 +18,10 @@ func NewUpdatable(ctx context.Context, db kv.RwDB, dbPrefix string) *UpdatableTr return ut } -func (t *UpdatableTree) UpsertLeaf(ctx context.Context, index uint32, leafHash common.Hash, expectedRoot *common.Hash) error { - return errors.New("not implemented") +func (t *UpdatableTree) UpsertLeaf(tx kv.RwTx, index uint32, leafHash common.Hash, expectedRoot *common.Hash) (func(), error) { + return func() {}, errors.New("not implemented") +} + +func (t *UpdatableTree) Reorg(tx kv.RwTx, firstReorgedIndex uint32) (func(), error) { + return func() {}, errors.New("not implemented") } From 5f0d15a23ca3c995fe9b81d8ee8c453cccdd5601 Mon Sep 17 00:00:00 2001 From: Arnau Date: Thu, 1 Aug 2024 18:09:30 +0200 Subject: [PATCH 32/49] implementation done, test WIP --- l1infotreesync/e2e_test.go | 59 +- l1infotreesync/l1infotreesync.go | 10 +- l1infotreesync/processor.go | 40 +- test/contracts/abi/verifybatchesmock.abi | 1 + test/contracts/bin/verifybatchesmock.bin | 1 + test/contracts/bind.sh | 11 + test/contracts/compile.sh | 1 + .../verifybatchesmock/VerifyBatchesMock.sol | 131 +++++ .../verifybatchesmock/verifybatchesmock.go | 506 ++++++++++++++++++ tree/appendonlytree.go | 51 +- tree/tree.go | 126 ++++- tree/updatabletree.go | 121 ++++- 12 files changed, 969 insertions(+), 89 deletions(-) create mode 100644 test/contracts/abi/verifybatchesmock.abi create mode 100644 test/contracts/bin/verifybatchesmock.bin create mode 100755 test/contracts/bind.sh create mode 100644 test/contracts/compile.sh create mode 100644 test/contracts/verifybatchesmock/VerifyBatchesMock.sol create mode 100644 test/contracts/verifybatchesmock/verifybatchesmock.go diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index 4ad76198..416df057 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -2,6 +2,7 @@ package l1infotreesync import ( "context" + "errors" "fmt" "math/big" "strconv" @@ -11,6 +12,7 @@ import ( "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/globalexitrootnopush0" "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/reorgdetector" + "github.com/0xPolygon/cdk/test/contracts/verifybatchesmock" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -23,9 +25,12 @@ import ( func newSimulatedClient(auth *bind.TransactOpts) ( client *simulated.Backend, gerAddr common.Address, + verifyAddr common.Address, gerContract *globalexitrootnopush0.Globalexitrootnopush0, + verifyContract *verifybatchesmock.Verifybatchesmock, err error, ) { + ctx := context.Background() balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd address := auth.From genesisAlloc := map[common.Address]types.Account{ @@ -36,9 +41,26 @@ func newSimulatedClient(auth *bind.TransactOpts) ( blockGasLimit := uint64(999999999999999999) //nolint:gomnd client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) - gerAddr, _, gerContract, err = globalexitrootnopush0.DeployGlobalexitrootnopush0(auth, client.Client(), auth.From, auth.From) + nonce, err := client.Client().PendingNonceAt(ctx, auth.From) + if err != nil { + return + } + precalculatedAddr := crypto.CreateAddress(auth.From, nonce+1) + verifyAddr, _, verifyContract, err = verifybatchesmock.DeployVerifybatchesmock(auth, client.Client(), precalculatedAddr) + if err != nil { + return + } + client.Commit() + gerAddr, _, gerContract, err = globalexitrootnopush0.DeployGlobalexitrootnopush0(auth, client.Client(), verifyAddr, auth.From) + if err != nil { + return + } client.Commit() + + if precalculatedAddr != gerAddr { + err = errors.New("error calculating addr") + } return } @@ -52,31 +74,56 @@ func TestE2E(t *testing.T) { rdm := NewReorgDetectorMock(t) rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{}, nil) rdm.On("AddBlockToTrack", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - client, gerAddr, gerSc, err := newSimulatedClient(auth) + client, gerAddr, verifyAddr, gerSc, verifySC, err := newSimulatedClient(auth) require.NoError(t, err) - syncer, err := New(ctx, dbPath, gerAddr, common.Address{}, 10, etherman.LatestBlock, rdm, client.Client(), time.Millisecond, 0, 100*time.Millisecond, 3) + syncer, err := New(ctx, dbPath, gerAddr, verifyAddr, 10, etherman.LatestBlock, rdm, client.Client(), time.Millisecond, 0, 100*time.Millisecond, 3) require.NoError(t, err) go syncer.Start(ctx) // Update GER 10 times - // TODO: test syncer restart for i := 0; i < 10; i++ { - _, err := gerSc.UpdateExitRoot(auth, common.HexToHash(strconv.Itoa(i))) + tx, err := gerSc.UpdateExitRoot(auth, common.HexToHash(strconv.Itoa(i))) require.NoError(t, err) client.Commit() // Let the processor catch up time.Sleep(time.Millisecond * 10) + receipt, err := client.Client().TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) + require.Equal(t, receipt.Status, types.ReceiptStatusSuccessful) expectedGER, err := gerSc.GetLastGlobalExitRoot(&bind.CallOpts{Pending: false}) require.NoError(t, err) info, err := syncer.GetInfoByIndex(ctx, uint32(i)) require.NoError(t, err) require.Equal(t, common.Hash(expectedGER), info.GlobalExitRoot, fmt.Sprintf("index: %d", i)) + require.Equal(t, receipt.BlockNumber.Uint64(), info.BlockNumber) expectedRoot, err := gerSc.GetRoot(&bind.CallOpts{Pending: false}) require.NoError(t, err) - actualRoot, err := syncer.GetRootByIndex(ctx, uint32(i)) + actualRoot, err := syncer.GetL1InfoTreeRootByIndex(ctx, uint32(i)) require.NoError(t, err) require.Equal(t, common.Hash(expectedRoot), actualRoot) } + + // Update 10 rollups 10 times + for rollupID := uint32(1); rollupID < 10; rollupID++ { + for i := 0; i < 10; i++ { + newLocalExitRoot := common.HexToHash(strconv.Itoa(int(rollupID)) + "ffff" + strconv.Itoa(i)) + tx, err := verifySC.VerifyBatches(auth, rollupID, 0, newLocalExitRoot, common.Hash{}, true) + require.NoError(t, err) + client.Commit() + // Let the processor catch up + time.Sleep(time.Millisecond * 100) + receipt, err := client.Client().TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) + require.Equal(t, receipt.Status, types.ReceiptStatusSuccessful) + require.True(t, len(receipt.Logs) == 2) + + expectedRollupExitRoot, err := verifySC.GetRollupExitRoot(&bind.CallOpts{Pending: false}) + require.NoError(t, err) + actualRollupExitRoot, err := syncer.GetLastRollupExitRoot(ctx) + require.NoError(t, err) + require.Equal(t, common.Hash(expectedRollupExitRoot), actualRollupExitRoot, fmt.Sprintf("rollupID: %d, i: %d", rollupID, i)) + } + } } diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index d526295b..2a288704 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -100,8 +100,8 @@ func (s *L1InfoTreeSync) Start(ctx context.Context) { s.driver.Sync(ctx) } -func (s *L1InfoTreeSync) ComputeMerkleProofByIndex(ctx context.Context, index uint32) ([]common.Hash, common.Hash, error) { - return s.processor.ComputeMerkleProofByIndex(ctx, index) +func (s *L1InfoTreeSync) GetL1InfoTreeMerkleProof(ctx context.Context, index uint32) ([]common.Hash, common.Hash, error) { + return s.processor.GetL1InfoTreeMerkleProof(ctx, index) } func (s *L1InfoTreeSync) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { @@ -112,7 +112,7 @@ func (s *L1InfoTreeSync) GetInfoByIndex(ctx context.Context, index uint32) (*L1I return s.processor.GetInfoByIndex(ctx, index) } -func (s *L1InfoTreeSync) GetRootByIndex(ctx context.Context, index uint32) (common.Hash, error) { +func (s *L1InfoTreeSync) GetL1InfoTreeRootByIndex(ctx context.Context, index uint32) (common.Hash, error) { tx, err := s.processor.db.BeginRo(ctx) if err != nil { return common.Hash{}, err @@ -121,3 +121,7 @@ func (s *L1InfoTreeSync) GetRootByIndex(ctx context.Context, index uint32) (comm return s.processor.l1InfoTree.GetRootByIndex(tx, index) } + +func (s *L1InfoTreeSync) GetLastRollupExitRoot(ctx context.Context) (common.Hash, error) { + return s.processor.rollupExitTree.GetLastRoot(ctx) +} diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 16af569e..67ffc26c 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -139,12 +139,15 @@ func newProcessor(ctx context.Context, dbPath string) (*processor, error) { return nil, err } p.l1InfoTree = l1InfoTree - rollupExitTree := tree.NewUpdatable(ctx, db, dbPrefix+rollupExitTreeSuffix) + rollupExitTree, err := tree.NewUpdatable(ctx, db, dbPrefix+rollupExitTreeSuffix) + if err != nil { + return nil, err + } p.rollupExitTree = rollupExitTree return p, nil } -func (p *processor) ComputeMerkleProofByIndex(ctx context.Context, index uint32) ([]ethCommon.Hash, ethCommon.Hash, error) { +func (p *processor) GetL1InfoTreeMerkleProof(ctx context.Context, index uint32) ([]ethCommon.Hash, ethCommon.Hash, error) { tx, err := p.db.BeginRo(ctx) if err != nil { return nil, ethCommon.Hash{}, err @@ -334,6 +337,7 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { l1InfoTreeRollback() } l1InfoTreeLeavesToAdd := []tree.Leaf{} + rollupExitTreeLeavesToAdd := []tree.Leaf{} if len(b.Events) > 0 { var initialIndex uint32 lastIndex, err := p.getLastIndex(tx) @@ -374,16 +378,11 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { // Since the previous event include the rollup exit root, this can use it to assert // that the computation of the tree is correct. However, there are some execution paths // on the contract that don't follow this (verifyBatches + pendingStateTimeout != 0) - rollupExitTreeRollback, err = p.rollupExitTree.UpsertLeaf( - tx, - event.VerifyBatches.RollupID, - event.VerifyBatches.ExitRoot, - nextExpectedRollupExitTreeRoot, - ) - if err != nil { - rollback() - return err - } + rollupExitTreeLeavesToAdd = append(rollupExitTreeLeavesToAdd, tree.Leaf{ + Index: event.VerifyBatches.RollupID, + Hash: event.VerifyBatches.ExitRoot, + ExpectedRoot: nextExpectedRollupExitTreeRoot, + }) nextExpectedRollupExitTreeRoot = nil } } @@ -400,18 +399,23 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { rollback() return err } + l1InfoTreeRollback, err = p.l1InfoTree.AddLeaves(tx, l1InfoTreeLeavesToAdd) + if err != nil { + rollback() + return err + } + + rollupExitTreeRollback, err = p.rollupExitTree.UpseartLeaves(tx, rollupExitTreeLeavesToAdd, b.Num) + if err != nil { + rollback() + return err + } } if err := p.updateLastProcessedBlock(tx, b.Num); err != nil { rollback() return err } - l1InfoTreeRollback, err = p.l1InfoTree.AddLeaves(tx, l1InfoTreeLeavesToAdd) - if err != nil { - rollback() - return err - } - if err := tx.Commit(); err != nil { rollback() return err diff --git a/test/contracts/abi/verifybatchesmock.abi b/test/contracts/abi/verifybatchesmock.abi new file mode 100644 index 00000000..2b314a92 --- /dev/null +++ b/test/contracts/abi/verifybatchesmock.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"contract IPolygonZkEVMGlobalExitRootV2","name":"_globalExitRootManager","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"rollupID","type":"uint32"},{"indexed":false,"internalType":"uint64","name":"numBatch","type":"uint64"},{"indexed":false,"internalType":"bytes32","name":"stateRoot","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"exitRoot","type":"bytes32"},{"indexed":true,"internalType":"address","name":"aggregator","type":"address"}],"name":"VerifyBatches","type":"event"},{"inputs":[],"name":"getRollupExitRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"globalExitRootManager","outputs":[{"internalType":"contract IPolygonZkEVMGlobalExitRootV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rollupCount","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"rollupID","type":"uint32"}],"name":"rollupIDToLastExitRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"rollupID","type":"uint32"},{"internalType":"uint64","name":"finalNewBatch","type":"uint64"},{"internalType":"bytes32","name":"newLocalExitRoot","type":"bytes32"},{"internalType":"bytes32","name":"newStateRoot","type":"bytes32"},{"internalType":"bool","name":"updateGER","type":"bool"}],"name":"verifyBatches","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/test/contracts/bin/verifybatchesmock.bin b/test/contracts/bin/verifybatchesmock.bin new file mode 100644 index 00000000..83350d34 --- /dev/null +++ b/test/contracts/bin/verifybatchesmock.bin @@ -0,0 +1 @@ +60a060405234801561001057600080fd5b50604051610d2e380380610d2e833981810160405281019061003291906100e1565b8073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff16815250505061010e565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061009c82610071565b9050919050565b60006100ae82610091565b9050919050565b6100be816100a3565b81146100c957600080fd5b50565b6000815190506100db816100b5565b92915050565b6000602082840312156100f7576100f661006c565b5b6000610105848285016100cc565b91505092915050565b608051610bfe610130600039600081816104d501526105680152610bfe6000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630680cf5c1461005c578063a2967d991461008c578063d02103ca146100aa578063db3abdb9146100c8578063f4e92675146100e4575b600080fd5b610076600480360381019061007191906106ae565b610102565b60405161008391906106f4565b60405180910390f35b61009461011a565b6040516100a191906106f4565b60405180910390f35b6100b26104d3565b6040516100bf919061078e565b60405180910390f35b6100e260048036038101906100dd919061084d565b6104f7565b005b6100ec610659565b6040516100f991906108d7565b60405180910390f35b60016020528060005260406000206000915090505481565b60008060008054906101000a900463ffffffff1663ffffffff1690506000810361014a576000801b9150506104d0565b60008167ffffffffffffffff811115610166576101656108f2565b5b6040519080825280602002602001820160405280156101945781602001602082028036833780820191505090505b50905060005b8281101561020057600160006001836101b3919061095a565b63ffffffff1663ffffffff168152602001908152602001600020548282815181106101e1576101e061098e565b5b60200260200101818152505080806101f8906109bd565b91505061019a565b5060008060001b90506000602090505b600184146104325760006002856102279190610a34565b6002866102349190610a65565b61023e919061095a565b905060008167ffffffffffffffff81111561025c5761025b6108f2565b5b60405190808252806020026020018201604052801561028a5781602001602082028036833780820191505090505b50905060005b828110156103eb576001836102a59190610a96565b811480156102bf575060016002886102bd9190610a34565b145b1561033757856002826102d29190610aca565b815181106102e3576102e261098e565b5b6020026020010151856040516020016102fd929190610b2d565b604051602081830303815290604052805190602001208282815181106103265761032561098e565b5b6020026020010181815250506103d8565b856002826103459190610aca565b815181106103565761035561098e565b5b602002602001015186600160028461036e9190610aca565b610378919061095a565b815181106103895761038861098e565b5b60200260200101516040516020016103a2929190610b2d565b604051602081830303815290604052805190602001208282815181106103cb576103ca61098e565b5b6020026020010181815250505b80806103e3906109bd565b915050610290565b508094508195508384604051602001610405929190610b2d565b604051602081830303815290604052805190602001209350828061042890610b59565b9350505050610210565b6000836000815181106104485761044761098e565b5b6020026020010151905060005b828110156104c6578184604051602001610470929190610b2d565b604051602081830303815290604052805190602001209150838460405160200161049b929190610b2d565b60405160208183030381529060405280519060200120935080806104be906109bd565b915050610455565b5080955050505050505b90565b7f000000000000000000000000000000000000000000000000000000000000000081565b60008054906101000a900463ffffffff1663ffffffff168563ffffffff16111561053c57846000806101000a81548163ffffffff021916908363ffffffff1602179055505b82600160008763ffffffff1663ffffffff1681526020019081526020016000208190555080156105f9577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166333d6247d6105aa61011a565b6040518263ffffffff1660e01b81526004016105c691906106f4565b600060405180830381600087803b1580156105e057600080fd5b505af11580156105f4573d6000803e3d6000fd5b505050505b3373ffffffffffffffffffffffffffffffffffffffff168563ffffffff167faac1e7a157b259544ebacd6e8a82ae5d6c8f174e12aa48696277bcc9a661f0b486858760405161064a93929190610b91565b60405180910390a35050505050565b60008054906101000a900463ffffffff1681565b600080fd5b600063ffffffff82169050919050565b61068b81610672565b811461069657600080fd5b50565b6000813590506106a881610682565b92915050565b6000602082840312156106c4576106c361066d565b5b60006106d284828501610699565b91505092915050565b6000819050919050565b6106ee816106db565b82525050565b600060208201905061070960008301846106e5565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600061075461074f61074a8461070f565b61072f565b61070f565b9050919050565b600061076682610739565b9050919050565b60006107788261075b565b9050919050565b6107888161076d565b82525050565b60006020820190506107a3600083018461077f565b92915050565b600067ffffffffffffffff82169050919050565b6107c6816107a9565b81146107d157600080fd5b50565b6000813590506107e3816107bd565b92915050565b6107f2816106db565b81146107fd57600080fd5b50565b60008135905061080f816107e9565b92915050565b60008115159050919050565b61082a81610815565b811461083557600080fd5b50565b60008135905061084781610821565b92915050565b600080600080600060a086880312156108695761086861066d565b5b600061087788828901610699565b9550506020610888888289016107d4565b945050604061089988828901610800565b93505060606108aa88828901610800565b92505060806108bb88828901610838565b9150509295509295909350565b6108d181610672565b82525050565b60006020820190506108ec60008301846108c8565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061096582610921565b915061097083610921565b92508282019050808211156109885761098761092b565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006109c882610921565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036109fa576109f961092b565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000610a3f82610921565b9150610a4a83610921565b925082610a5a57610a59610a05565b5b828206905092915050565b6000610a7082610921565b9150610a7b83610921565b925082610a8b57610a8a610a05565b5b828204905092915050565b6000610aa182610921565b9150610aac83610921565b9250828203905081811115610ac457610ac361092b565b5b92915050565b6000610ad582610921565b9150610ae083610921565b9250828202610aee81610921565b91508282048414831517610b0557610b0461092b565b5b5092915050565b6000819050919050565b610b27610b22826106db565b610b0c565b82525050565b6000610b398285610b16565b602082019150610b498284610b16565b6020820191508190509392505050565b6000610b6482610921565b915060008203610b7757610b7661092b565b5b600182039050919050565b610b8b816107a9565b82525050565b6000606082019050610ba66000830186610b82565b610bb360208301856106e5565b610bc060408301846106e5565b94935050505056fea2646970667358221220638bb76fb15c02bf12ba8ed137eb377ccf7928acb4bfb7cf7d3aeb9c24f9163564736f6c63430008120033 \ No newline at end of file diff --git a/test/contracts/bind.sh b/test/contracts/bind.sh new file mode 100755 index 00000000..f0150d16 --- /dev/null +++ b/test/contracts/bind.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +gen() { + local package=$1 + + abigen --bin bin/${package}.bin --abi abi/${package}.abi --pkg=${package} --out=${package}/${package}.go +} + +gen verifybatchesmock \ No newline at end of file diff --git a/test/contracts/compile.sh b/test/contracts/compile.sh new file mode 100644 index 00000000..642cb4ad --- /dev/null +++ b/test/contracts/compile.sh @@ -0,0 +1 @@ +docker run --rm -v $(pwd):/contracts ethereum/solc:0.8.18-alpine - /contracts/verifybatchesmock/VerifyBatchesMock.sol -o /contracts --abi --bin --overwrite \ No newline at end of file diff --git a/test/contracts/verifybatchesmock/VerifyBatchesMock.sol b/test/contracts/verifybatchesmock/VerifyBatchesMock.sol new file mode 100644 index 00000000..6a65a548 --- /dev/null +++ b/test/contracts/verifybatchesmock/VerifyBatchesMock.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: AGPL-3.0 + +pragma solidity 0.8.18; + +interface IBasePolygonZkEVMGlobalExitRoot { + /** + * @dev Thrown when the caller is not the allowed contracts + */ + error OnlyAllowedContracts(); + + function updateExitRoot(bytes32 newRollupExitRoot) external; + + function globalExitRootMap( + bytes32 globalExitRootNum + ) external returns (uint256); +} + +interface IPolygonZkEVMGlobalExitRootV2 is IBasePolygonZkEVMGlobalExitRoot { + function getLastGlobalExitRoot() external view returns (bytes32); + + function getRoot() external view returns (bytes32); +} + +contract VerifyBatchesMock { + uint256 internal constant _EXIT_TREE_DEPTH = 32; + IPolygonZkEVMGlobalExitRootV2 public immutable globalExitRootManager; + uint32 public rollupCount; + mapping(uint32 rollupID => bytes32) public rollupIDToLastExitRoot; + + event VerifyBatches( + uint32 indexed rollupID, + uint64 numBatch, + bytes32 stateRoot, + bytes32 exitRoot, + address indexed aggregator + ); + + constructor( + IPolygonZkEVMGlobalExitRootV2 _globalExitRootManager + ) { + globalExitRootManager = _globalExitRootManager; + } + + function verifyBatches( + uint32 rollupID, + uint64 finalNewBatch, + bytes32 newLocalExitRoot, + bytes32 newStateRoot, + bool updateGER + ) external { + if (rollupID > rollupCount) { + rollupCount = rollupID; + } + rollupIDToLastExitRoot[rollupID] = newLocalExitRoot; + if (updateGER) { + globalExitRootManager.updateExitRoot(getRollupExitRoot()); + } + + emit VerifyBatches( + rollupID, + finalNewBatch, + newStateRoot, + newLocalExitRoot, + msg.sender + ); + } + + + function getRollupExitRoot() public view returns (bytes32) { + uint256 currentNodes = rollupCount; + + // If there are no nodes return 0 + if (currentNodes == 0) { + return bytes32(0); + } + + // This array will contain the nodes of the current iteration + bytes32[] memory tmpTree = new bytes32[](currentNodes); + + // In the first iteration the nodes will be the leafs which are the local exit roots of each network + for (uint256 i = 0; i < currentNodes; i++) { + // The first rollup ID starts on 1 + tmpTree[i] = rollupIDToLastExitRoot[uint32(i + 1)]; + } + + // This variable will keep track of the zero hashes + bytes32 currentZeroHashHeight = 0; + + // This variable will keep track of the reamining levels to compute + uint256 remainingLevels = _EXIT_TREE_DEPTH; + + // Calculate the root of the sub-tree that contains all the localExitRoots + while (currentNodes != 1) { + uint256 nextIterationNodes = currentNodes / 2 + (currentNodes % 2); + bytes32[] memory nextTmpTree = new bytes32[](nextIterationNodes); + for (uint256 i = 0; i < nextIterationNodes; i++) { + // if we are on the last iteration of the current level and the nodes are odd + if (i == nextIterationNodes - 1 && (currentNodes % 2) == 1) { + nextTmpTree[i] = keccak256( + abi.encodePacked(tmpTree[i * 2], currentZeroHashHeight) + ); + } else { + nextTmpTree[i] = keccak256( + abi.encodePacked(tmpTree[i * 2], tmpTree[(i * 2) + 1]) + ); + } + } + + // Update tree variables + tmpTree = nextTmpTree; + currentNodes = nextIterationNodes; + currentZeroHashHeight = keccak256( + abi.encodePacked(currentZeroHashHeight, currentZeroHashHeight) + ); + remainingLevels--; + } + + bytes32 currentRoot = tmpTree[0]; + + // Calculate remaining levels, since it's a sequencial merkle tree, the rest of the tree are zeroes + for (uint256 i = 0; i < remainingLevels; i++) { + currentRoot = keccak256( + abi.encodePacked(currentRoot, currentZeroHashHeight) + ); + currentZeroHashHeight = keccak256( + abi.encodePacked(currentZeroHashHeight, currentZeroHashHeight) + ); + } + return currentRoot; + } +} \ No newline at end of file diff --git a/test/contracts/verifybatchesmock/verifybatchesmock.go b/test/contracts/verifybatchesmock/verifybatchesmock.go new file mode 100644 index 00000000..c3013f66 --- /dev/null +++ b/test/contracts/verifybatchesmock/verifybatchesmock.go @@ -0,0 +1,506 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package verifybatchesmock + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// VerifybatchesmockMetaData contains all meta data concerning the Verifybatchesmock contract. +var VerifybatchesmockMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"contractIPolygonZkEVMGlobalExitRootV2\",\"name\":\"_globalExitRootManager\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"rollupID\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"numBatch\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"exitRoot\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"aggregator\",\"type\":\"address\"}],\"name\":\"VerifyBatches\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"getRollupExitRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"globalExitRootManager\",\"outputs\":[{\"internalType\":\"contractIPolygonZkEVMGlobalExitRootV2\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rollupCount\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"rollupID\",\"type\":\"uint32\"}],\"name\":\"rollupIDToLastExitRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"rollupID\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"finalNewBatch\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"newLocalExitRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"newStateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"updateGER\",\"type\":\"bool\"}],\"name\":\"verifyBatches\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60a060405234801561001057600080fd5b50604051610d2e380380610d2e833981810160405281019061003291906100e1565b8073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff16815250505061010e565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061009c82610071565b9050919050565b60006100ae82610091565b9050919050565b6100be816100a3565b81146100c957600080fd5b50565b6000815190506100db816100b5565b92915050565b6000602082840312156100f7576100f661006c565b5b6000610105848285016100cc565b91505092915050565b608051610bfe610130600039600081816104d501526105680152610bfe6000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630680cf5c1461005c578063a2967d991461008c578063d02103ca146100aa578063db3abdb9146100c8578063f4e92675146100e4575b600080fd5b610076600480360381019061007191906106ae565b610102565b60405161008391906106f4565b60405180910390f35b61009461011a565b6040516100a191906106f4565b60405180910390f35b6100b26104d3565b6040516100bf919061078e565b60405180910390f35b6100e260048036038101906100dd919061084d565b6104f7565b005b6100ec610659565b6040516100f991906108d7565b60405180910390f35b60016020528060005260406000206000915090505481565b60008060008054906101000a900463ffffffff1663ffffffff1690506000810361014a576000801b9150506104d0565b60008167ffffffffffffffff811115610166576101656108f2565b5b6040519080825280602002602001820160405280156101945781602001602082028036833780820191505090505b50905060005b8281101561020057600160006001836101b3919061095a565b63ffffffff1663ffffffff168152602001908152602001600020548282815181106101e1576101e061098e565b5b60200260200101818152505080806101f8906109bd565b91505061019a565b5060008060001b90506000602090505b600184146104325760006002856102279190610a34565b6002866102349190610a65565b61023e919061095a565b905060008167ffffffffffffffff81111561025c5761025b6108f2565b5b60405190808252806020026020018201604052801561028a5781602001602082028036833780820191505090505b50905060005b828110156103eb576001836102a59190610a96565b811480156102bf575060016002886102bd9190610a34565b145b1561033757856002826102d29190610aca565b815181106102e3576102e261098e565b5b6020026020010151856040516020016102fd929190610b2d565b604051602081830303815290604052805190602001208282815181106103265761032561098e565b5b6020026020010181815250506103d8565b856002826103459190610aca565b815181106103565761035561098e565b5b602002602001015186600160028461036e9190610aca565b610378919061095a565b815181106103895761038861098e565b5b60200260200101516040516020016103a2929190610b2d565b604051602081830303815290604052805190602001208282815181106103cb576103ca61098e565b5b6020026020010181815250505b80806103e3906109bd565b915050610290565b508094508195508384604051602001610405929190610b2d565b604051602081830303815290604052805190602001209350828061042890610b59565b9350505050610210565b6000836000815181106104485761044761098e565b5b6020026020010151905060005b828110156104c6578184604051602001610470929190610b2d565b604051602081830303815290604052805190602001209150838460405160200161049b929190610b2d565b60405160208183030381529060405280519060200120935080806104be906109bd565b915050610455565b5080955050505050505b90565b7f000000000000000000000000000000000000000000000000000000000000000081565b60008054906101000a900463ffffffff1663ffffffff168563ffffffff16111561053c57846000806101000a81548163ffffffff021916908363ffffffff1602179055505b82600160008763ffffffff1663ffffffff1681526020019081526020016000208190555080156105f9577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166333d6247d6105aa61011a565b6040518263ffffffff1660e01b81526004016105c691906106f4565b600060405180830381600087803b1580156105e057600080fd5b505af11580156105f4573d6000803e3d6000fd5b505050505b3373ffffffffffffffffffffffffffffffffffffffff168563ffffffff167faac1e7a157b259544ebacd6e8a82ae5d6c8f174e12aa48696277bcc9a661f0b486858760405161064a93929190610b91565b60405180910390a35050505050565b60008054906101000a900463ffffffff1681565b600080fd5b600063ffffffff82169050919050565b61068b81610672565b811461069657600080fd5b50565b6000813590506106a881610682565b92915050565b6000602082840312156106c4576106c361066d565b5b60006106d284828501610699565b91505092915050565b6000819050919050565b6106ee816106db565b82525050565b600060208201905061070960008301846106e5565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600061075461074f61074a8461070f565b61072f565b61070f565b9050919050565b600061076682610739565b9050919050565b60006107788261075b565b9050919050565b6107888161076d565b82525050565b60006020820190506107a3600083018461077f565b92915050565b600067ffffffffffffffff82169050919050565b6107c6816107a9565b81146107d157600080fd5b50565b6000813590506107e3816107bd565b92915050565b6107f2816106db565b81146107fd57600080fd5b50565b60008135905061080f816107e9565b92915050565b60008115159050919050565b61082a81610815565b811461083557600080fd5b50565b60008135905061084781610821565b92915050565b600080600080600060a086880312156108695761086861066d565b5b600061087788828901610699565b9550506020610888888289016107d4565b945050604061089988828901610800565b93505060606108aa88828901610800565b92505060806108bb88828901610838565b9150509295509295909350565b6108d181610672565b82525050565b60006020820190506108ec60008301846108c8565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061096582610921565b915061097083610921565b92508282019050808211156109885761098761092b565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006109c882610921565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036109fa576109f961092b565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000610a3f82610921565b9150610a4a83610921565b925082610a5a57610a59610a05565b5b828206905092915050565b6000610a7082610921565b9150610a7b83610921565b925082610a8b57610a8a610a05565b5b828204905092915050565b6000610aa182610921565b9150610aac83610921565b9250828203905081811115610ac457610ac361092b565b5b92915050565b6000610ad582610921565b9150610ae083610921565b9250828202610aee81610921565b91508282048414831517610b0557610b0461092b565b5b5092915050565b6000819050919050565b610b27610b22826106db565b610b0c565b82525050565b6000610b398285610b16565b602082019150610b498284610b16565b6020820191508190509392505050565b6000610b6482610921565b915060008203610b7757610b7661092b565b5b600182039050919050565b610b8b816107a9565b82525050565b6000606082019050610ba66000830186610b82565b610bb360208301856106e5565b610bc060408301846106e5565b94935050505056fea2646970667358221220638bb76fb15c02bf12ba8ed137eb377ccf7928acb4bfb7cf7d3aeb9c24f9163564736f6c63430008120033", +} + +// VerifybatchesmockABI is the input ABI used to generate the binding from. +// Deprecated: Use VerifybatchesmockMetaData.ABI instead. +var VerifybatchesmockABI = VerifybatchesmockMetaData.ABI + +// VerifybatchesmockBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use VerifybatchesmockMetaData.Bin instead. +var VerifybatchesmockBin = VerifybatchesmockMetaData.Bin + +// DeployVerifybatchesmock deploys a new Ethereum contract, binding an instance of Verifybatchesmock to it. +func DeployVerifybatchesmock(auth *bind.TransactOpts, backend bind.ContractBackend, _globalExitRootManager common.Address) (common.Address, *types.Transaction, *Verifybatchesmock, error) { + parsed, err := VerifybatchesmockMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(VerifybatchesmockBin), backend, _globalExitRootManager) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Verifybatchesmock{VerifybatchesmockCaller: VerifybatchesmockCaller{contract: contract}, VerifybatchesmockTransactor: VerifybatchesmockTransactor{contract: contract}, VerifybatchesmockFilterer: VerifybatchesmockFilterer{contract: contract}}, nil +} + +// Verifybatchesmock is an auto generated Go binding around an Ethereum contract. +type Verifybatchesmock struct { + VerifybatchesmockCaller // Read-only binding to the contract + VerifybatchesmockTransactor // Write-only binding to the contract + VerifybatchesmockFilterer // Log filterer for contract events +} + +// VerifybatchesmockCaller is an auto generated read-only Go binding around an Ethereum contract. +type VerifybatchesmockCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// VerifybatchesmockTransactor is an auto generated write-only Go binding around an Ethereum contract. +type VerifybatchesmockTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// VerifybatchesmockFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type VerifybatchesmockFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// VerifybatchesmockSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type VerifybatchesmockSession struct { + Contract *Verifybatchesmock // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// VerifybatchesmockCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type VerifybatchesmockCallerSession struct { + Contract *VerifybatchesmockCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// VerifybatchesmockTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type VerifybatchesmockTransactorSession struct { + Contract *VerifybatchesmockTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// VerifybatchesmockRaw is an auto generated low-level Go binding around an Ethereum contract. +type VerifybatchesmockRaw struct { + Contract *Verifybatchesmock // Generic contract binding to access the raw methods on +} + +// VerifybatchesmockCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type VerifybatchesmockCallerRaw struct { + Contract *VerifybatchesmockCaller // Generic read-only contract binding to access the raw methods on +} + +// VerifybatchesmockTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type VerifybatchesmockTransactorRaw struct { + Contract *VerifybatchesmockTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewVerifybatchesmock creates a new instance of Verifybatchesmock, bound to a specific deployed contract. +func NewVerifybatchesmock(address common.Address, backend bind.ContractBackend) (*Verifybatchesmock, error) { + contract, err := bindVerifybatchesmock(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Verifybatchesmock{VerifybatchesmockCaller: VerifybatchesmockCaller{contract: contract}, VerifybatchesmockTransactor: VerifybatchesmockTransactor{contract: contract}, VerifybatchesmockFilterer: VerifybatchesmockFilterer{contract: contract}}, nil +} + +// NewVerifybatchesmockCaller creates a new read-only instance of Verifybatchesmock, bound to a specific deployed contract. +func NewVerifybatchesmockCaller(address common.Address, caller bind.ContractCaller) (*VerifybatchesmockCaller, error) { + contract, err := bindVerifybatchesmock(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &VerifybatchesmockCaller{contract: contract}, nil +} + +// NewVerifybatchesmockTransactor creates a new write-only instance of Verifybatchesmock, bound to a specific deployed contract. +func NewVerifybatchesmockTransactor(address common.Address, transactor bind.ContractTransactor) (*VerifybatchesmockTransactor, error) { + contract, err := bindVerifybatchesmock(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &VerifybatchesmockTransactor{contract: contract}, nil +} + +// NewVerifybatchesmockFilterer creates a new log filterer instance of Verifybatchesmock, bound to a specific deployed contract. +func NewVerifybatchesmockFilterer(address common.Address, filterer bind.ContractFilterer) (*VerifybatchesmockFilterer, error) { + contract, err := bindVerifybatchesmock(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &VerifybatchesmockFilterer{contract: contract}, nil +} + +// bindVerifybatchesmock binds a generic wrapper to an already deployed contract. +func bindVerifybatchesmock(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := VerifybatchesmockMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Verifybatchesmock *VerifybatchesmockRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Verifybatchesmock.Contract.VerifybatchesmockCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Verifybatchesmock *VerifybatchesmockRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Verifybatchesmock.Contract.VerifybatchesmockTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Verifybatchesmock *VerifybatchesmockRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Verifybatchesmock.Contract.VerifybatchesmockTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Verifybatchesmock *VerifybatchesmockCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Verifybatchesmock.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Verifybatchesmock *VerifybatchesmockTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Verifybatchesmock.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Verifybatchesmock *VerifybatchesmockTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Verifybatchesmock.Contract.contract.Transact(opts, method, params...) +} + +// GetRollupExitRoot is a free data retrieval call binding the contract method 0xa2967d99. +// +// Solidity: function getRollupExitRoot() view returns(bytes32) +func (_Verifybatchesmock *VerifybatchesmockCaller) GetRollupExitRoot(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _Verifybatchesmock.contract.Call(opts, &out, "getRollupExitRoot") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// GetRollupExitRoot is a free data retrieval call binding the contract method 0xa2967d99. +// +// Solidity: function getRollupExitRoot() view returns(bytes32) +func (_Verifybatchesmock *VerifybatchesmockSession) GetRollupExitRoot() ([32]byte, error) { + return _Verifybatchesmock.Contract.GetRollupExitRoot(&_Verifybatchesmock.CallOpts) +} + +// GetRollupExitRoot is a free data retrieval call binding the contract method 0xa2967d99. +// +// Solidity: function getRollupExitRoot() view returns(bytes32) +func (_Verifybatchesmock *VerifybatchesmockCallerSession) GetRollupExitRoot() ([32]byte, error) { + return _Verifybatchesmock.Contract.GetRollupExitRoot(&_Verifybatchesmock.CallOpts) +} + +// GlobalExitRootManager is a free data retrieval call binding the contract method 0xd02103ca. +// +// Solidity: function globalExitRootManager() view returns(address) +func (_Verifybatchesmock *VerifybatchesmockCaller) GlobalExitRootManager(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _Verifybatchesmock.contract.Call(opts, &out, "globalExitRootManager") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GlobalExitRootManager is a free data retrieval call binding the contract method 0xd02103ca. +// +// Solidity: function globalExitRootManager() view returns(address) +func (_Verifybatchesmock *VerifybatchesmockSession) GlobalExitRootManager() (common.Address, error) { + return _Verifybatchesmock.Contract.GlobalExitRootManager(&_Verifybatchesmock.CallOpts) +} + +// GlobalExitRootManager is a free data retrieval call binding the contract method 0xd02103ca. +// +// Solidity: function globalExitRootManager() view returns(address) +func (_Verifybatchesmock *VerifybatchesmockCallerSession) GlobalExitRootManager() (common.Address, error) { + return _Verifybatchesmock.Contract.GlobalExitRootManager(&_Verifybatchesmock.CallOpts) +} + +// RollupCount is a free data retrieval call binding the contract method 0xf4e92675. +// +// Solidity: function rollupCount() view returns(uint32) +func (_Verifybatchesmock *VerifybatchesmockCaller) RollupCount(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _Verifybatchesmock.contract.Call(opts, &out, "rollupCount") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// RollupCount is a free data retrieval call binding the contract method 0xf4e92675. +// +// Solidity: function rollupCount() view returns(uint32) +func (_Verifybatchesmock *VerifybatchesmockSession) RollupCount() (uint32, error) { + return _Verifybatchesmock.Contract.RollupCount(&_Verifybatchesmock.CallOpts) +} + +// RollupCount is a free data retrieval call binding the contract method 0xf4e92675. +// +// Solidity: function rollupCount() view returns(uint32) +func (_Verifybatchesmock *VerifybatchesmockCallerSession) RollupCount() (uint32, error) { + return _Verifybatchesmock.Contract.RollupCount(&_Verifybatchesmock.CallOpts) +} + +// RollupIDToLastExitRoot is a free data retrieval call binding the contract method 0x0680cf5c. +// +// Solidity: function rollupIDToLastExitRoot(uint32 rollupID) view returns(bytes32) +func (_Verifybatchesmock *VerifybatchesmockCaller) RollupIDToLastExitRoot(opts *bind.CallOpts, rollupID uint32) ([32]byte, error) { + var out []interface{} + err := _Verifybatchesmock.contract.Call(opts, &out, "rollupIDToLastExitRoot", rollupID) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// RollupIDToLastExitRoot is a free data retrieval call binding the contract method 0x0680cf5c. +// +// Solidity: function rollupIDToLastExitRoot(uint32 rollupID) view returns(bytes32) +func (_Verifybatchesmock *VerifybatchesmockSession) RollupIDToLastExitRoot(rollupID uint32) ([32]byte, error) { + return _Verifybatchesmock.Contract.RollupIDToLastExitRoot(&_Verifybatchesmock.CallOpts, rollupID) +} + +// RollupIDToLastExitRoot is a free data retrieval call binding the contract method 0x0680cf5c. +// +// Solidity: function rollupIDToLastExitRoot(uint32 rollupID) view returns(bytes32) +func (_Verifybatchesmock *VerifybatchesmockCallerSession) RollupIDToLastExitRoot(rollupID uint32) ([32]byte, error) { + return _Verifybatchesmock.Contract.RollupIDToLastExitRoot(&_Verifybatchesmock.CallOpts, rollupID) +} + +// VerifyBatches is a paid mutator transaction binding the contract method 0xdb3abdb9. +// +// Solidity: function verifyBatches(uint32 rollupID, uint64 finalNewBatch, bytes32 newLocalExitRoot, bytes32 newStateRoot, bool updateGER) returns() +func (_Verifybatchesmock *VerifybatchesmockTransactor) VerifyBatches(opts *bind.TransactOpts, rollupID uint32, finalNewBatch uint64, newLocalExitRoot [32]byte, newStateRoot [32]byte, updateGER bool) (*types.Transaction, error) { + return _Verifybatchesmock.contract.Transact(opts, "verifyBatches", rollupID, finalNewBatch, newLocalExitRoot, newStateRoot, updateGER) +} + +// VerifyBatches is a paid mutator transaction binding the contract method 0xdb3abdb9. +// +// Solidity: function verifyBatches(uint32 rollupID, uint64 finalNewBatch, bytes32 newLocalExitRoot, bytes32 newStateRoot, bool updateGER) returns() +func (_Verifybatchesmock *VerifybatchesmockSession) VerifyBatches(rollupID uint32, finalNewBatch uint64, newLocalExitRoot [32]byte, newStateRoot [32]byte, updateGER bool) (*types.Transaction, error) { + return _Verifybatchesmock.Contract.VerifyBatches(&_Verifybatchesmock.TransactOpts, rollupID, finalNewBatch, newLocalExitRoot, newStateRoot, updateGER) +} + +// VerifyBatches is a paid mutator transaction binding the contract method 0xdb3abdb9. +// +// Solidity: function verifyBatches(uint32 rollupID, uint64 finalNewBatch, bytes32 newLocalExitRoot, bytes32 newStateRoot, bool updateGER) returns() +func (_Verifybatchesmock *VerifybatchesmockTransactorSession) VerifyBatches(rollupID uint32, finalNewBatch uint64, newLocalExitRoot [32]byte, newStateRoot [32]byte, updateGER bool) (*types.Transaction, error) { + return _Verifybatchesmock.Contract.VerifyBatches(&_Verifybatchesmock.TransactOpts, rollupID, finalNewBatch, newLocalExitRoot, newStateRoot, updateGER) +} + +// VerifybatchesmockVerifyBatchesIterator is returned from FilterVerifyBatches and is used to iterate over the raw logs and unpacked data for VerifyBatches events raised by the Verifybatchesmock contract. +type VerifybatchesmockVerifyBatchesIterator struct { + Event *VerifybatchesmockVerifyBatches // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *VerifybatchesmockVerifyBatchesIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(VerifybatchesmockVerifyBatches) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(VerifybatchesmockVerifyBatches) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *VerifybatchesmockVerifyBatchesIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *VerifybatchesmockVerifyBatchesIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// VerifybatchesmockVerifyBatches represents a VerifyBatches event raised by the Verifybatchesmock contract. +type VerifybatchesmockVerifyBatches struct { + RollupID uint32 + NumBatch uint64 + StateRoot [32]byte + ExitRoot [32]byte + Aggregator common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterVerifyBatches is a free log retrieval operation binding the contract event 0xaac1e7a157b259544ebacd6e8a82ae5d6c8f174e12aa48696277bcc9a661f0b4. +// +// Solidity: event VerifyBatches(uint32 indexed rollupID, uint64 numBatch, bytes32 stateRoot, bytes32 exitRoot, address indexed aggregator) +func (_Verifybatchesmock *VerifybatchesmockFilterer) FilterVerifyBatches(opts *bind.FilterOpts, rollupID []uint32, aggregator []common.Address) (*VerifybatchesmockVerifyBatchesIterator, error) { + + var rollupIDRule []interface{} + for _, rollupIDItem := range rollupID { + rollupIDRule = append(rollupIDRule, rollupIDItem) + } + + var aggregatorRule []interface{} + for _, aggregatorItem := range aggregator { + aggregatorRule = append(aggregatorRule, aggregatorItem) + } + + logs, sub, err := _Verifybatchesmock.contract.FilterLogs(opts, "VerifyBatches", rollupIDRule, aggregatorRule) + if err != nil { + return nil, err + } + return &VerifybatchesmockVerifyBatchesIterator{contract: _Verifybatchesmock.contract, event: "VerifyBatches", logs: logs, sub: sub}, nil +} + +// WatchVerifyBatches is a free log subscription operation binding the contract event 0xaac1e7a157b259544ebacd6e8a82ae5d6c8f174e12aa48696277bcc9a661f0b4. +// +// Solidity: event VerifyBatches(uint32 indexed rollupID, uint64 numBatch, bytes32 stateRoot, bytes32 exitRoot, address indexed aggregator) +func (_Verifybatchesmock *VerifybatchesmockFilterer) WatchVerifyBatches(opts *bind.WatchOpts, sink chan<- *VerifybatchesmockVerifyBatches, rollupID []uint32, aggregator []common.Address) (event.Subscription, error) { + + var rollupIDRule []interface{} + for _, rollupIDItem := range rollupID { + rollupIDRule = append(rollupIDRule, rollupIDItem) + } + + var aggregatorRule []interface{} + for _, aggregatorItem := range aggregator { + aggregatorRule = append(aggregatorRule, aggregatorItem) + } + + logs, sub, err := _Verifybatchesmock.contract.WatchLogs(opts, "VerifyBatches", rollupIDRule, aggregatorRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(VerifybatchesmockVerifyBatches) + if err := _Verifybatchesmock.contract.UnpackLog(event, "VerifyBatches", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseVerifyBatches is a log parse operation binding the contract event 0xaac1e7a157b259544ebacd6e8a82ae5d6c8f174e12aa48696277bcc9a661f0b4. +// +// Solidity: event VerifyBatches(uint32 indexed rollupID, uint64 numBatch, bytes32 stateRoot, bytes32 exitRoot, address indexed aggregator) +func (_Verifybatchesmock *VerifybatchesmockFilterer) ParseVerifyBatches(log types.Log) (*VerifybatchesmockVerifyBatches, error) { + event := new(VerifybatchesmockVerifyBatches) + if err := _Verifybatchesmock.contract.UnpackLog(event, "VerifyBatches", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/tree/appendonlytree.go b/tree/appendonlytree.go index 916d70cd..0d23a735 100644 --- a/tree/appendonlytree.go +++ b/tree/appendonlytree.go @@ -3,7 +3,6 @@ package tree import ( "context" "fmt" - "math" dbCommon "github.com/0xPolygon/cdk/common" "github.com/ethereum/go-ethereum/common" @@ -81,27 +80,28 @@ func (t *AppendOnlyTree) addLeaf(tx kv.RwTx, leaf Leaf) error { newNodes = append(newNodes, parent) } + if err := assertRoot(leaf.ExpectedRoot, currentChildHash); err != nil { + return err + } + // store root + t.storeRoot(tx, uint64(leaf.Index), currentChildHash) root := currentChildHash - if err := tx.Put(t.rootTable, dbCommon.Uint32ToBytes(leaf.Index), root[:]); err != nil { + if err := tx.Put(t.rootTable, dbCommon.Uint64ToBytes(uint64(leaf.Index)), root[:]); err != nil { return err } - // store nodes - for _, node := range newNodes { - value, err := node.MarshalBinary() - if err != nil { - return err - } - if err := tx.Put(t.rhtTable, node.hash().Bytes(), value); err != nil { - return err - } + if err := t.storeNodes(tx, newNodes); err != nil { + return err } - t.lastIndex++ return nil } +func (t *AppendOnlyTree) GetRootByIndex(tx kv.Tx, index uint32) (common.Hash, error) { + return t.getRootByIndex(tx, uint64(index)) +} + func (t *AppendOnlyTree) initLastLeftCacheAndLastDepositCount(ctx context.Context) error { tx, err := t.db.BeginRw(ctx) if err != nil { @@ -116,29 +116,6 @@ func (t *AppendOnlyTree) initLastLeftCacheAndLastDepositCount(ctx context.Contex return t.initLastLeftCache(tx, t.lastIndex, root) } -// getLastIndexAndRoot return the index and the root associated to the last leaf inserted. -// If index == -1, it means no leaf added yet -func (t *AppendOnlyTree) getLastIndexAndRoot(tx kv.Tx) (int64, common.Hash, error) { - iter, err := tx.RangeDescend( - t.rootTable, - dbCommon.Uint32ToBytes(math.MaxUint32), - dbCommon.Uint32ToBytes(0), - 1, - ) - if err != nil { - return 0, common.Hash{}, err - } - - lastIndexBytes, rootBytes, err := iter.Next() - if err != nil { - return 0, common.Hash{}, err - } - if lastIndexBytes == nil { - return -1, common.Hash{}, nil - } - return int64(dbCommon.BytesToUint32(lastIndexBytes)), common.Hash(rootBytes), nil -} - func (t *AppendOnlyTree) initLastIndex(tx kv.Tx) (common.Hash, error) { ldc, root, err := t.getLastIndexAndRoot(tx) if err != nil { @@ -193,7 +170,7 @@ func (t *AppendOnlyTree) Reorg(tx kv.RwTx, firstReorgedIndex uint32) (func(), er } // Clean root table for i := firstReorgedIndex; i <= uint32(t.lastIndex); i++ { - if err := tx.Delete(t.rootTable, dbCommon.Uint32ToBytes(i)); err != nil { + if err := tx.Delete(t.rootTable, dbCommon.Uint64ToBytes(uint64(i))); err != nil { return func() {}, err } } @@ -201,7 +178,7 @@ func (t *AppendOnlyTree) Reorg(tx kv.RwTx, firstReorgedIndex uint32) (func(), er // Reset root := common.Hash{} if firstReorgedIndex > 0 { - rootBytes, err := tx.GetOne(t.rootTable, dbCommon.Uint32ToBytes(firstReorgedIndex-1)) + rootBytes, err := tx.GetOne(t.rootTable, dbCommon.Uint64ToBytes(uint64(firstReorgedIndex)-1)) if err != nil { return func() {}, err } diff --git a/tree/tree.go b/tree/tree.go index ed75dd89..a93b8c28 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" dbCommon "github.com/0xPolygon/cdk/common" "github.com/ethereum/go-ethereum/common" @@ -22,8 +23,9 @@ var ( ) type Leaf struct { - Index uint32 - Hash common.Hash + Index uint32 + Hash common.Hash + ExpectedRoot *common.Hash } type Tree struct { @@ -82,8 +84,8 @@ func newTree(db kv.RwDB, dbPrefix string) *Tree { return t } -func (t *Tree) GetRootByIndex(tx kv.Tx, index uint32) (common.Hash, error) { - rootBytes, err := tx.GetOne(t.rootTable, dbCommon.Uint32ToBytes(index)) +func (t *Tree) getRootByIndex(tx kv.Tx, index uint64) (common.Hash, error) { + rootBytes, err := tx.GetOne(t.rootTable, dbCommon.Uint64ToBytes(index)) if err != nil { return common.Hash{}, err } @@ -93,24 +95,30 @@ func (t *Tree) GetRootByIndex(tx kv.Tx, index uint32) (common.Hash, error) { return common.BytesToHash(rootBytes), nil } -// GetProof returns the merkle proof for a given index and root. -func (t *Tree) GetProof(ctx context.Context, index uint32, root common.Hash) ([]common.Hash, error) { - tx, err := t.db.BeginRw(ctx) - if err != nil { - return nil, err - } - defer tx.Rollback() - siblings := make([]common.Hash, int(t.height)) +func (t *Tree) getSiblings(tx kv.Tx, index uint32, root common.Hash) ( + siblings []common.Hash, + hasUsedZeroHashes bool, + err error, +) { + siblings = make([]common.Hash, int(t.height)) currentNodeHash := root // It starts in height-1 because 0 is the level of the leafs for h := int(t.height - 1); h >= 0; h-- { - currentNode, err := t.getRHTNode(tx, currentNodeHash) + var currentNode *treeNode + currentNode, err = t.getRHTNode(tx, currentNodeHash) if err != nil { - return nil, fmt.Errorf( - "height: %d, currentNode: %s, error: %v", - h, currentNodeHash.Hex(), err, - ) + if err == ErrNotFound { + hasUsedZeroHashes = true + siblings = append(siblings, t.zeroHashes[h]) + continue + } else { + err = fmt.Errorf( + "height: %d, currentNode: %s, error: %v", + h, currentNodeHash.Hex(), err, + ) + return + } } /* * Root (level h=3 => height=4) @@ -147,6 +155,23 @@ func (t *Tree) GetProof(ctx context.Context, index uint32, root common.Hash) ([] siblings[i], siblings[j] = siblings[j], siblings[i] } + return +} + +// GetProof returns the merkle proof for a given index and root. +func (t *Tree) GetProof(ctx context.Context, index uint32, root common.Hash) ([]common.Hash, error) { + tx, err := t.db.BeginRw(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + siblings, isErrNotFound, err := t.getSiblings(tx, index, root) + if err != nil { + return nil, err + } + if isErrNotFound { + return nil, ErrNotFound + } return siblings, nil } @@ -179,3 +204,70 @@ func generateZeroHashes(height uint8) []common.Hash { } return zeroHashes } + +func (t *Tree) storeNodes(tx kv.RwTx, nodes []treeNode) error { + for _, node := range nodes { + value, err := node.MarshalBinary() + if err != nil { + return err + } + if err := tx.Put(t.rhtTable, node.hash().Bytes(), value); err != nil { + return err + } + } + return nil +} + +func (t *Tree) storeRoot(tx kv.RwTx, rootIndex uint64, root common.Hash) error { + return tx.Put(t.rootTable, dbCommon.Uint64ToBytes(rootIndex), root[:]) +} + +func (t *Tree) GetLastRoot(ctx context.Context) (common.Hash, error) { + tx, err := t.db.BeginRo(ctx) + if err != nil { + return common.Hash{}, err + } + + i, root, err := t.getLastIndexAndRoot(tx) + if err != nil { + return common.Hash{}, err + } + if i == -1 { + return common.Hash{}, ErrNotFound + } + return root, nil +} + +// getLastIndexAndRoot return the index and the root associated to the last leaf inserted. +// If index == -1, it means no leaf added yet +func (t *Tree) getLastIndexAndRoot(tx kv.Tx) (int64, common.Hash, error) { + iter, err := tx.RangeDescend( + t.rootTable, + dbCommon.Uint64ToBytes(math.MaxUint64), + dbCommon.Uint64ToBytes(0), + 1, + ) + if err != nil { + return 0, common.Hash{}, err + } + + lastIndexBytes, rootBytes, err := iter.Next() + if err != nil { + return 0, common.Hash{}, err + } + if lastIndexBytes == nil { + return -1, common.Hash{}, nil + } + return int64(dbCommon.BytesToUint32(lastIndexBytes)), common.Hash(rootBytes), nil +} + +func assertRoot(expected *common.Hash, actual common.Hash) error { + if expected != nil && *expected != actual { + return fmt.Errorf( + "root missmatch. Expected %s actual %s", + expected.Hex(), + actual.Hex(), + ) + } + return nil +} diff --git a/tree/updatabletree.go b/tree/updatabletree.go index 35db5243..684350d0 100644 --- a/tree/updatabletree.go +++ b/tree/updatabletree.go @@ -2,26 +2,131 @@ package tree import ( "context" - "errors" + "math" + dbCommon "github.com/0xPolygon/cdk/common" "github.com/ethereum/go-ethereum/common" "github.com/ledgerwatch/erigon-lib/kv" ) type UpdatableTree struct { *Tree + lastRoot common.Hash } -func NewUpdatable(ctx context.Context, db kv.RwDB, dbPrefix string) *UpdatableTree { +func NewUpdatable(ctx context.Context, db kv.RwDB, dbPrefix string) (*UpdatableTree, error) { + // TODO: Load last root t := newTree(db, dbPrefix) - ut := &UpdatableTree{Tree: t} - return ut + tx, err := t.db.BeginRw(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + rootIndex, root, err := t.getLastIndexAndRoot(tx) + if err != nil { + return nil, err + } + if rootIndex == -1 { + root = t.zeroHashes[t.height] + } + ut := &UpdatableTree{ + Tree: t, + lastRoot: root, + } + return ut, nil } -func (t *UpdatableTree) UpsertLeaf(tx kv.RwTx, index uint32, leafHash common.Hash, expectedRoot *common.Hash) (func(), error) { - return func() {}, errors.New("not implemented") +func (t *UpdatableTree) UpseartLeaves(tx kv.RwTx, leaves []Leaf, rootIndex uint64) (func(), error) { + if len(leaves) == 0 { + return func() {}, nil + } + rootBackup := t.lastRoot + rollback := func() { + t.lastRoot = rootBackup + } + + for _, l := range leaves { + t.upsertLeaf(tx, l) + } + + if err := t.storeRoot(tx, rootIndex, t.lastRoot); err != nil { + return rollback, err + } + return rollback, nil +} + +func (t *UpdatableTree) upsertLeaf(tx kv.RwTx, leaf Leaf) error { + siblings, _, err := t.getSiblings(tx, leaf.Index, t.lastRoot) + if err != nil { + return err + } + currentChildHash := leaf.Hash + newNodes := []treeNode{} + for h := uint8(0); h < t.height; h++ { + var parent treeNode + if leaf.Index&(1< 0 { + // Add child to the right + parent = treeNode{ + left: siblings[h], + right: currentChildHash, + } + } else { + // Add child to the left + parent = treeNode{ + left: currentChildHash, + right: siblings[h], + } + } + currentChildHash = parent.hash() + newNodes = append(newNodes, parent) + } + + if err := assertRoot(leaf.ExpectedRoot, currentChildHash); err != nil { + return err + } + + if err := t.storeNodes(tx, newNodes); err != nil { + return err + } + t.lastRoot = currentChildHash + return nil +} + +func (t *UpdatableTree) Reorg(tx kv.RwTx, firstReorgedIndex uint64) (func(), error) { + iter, err := tx.RangeDescend( + t.rootTable, + dbCommon.Uint64ToBytes(math.MaxUint64), + dbCommon.Uint64ToBytes(0), + 0, + ) + if err != nil { + return func() {}, err + } + rootBackup := t.lastRoot + rollback := func() { + t.lastRoot = rootBackup + } + + for lastIndexBytes, rootBytes, err := iter.Next(); lastIndexBytes != nil; lastIndexBytes, rootBytes, err = iter.Next() { + if err != nil { + return rollback, err + } + + if dbCommon.BytesToUint64(lastIndexBytes) >= firstReorgedIndex { + if err := tx.Delete(t.rootTable, lastIndexBytes); err != nil { + return rollback, err + } + } else { + t.lastRoot = common.Hash(rootBytes) + return rollback, nil + } + } + + // no root found after reorg, going back to empty tree + t.lastRoot = t.zeroHashes[t.height] + return rollback, nil } -func (t *UpdatableTree) Reorg(tx kv.RwTx, firstReorgedIndex uint32) (func(), error) { - return func() {}, errors.New("not implemented") +func (t *UpdatableTree) GetRootByIndex(tx kv.Tx, rootIndex uint64) (common.Hash, error) { + return t.getRootByIndex(tx, rootIndex) } From acd372e709ae63fb511654ffb5c82a9819584446 Mon Sep 17 00:00:00 2001 From: Arnau Date: Fri, 2 Aug 2024 11:54:16 +0200 Subject: [PATCH 33/49] pass E2E test --- common/common.go | 8 +- l1infotreesync/e2e_test.go | 140 +++++++++++++++++++++++++++++-- l1infotreesync/l1infotreesync.go | 8 ++ l1infotreesync/processor.go | 69 +++++++-------- tree/appendonlytree.go | 34 +++++--- tree/tree.go | 25 ++---- tree/tree_test.go | 6 +- tree/updatabletree.go | 10 +-- 8 files changed, 217 insertions(+), 83 deletions(-) diff --git a/common/common.go b/common/common.go index ebbafd69..f0f36575 100644 --- a/common/common.go +++ b/common/common.go @@ -12,26 +12,26 @@ import ( // Uint64ToBytes converts a uint64 to a byte slice func Uint64ToBytes(num uint64) []byte { bytes := make([]byte, 8) - binary.LittleEndian.PutUint64(bytes, num) + binary.BigEndian.PutUint64(bytes, num) return bytes } // BytesToUint64 converts a byte slice to a uint64 func BytesToUint64(bytes []byte) uint64 { - return binary.LittleEndian.Uint64(bytes) + return binary.BigEndian.Uint64(bytes) } // Uint32To2Bytes converts a uint32 to a byte slice func Uint32ToBytes(num uint32) []byte { key := make([]byte, 4) - binary.LittleEndian.PutUint32(key, num) + binary.BigEndian.PutUint32(key, num) return key } // BytesToUint32 converts a byte slice to a uint32 func BytesToUint32(bytes []byte) uint32 { - return binary.LittleEndian.Uint32(bytes) + return binary.BigEndian.Uint32(bytes) } func CalculateAccInputHash( diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index 416df057..e73576e7 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -1,4 +1,4 @@ -package l1infotreesync +package l1infotreesync_test import ( "context" @@ -11,6 +11,7 @@ import ( "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/globalexitrootnopush0" "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/reorgdetector" "github.com/0xPolygon/cdk/test/contracts/verifybatchesmock" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -71,12 +72,12 @@ func TestE2E(t *testing.T) { require.NoError(t, err) auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337)) require.NoError(t, err) - rdm := NewReorgDetectorMock(t) - rdm.On("Subscribe", reorgDetectorID).Return(&reorgdetector.Subscription{}, nil) + rdm := l1infotreesync.NewReorgDetectorMock(t) + rdm.On("Subscribe", mock.Anything).Return(&reorgdetector.Subscription{}, nil) 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 := 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) require.NoError(t, err) go syncer.Start(ctx) @@ -105,19 +106,19 @@ func TestE2E(t *testing.T) { require.Equal(t, common.Hash(expectedRoot), actualRoot) } - // Update 10 rollups 10 times + // Update 10 rollups (verify batches event) 10 times for rollupID := uint32(1); rollupID < 10; rollupID++ { for i := 0; i < 10; i++ { newLocalExitRoot := common.HexToHash(strconv.Itoa(int(rollupID)) + "ffff" + strconv.Itoa(i)) - tx, err := verifySC.VerifyBatches(auth, rollupID, 0, newLocalExitRoot, common.Hash{}, true) + tx, err := verifySC.VerifyBatches(auth, rollupID, 0, newLocalExitRoot, common.Hash{}, i%2 != 0) require.NoError(t, err) client.Commit() // Let the processor catch up - time.Sleep(time.Millisecond * 100) + time.Sleep(time.Millisecond * 10) receipt, err := client.Client().TransactionReceipt(ctx, tx.Hash()) require.NoError(t, err) require.Equal(t, receipt.Status, types.ReceiptStatusSuccessful) - require.True(t, len(receipt.Logs) == 2) + require.True(t, len(receipt.Logs) == 1+i%2) expectedRollupExitRoot, err := verifySC.GetRollupExitRoot(&bind.CallOpts{Pending: false}) require.NoError(t, err) @@ -127,3 +128,126 @@ func TestE2E(t *testing.T) { } } } + +func TestFinalised(t *testing.T) { + ctx := context.Background() + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337)) + require.NoError(t, err) + client, _, _, _, _, err := newSimulatedClient(auth) + require.NoError(t, err) + for i := 0; i < 100; i++ { + client.Commit() + } + + n4, err := client.Client().HeaderByNumber(ctx, big.NewInt(-4)) + require.NoError(t, err) + fmt.Println("-4", n4.Number) + n3, err := client.Client().HeaderByNumber(ctx, big.NewInt(-3)) + require.NoError(t, err) + fmt.Println("-3", n3.Number) + n2, err := client.Client().HeaderByNumber(ctx, big.NewInt(-2)) + require.NoError(t, err) + fmt.Println("-2", n2.Number) + n1, err := client.Client().HeaderByNumber(ctx, big.NewInt(-1)) + require.NoError(t, err) + fmt.Println("-1", n1.Number) + n0, err := client.Client().HeaderByNumber(ctx, nil) + require.NoError(t, err) + fmt.Println("0", n0.Number) + fmt.Printf("amount of blocks latest - finalised: %d", n0.Number.Uint64()-n3.Number.Uint64()) +} + +func TestStressAndReorgs(t *testing.T) { + const ( + totalIterations = 10_124 + enableReorgs = false // test fails when set to true + reorgEveryXIterations = 53 + maxReorgDepth = 5 + maxEventsPerBlock = 7 + maxRollups = 31 + ) + + ctx := context.Background() + dbPathSyncer := t.TempDir() + dbPathReorg := t.TempDir() + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337)) + require.NoError(t, err) + client, gerAddr, verifyAddr, gerSc, verifySC, err := newSimulatedClient(auth) + require.NoError(t, err) + rd, err := reorgdetector.New(ctx, client.Client(), dbPathReorg) + go rd.Start(ctx) + syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerAddr, verifyAddr, 10, etherman.LatestBlock, rd, client.Client(), time.Millisecond, 0, 100*time.Millisecond, 3) + require.NoError(t, err) + go syncer.Start(ctx) + + for i := 0; i < totalIterations; i++ { + for j := 0; j < i%maxEventsPerBlock; j++ { + switch j % 3 { + case 0: // Update L1 Info Tree + _, err := gerSc.UpdateExitRoot(auth, common.HexToHash(strconv.Itoa(i))) + require.NoError(t, err) + case 1: // Update L1 Info Tree + Rollup Exit Tree + newLocalExitRoot := common.HexToHash(strconv.Itoa(i) + "ffff" + strconv.Itoa(j)) + _, err := verifySC.VerifyBatches(auth, 1+uint32(i%maxRollups), 0, newLocalExitRoot, common.Hash{}, true) + require.NoError(t, err) + case 2: // Update Rollup Exit Tree + newLocalExitRoot := common.HexToHash(strconv.Itoa(i) + "ffff" + strconv.Itoa(j)) + _, err := verifySC.VerifyBatches(auth, 1+uint32(i%maxRollups), 0, newLocalExitRoot, common.Hash{}, false) + require.NoError(t, err) + } + } + client.Commit() + time.Sleep(time.Microsecond * 10) // Sleep just enough for goroutine to switch + if enableReorgs && i%reorgEveryXIterations == 0 { + reorgDepth := i%maxReorgDepth + 1 + currentBlockNum, err := client.Client().BlockNumber(ctx) + require.NoError(t, err) + targetReorgBlockNum := currentBlockNum - uint64(reorgDepth) + if targetReorgBlockNum < currentBlockNum { // we are dealing with uints... + reorgBlock, err := client.Client().BlockByNumber(ctx, big.NewInt(int64(targetReorgBlockNum))) + require.NoError(t, err) + client.Fork(reorgBlock.Hash()) + } + } + } + + syncerUpToDate := false + var errMsg string + for i := 0; i < 50; i++ { + lpb, err := syncer.GetLastProcessedBlock(ctx) + require.NoError(t, err) + lb, err := client.Client().BlockNumber(ctx) + require.NoError(t, err) + if lpb == lb { + syncerUpToDate = true + break + } + time.Sleep(time.Millisecond * 100) + errMsg = fmt.Sprintf("last block from client: %d, last block from syncer: %d", lb, lpb) + } + require.True(t, syncerUpToDate, errMsg) + + // Assert rollup exit root + expectedRollupExitRoot, err := verifySC.GetRollupExitRoot(&bind.CallOpts{Pending: false}) + require.NoError(t, err) + actualRollupExitRoot, err := syncer.GetLastRollupExitRoot(ctx) + require.NoError(t, err) + require.Equal(t, common.Hash(expectedRollupExitRoot), actualRollupExitRoot) + + // Assert L1 Info tree root + expectedL1InfoRoot, err := gerSc.GetRoot(&bind.CallOpts{Pending: false}) + require.NoError(t, err) + expectedGER, err := gerSc.GetLastGlobalExitRoot(&bind.CallOpts{Pending: false}) + require.NoError(t, err) + index, actualL1InfoRoot, err := syncer.GetLastL1InfoTreeRootAndIndex(ctx) + require.NoError(t, err) + info, err := syncer.GetInfoByIndex(ctx, index) + require.NoError(t, err, fmt.Sprintf("index: %d", index)) + + require.Equal(t, common.Hash(expectedL1InfoRoot), actualL1InfoRoot) + require.Equal(t, common.Hash(expectedGER), info.GlobalExitRoot, fmt.Sprintf("%+v", info)) +} diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index 2a288704..1dea0bef 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -125,3 +125,11 @@ func (s *L1InfoTreeSync) GetL1InfoTreeRootByIndex(ctx context.Context, index uin func (s *L1InfoTreeSync) GetLastRollupExitRoot(ctx context.Context) (common.Hash, error) { return s.processor.rollupExitTree.GetLastRoot(ctx) } + +func (s *L1InfoTreeSync) GetLastL1InfoTreeRootAndIndex(ctx context.Context) (uint32, common.Hash, error) { + return s.processor.l1InfoTree.GetLastIndexAndRoot(ctx) +} + +func (s *L1InfoTreeSync) GetLastProcessedBlock(ctx context.Context) (uint64, error) { + return s.processor.GetLastProcessedBlock(ctx) +} diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 67ffc26c..21f872ce 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -339,24 +339,25 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { l1InfoTreeLeavesToAdd := []tree.Leaf{} rollupExitTreeLeavesToAdd := []tree.Leaf{} if len(b.Events) > 0 { - var initialIndex uint32 + var initialL1InfoIndex uint32 + var l1InfoLeavesAdded uint32 lastIndex, err := p.getLastIndex(tx) if err == ErrNotFound { - initialIndex = 0 + initialL1InfoIndex = 0 } else if err != nil { rollback() return err } else { - initialIndex = lastIndex + 1 + initialL1InfoIndex = lastIndex + 1 } - var nextExpectedRollupExitTreeRoot *ethCommon.Hash - for i, e := range b.Events { + for _, e := range b.Events { event := e.(Event) events = append(events, event) if event.UpdateL1InfoTree != nil { + index := initialL1InfoIndex + l1InfoLeavesAdded leafToStore := storeLeaf{ BlockNumber: b.Num, - Index: initialIndex + uint32(i), + Index: index, MainnetExitRoot: event.UpdateL1InfoTree.MainnetExitRoot, RollupExitRoot: event.UpdateL1InfoTree.RollupExitRoot, ParentHash: event.UpdateL1InfoTree.ParentHash, @@ -367,10 +368,10 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { return err } l1InfoTreeLeavesToAdd = append(l1InfoTreeLeavesToAdd, tree.Leaf{ - Index: initialIndex + uint32(i), + Index: leafToStore.Index, Hash: leafToStore.Hash(), }) - nextExpectedRollupExitTreeRoot = &leafToStore.RollupExitRoot + l1InfoLeavesAdded++ } if event.VerifyBatches != nil { @@ -379,36 +380,38 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { // that the computation of the tree is correct. However, there are some execution paths // on the contract that don't follow this (verifyBatches + pendingStateTimeout != 0) rollupExitTreeLeavesToAdd = append(rollupExitTreeLeavesToAdd, tree.Leaf{ - Index: event.VerifyBatches.RollupID, - Hash: event.VerifyBatches.ExitRoot, - ExpectedRoot: nextExpectedRollupExitTreeRoot, + Index: event.VerifyBatches.RollupID - 1, + Hash: event.VerifyBatches.ExitRoot, }) - nextExpectedRollupExitTreeRoot = nil } } - bwl := blockWithLeafs{ - FirstIndex: initialIndex, - LastIndex: initialIndex + uint32(len(b.Events)), - } - blockValue, err := json.Marshal(bwl) - if err != nil { - rollback() - return err - } - if err := tx.Put(blockTable, common.Uint64ToBytes(b.Num), blockValue); err != nil { - rollback() - return err - } - l1InfoTreeRollback, err = p.l1InfoTree.AddLeaves(tx, l1InfoTreeLeavesToAdd) - if err != nil { - rollback() - return err + if l1InfoLeavesAdded > 0 { + bwl := blockWithLeafs{ + FirstIndex: initialL1InfoIndex, + LastIndex: initialL1InfoIndex + l1InfoLeavesAdded, + } + blockValue, err := json.Marshal(bwl) + if err != nil { + rollback() + return err + } + if err := tx.Put(blockTable, common.Uint64ToBytes(b.Num), blockValue); err != nil { + rollback() + return err + } + l1InfoTreeRollback, err = p.l1InfoTree.AddLeaves(tx, l1InfoTreeLeavesToAdd) + if err != nil { + rollback() + return err + } } - rollupExitTreeRollback, err = p.rollupExitTree.UpseartLeaves(tx, rollupExitTreeLeavesToAdd, b.Num) - if err != nil { - rollback() - return err + if len(rollupExitTreeLeavesToAdd) > 0 { + rollupExitTreeRollback, err = p.rollupExitTree.UpseartLeaves(tx, rollupExitTreeLeavesToAdd, b.Num) + if err != nil { + rollback() + return err + } } } if err := p.updateLastProcessedBlock(tx, b.Num); err != nil { diff --git a/tree/appendonlytree.go b/tree/appendonlytree.go index 0d23a735..db78bb80 100644 --- a/tree/appendonlytree.go +++ b/tree/appendonlytree.go @@ -30,12 +30,7 @@ func (t *AppendOnlyTree) AddLeaves(tx kv.RwTx, leaves []Leaf) (func(), error) { if len(leaves) == 0 { return func() {}, nil } - if int64(leaves[0].Index) != t.lastIndex+1 { - return func() {}, fmt.Errorf( - "mismatched index. Expected: %d, actual: %d", - t.lastIndex+1, leaves[0].Index, - ) - } + backupIndx := t.lastIndex backupCache := make([]common.Hash, len(t.lastLeftCache)) copy(backupCache, t.lastLeftCache) @@ -54,6 +49,12 @@ func (t *AppendOnlyTree) AddLeaves(tx kv.RwTx, leaves []Leaf) (func(), error) { } func (t *AppendOnlyTree) addLeaf(tx kv.RwTx, leaf Leaf) error { + if int64(leaf.Index) != t.lastIndex+1 { + return fmt.Errorf( + "mismatched index. Expected: %d, actual: %d", + t.lastIndex+1, leaf.Index, + ) + } // Calculate new tree nodes currentChildHash := leaf.Hash newNodes := []treeNode{} @@ -80,10 +81,6 @@ func (t *AppendOnlyTree) addLeaf(tx kv.RwTx, leaf Leaf) error { newNodes = append(newNodes, parent) } - if err := assertRoot(leaf.ExpectedRoot, currentChildHash); err != nil { - return err - } - // store root t.storeRoot(tx, uint64(leaf.Index), currentChildHash) root := currentChildHash @@ -102,6 +99,21 @@ func (t *AppendOnlyTree) GetRootByIndex(tx kv.Tx, index uint32) (common.Hash, er return t.getRootByIndex(tx, uint64(index)) } +func (t *AppendOnlyTree) GetLastIndexAndRoot(ctx context.Context) (uint32, common.Hash, error) { + tx, err := t.db.BeginRo(ctx) + if err != nil { + return 0, common.Hash{}, err + } + i, root, err := t.getLastIndexAndRootWithTx(tx) + if err != nil { + return 0, common.Hash{}, err + } + if i == -1 { + return 0, common.Hash{}, ErrNotFound + } + return uint32(i), root, nil +} + func (t *AppendOnlyTree) initLastLeftCacheAndLastDepositCount(ctx context.Context) error { tx, err := t.db.BeginRw(ctx) if err != nil { @@ -117,7 +129,7 @@ func (t *AppendOnlyTree) initLastLeftCacheAndLastDepositCount(ctx context.Contex } func (t *AppendOnlyTree) initLastIndex(tx kv.Tx) (common.Hash, error) { - ldc, root, err := t.getLastIndexAndRoot(tx) + ldc, root, err := t.getLastIndexAndRootWithTx(tx) if err != nil { return common.Hash{}, err } diff --git a/tree/tree.go b/tree/tree.go index a93b8c28..8564db50 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -23,9 +23,8 @@ var ( ) type Leaf struct { - Index uint32 - Hash common.Hash - ExpectedRoot *common.Hash + Index uint32 + Hash common.Hash } type Tree struct { @@ -111,6 +110,7 @@ func (t *Tree) getSiblings(tx kv.Tx, index uint32, root common.Hash) ( if err == ErrNotFound { hasUsedZeroHashes = true siblings = append(siblings, t.zeroHashes[h]) + err = nil continue } else { err = fmt.Errorf( @@ -228,7 +228,7 @@ func (t *Tree) GetLastRoot(ctx context.Context) (common.Hash, error) { return common.Hash{}, err } - i, root, err := t.getLastIndexAndRoot(tx) + i, root, err := t.getLastIndexAndRootWithTx(tx) if err != nil { return common.Hash{}, err } @@ -238,9 +238,9 @@ func (t *Tree) GetLastRoot(ctx context.Context) (common.Hash, error) { return root, nil } -// getLastIndexAndRoot return the index and the root associated to the last leaf inserted. +// getLastIndexAndRootWithTx return the index and the root associated to the last leaf inserted. // If index == -1, it means no leaf added yet -func (t *Tree) getLastIndexAndRoot(tx kv.Tx) (int64, common.Hash, error) { +func (t *Tree) getLastIndexAndRootWithTx(tx kv.Tx) (int64, common.Hash, error) { iter, err := tx.RangeDescend( t.rootTable, dbCommon.Uint64ToBytes(math.MaxUint64), @@ -258,16 +258,5 @@ func (t *Tree) getLastIndexAndRoot(tx kv.Tx) (int64, common.Hash, error) { if lastIndexBytes == nil { return -1, common.Hash{}, nil } - return int64(dbCommon.BytesToUint32(lastIndexBytes)), common.Hash(rootBytes), nil -} - -func assertRoot(expected *common.Hash, actual common.Hash) error { - if expected != nil && *expected != actual { - return fmt.Errorf( - "root missmatch. Expected %s actual %s", - expected.Hex(), - actual.Hex(), - ) - } - return nil + return int64(dbCommon.BytesToUint64(lastIndexBytes)), common.Hash(rootBytes), nil } diff --git a/tree/tree_test.go b/tree/tree_test.go index cd1348dd..d826c5b8 100644 --- a/tree/tree_test.go +++ b/tree/tree_test.go @@ -56,7 +56,7 @@ func TestMTAddLeaf(t *testing.T) { if len(testVector.ExistingLeaves) > 0 { txRo, err := tree.db.BeginRo(ctx) require.NoError(t, err) - _, actualRoot, err := tree.getLastIndexAndRoot(txRo) + _, actualRoot, err := tree.getLastIndexAndRootWithTx(txRo) txRo.Rollback() require.NoError(t, err) require.Equal(t, common.HexToHash(testVector.CurrentRoot), actualRoot) @@ -74,7 +74,7 @@ func TestMTAddLeaf(t *testing.T) { txRo, err := tree.db.BeginRo(ctx) require.NoError(t, err) - _, actualRoot, err := tree.getLastIndexAndRoot(txRo) + _, actualRoot, err := tree.getLastIndexAndRootWithTx(txRo) txRo.Rollback() require.NoError(t, err) require.Equal(t, common.HexToHash(testVector.NewRoot), actualRoot) @@ -123,7 +123,7 @@ func TestMTGetProof(t *testing.T) { txRo, err := tree.db.BeginRo(ctx) require.NoError(t, err) - _, actualRoot, err := tree.getLastIndexAndRoot(txRo) + _, actualRoot, err := tree.getLastIndexAndRootWithTx(txRo) txRo.Rollback() expectedRoot := common.HexToHash(testVector.ExpectedRoot) require.Equal(t, expectedRoot, actualRoot) diff --git a/tree/updatabletree.go b/tree/updatabletree.go index 684350d0..0f77b259 100644 --- a/tree/updatabletree.go +++ b/tree/updatabletree.go @@ -22,7 +22,7 @@ func NewUpdatable(ctx context.Context, db kv.RwDB, dbPrefix string) (*UpdatableT return nil, err } defer tx.Rollback() - rootIndex, root, err := t.getLastIndexAndRoot(tx) + rootIndex, root, err := t.getLastIndexAndRootWithTx(tx) if err != nil { return nil, err } @@ -46,7 +46,9 @@ func (t *UpdatableTree) UpseartLeaves(tx kv.RwTx, leaves []Leaf, rootIndex uint6 } for _, l := range leaves { - t.upsertLeaf(tx, l) + if err := t.upsertLeaf(tx, l); err != nil { + return rollback, err + } } if err := t.storeRoot(tx, rootIndex, t.lastRoot); err != nil { @@ -81,10 +83,6 @@ func (t *UpdatableTree) upsertLeaf(tx kv.RwTx, leaf Leaf) error { newNodes = append(newNodes, parent) } - if err := assertRoot(leaf.ExpectedRoot, currentChildHash); err != nil { - return err - } - if err := t.storeNodes(tx, newNodes); err != nil { return err } From 64cd1d4a2853407ed48e1a9822c93602def0a543 Mon Sep 17 00:00:00 2001 From: Arnau Date: Fri, 2 Aug 2024 12:25:15 +0200 Subject: [PATCH 34/49] remove outdated coment --- l1infotreesync/processor.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index d30f2acf..02e0112d 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -374,10 +374,6 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { } if event.VerifyBatches != nil { - // before the verify batches event happens, the updateExitRoot event is emitted. - // Since the previous event include the rollup exit root, this can use it to assert - // that the computation of the tree is correct. However, there are some execution paths - // on the contract that don't follow this (verifyBatches + pendingStateTimeout != 0) rollupExitTreeLeavesToAdd = append(rollupExitTreeLeavesToAdd, tree.Leaf{ Index: event.VerifyBatches.RollupID - 1, Hash: event.VerifyBatches.ExitRoot, From f2f2fda060b2a098304223a871573477e94217e4 Mon Sep 17 00:00:00 2001 From: Arnau Date: Fri, 2 Aug 2024 12:35:19 +0200 Subject: [PATCH 35/49] make test shorter --- l1infotreesync/e2e_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index e73576e7..a6ad7b76 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -161,7 +161,7 @@ func TestFinalised(t *testing.T) { func TestStressAndReorgs(t *testing.T) { const ( - totalIterations = 10_124 + totalIterations = 200 // Have tested with much larger number (+10k) enableReorgs = false // test fails when set to true reorgEveryXIterations = 53 maxReorgDepth = 5 From da783445cbd775a591b7ef13729afc17a9ab6116 Mon Sep 17 00:00:00 2001 From: Arnau Date: Fri, 2 Aug 2024 12:58:47 +0200 Subject: [PATCH 36/49] Add coments --- bridgesync/bridgesync.go | 3 +++ bridgesync/processor.go | 12 +++++++++++- l1infotreesync/l1infotreesync.go | 11 +++++++++++ l1infotreesync/processor.go | 18 ++++++++++++++---- tree/appendonlytree.go | 11 +++++++++-- tree/tree.go | 2 ++ tree/tree_test.go | 4 ++-- tree/updatabletree.go | 14 ++++++++++++-- 8 files changed, 64 insertions(+), 11 deletions(-) diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index d4c21da8..eac014d1 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -27,6 +27,7 @@ type LocalBridgeSync struct { driver *sync.EVMDriver } +// NewL1 creates a bridge syncer that synchronizes the mainnet exit tree func NewL1( ctx context.Context, dbPath string, @@ -51,6 +52,7 @@ func NewL1( ) } +// NewL2 creates a bridge syncer that synchronizes the local exit tree func NewL2( ctx context.Context, dbPath string, @@ -134,6 +136,7 @@ func new( }, nil } +// Start starts the synchronization process func (s *LocalBridgeSync) Start(ctx context.Context) { s.driver.Sync(ctx) } diff --git a/bridgesync/processor.go b/bridgesync/processor.go index a4ffdd7f..77938a69 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -27,6 +27,7 @@ var ( lastBlokcKey = []byte("lb") ) +// Bridge is the representation of a bridge event type Bridge struct { LeafType uint8 OriginNetwork uint32 @@ -38,6 +39,7 @@ type Bridge struct { DepositCount uint32 } +// Hash returns the hash of the bridge event as expected by the exit tree func (b *Bridge) Hash() common.Hash { origNet := make([]byte, 4) //nolint:gomnd binary.BigEndian.PutUint32(origNet, uint32(b.OriginNetwork)) @@ -65,6 +67,7 @@ func (b *Bridge) Hash() common.Hash { return hash } +// Claim representation of a claim event type Claim struct { GlobalIndex *big.Int OriginNetwork uint32 @@ -73,6 +76,7 @@ type Claim struct { Amount *big.Int } +// Event combination of bridge and claim events type Event struct { Bridge *Bridge Claim *Claim @@ -103,7 +107,7 @@ func newProcessor(ctx context.Context, dbPath, dbPrefix string) (*processor, err if err != nil { return nil, err } - exitTree, err := tree.NewAppendOnly(ctx, db, dbPrefix) + exitTree, err := tree.NewAppendOnlyTree(ctx, db, dbPrefix) if err != nil { return nil, err } @@ -158,6 +162,8 @@ func (p *processor) GetClaimsAndBridges( return events, nil } +// GetLastProcessedBlock returns the last processed block oby the processor, including blocks +// that don't have events func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { tx, err := p.db.BeginRo(ctx) if err != nil { @@ -177,6 +183,8 @@ func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { } } +// Reorg triggers a purge and reset process on the processot to leave it on a state +// as if the last block processed was firstReorgedBlock-1 func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { tx, err := p.db.BeginRw(ctx) if err != nil { @@ -231,6 +239,8 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { return nil } +// ProcessBlock procees the events of the block to build the exit tree +// and updates the last processed block (can be called without events for that purpose) func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { tx, err := p.db.BeginRw(ctx) if err != nil { diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index 1dea0bef..32db8751 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -34,6 +34,8 @@ type L1InfoTreeSync struct { driver *sync.EVMDriver } +// New creates a L1 Info tree syncer that syncs the L1 info tree +// and the rollup exit tree func New( ctx context.Context, dbPath string, @@ -96,22 +98,28 @@ func New( }, nil } +// Start starts the synchronization process func (s *L1InfoTreeSync) Start(ctx context.Context) { s.driver.Sync(ctx) } +// GetL1InfoTreeMerkleProof creates a merkle proof for the L1 Info tree func (s *L1InfoTreeSync) GetL1InfoTreeMerkleProof(ctx context.Context, index uint32) ([]common.Hash, common.Hash, error) { return s.processor.GetL1InfoTreeMerkleProof(ctx, index) } +// 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 func (s *L1InfoTreeSync) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { return s.processor.GetLatestInfoUntilBlock(ctx, blockNum) } +// GetInfoByIndex returns the value of a leave (not the hash) of the L1 info tree func (s *L1InfoTreeSync) GetInfoByIndex(ctx context.Context, index uint32) (*L1InfoTreeLeaf, error) { return s.processor.GetInfoByIndex(ctx, index) } +// GetL1InfoTreeRootByIndex returns the root of the L1 info tree at the moment the leaf with the given index was added func (s *L1InfoTreeSync) GetL1InfoTreeRootByIndex(ctx context.Context, index uint32) (common.Hash, error) { tx, err := s.processor.db.BeginRo(ctx) if err != nil { @@ -122,14 +130,17 @@ func (s *L1InfoTreeSync) GetL1InfoTreeRootByIndex(ctx context.Context, index uin return s.processor.l1InfoTree.GetRootByIndex(tx, index) } +// GetLastRollupExitRoot return the last rollup exit root processed func (s *L1InfoTreeSync) GetLastRollupExitRoot(ctx context.Context) (common.Hash, error) { return s.processor.rollupExitTree.GetLastRoot(ctx) } +// GetLastL1InfoTreeRootAndIndex return the last root and index processed from the L1 Info tree func (s *L1InfoTreeSync) GetLastL1InfoTreeRootAndIndex(ctx context.Context) (uint32, common.Hash, error) { return s.processor.l1InfoTree.GetLastIndexAndRoot(ctx) } +// GetLastProcessedBlock return the last processed block func (s *L1InfoTreeSync) GetLastProcessedBlock(ctx context.Context) (uint64, error) { return s.processor.GetLastProcessedBlock(ctx) } diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 02e0112d..1d258889 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -49,6 +49,7 @@ type processor struct { rollupExitTree *tree.UpdatableTree } +// UpdateL1InfoTree representation of the UpdateL1InfoTree event type UpdateL1InfoTree struct { MainnetExitRoot ethCommon.Hash RollupExitRoot ethCommon.Hash @@ -56,6 +57,7 @@ type UpdateL1InfoTree struct { Timestamp uint64 } +// VerifyBatches representation of the VerifyBatches and VerifyBatchesTrustedAggregator events type VerifyBatches struct { RollupID uint32 NumBatch uint64 @@ -69,6 +71,7 @@ type Event struct { VerifyBatches *VerifyBatches } +// L1InfoTreeLeaf representation of a leaf of the L1 Info tree type L1InfoTreeLeaf struct { L1InfoTreeIndex uint32 PreviousBlockHash ethCommon.Hash @@ -88,6 +91,7 @@ type storeLeaf struct { Timestamp uint64 } +// Hash as expected by the tree func (l *storeLeaf) Hash() ethCommon.Hash { var res [32]byte t := make([]byte, 8) //nolint:gomnd @@ -103,6 +107,7 @@ type blockWithLeafs struct { LastIndex uint32 } +// GlobalExitRoot returns the GER func (l *storeLeaf) GlobalExitRoot() ethCommon.Hash { var gerBytes [32]byte hasher := sha3.NewLegacyKeccak256() @@ -134,12 +139,12 @@ func newProcessor(ctx context.Context, dbPath string) (*processor, error) { db: db, } - l1InfoTree, err := tree.NewAppendOnly(ctx, db, dbPrefix+l1InfoTreeSuffix) + l1InfoTree, err := tree.NewAppendOnlyTree(ctx, db, dbPrefix+l1InfoTreeSuffix) if err != nil { return nil, err } p.l1InfoTree = l1InfoTree - rollupExitTree, err := tree.NewUpdatable(ctx, db, dbPrefix+rollupExitTreeSuffix) + rollupExitTree, err := tree.NewUpdatableTree(ctx, db, dbPrefix+rollupExitTreeSuffix) if err != nil { return nil, err } @@ -147,6 +152,7 @@ func newProcessor(ctx context.Context, dbPath string) (*processor, error) { return p, nil } +// GetL1InfoTreeMerkleProof creates a merkle proof for the L1 Info tree func (p *processor) GetL1InfoTreeMerkleProof(ctx context.Context, index uint32) ([]ethCommon.Hash, ethCommon.Hash, error) { tx, err := p.db.BeginRo(ctx) if err != nil { @@ -206,6 +212,7 @@ func (p *processor) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64 return p.getInfoByIndexWithTx(tx, blk.LastIndex-1) } +// GetInfoByIndex returns the value of a leave (not the hash) of the L1 info tree func (p *processor) GetInfoByIndex(ctx context.Context, index uint32) (*L1InfoTreeLeaf, error) { tx, err := p.db.BeginRo(ctx) if err != nil { @@ -238,6 +245,7 @@ func (p *processor) getInfoByIndexWithTx(tx kv.Tx, index uint32) (*L1InfoTreeLea }, nil } +// GetLastProcessedBlock returns the last processed block func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { tx, err := p.db.BeginRo(ctx) if err != nil { @@ -257,6 +265,8 @@ func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { return common.BytesToUint64(blockNumBytes), nil } +// Reorg triggers a purge and reset process on the processot to leave it on a state +// as if the last block processed was firstReorgedBlock-1 func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { tx, err := p.db.BeginRw(ctx) if err != nil { @@ -320,8 +330,8 @@ func (p *processor) deleteLeaf(tx kv.RwTx, index uint32) error { return nil } -// ProcessBlock process the leafs of the L1 info tree found on a block -// this function can be called without leafs with the intention to track the last processed block +// ProcessBlock procees 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 { tx, err := p.db.BeginRw(ctx) if err != nil { diff --git a/tree/appendonlytree.go b/tree/appendonlytree.go index db78bb80..15216dd8 100644 --- a/tree/appendonlytree.go +++ b/tree/appendonlytree.go @@ -9,13 +9,15 @@ import ( "github.com/ledgerwatch/erigon-lib/kv" ) +// AppendOnlyTree is a tree where leaves are added sequentially (by index) type AppendOnlyTree struct { *Tree lastLeftCache []common.Hash lastIndex int64 } -func NewAppendOnly(ctx context.Context, db kv.RwDB, dbPrefix string) (*AppendOnlyTree, error) { +// NewAppendOnlyTree creates a AppendOnlyTree +func NewAppendOnlyTree(ctx context.Context, db kv.RwDB, dbPrefix string) (*AppendOnlyTree, error) { t := newTree(db, dbPrefix) at := &AppendOnlyTree{Tree: t} if err := at.initLastLeftCacheAndLastDepositCount(ctx); err != nil { @@ -24,7 +26,9 @@ func NewAppendOnly(ctx context.Context, db kv.RwDB, dbPrefix string) (*AppendOnl return at, nil } -// AddLeaves adds a list leaves into the tree +// AddLeaves adds a list leaves into the tree. The indexes of the leaves must be consecutive, +// starting by the index of the last leave added +1 +// It returns a function that must be called to rollback the changes done by this interaction func (t *AppendOnlyTree) AddLeaves(tx kv.RwTx, leaves []Leaf) (func(), error) { // Sanity check if len(leaves) == 0 { @@ -95,10 +99,12 @@ func (t *AppendOnlyTree) addLeaf(tx kv.RwTx, leaf Leaf) error { return nil } +// GetRootByIndex returns the root of the tree as it was right after adding the leaf with index func (t *AppendOnlyTree) GetRootByIndex(tx kv.Tx, index uint32) (common.Hash, error) { return t.getRootByIndex(tx, uint64(index)) } +// GetLastIndexAndRoot returns the last index and root added to the tree func (t *AppendOnlyTree) GetLastIndexAndRoot(ctx context.Context) (uint32, common.Hash, error) { tx, err := t.db.BeginRo(ctx) if err != nil { @@ -176,6 +182,7 @@ func (t *AppendOnlyTree) initLastLeftCache(tx kv.Tx, lastIndex int64, lastRoot c // Reorg deletes all the data relevant from firstReorgedIndex (includded) and onwards // and prepares the tree tfor being used as it was at firstReorgedIndex-1 +// It returns a function that must be called to rollback the changes done by this interaction func (t *AppendOnlyTree) Reorg(tx kv.RwTx, firstReorgedIndex uint32) (func(), error) { if t.lastIndex == -1 { return func() {}, nil diff --git a/tree/tree.go b/tree/tree.go index 8564db50..6c21fb7d 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -62,6 +62,7 @@ func (n *treeNode) UnmarshalBinary(data []byte) error { return nil } +// AddTables add the needed tables for the tree to work in a tableCfg func AddTables(tableCfg map[string]kv.TableCfgItem, dbPrefix string) { rootTable := dbPrefix + rootTableSufix rhtTable := dbPrefix + rhtTableSufix @@ -222,6 +223,7 @@ func (t *Tree) storeRoot(tx kv.RwTx, rootIndex uint64, root common.Hash) error { return tx.Put(t.rootTable, dbCommon.Uint64ToBytes(rootIndex), root[:]) } +// GetLastRoot returns the last processed root func (t *Tree) GetLastRoot(ctx context.Context) (common.Hash, error) { tx, err := t.db.BeginRo(ctx) if err != nil { diff --git a/tree/tree_test.go b/tree/tree_test.go index d826c5b8..5b347df2 100644 --- a/tree/tree_test.go +++ b/tree/tree_test.go @@ -37,7 +37,7 @@ func TestMTAddLeaf(t *testing.T) { WithTableCfg(tableCfgFunc). Open() require.NoError(t, err) - tree, err := NewAppendOnly(context.Background(), db, dbPrefix) + tree, err := NewAppendOnlyTree(context.Background(), db, dbPrefix) require.NoError(t, err) // Add exisiting leaves @@ -105,7 +105,7 @@ func TestMTGetProof(t *testing.T) { WithTableCfg(tableCfgFunc). Open() require.NoError(t, err) - tree, err := NewAppendOnly(context.Background(), db, dbPrefix) + tree, err := NewAppendOnlyTree(context.Background(), db, dbPrefix) require.NoError(t, err) leaves := []Leaf{} diff --git a/tree/updatabletree.go b/tree/updatabletree.go index 0f77b259..ddebd5df 100644 --- a/tree/updatabletree.go +++ b/tree/updatabletree.go @@ -9,12 +9,14 @@ import ( "github.com/ledgerwatch/erigon-lib/kv" ) +// UpdatableTree is a tree that have updatable leaves, and doesn't need to have sequential inserts type UpdatableTree struct { *Tree lastRoot common.Hash } -func NewUpdatable(ctx context.Context, db kv.RwDB, dbPrefix string) (*UpdatableTree, error) { +// NewUpdatableTree returns an UpdatableTree +func NewUpdatableTree(ctx context.Context, db kv.RwDB, dbPrefix string) (*UpdatableTree, error) { // TODO: Load last root t := newTree(db, dbPrefix) tx, err := t.db.BeginRw(ctx) @@ -36,6 +38,10 @@ func NewUpdatable(ctx context.Context, db kv.RwDB, dbPrefix string) (*UpdatableT return ut, nil } +// UpseartLeaves inserts or updates a list of leaves. The root index will be used to index the resulting +// root after performing all the operations. Root index must be greater than the last used root index, +// but doesn't need to be sequential. Great for relating block nums and roots :) +// It returns a function that must be called to rollback the changes done by this interaction func (t *UpdatableTree) UpseartLeaves(tx kv.RwTx, leaves []Leaf, rootIndex uint64) (func(), error) { if len(leaves) == 0 { return func() {}, nil @@ -90,6 +96,9 @@ func (t *UpdatableTree) upsertLeaf(tx kv.RwTx, leaf Leaf) error { return nil } +// Reorg deletes all the data relevant from firstReorgedIndex (includded) and onwards +// and prepares the tree tfor being used as it was at firstReorgedIndex-1. +// It returns a function that must be called to rollback the changes done by this interaction func (t *UpdatableTree) Reorg(tx kv.RwTx, firstReorgedIndex uint64) (func(), error) { iter, err := tx.RangeDescend( t.rootTable, @@ -125,6 +134,7 @@ func (t *UpdatableTree) Reorg(tx kv.RwTx, firstReorgedIndex uint64) (func(), err return rollback, nil } -func (t *UpdatableTree) GetRootByIndex(tx kv.Tx, rootIndex uint64) (common.Hash, error) { +// GetRootByRootIndex returns the root of the tree as it was right after adding the leaf with index +func (t *UpdatableTree) GetRootByRootIndex(tx kv.Tx, rootIndex uint64) (common.Hash, error) { return t.getRootByIndex(tx, rootIndex) } From 4c17d6eb03835b7849cc7e937d441d9eaad5ae5c Mon Sep 17 00:00:00 2001 From: Arnau Date: Fri, 2 Aug 2024 17:21:35 +0200 Subject: [PATCH 37/49] WIP --- bridgesync/bridgesync.go | 16 ++- l1bridge2infoindexsync/downloader.go | 67 ++++++++++ l1bridge2infoindexsync/driver.go | 114 ++++++++++++++++++ .../l1bridge2infoindexsync.go | 15 +++ l1bridge2infoindexsync/processor.go | 58 +++++++++ 5 files changed, 264 insertions(+), 6 deletions(-) create mode 100644 l1bridge2infoindexsync/downloader.go create mode 100644 l1bridge2infoindexsync/driver.go create mode 100644 l1bridge2infoindexsync/l1bridge2infoindexsync.go create mode 100644 l1bridge2infoindexsync/processor.go diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index eac014d1..3f0202dc 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -22,7 +22,7 @@ var ( maxRetryAttemptsAfterError = 5 ) -type LocalBridgeSync struct { +type BridgeSync struct { processor *processor driver *sync.EVMDriver } @@ -37,7 +37,7 @@ func NewL1( rd sync.ReorgDetector, ethClient EthClienter, initialBlock uint64, -) (*LocalBridgeSync, error) { +) (*BridgeSync, error) { return new( ctx, dbPath, @@ -62,7 +62,7 @@ func NewL2( rd sync.ReorgDetector, ethClient EthClienter, initialBlock uint64, -) (*LocalBridgeSync, error) { +) (*BridgeSync, error) { return new( ctx, dbPath, @@ -87,7 +87,7 @@ func new( ethClient EthClienter, initialBlock uint64, dbPrefix, reorgDetectorID string, -) (*LocalBridgeSync, error) { +) (*BridgeSync, error) { processor, err := newProcessor(ctx, dbPath, dbPrefix) if err != nil { return nil, err @@ -130,15 +130,19 @@ func new( if err != nil { return nil, err } - return &LocalBridgeSync{ + return &BridgeSync{ processor: processor, driver: driver, }, nil } // Start starts the synchronization process -func (s *LocalBridgeSync) Start(ctx context.Context) { +func (s *BridgeSync) Start(ctx context.Context) { s.driver.Sync(ctx) } +func (s *BridgeSync) GetLastProcessedBlock(ctx context.Context) (uint64, error) { + return s.GetLastProcessedBlock(ctx) +} + // TODO: expose methods from the processor for consumers diff --git a/l1bridge2infoindexsync/downloader.go b/l1bridge2infoindexsync/downloader.go new file mode 100644 index 00000000..fd8af602 --- /dev/null +++ b/l1bridge2infoindexsync/downloader.go @@ -0,0 +1,67 @@ +package l1bridge2infoindexsync + +import ( + "context" + "errors" + "math/big" + + "github.com/0xPolygon/cdk/bridgesync" + "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" +) + +type downloader struct { + l1Bridge *bridgesync.BridgeSync + l1Info *l1infotreesync.L1InfoTreeSync + l1Client ethereum.ChainReader +} + +func newDownloader( + l1Bridge *bridgesync.BridgeSync, + l1Info *l1infotreesync.L1InfoTreeSync, + l1Client ethereum.ChainReader, +) *downloader { + return &downloader{ + l1Bridge: l1Bridge, + l1Info: l1Info, + l1Client: l1Client, + } +} + +func (d *downloader) getLastFinalisedL1Block(ctx context.Context) (uint64, error) { + b, err := d.l1Client.BlockByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber))) + if err != nil { + return 0, err + } + return b.NumberU64(), nil +} + +func (d *downloader) getLastProcessedBlockBridge(ctx context.Context) (uint64, error) { + return d.l1Bridge.GetLastProcessedBlock(ctx) +} + +func (d *downloader) getLastProcessedBlockL1InfoTree(ctx context.Context) (uint64, error) { + return d.l1Info.GetLastProcessedBlock(ctx) +} + +func (d *downloader) getLastL1InfoIndexUntilBlock(ctx context.Context, blockNum uint64) (uint32, error) { + info, err := d.l1Info.GetLatestInfoUntilBlock(ctx, blockNum) + if err != nil { + return 0, err + } + return info.L1InfoTreeIndex, nil +} + +func (d *downloader) getMainnetExitRootAtL1InfoTreeIndex(ctx context.Context, index uint32) (common.Hash, error) { + leaf, err := d.l1Info.GetInfoByIndex(ctx, index) + if err != nil { + return common.Hash{}, err + } + return leaf.MainnetExitRoot, nil +} + +func (d *downloader) getBridgeIndex(ctx context.Context, mainnetExitRoot common.Hash) (uint32, error) { + return 0, errors.New("need to implement root -> index @ bridgesync") +} diff --git a/l1bridge2infoindexsync/driver.go b/l1bridge2infoindexsync/driver.go new file mode 100644 index 00000000..b9a4debc --- /dev/null +++ b/l1bridge2infoindexsync/driver.go @@ -0,0 +1,114 @@ +package l1bridge2infoindexsync + +import ( + "context" + + "github.com/0xPolygon/cdk/log" +) + +type driver struct { + downloader *downloader + processor *processor +} + +func newDriver( + downloader *downloader, + processor *processor) *driver { + return &driver{ + downloader: downloader, + processor: processor, + } +} + +func (d *driver) sync(ctx context.Context) { + lpbProcessor, err := d.processor.GetLastProcessedBlock(ctx) + if err != nil { + log.Fatal("TODO") + } + lastProcessedL1InfoIndex, err := d.processor.getLastL1InfoTreeIndexProcessed(ctx) + if err != nil { + log.Fatal("TODO") + } + for { + syncUntilBlock, shouldWait, err := d.getTargetSynchronizationBlock(ctx, lpbProcessor) + if err != nil { + log.Fatal("TODO") + } + if shouldWait { + // TODO: wait using ticker + continue + } + lastL1InfoTreeIndex, err := d.downloader.getLastL1InfoIndexUntilBlock(ctx, syncUntilBlock) + if err != nil { + log.Fatal("TODO") + } + relations := []bridge2L1InfoRelation{} + for i := lastProcessedL1InfoIndex + 1; i <= lastL1InfoTreeIndex; i++ { + relation, err := d.getRelation(ctx, i) + if err != nil { + log.Fatal("TODO") + } + relations = append(relations, relation) + } + if err := d.processor.addBridge2L1InfoRelations(ctx, syncUntilBlock, relations); err != nil { + log.Fatal("TODO") + } + lpbProcessor = syncUntilBlock + lastProcessedL1InfoIndex = 0 // TODO + } +} + +func (d *driver) getTargetSynchronizationBlock(ctx context.Context, lpbProcessor uint64) (syncUntilBlock uint64, shouldWait bool, err error) { + lastFinalised, err := d.downloader.getLastFinalisedL1Block(ctx) // TODO: configure finality, but then we need to deal with reorgs? + if err != nil { + return + } + if lpbProcessor >= lastFinalised { + shouldWait = true + return + } + lpbInfo, err := d.downloader.getLastProcessedBlockL1InfoTree(ctx) + if err != nil { + return + } + if lpbProcessor >= lpbInfo { + shouldWait = true + return + } + lpbBridge, err := d.downloader.getLastProcessedBlockBridge(ctx) + if err != nil { + return + } + if lpbProcessor >= lpbBridge { + shouldWait = true + return + } + + // Bridge, L1Info and L1 ahead of procesor. Pick the smallest block num as target + if lastFinalised <= lpbInfo { + syncUntilBlock = lastFinalised + } else { + syncUntilBlock = lpbInfo + } + if lpbBridge < syncUntilBlock { + syncUntilBlock = lpbBridge + } + return +} + +func (d *driver) getRelation(ctx context.Context, l1InfoIndex uint32) (bridge2L1InfoRelation, error) { + mer, err := d.downloader.getMainnetExitRootAtL1InfoTreeIndex(ctx, l1InfoIndex) + if err != nil { + return bridge2L1InfoRelation{}, err + } + + bridgeIndex, err := d.downloader.getBridgeIndex(ctx, mer) + if err != nil { + return bridge2L1InfoRelation{}, err + } + + return bridge2L1InfoRelation{ + bridgeIndex: bridgeIndex, + l1InfoTreeIndex: l1InfoIndex, + }, nil +} diff --git a/l1bridge2infoindexsync/l1bridge2infoindexsync.go b/l1bridge2infoindexsync/l1bridge2infoindexsync.go new file mode 100644 index 00000000..e6b25c34 --- /dev/null +++ b/l1bridge2infoindexsync/l1bridge2infoindexsync.go @@ -0,0 +1,15 @@ +package l1bridge2infoindexsync + +import ( + "context" + "errors" +) + +type L1Bridge2InfoIndexSync struct { +} + +func New() (*L1Bridge2InfoIndexSync, error) { + return nil, errors.New("not implemented") +} + +func (s *L1Bridge2InfoIndexSync) Start(ctx context.Context) {} diff --git a/l1bridge2infoindexsync/processor.go b/l1bridge2infoindexsync/processor.go new file mode 100644 index 00000000..33cefc6a --- /dev/null +++ b/l1bridge2infoindexsync/processor.go @@ -0,0 +1,58 @@ +package l1bridge2infoindexsync + +import ( + "context" + "errors" + + "github.com/0xPolygon/cdk/common" + "github.com/ledgerwatch/erigon-lib/kv" +) + +var ( + lastBlokcKey = []byte("lb") +) + +type processor struct { + db kv.RwDB + lastBlockTable string +} + +type bridge2L1InfoRelation struct { + bridgeIndex uint32 + l1InfoTreeIndex uint32 +} + +// GetLastProcessedBlock returns the last processed block oby the processor, including blocks +// that don't have events +func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { + tx, err := p.db.BeginRo(ctx) + if err != nil { + return 0, err + } + defer tx.Rollback() + return p.getLastProcessedBlockWithTx(tx) +} + +func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { + if blockNumBytes, err := tx.GetOne(p.lastBlockTable, lastBlokcKey); err != nil { + return 0, err + } else if blockNumBytes == nil { + return 0, nil + } else { + return common.BytesToUint64(blockNumBytes), nil + } +} + +func (p *processor) getLastL1InfoTreeIndexProcessed(ctx context.Context) (uint32, error) { + return 0, errors.New("not implemented") +} + +func (p *processor) updateLastProcessedBlock(ctx context.Context, blockNum uint64) error { + return errors.New("not implemented") +} + +func (p *processor) addBridge2L1InfoRelations(ctx context.Context, lastProcessedBlcok uint64, relations []bridge2L1InfoRelation) error { + // Note that indexes could be repeated as the L1 Info tree update can be produced by a rollup and not mainnet. + // Hence if the index already exist, do not update as it's better to have the lowest indes possible for the relation + return errors.New("not implemented") +} From 1430a700ecd99514764181c539745deb65caf280 Mon Sep 17 00:00:00 2001 From: Arnau Date: Mon, 5 Aug 2024 18:37:26 +0200 Subject: [PATCH 38/49] added E2E test --- bridgesync/bridgesync.go | 10 +- bridgesync/e2e_test.go | 120 ++- go.mod | 2 +- go.sum | 4 +- l1bridge2infoindexsync/downloader.go | 3 +- l1bridge2infoindexsync/driver.go | 115 ++- l1bridge2infoindexsync/e2e_test.go | 224 +++++ .../l1bridge2infoindexsync.go | 50 +- l1bridge2infoindexsync/processor.go | 164 +++- .../transparentupgradableproxy.go | 773 ++++++++++++++++++ tree/appendonlytree.go | 9 + tree/tree.go | 28 +- 12 files changed, 1439 insertions(+), 63 deletions(-) create mode 100644 l1bridge2infoindexsync/e2e_test.go create mode 100644 test/contracts/transparentupgradableproxy/transparentupgradableproxy.go diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index 3f0202dc..3223a095 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -142,7 +142,13 @@ func (s *BridgeSync) Start(ctx context.Context) { } func (s *BridgeSync) GetLastProcessedBlock(ctx context.Context) (uint64, error) { - return s.GetLastProcessedBlock(ctx) + return s.processor.GetLastProcessedBlock(ctx) } -// TODO: expose methods from the processor for consumers +func (s *BridgeSync) GetBridgeIndexByRoot(ctx context.Context, root common.Hash) (uint32, error) { + return s.processor.exitTree.GetIndexByRoot(ctx, root) +} + +func (s *BridgeSync) GetClaimsAndBridges(ctx context.Context, fromBlock, toBlock uint64) ([]Event, error) { + return s.processor.GetClaimsAndBridges(ctx, fromBlock, toBlock) +} diff --git a/bridgesync/e2e_test.go b/bridgesync/e2e_test.go index b7834fdf..ccb7cba0 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -1,3 +1,119 @@ -package bridgesync +package bridgesync_test -// TODO: add E2E test, prolly need a mock contract +import ( + "context" + "fmt" + "math/big" + "testing" + "time" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry-paris/polygonzkevmbridgev2" + "github.com/0xPolygon/cdk/bridgesync" + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/reorgdetector" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/stretchr/testify/require" +) + +func newSimulatedClient(auth *bind.TransactOpts) ( + client *simulated.Backend, + bridgeAddr common.Address, + bridgeContract *polygonzkevmbridgev2.Polygonzkevmbridgev2, + err error, +) { + // ctx := context.Background() + balance, _ := big.NewInt(0).SetString("10000000000000000000000000", 10) //nolint:gomnd + address := auth.From + genesisAlloc := map[common.Address]types.Account{ + address: { + Balance: balance, + }, + } + blockGasLimit := uint64(999999999999999999) //nolint:gomnd + client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) + + bridgeAddr, _, bridgeContract, err = polygonzkevmbridgev2.DeployPolygonzkevmbridgev2(auth, client.Client()) + client.Commit() + return +} + +func TestBridgeEventE2E(t *testing.T) { + ctx := context.Background() + dbPathSyncer := t.TempDir() + dbPathReorg := t.TempDir() + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337)) + require.NoError(t, err) + client, bridgeAddr, bridgeSc, err := newSimulatedClient(auth) + require.NoError(t, err) + rd, err := reorgdetector.New(ctx, client.Client(), dbPathReorg) + go rd.Start(ctx) + + syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, bridgeAddr, 10, etherman.LatestBlock, rd, client.Client(), 0) + require.NoError(t, err) + go syncer.Start(ctx) + + // Send bridge txs + expectedBridges := []bridgesync.Bridge{} + for i := 0; i < 100; i++ { + bridge := bridgesync.Bridge{ + Amount: big.NewInt(0), + DepositCount: uint32(i), + DestinationNetwork: 3, + DestinationAddress: common.HexToAddress("f00"), + Metadata: []byte{}, + } + tx, err := bridgeSc.BridgeAsset( + auth, + bridge.DestinationNetwork, + bridge.DestinationAddress, + bridge.Amount, + bridge.OriginAddress, + false, nil, + ) + client.Commit() + receipt, err := client.Client().TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) + require.Equal(t, receipt.Status, types.ReceiptStatusSuccessful) + expectedBridges = append(expectedBridges, bridge) + } + + // Wait for syncer to catch up + syncerUpToDate := false + var errMsg string + for i := 0; i < 10; i++ { + lpb, err := syncer.GetLastProcessedBlock(ctx) + require.NoError(t, err) + lb, err := client.Client().BlockNumber(ctx) + require.NoError(t, err) + if lpb == lb { + syncerUpToDate = true + break + } + time.Sleep(time.Millisecond * 10) + errMsg = fmt.Sprintf("last block from client: %d, last block from syncer: %d", lb, lpb) + } + require.True(t, syncerUpToDate, errMsg) + + // Get bridges + lastBlcok, err := client.Client().BlockNumber(ctx) + require.NoError(t, err) + events, err := syncer.GetClaimsAndBridges(ctx, 0, lastBlcok) + require.NoError(t, err) + actualBridges := []bridgesync.Bridge{} + for _, event := range events { + if event.Bridge != nil { + actualBridges = append(actualBridges, *event.Bridge) + } + } + + // Assert bridges + require.Equal(t, expectedBridges, actualBridges) +} + +// TODO: test claims and claims + bridges combined diff --git a/go.mod b/go.mod index 5b2fe026..79e052b9 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/0xPolygon/cdk go 1.22.4 require ( - github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726125827-301fa4c59245 + github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240729074811-e02ffdac7080 github.com/0xPolygon/cdk-data-availability v0.0.8 github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3-RC4 diff --git a/go.sum b/go.sum index a1c78e52..1abda2da 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726125827-301fa4c59245 h1:BBmVd50JQID9UyUR3vWFMKr2pMHD3mrqjpuB9DDepBw= -github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240726125827-301fa4c59245/go.mod h1:mFlcEjsm2YBBsu8atHJ3zyVnwM+Z/fMXpVmIJge+WVU= +github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240729074811-e02ffdac7080 h1:PtNUv842Z09eNLMKTFky6Ux9MhNWJYsDYHMwKihHakY= +github.com/0xPolygon/cdk-contracts-tooling v0.0.0-20240729074811-e02ffdac7080/go.mod h1:mFlcEjsm2YBBsu8atHJ3zyVnwM+Z/fMXpVmIJge+WVU= github.com/0xPolygon/cdk-data-availability v0.0.8 h1:bMmOYZ7Ei683y80ric3KzMPXtRGmchAmfjIRzghaHb4= github.com/0xPolygon/cdk-data-availability v0.0.8/go.mod h1:3XkZ0zn0GsvAT01MPQMmukF534CVSFmtrcoK3F/BK6Q= github.com/0xPolygon/cdk-rpc v0.0.0-20240419104226-c0a62ba0f49d h1:sxh6hZ2jF/sxxj2jd5o1vuNNCZjYmn4aRG9SRlVaEFs= diff --git a/l1bridge2infoindexsync/downloader.go b/l1bridge2infoindexsync/downloader.go index fd8af602..6fe80059 100644 --- a/l1bridge2infoindexsync/downloader.go +++ b/l1bridge2infoindexsync/downloader.go @@ -2,7 +2,6 @@ package l1bridge2infoindexsync import ( "context" - "errors" "math/big" "github.com/0xPolygon/cdk/bridgesync" @@ -63,5 +62,5 @@ func (d *downloader) getMainnetExitRootAtL1InfoTreeIndex(ctx context.Context, in } func (d *downloader) getBridgeIndex(ctx context.Context, mainnetExitRoot common.Hash) (uint32, error) { - return 0, errors.New("need to implement root -> index @ bridgesync") + return d.l1Bridge.GetBridgeIndexByRoot(ctx, mainnetExitRoot) } diff --git a/l1bridge2infoindexsync/driver.go b/l1bridge2infoindexsync/driver.go index b9a4debc..9c16379b 100644 --- a/l1bridge2infoindexsync/driver.go +++ b/l1bridge2infoindexsync/driver.go @@ -2,59 +2,122 @@ package l1bridge2infoindexsync import ( "context" + "time" "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/sync" ) type driver struct { - downloader *downloader - processor *processor + downloader *downloader + processor *processor + rh *sync.RetryHandler + waitForSyncersPeriod time.Duration } func newDriver( downloader *downloader, - processor *processor) *driver { + processor *processor, + rh *sync.RetryHandler, + waitForSyncersPeriod time.Duration, +) *driver { return &driver{ - downloader: downloader, - processor: processor, + downloader: downloader, + processor: processor, + rh: rh, + waitForSyncersPeriod: waitForSyncersPeriod, } } func (d *driver) sync(ctx context.Context) { - lpbProcessor, err := d.processor.GetLastProcessedBlock(ctx) - if err != nil { - log.Fatal("TODO") - } - lastProcessedL1InfoIndex, err := d.processor.getLastL1InfoTreeIndexProcessed(ctx) - if err != nil { - log.Fatal("TODO") - } + var ( + attempts int + lpbProcessor uint64 + lastProcessedL1InfoIndex uint32 + err error + ) for { - syncUntilBlock, shouldWait, err := d.getTargetSynchronizationBlock(ctx, lpbProcessor) + lpbProcessor, lastProcessedL1InfoIndex, err = d.processor.GetLastProcessedBlockAndL1InfoTreeIndex(ctx) if err != nil { - log.Fatal("TODO") + attempts++ + log.Errorf("error getting last processed block and index: %v", err) + d.rh.Handle("GetLastProcessedBlockAndL1InfoTreeIndex", attempts) + continue + } + break + } + for { + attempts = 0 + var ( + syncUntilBlock uint64 + shouldWait bool + ) + for { + syncUntilBlock, shouldWait, err = d.getTargetSynchronizationBlock(ctx, lpbProcessor) + if err != nil { + attempts++ + log.Errorf("error getting target sync block: %v", err) + d.rh.Handle("getTargetSynchronizationBlock", attempts) + continue + } + break } if shouldWait { // TODO: wait using ticker + log.Debugf("waiting for syncers to catch up") + time.Sleep(d.waitForSyncersPeriod) continue } - lastL1InfoTreeIndex, err := d.downloader.getLastL1InfoIndexUntilBlock(ctx, syncUntilBlock) - if err != nil { - log.Fatal("TODO") + + attempts = 0 + var lastL1InfoTreeIndex uint32 + for { + lastL1InfoTreeIndex, err = d.downloader.getLastL1InfoIndexUntilBlock(ctx, syncUntilBlock) + if err != nil { + attempts++ + log.Errorf("error getting last l1 info tree index: %v", err) + d.rh.Handle("getLastL1InfoIndexUntilBlock", attempts) + continue + } + break } + relations := []bridge2L1InfoRelation{} - for i := lastProcessedL1InfoIndex + 1; i <= lastL1InfoTreeIndex; i++ { - relation, err := d.getRelation(ctx, i) - if err != nil { - log.Fatal("TODO") + var init uint32 + if lastProcessedL1InfoIndex > 0 { + init = lastProcessedL1InfoIndex + 1 + } + for i := init; i <= lastL1InfoTreeIndex; i++ { + attempts = 0 + for { + relation, err := d.getRelation(ctx, i) + if err != nil { + attempts++ + log.Errorf("error getting relation: %v", err) + d.rh.Handle("getRelation", attempts) + continue + } + relations = append(relations, relation) + break } - relations = append(relations, relation) } - if err := d.processor.addBridge2L1InfoRelations(ctx, syncUntilBlock, relations); err != nil { - log.Fatal("TODO") + + attempts = 0 + log.Debugf("processing until block %d: %+v", syncUntilBlock, relations) + for { + if err := d.processor.processUntilBlock(ctx, syncUntilBlock, relations); err != nil { + attempts++ + log.Errorf("error processing block: %v", err) + d.rh.Handle("processUntilBlock", attempts) + continue + } + break } + lpbProcessor = syncUntilBlock - lastProcessedL1InfoIndex = 0 // TODO + if len(relations) > 0 { + lastProcessedL1InfoIndex = relations[len(relations)-1].l1InfoTreeIndex + } } } diff --git a/l1bridge2infoindexsync/e2e_test.go b/l1bridge2infoindexsync/e2e_test.go new file mode 100644 index 00000000..71f5a008 --- /dev/null +++ b/l1bridge2infoindexsync/e2e_test.go @@ -0,0 +1,224 @@ +package l1bridge2infoindexsync_test + +import ( + "context" + "errors" + "fmt" + "math/big" + "strconv" + "testing" + "time" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry-paris/polygonzkevmbridgev2" + "github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry-paris/polygonzkevmglobalexitrootv2" + "github.com/0xPolygon/cdk/bridgesync" + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/l1bridge2infoindexsync" + "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/reorgdetector" + "github.com/0xPolygon/cdk/test/contracts/transparentupgradableproxy" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" +) + +func newSimulatedClient(authDeployer, authCaller *bind.TransactOpts) ( + client *simulated.Backend, + gerAddr common.Address, + bridgeAddr common.Address, + gerContract *polygonzkevmglobalexitrootv2.Polygonzkevmglobalexitrootv2, + bridgeContract *polygonzkevmbridgev2.Polygonzkevmbridgev2, + err error, +) { + ctx := context.Background() + balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd + genesisAlloc := map[common.Address]types.Account{ + authDeployer.From: { + Balance: balance, + }, + authCaller.From: { + Balance: balance, + }, + } + blockGasLimit := uint64(999999999999999999) //nolint:gomnd + client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) + + bridgeImplementationAddr, _, _, err := polygonzkevmbridgev2.DeployPolygonzkevmbridgev2(authDeployer, client.Client()) + if err != nil { + return + } + client.Commit() + + nonce, err := client.Client().PendingNonceAt(ctx, authDeployer.From) + if err != nil { + return + } + precalculatedAddr := crypto.CreateAddress(authDeployer.From, nonce+1) + bridgeABI, err := polygonzkevmbridgev2.Polygonzkevmbridgev2MetaData.GetAbi() + if err != nil { + return + } + if bridgeABI == nil { + err = errors.New("GetABI returned nil") + return + } + dataCallProxy, err := bridgeABI.Pack("initialize", + uint32(0), // networkIDMainnet + common.Address{}, // gasTokenAddressMainnet" + uint32(0), // gasTokenNetworkMainnet + precalculatedAddr, + common.Address{}, + []byte{}, // gasTokenMetadata + ) + if err != nil { + return + } + bridgeAddr, _, _, err = transparentupgradableproxy.DeployTransparentupgradableproxy( + authDeployer, + client.Client(), + bridgeImplementationAddr, + authDeployer.From, + dataCallProxy, + ) + if err != nil { + return + } + client.Commit() + bridgeContract, err = polygonzkevmbridgev2.NewPolygonzkevmbridgev2(bridgeAddr, client.Client()) + if err != nil { + return + } + checkGERAddr, err := bridgeContract.GlobalExitRootManager(&bind.CallOpts{}) + if err != nil { + return + } + if precalculatedAddr != checkGERAddr { + err = errors.New("error deploying bridge") + } + + gerAddr, _, gerContract, err = polygonzkevmglobalexitrootv2.DeployPolygonzkevmglobalexitrootv2( + authDeployer, client.Client(), authCaller.From, bridgeAddr, + ) + if err != nil { + return + } + client.Commit() + + if precalculatedAddr != gerAddr { + err = errors.New("error calculating addr") + } + return +} + +func TestE2E(t *testing.T) { + ctx := context.Background() + dbPathBridgeSync := t.TempDir() + dbPathL1Sync := t.TempDir() + dbPathReorg := t.TempDir() + dbPathL12InfoSync := t.TempDir() + + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + authDeployer, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337)) + require.NoError(t, err) + privateKey, err = crypto.GenerateKey() + require.NoError(t, err) + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337)) + require.NoError(t, err) + require.NotEqual(t, authDeployer.From, auth.From) + client, gerAddr, bridgeAddr, gerSc, bridgeSc, err := newSimulatedClient(authDeployer, auth) + require.NoError(t, err) + rd, err := reorgdetector.New(ctx, client.Client(), dbPathReorg) + go rd.Start(ctx) + + bridgeSync, err := bridgesync.NewL1(ctx, dbPathBridgeSync, bridgeAddr, 10, etherman.LatestBlock, rd, client.Client(), 0) + require.NoError(t, err) + go bridgeSync.Start(ctx) + + l1Sync, err := l1infotreesync.New( + ctx, + dbPathL1Sync, + gerAddr, + common.Address{}, + 10, + etherman.SafeBlock, + rd, + client.Client(), + time.Millisecond, + 0, + time.Millisecond, + 3, + ) + require.NoError(t, err) + go l1Sync.Start(ctx) + + bridge2InfoSync, err := l1bridge2infoindexsync.New(dbPathL12InfoSync, bridgeSync, l1Sync, client.Client(), 0, 0, time.Millisecond) + require.NoError(t, err) + go bridge2InfoSync.Start(ctx) + + // Send bridge txs + expectedIndex := -1 + for i := 0; i < 10; i++ { + bridge := bridgesync.Bridge{ + Amount: big.NewInt(0), + DestinationNetwork: 3, + DestinationAddress: common.HexToAddress("f00"), + } + _, err := bridgeSc.BridgeAsset( + auth, + bridge.DestinationNetwork, + bridge.DestinationAddress, + bridge.Amount, + bridge.OriginAddress, + true, nil, + ) + require.NoError(t, err) + expectedIndex++ + client.Commit() + + // Wait for block to be finalised + updateAtBlock, err := client.Client().BlockNumber(ctx) + for { + lastFinalisedBlock, err := client.Client().BlockByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber))) + require.NoError(t, err) + if lastFinalisedBlock.NumberU64() >= updateAtBlock { + break + } + client.Commit() + time.Sleep(time.Microsecond) + } + + // Wait for syncer to catch up + syncerUpToDate := false + var errMsg string + for i := 0; i < 10; i++ { + lpb, err := bridge2InfoSync.GetLastProcessedBlock(ctx) + require.NoError(t, err) + lb, err := client.Client().BlockByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber))) + require.NoError(t, err) + if lpb == lb.NumberU64() { + syncerUpToDate = true + break + } + time.Sleep(time.Millisecond * 10) + errMsg = fmt.Sprintf("last block from client: %d, last block from syncer: %d", lb.NumberU64(), lpb) + } + require.True(t, syncerUpToDate, errMsg) + + actualIndex, err := bridge2InfoSync.GetL1InfoTreeIndexByDepositCount(ctx, uint32(i)) + require.NoError(t, err) + require.Equal(t, uint32(expectedIndex), actualIndex) + + if i%2 == 1 { + // Update L1 info tree without a bridge on L1 + _, err = gerSc.UpdateExitRoot(auth, common.HexToHash(strconv.Itoa(i))) + require.NoError(t, err) + expectedIndex++ + client.Commit() + } + } +} diff --git a/l1bridge2infoindexsync/l1bridge2infoindexsync.go b/l1bridge2infoindexsync/l1bridge2infoindexsync.go index e6b25c34..22fefb54 100644 --- a/l1bridge2infoindexsync/l1bridge2infoindexsync.go +++ b/l1bridge2infoindexsync/l1bridge2infoindexsync.go @@ -2,14 +2,56 @@ package l1bridge2infoindexsync import ( "context" - "errors" + "time" + + "github.com/0xPolygon/cdk/bridgesync" + "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/sync" + "github.com/ethereum/go-ethereum" ) type L1Bridge2InfoIndexSync struct { + processor *processor + driver *driver } -func New() (*L1Bridge2InfoIndexSync, error) { - return nil, errors.New("not implemented") +func New( + dbPath string, + l1Bridge *bridgesync.BridgeSync, + l1Info *l1infotreesync.L1InfoTreeSync, + l1Client ethereum.ChainReader, + retryAfterErrorPeriod time.Duration, + maxRetryAttemptsAfterError int, + waitForSyncersPeriod time.Duration, +) (*L1Bridge2InfoIndexSync, error) { + dwn := newDownloader(l1Bridge, l1Info, l1Client) + + prc, err := newProcessor(dbPath) + if err != nil { + return nil, err + } + + rh := &sync.RetryHandler{ + RetryAfterErrorPeriod: retryAfterErrorPeriod, + MaxRetryAttemptsAfterError: maxRetryAttemptsAfterError, + } + drv := newDriver(dwn, prc, rh, waitForSyncersPeriod) + + return &L1Bridge2InfoIndexSync{ + driver: drv, + processor: prc, + }, nil } -func (s *L1Bridge2InfoIndexSync) Start(ctx context.Context) {} +func (s *L1Bridge2InfoIndexSync) Start(ctx context.Context) { + s.driver.sync(ctx) +} + +func (s *L1Bridge2InfoIndexSync) GetLastProcessedBlock(ctx context.Context) (uint64, error) { + lpb, _, err := s.processor.GetLastProcessedBlockAndL1InfoTreeIndex(ctx) + return lpb, err +} + +func (s *L1Bridge2InfoIndexSync) GetL1InfoTreeIndexByDepositCount(ctx context.Context, depositCount uint32) (uint32, error) { + return s.processor.getL1InfoTreeIndexByBrdigeIndex(ctx, depositCount) +} diff --git a/l1bridge2infoindexsync/processor.go b/l1bridge2infoindexsync/processor.go index 33cefc6a..0e4fd5dc 100644 --- a/l1bridge2infoindexsync/processor.go +++ b/l1bridge2infoindexsync/processor.go @@ -3,18 +3,25 @@ package l1bridge2infoindexsync import ( "context" "errors" + "fmt" "github.com/0xPolygon/cdk/common" "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/kv/mdbx" +) + +const ( + lastProcessedTable = "l1bridge2infoindexsync-lastProcessed" + relationTable = "l1bridge2infoindexsync-relation" ) var ( - lastBlokcKey = []byte("lb") + lastProcessedKey = []byte("lp") + ErrNotFound = errors.New("not found") ) type processor struct { - db kv.RwDB - lastBlockTable string + db kv.RwDB } type bridge2L1InfoRelation struct { @@ -22,37 +29,154 @@ type bridge2L1InfoRelation struct { l1InfoTreeIndex uint32 } -// GetLastProcessedBlock returns the last processed block oby the processor, including blocks +type lastProcessed struct { + block uint64 + index uint32 +} + +func (lp *lastProcessed) MarshalBinary() ([]byte, error) { + return append(common.Uint64ToBytes(lp.block), common.Uint32ToBytes(lp.index)...), nil +} + +func (lp *lastProcessed) UnmarshalBinary(data []byte) error { + if len(data) != 12 { + return fmt.Errorf("expected len %d, actual len %d", 12, len(data)) + } + lp.block = common.BytesToUint64(data[:8]) + lp.index = common.BytesToUint32(data[8:]) + return nil +} + +func newProcessor(dbPath string) (*processor, error) { + tableCfgFunc := func(defaultBuckets kv.TableCfg) kv.TableCfg { + cfg := kv.TableCfg{ + lastProcessedTable: {}, + relationTable: {}, + } + return cfg + } + db, err := mdbx.NewMDBX(nil). + Path(dbPath). + WithTableCfg(tableCfgFunc). + Open() + if err != nil { + return nil, err + } + return &processor{ + db: db, + }, nil +} + +// GetLastProcessedBlockAndL1InfoTreeIndex returns the last processed block oby the processor, including blocks // that don't have events -func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { +func (p *processor) GetLastProcessedBlockAndL1InfoTreeIndex(ctx context.Context) (uint64, uint32, error) { tx, err := p.db.BeginRo(ctx) if err != nil { - return 0, err + return 0, 0, err } defer tx.Rollback() - return p.getLastProcessedBlockWithTx(tx) + return p.getLastProcessedBlockAndL1InfoTreeIndexWithTx(tx) } -func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { - if blockNumBytes, err := tx.GetOne(p.lastBlockTable, lastBlokcKey); err != nil { - return 0, err - } else if blockNumBytes == nil { - return 0, nil +func (p *processor) getLastProcessedBlockAndL1InfoTreeIndexWithTx(tx kv.Tx) (uint64, uint32, error) { + if lastProcessedBytes, err := tx.GetOne(lastProcessedTable, lastProcessedKey); err != nil { + return 0, 0, err + } else if lastProcessedBytes == nil { + return 0, 0, nil } else { - return common.BytesToUint64(blockNumBytes), nil + lp := &lastProcessed{} + if err := lp.UnmarshalBinary(lastProcessedBytes); err != nil { + return 0, 0, err + } + return lp.block, lp.index, nil } } -func (p *processor) getLastL1InfoTreeIndexProcessed(ctx context.Context) (uint32, error) { - return 0, errors.New("not implemented") +func (p *processor) updateLastProcessedBlockAndL1InfoTreeIndex(ctx context.Context, blockNum uint64, index uint32) error { + tx, err := p.db.BeginRw(ctx) + if err != nil { + return err + } + if err := p.updateLastProcessedBlockAndL1InfoTreeIndexWithTx(tx, blockNum, index); err != nil { + tx.Rollback() + return err + } + return tx.Commit() } -func (p *processor) updateLastProcessedBlock(ctx context.Context, blockNum uint64) error { - return errors.New("not implemented") +func (p *processor) updateLastProcessedBlockAndL1InfoTreeIndexWithTx(tx kv.RwTx, blockNum uint64, index uint32) error { + lp := &lastProcessed{ + block: blockNum, + index: index, + } + value, err := lp.MarshalBinary() + if err != nil { + return err + } + return tx.Put(lastProcessedTable, lastProcessedKey, value) } -func (p *processor) addBridge2L1InfoRelations(ctx context.Context, lastProcessedBlcok uint64, relations []bridge2L1InfoRelation) error { +func (p *processor) processUntilBlock(ctx context.Context, lastProcessedBlock uint64, relations []bridge2L1InfoRelation) error { // Note that indexes could be repeated as the L1 Info tree update can be produced by a rollup and not mainnet. - // Hence if the index already exist, do not update as it's better to have the lowest indes possible for the relation - return errors.New("not implemented") + // Hence if the index already exist, do not update as it's better to have the lowest index possible for the relation + tx, err := p.db.BeginRw(ctx) + if err != nil { + return err + } + + for _, relation := range relations { + if err := tx.Put( + relationTable, + common.Uint32ToBytes(relation.bridgeIndex), + common.Uint32ToBytes(relation.l1InfoTreeIndex), + ); err != nil { + // TODO: if duplicated key, ignore error + tx.Rollback() + return err + } + } + + if len(relations) > 0 { + if err := p.updateLastProcessedBlockAndL1InfoTreeIndexWithTx( + tx, + lastProcessedBlock, + relations[len(relations)-1].l1InfoTreeIndex, + ); err != nil { + tx.Rollback() + return err + } + } else { + _, lastIndex, err := p.getLastProcessedBlockAndL1InfoTreeIndexWithTx(tx) + if err != nil { + tx.Rollback() + return err + } + if err := p.updateLastProcessedBlockAndL1InfoTreeIndexWithTx( + tx, + lastProcessedBlock, + lastIndex, + ); err != nil { + tx.Rollback() + return err + } + } + + return tx.Commit() +} + +func (p *processor) getL1InfoTreeIndexByBrdigeIndex(ctx context.Context, depositCount uint32) (uint32, error) { + tx, err := p.db.BeginRo(ctx) + if err != nil { + return 0, err + } + defer tx.Rollback() + + indexBytes, err := tx.GetOne(relationTable, common.Uint32ToBytes(depositCount)) + if err != nil { + return 0, err + } + if indexBytes == nil { + return 0, ErrNotFound + } + return common.BytesToUint32(indexBytes), nil } diff --git a/test/contracts/transparentupgradableproxy/transparentupgradableproxy.go b/test/contracts/transparentupgradableproxy/transparentupgradableproxy.go new file mode 100644 index 00000000..2fb3e042 --- /dev/null +++ b/test/contracts/transparentupgradableproxy/transparentupgradableproxy.go @@ -0,0 +1,773 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package transparentupgradableproxy + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// TransparentupgradableproxyMetaData contains all meta data concerning the Transparentupgradableproxy contract. +var TransparentupgradableproxyMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_logic\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"previousAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"AdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"beacon\",\"type\":\"address\"}],\"name\":\"BeaconUpgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"admin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"changeAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"implementation_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"upgradeTo\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", + Bin: "0x608060405260405162000f6838038062000f68833981016040819052620000269162000415565b82816200003582825f6200004c565b50620000439050826200007d565b50505062000540565b6200005783620000ee565b5f82511180620000645750805b1562000078576200007683836200012f565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f620000be5f8051602062000f21833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a1620000eb816200015e565b50565b620000f981620001fb565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606062000157838360405180606001604052806027815260200162000f416027913962000292565b9392505050565b6001600160a01b038116620001c95760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b805f8051602062000f218339815191525b80546001600160a01b0319166001600160a01b039290921691909117905550565b6001600160a01b0381163b6200026a5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401620001c0565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc620001da565b60605f80856001600160a01b031685604051620002b09190620004ef565b5f60405180830381855af49150503d805f8114620002ea576040519150601f19603f3d011682016040523d82523d5f602084013e620002ef565b606091505b50909250905062000303868383876200030d565b9695505050505050565b60608315620003805782515f0362000378576001600160a01b0385163b620003785760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401620001c0565b50816200038c565b6200038c838362000394565b949350505050565b815115620003a55781518083602001fd5b8060405162461bcd60e51b8152600401620001c091906200050c565b80516001600160a01b0381168114620003d8575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156200040d578181015183820152602001620003f3565b50505f910152565b5f805f6060848603121562000428575f80fd5b6200043384620003c1565b92506200044360208501620003c1565b60408501519092506001600160401b038082111562000460575f80fd5b818601915086601f83011262000474575f80fd5b815181811115620004895762000489620003dd565b604051601f8201601f19908116603f01168101908382118183101715620004b457620004b4620003dd565b81604052828152896020848701011115620004cd575f80fd5b620004e0836020830160208801620003f1565b80955050505050509250925092565b5f825162000502818460208701620003f1565b9190910192915050565b602081525f82518060208401526200052c816040850160208701620003f1565b601f01601f19169190910160400192915050565b6109d3806200054e5f395ff3fe60806040526004361061005d575f3560e01c80635c60da1b116100425780635c60da1b146100a65780638f283970146100e3578063f851a440146101025761006c565b80633659cfe6146100745780634f1ef286146100935761006c565b3661006c5761006a610116565b005b61006a610116565b34801561007f575f80fd5b5061006a61008e366004610854565b610130565b61006a6100a136600461086d565b610178565b3480156100b1575f80fd5b506100ba6101eb565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100ee575f80fd5b5061006a6100fd366004610854565b610228565b34801561010d575f80fd5b506100ba610255565b61011e610282565b61012e610129610359565b610362565b565b610138610380565b73ffffffffffffffffffffffffffffffffffffffff1633036101705761016d8160405180602001604052805f8152505f6103bf565b50565b61016d610116565b610180610380565b73ffffffffffffffffffffffffffffffffffffffff1633036101e3576101de8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250600192506103bf915050565b505050565b6101de610116565b5f6101f4610380565b73ffffffffffffffffffffffffffffffffffffffff16330361021d57610218610359565b905090565b610225610116565b90565b610230610380565b73ffffffffffffffffffffffffffffffffffffffff1633036101705761016d816103e9565b5f61025e610380565b73ffffffffffffffffffffffffffffffffffffffff16330361021d57610218610380565b61028a610380565b73ffffffffffffffffffffffffffffffffffffffff16330361012e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f6574000000000000000000000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b5f61021861044a565b365f80375f80365f845af43d5f803e80801561037c573d5ff35b3d5ffd5b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b5473ffffffffffffffffffffffffffffffffffffffff16919050565b6103c883610471565b5f825111806103d45750805b156101de576103e383836104bd565b50505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610412610380565b6040805173ffffffffffffffffffffffffffffffffffffffff928316815291841660208301520160405180910390a161016d816104e9565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6103a3565b61047a816105f5565b60405173ffffffffffffffffffffffffffffffffffffffff8216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606104e28383604051806060016040528060278152602001610977602791396106c0565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff811661058c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610350565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9290921691909117905550565b73ffffffffffffffffffffffffffffffffffffffff81163b610699576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e7472616374000000000000000000000000000000000000006064820152608401610350565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6105af565b60605f808573ffffffffffffffffffffffffffffffffffffffff16856040516106e9919061090b565b5f60405180830381855af49150503d805f8114610721576040519150601f19603f3d011682016040523d82523d5f602084013e610726565b606091505b509150915061073786838387610741565b9695505050505050565b606083156107d65782515f036107cf5773ffffffffffffffffffffffffffffffffffffffff85163b6107cf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610350565b50816107e0565b6107e083836107e8565b949350505050565b8151156107f85781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103509190610926565b803573ffffffffffffffffffffffffffffffffffffffff8116811461084f575f80fd5b919050565b5f60208284031215610864575f80fd5b6104e28261082c565b5f805f6040848603121561087f575f80fd5b6108888461082c565b9250602084013567ffffffffffffffff808211156108a4575f80fd5b818601915086601f8301126108b7575f80fd5b8135818111156108c5575f80fd5b8760208285010111156108d6575f80fd5b6020830194508093505050509250925092565b5f5b838110156109035781810151838201526020016108eb565b50505f910152565b5f825161091c8184602087016108e9565b9190910192915050565b602081525f82518060208401526109448160408501602087016108e9565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212202ac98acbfbb3d3ac1b74050e18c4e76db25a3ff2801ec69bf85d0c61414d502b64736f6c63430008140033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564", +} + +// TransparentupgradableproxyABI is the input ABI used to generate the binding from. +// Deprecated: Use TransparentupgradableproxyMetaData.ABI instead. +var TransparentupgradableproxyABI = TransparentupgradableproxyMetaData.ABI + +// TransparentupgradableproxyBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use TransparentupgradableproxyMetaData.Bin instead. +var TransparentupgradableproxyBin = TransparentupgradableproxyMetaData.Bin + +// DeployTransparentupgradableproxy deploys a new Ethereum contract, binding an instance of Transparentupgradableproxy to it. +func DeployTransparentupgradableproxy(auth *bind.TransactOpts, backend bind.ContractBackend, _logic common.Address, admin_ common.Address, _data []byte) (common.Address, *types.Transaction, *Transparentupgradableproxy, error) { + parsed, err := TransparentupgradableproxyMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(TransparentupgradableproxyBin), backend, _logic, admin_, _data) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Transparentupgradableproxy{TransparentupgradableproxyCaller: TransparentupgradableproxyCaller{contract: contract}, TransparentupgradableproxyTransactor: TransparentupgradableproxyTransactor{contract: contract}, TransparentupgradableproxyFilterer: TransparentupgradableproxyFilterer{contract: contract}}, nil +} + +// Transparentupgradableproxy is an auto generated Go binding around an Ethereum contract. +type Transparentupgradableproxy struct { + TransparentupgradableproxyCaller // Read-only binding to the contract + TransparentupgradableproxyTransactor // Write-only binding to the contract + TransparentupgradableproxyFilterer // Log filterer for contract events +} + +// TransparentupgradableproxyCaller is an auto generated read-only Go binding around an Ethereum contract. +type TransparentupgradableproxyCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TransparentupgradableproxyTransactor is an auto generated write-only Go binding around an Ethereum contract. +type TransparentupgradableproxyTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TransparentupgradableproxyFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type TransparentupgradableproxyFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TransparentupgradableproxySession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type TransparentupgradableproxySession struct { + Contract *Transparentupgradableproxy // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// TransparentupgradableproxyCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type TransparentupgradableproxyCallerSession struct { + Contract *TransparentupgradableproxyCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// TransparentupgradableproxyTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type TransparentupgradableproxyTransactorSession struct { + Contract *TransparentupgradableproxyTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// TransparentupgradableproxyRaw is an auto generated low-level Go binding around an Ethereum contract. +type TransparentupgradableproxyRaw struct { + Contract *Transparentupgradableproxy // Generic contract binding to access the raw methods on +} + +// TransparentupgradableproxyCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type TransparentupgradableproxyCallerRaw struct { + Contract *TransparentupgradableproxyCaller // Generic read-only contract binding to access the raw methods on +} + +// TransparentupgradableproxyTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type TransparentupgradableproxyTransactorRaw struct { + Contract *TransparentupgradableproxyTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewTransparentupgradableproxy creates a new instance of Transparentupgradableproxy, bound to a specific deployed contract. +func NewTransparentupgradableproxy(address common.Address, backend bind.ContractBackend) (*Transparentupgradableproxy, error) { + contract, err := bindTransparentupgradableproxy(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Transparentupgradableproxy{TransparentupgradableproxyCaller: TransparentupgradableproxyCaller{contract: contract}, TransparentupgradableproxyTransactor: TransparentupgradableproxyTransactor{contract: contract}, TransparentupgradableproxyFilterer: TransparentupgradableproxyFilterer{contract: contract}}, nil +} + +// NewTransparentupgradableproxyCaller creates a new read-only instance of Transparentupgradableproxy, bound to a specific deployed contract. +func NewTransparentupgradableproxyCaller(address common.Address, caller bind.ContractCaller) (*TransparentupgradableproxyCaller, error) { + contract, err := bindTransparentupgradableproxy(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &TransparentupgradableproxyCaller{contract: contract}, nil +} + +// NewTransparentupgradableproxyTransactor creates a new write-only instance of Transparentupgradableproxy, bound to a specific deployed contract. +func NewTransparentupgradableproxyTransactor(address common.Address, transactor bind.ContractTransactor) (*TransparentupgradableproxyTransactor, error) { + contract, err := bindTransparentupgradableproxy(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &TransparentupgradableproxyTransactor{contract: contract}, nil +} + +// NewTransparentupgradableproxyFilterer creates a new log filterer instance of Transparentupgradableproxy, bound to a specific deployed contract. +func NewTransparentupgradableproxyFilterer(address common.Address, filterer bind.ContractFilterer) (*TransparentupgradableproxyFilterer, error) { + contract, err := bindTransparentupgradableproxy(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &TransparentupgradableproxyFilterer{contract: contract}, nil +} + +// bindTransparentupgradableproxy binds a generic wrapper to an already deployed contract. +func bindTransparentupgradableproxy(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := TransparentupgradableproxyMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Transparentupgradableproxy *TransparentupgradableproxyRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Transparentupgradableproxy.Contract.TransparentupgradableproxyCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Transparentupgradableproxy *TransparentupgradableproxyRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.TransparentupgradableproxyTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Transparentupgradableproxy *TransparentupgradableproxyRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.TransparentupgradableproxyTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Transparentupgradableproxy *TransparentupgradableproxyCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Transparentupgradableproxy.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.contract.Transact(opts, method, params...) +} + +// Admin is a paid mutator transaction binding the contract method 0xf851a440. +// +// Solidity: function admin() returns(address admin_) +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactor) Admin(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Transparentupgradableproxy.contract.Transact(opts, "admin") +} + +// Admin is a paid mutator transaction binding the contract method 0xf851a440. +// +// Solidity: function admin() returns(address admin_) +func (_Transparentupgradableproxy *TransparentupgradableproxySession) Admin() (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.Admin(&_Transparentupgradableproxy.TransactOpts) +} + +// Admin is a paid mutator transaction binding the contract method 0xf851a440. +// +// Solidity: function admin() returns(address admin_) +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactorSession) Admin() (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.Admin(&_Transparentupgradableproxy.TransactOpts) +} + +// ChangeAdmin is a paid mutator transaction binding the contract method 0x8f283970. +// +// Solidity: function changeAdmin(address newAdmin) returns() +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactor) ChangeAdmin(opts *bind.TransactOpts, newAdmin common.Address) (*types.Transaction, error) { + return _Transparentupgradableproxy.contract.Transact(opts, "changeAdmin", newAdmin) +} + +// ChangeAdmin is a paid mutator transaction binding the contract method 0x8f283970. +// +// Solidity: function changeAdmin(address newAdmin) returns() +func (_Transparentupgradableproxy *TransparentupgradableproxySession) ChangeAdmin(newAdmin common.Address) (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.ChangeAdmin(&_Transparentupgradableproxy.TransactOpts, newAdmin) +} + +// ChangeAdmin is a paid mutator transaction binding the contract method 0x8f283970. +// +// Solidity: function changeAdmin(address newAdmin) returns() +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactorSession) ChangeAdmin(newAdmin common.Address) (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.ChangeAdmin(&_Transparentupgradableproxy.TransactOpts, newAdmin) +} + +// Implementation is a paid mutator transaction binding the contract method 0x5c60da1b. +// +// Solidity: function implementation() returns(address implementation_) +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactor) Implementation(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Transparentupgradableproxy.contract.Transact(opts, "implementation") +} + +// Implementation is a paid mutator transaction binding the contract method 0x5c60da1b. +// +// Solidity: function implementation() returns(address implementation_) +func (_Transparentupgradableproxy *TransparentupgradableproxySession) Implementation() (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.Implementation(&_Transparentupgradableproxy.TransactOpts) +} + +// Implementation is a paid mutator transaction binding the contract method 0x5c60da1b. +// +// Solidity: function implementation() returns(address implementation_) +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactorSession) Implementation() (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.Implementation(&_Transparentupgradableproxy.TransactOpts) +} + +// UpgradeTo is a paid mutator transaction binding the contract method 0x3659cfe6. +// +// Solidity: function upgradeTo(address newImplementation) returns() +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactor) UpgradeTo(opts *bind.TransactOpts, newImplementation common.Address) (*types.Transaction, error) { + return _Transparentupgradableproxy.contract.Transact(opts, "upgradeTo", newImplementation) +} + +// UpgradeTo is a paid mutator transaction binding the contract method 0x3659cfe6. +// +// Solidity: function upgradeTo(address newImplementation) returns() +func (_Transparentupgradableproxy *TransparentupgradableproxySession) UpgradeTo(newImplementation common.Address) (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.UpgradeTo(&_Transparentupgradableproxy.TransactOpts, newImplementation) +} + +// UpgradeTo is a paid mutator transaction binding the contract method 0x3659cfe6. +// +// Solidity: function upgradeTo(address newImplementation) returns() +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactorSession) UpgradeTo(newImplementation common.Address) (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.UpgradeTo(&_Transparentupgradableproxy.TransactOpts, newImplementation) +} + +// UpgradeToAndCall is a paid mutator transaction binding the contract method 0x4f1ef286. +// +// Solidity: function upgradeToAndCall(address newImplementation, bytes data) payable returns() +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactor) UpgradeToAndCall(opts *bind.TransactOpts, newImplementation common.Address, data []byte) (*types.Transaction, error) { + return _Transparentupgradableproxy.contract.Transact(opts, "upgradeToAndCall", newImplementation, data) +} + +// UpgradeToAndCall is a paid mutator transaction binding the contract method 0x4f1ef286. +// +// Solidity: function upgradeToAndCall(address newImplementation, bytes data) payable returns() +func (_Transparentupgradableproxy *TransparentupgradableproxySession) UpgradeToAndCall(newImplementation common.Address, data []byte) (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.UpgradeToAndCall(&_Transparentupgradableproxy.TransactOpts, newImplementation, data) +} + +// UpgradeToAndCall is a paid mutator transaction binding the contract method 0x4f1ef286. +// +// Solidity: function upgradeToAndCall(address newImplementation, bytes data) payable returns() +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactorSession) UpgradeToAndCall(newImplementation common.Address, data []byte) (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.UpgradeToAndCall(&_Transparentupgradableproxy.TransactOpts, newImplementation, data) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { + return _Transparentupgradableproxy.contract.RawTransact(opts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_Transparentupgradableproxy *TransparentupgradableproxySession) Fallback(calldata []byte) (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.Fallback(&_Transparentupgradableproxy.TransactOpts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.Fallback(&_Transparentupgradableproxy.TransactOpts, calldata) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Transparentupgradableproxy.contract.RawTransact(opts, nil) // calldata is disallowed for receive function +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_Transparentupgradableproxy *TransparentupgradableproxySession) Receive() (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.Receive(&_Transparentupgradableproxy.TransactOpts) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_Transparentupgradableproxy *TransparentupgradableproxyTransactorSession) Receive() (*types.Transaction, error) { + return _Transparentupgradableproxy.Contract.Receive(&_Transparentupgradableproxy.TransactOpts) +} + +// TransparentupgradableproxyAdminChangedIterator is returned from FilterAdminChanged and is used to iterate over the raw logs and unpacked data for AdminChanged events raised by the Transparentupgradableproxy contract. +type TransparentupgradableproxyAdminChangedIterator struct { + Event *TransparentupgradableproxyAdminChanged // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *TransparentupgradableproxyAdminChangedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(TransparentupgradableproxyAdminChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(TransparentupgradableproxyAdminChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *TransparentupgradableproxyAdminChangedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TransparentupgradableproxyAdminChangedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TransparentupgradableproxyAdminChanged represents a AdminChanged event raised by the Transparentupgradableproxy contract. +type TransparentupgradableproxyAdminChanged struct { + PreviousAdmin common.Address + NewAdmin common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAdminChanged is a free log retrieval operation binding the contract event 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f. +// +// Solidity: event AdminChanged(address previousAdmin, address newAdmin) +func (_Transparentupgradableproxy *TransparentupgradableproxyFilterer) FilterAdminChanged(opts *bind.FilterOpts) (*TransparentupgradableproxyAdminChangedIterator, error) { + + logs, sub, err := _Transparentupgradableproxy.contract.FilterLogs(opts, "AdminChanged") + if err != nil { + return nil, err + } + return &TransparentupgradableproxyAdminChangedIterator{contract: _Transparentupgradableproxy.contract, event: "AdminChanged", logs: logs, sub: sub}, nil +} + +// WatchAdminChanged is a free log subscription operation binding the contract event 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f. +// +// Solidity: event AdminChanged(address previousAdmin, address newAdmin) +func (_Transparentupgradableproxy *TransparentupgradableproxyFilterer) WatchAdminChanged(opts *bind.WatchOpts, sink chan<- *TransparentupgradableproxyAdminChanged) (event.Subscription, error) { + + logs, sub, err := _Transparentupgradableproxy.contract.WatchLogs(opts, "AdminChanged") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(TransparentupgradableproxyAdminChanged) + if err := _Transparentupgradableproxy.contract.UnpackLog(event, "AdminChanged", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAdminChanged is a log parse operation binding the contract event 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f. +// +// Solidity: event AdminChanged(address previousAdmin, address newAdmin) +func (_Transparentupgradableproxy *TransparentupgradableproxyFilterer) ParseAdminChanged(log types.Log) (*TransparentupgradableproxyAdminChanged, error) { + event := new(TransparentupgradableproxyAdminChanged) + if err := _Transparentupgradableproxy.contract.UnpackLog(event, "AdminChanged", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// TransparentupgradableproxyBeaconUpgradedIterator is returned from FilterBeaconUpgraded and is used to iterate over the raw logs and unpacked data for BeaconUpgraded events raised by the Transparentupgradableproxy contract. +type TransparentupgradableproxyBeaconUpgradedIterator struct { + Event *TransparentupgradableproxyBeaconUpgraded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *TransparentupgradableproxyBeaconUpgradedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(TransparentupgradableproxyBeaconUpgraded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(TransparentupgradableproxyBeaconUpgraded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *TransparentupgradableproxyBeaconUpgradedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TransparentupgradableproxyBeaconUpgradedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TransparentupgradableproxyBeaconUpgraded represents a BeaconUpgraded event raised by the Transparentupgradableproxy contract. +type TransparentupgradableproxyBeaconUpgraded struct { + Beacon common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterBeaconUpgraded is a free log retrieval operation binding the contract event 0x1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e. +// +// Solidity: event BeaconUpgraded(address indexed beacon) +func (_Transparentupgradableproxy *TransparentupgradableproxyFilterer) FilterBeaconUpgraded(opts *bind.FilterOpts, beacon []common.Address) (*TransparentupgradableproxyBeaconUpgradedIterator, error) { + + var beaconRule []interface{} + for _, beaconItem := range beacon { + beaconRule = append(beaconRule, beaconItem) + } + + logs, sub, err := _Transparentupgradableproxy.contract.FilterLogs(opts, "BeaconUpgraded", beaconRule) + if err != nil { + return nil, err + } + return &TransparentupgradableproxyBeaconUpgradedIterator{contract: _Transparentupgradableproxy.contract, event: "BeaconUpgraded", logs: logs, sub: sub}, nil +} + +// WatchBeaconUpgraded is a free log subscription operation binding the contract event 0x1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e. +// +// Solidity: event BeaconUpgraded(address indexed beacon) +func (_Transparentupgradableproxy *TransparentupgradableproxyFilterer) WatchBeaconUpgraded(opts *bind.WatchOpts, sink chan<- *TransparentupgradableproxyBeaconUpgraded, beacon []common.Address) (event.Subscription, error) { + + var beaconRule []interface{} + for _, beaconItem := range beacon { + beaconRule = append(beaconRule, beaconItem) + } + + logs, sub, err := _Transparentupgradableproxy.contract.WatchLogs(opts, "BeaconUpgraded", beaconRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(TransparentupgradableproxyBeaconUpgraded) + if err := _Transparentupgradableproxy.contract.UnpackLog(event, "BeaconUpgraded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseBeaconUpgraded is a log parse operation binding the contract event 0x1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e. +// +// Solidity: event BeaconUpgraded(address indexed beacon) +func (_Transparentupgradableproxy *TransparentupgradableproxyFilterer) ParseBeaconUpgraded(log types.Log) (*TransparentupgradableproxyBeaconUpgraded, error) { + event := new(TransparentupgradableproxyBeaconUpgraded) + if err := _Transparentupgradableproxy.contract.UnpackLog(event, "BeaconUpgraded", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// TransparentupgradableproxyUpgradedIterator is returned from FilterUpgraded and is used to iterate over the raw logs and unpacked data for Upgraded events raised by the Transparentupgradableproxy contract. +type TransparentupgradableproxyUpgradedIterator struct { + Event *TransparentupgradableproxyUpgraded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *TransparentupgradableproxyUpgradedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(TransparentupgradableproxyUpgraded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(TransparentupgradableproxyUpgraded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *TransparentupgradableproxyUpgradedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TransparentupgradableproxyUpgradedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TransparentupgradableproxyUpgraded represents a Upgraded event raised by the Transparentupgradableproxy contract. +type TransparentupgradableproxyUpgraded struct { + Implementation common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterUpgraded is a free log retrieval operation binding the contract event 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b. +// +// Solidity: event Upgraded(address indexed implementation) +func (_Transparentupgradableproxy *TransparentupgradableproxyFilterer) FilterUpgraded(opts *bind.FilterOpts, implementation []common.Address) (*TransparentupgradableproxyUpgradedIterator, error) { + + var implementationRule []interface{} + for _, implementationItem := range implementation { + implementationRule = append(implementationRule, implementationItem) + } + + logs, sub, err := _Transparentupgradableproxy.contract.FilterLogs(opts, "Upgraded", implementationRule) + if err != nil { + return nil, err + } + return &TransparentupgradableproxyUpgradedIterator{contract: _Transparentupgradableproxy.contract, event: "Upgraded", logs: logs, sub: sub}, nil +} + +// WatchUpgraded is a free log subscription operation binding the contract event 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b. +// +// Solidity: event Upgraded(address indexed implementation) +func (_Transparentupgradableproxy *TransparentupgradableproxyFilterer) WatchUpgraded(opts *bind.WatchOpts, sink chan<- *TransparentupgradableproxyUpgraded, implementation []common.Address) (event.Subscription, error) { + + var implementationRule []interface{} + for _, implementationItem := range implementation { + implementationRule = append(implementationRule, implementationItem) + } + + logs, sub, err := _Transparentupgradableproxy.contract.WatchLogs(opts, "Upgraded", implementationRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(TransparentupgradableproxyUpgraded) + if err := _Transparentupgradableproxy.contract.UnpackLog(event, "Upgraded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseUpgraded is a log parse operation binding the contract event 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b. +// +// Solidity: event Upgraded(address indexed implementation) +func (_Transparentupgradableproxy *TransparentupgradableproxyFilterer) ParseUpgraded(log types.Log) (*TransparentupgradableproxyUpgraded, error) { + event := new(TransparentupgradableproxyUpgraded) + if err := _Transparentupgradableproxy.contract.UnpackLog(event, "Upgraded", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/tree/appendonlytree.go b/tree/appendonlytree.go index 15216dd8..376f4c05 100644 --- a/tree/appendonlytree.go +++ b/tree/appendonlytree.go @@ -104,6 +104,15 @@ func (t *AppendOnlyTree) GetRootByIndex(tx kv.Tx, index uint32) (common.Hash, er return t.getRootByIndex(tx, uint64(index)) } +func (t *AppendOnlyTree) GetIndexByRoot(ctx context.Context, root common.Hash) (uint32, error) { + tx, err := t.db.BeginRo(ctx) + if err != nil { + return 0, err + } + index, err := t.getIndexByRoot(tx, root) + return uint32(index), err +} + // GetLastIndexAndRoot returns the last index and root added to the tree func (t *AppendOnlyTree) GetLastIndexAndRoot(ctx context.Context) (uint32, common.Hash, error) { tx, err := t.db.BeginRo(ctx) diff --git a/tree/tree.go b/tree/tree.go index 6c21fb7d..30f846db 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -13,9 +13,10 @@ import ( ) const ( - defaultHeight uint8 = 32 - rootTableSufix = "-root" - rhtTableSufix = "-rht" + defaultHeight uint8 = 32 + rootTableSufix = "-root" + rhtTableSufix = "-rht" + indexTableSufix = "-index" ) var ( @@ -31,6 +32,7 @@ type Tree struct { db kv.RwDB rhtTable string rootTable string + indexTable string height uint8 zeroHashes []common.Hash } @@ -66,16 +68,20 @@ func (n *treeNode) UnmarshalBinary(data []byte) error { func AddTables(tableCfg map[string]kv.TableCfgItem, dbPrefix string) { rootTable := dbPrefix + rootTableSufix rhtTable := dbPrefix + rhtTableSufix + indexTable := dbPrefix + indexTableSufix tableCfg[rootTable] = kv.TableCfgItem{} tableCfg[rhtTable] = kv.TableCfgItem{} + tableCfg[indexTable] = kv.TableCfgItem{} } func newTree(db kv.RwDB, dbPrefix string) *Tree { rootTable := dbPrefix + rootTableSufix rhtTable := dbPrefix + rhtTableSufix + indexTable := dbPrefix + indexTableSufix t := &Tree{ rhtTable: rhtTable, rootTable: rootTable, + indexTable: indexTable, db: db, height: defaultHeight, zeroHashes: generateZeroHashes(defaultHeight), @@ -95,6 +101,17 @@ func (t *Tree) getRootByIndex(tx kv.Tx, index uint64) (common.Hash, error) { return common.BytesToHash(rootBytes), nil } +func (t *Tree) getIndexByRoot(tx kv.Tx, root common.Hash) (uint64, error) { + indexBytes, err := tx.GetOne(t.indexTable, root[:]) + if err != nil { + return 0, err + } + if indexBytes == nil { + return 0, ErrNotFound + } + return dbCommon.BytesToUint64(indexBytes), nil +} + func (t *Tree) getSiblings(tx kv.Tx, index uint32, root common.Hash) ( siblings []common.Hash, hasUsedZeroHashes bool, @@ -220,7 +237,10 @@ func (t *Tree) storeNodes(tx kv.RwTx, nodes []treeNode) error { } func (t *Tree) storeRoot(tx kv.RwTx, rootIndex uint64, root common.Hash) error { - return tx.Put(t.rootTable, dbCommon.Uint64ToBytes(rootIndex), root[:]) + if err := tx.Put(t.rootTable, dbCommon.Uint64ToBytes(rootIndex), root[:]); err != nil { + return err + } + return tx.Put(t.indexTable, root[:], dbCommon.Uint64ToBytes(rootIndex)) } // GetLastRoot returns the last processed root From 27aa35c82281d5ba44b8cd4f3b8799f135acbe9c Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 6 Aug 2024 10:39:25 +0200 Subject: [PATCH 39/49] fix duplicated key case --- l1bridge2infoindexsync/processor.go | 12 +++++++++--- l1bridge2infoindexsync/processor_test.go | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 l1bridge2infoindexsync/processor_test.go diff --git a/l1bridge2infoindexsync/processor.go b/l1bridge2infoindexsync/processor.go index 0e4fd5dc..91861da3 100644 --- a/l1bridge2infoindexsync/processor.go +++ b/l1bridge2infoindexsync/processor.go @@ -117,20 +117,22 @@ func (p *processor) updateLastProcessedBlockAndL1InfoTreeIndexWithTx(tx kv.RwTx, } func (p *processor) processUntilBlock(ctx context.Context, lastProcessedBlock uint64, relations []bridge2L1InfoRelation) error { - // Note that indexes could be repeated as the L1 Info tree update can be produced by a rollup and not mainnet. - // Hence if the index already exist, do not update as it's better to have the lowest index possible for the relation tx, err := p.db.BeginRw(ctx) if err != nil { return err } for _, relation := range relations { + if _, err := p.getL1InfoTreeIndexByBrdigeIndexWithTx(tx, relation.bridgeIndex); err != ErrNotFound { + // Note that indexes could be repeated as the L1 Info tree update can be produced by a rollup and not mainnet. + // Hence if the index already exist, do not update as it's better to have the lowest index possible for the relation + continue + } if err := tx.Put( relationTable, common.Uint32ToBytes(relation.bridgeIndex), common.Uint32ToBytes(relation.l1InfoTreeIndex), ); err != nil { - // TODO: if duplicated key, ignore error tx.Rollback() return err } @@ -171,6 +173,10 @@ func (p *processor) getL1InfoTreeIndexByBrdigeIndex(ctx context.Context, deposit } defer tx.Rollback() + return p.getL1InfoTreeIndexByBrdigeIndexWithTx(tx, depositCount) +} + +func (p *processor) getL1InfoTreeIndexByBrdigeIndexWithTx(tx kv.Tx, depositCount uint32) (uint32, error) { indexBytes, err := tx.GetOne(relationTable, common.Uint32ToBytes(depositCount)) if err != nil { return 0, err diff --git a/l1bridge2infoindexsync/processor_test.go b/l1bridge2infoindexsync/processor_test.go new file mode 100644 index 00000000..2389e9c6 --- /dev/null +++ b/l1bridge2infoindexsync/processor_test.go @@ -0,0 +1,22 @@ +package l1bridge2infoindexsync + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDuplicatedKey(t *testing.T) { + dbPath := t.TempDir() + p, err := newProcessor(dbPath) + require.NoError(t, err) + ctx := context.Background() + err = p.processUntilBlock(ctx, 5, []bridge2L1InfoRelation{{bridgeIndex: 2, l1InfoTreeIndex: 2}}) + require.NoError(t, err) + err = p.processUntilBlock(ctx, 7, []bridge2L1InfoRelation{{bridgeIndex: 2, l1InfoTreeIndex: 3}}) + require.NoError(t, err) + l1InfoTreeIndex, err := p.getL1InfoTreeIndexByBrdigeIndex(ctx, 2) + require.NoError(t, err) + require.Equal(t, uint32(2), l1InfoTreeIndex) +} From bd0036f6ac24b4b3cd55b3c3adb11155f842f3ea Mon Sep 17 00:00:00 2001 From: Arnau Bennassar Date: Tue, 13 Aug 2024 10:35:15 +0200 Subject: [PATCH 40/49] Feature/lastgersync (#33) * wip * wip * implementation done * separated setup logic from aggoracle e2e * pass e2e * Fix sync UTs * Feature/claim sponsor (#35) * implementation done, missing finish e2e test * pass E2E --- aggoracle/e2e_test.go | 179 +-------- bridgesync/bridgesync.go | 9 + bridgesync/downloader.go | 5 - bridgesync/e2e_test.go | 2 +- bridgesync/processor.go | 18 + claimsponsor/claimsponsor.go | 356 ++++++++++++++++++ claimsponsor/e2e_test.go | 103 +++++ claimsponsor/evmclaimsponsor.go | 181 +++++++++ l1bridge2infoindexsync/e2e_test.go | 2 +- l1infotreesync/l1infotreesync.go | 11 +- l1infotreesync/processor.go | 8 +- lastgersync/e2e_test.go | 72 ++++ lastgersync/evmdownloader.go | 166 ++++++++ lastgersync/lastgersync.go | 78 ++++ lastgersync/processor.go | 250 ++++++++++++ sync/evmdownloader.go | 60 ++- sync/evmdownloader_test.go | 42 +-- sync/evmdriver.go | 14 +- sync/evmdriver_test.go | 2 +- sync/mock_downloader_test.go | 20 +- test/Makefile | 6 +- test/helpers/aggoracle_e2e.go | 332 ++++++++++++++++ test/helpers/ethtxmanmock_e2e.go | 83 ++++ .../helpers/mock_ethtxmanager.go | 2 +- tree/appendonlytree.go | 14 +- tree/tree.go | 28 +- tree/updatabletree.go | 6 +- 27 files changed, 1772 insertions(+), 277 deletions(-) create mode 100644 claimsponsor/claimsponsor.go create mode 100644 claimsponsor/e2e_test.go create mode 100644 claimsponsor/evmclaimsponsor.go create mode 100644 lastgersync/e2e_test.go create mode 100644 lastgersync/evmdownloader.go create mode 100644 lastgersync/lastgersync.go create mode 100644 lastgersync/processor.go create mode 100644 test/helpers/aggoracle_e2e.go create mode 100644 test/helpers/ethtxmanmock_e2e.go rename aggoracle/mock_ethtxmanager_test.go => test/helpers/mock_ethtxmanager.go (99%) diff --git a/aggoracle/e2e_test.go b/aggoracle/e2e_test.go index 600021e0..1eae0727 100644 --- a/aggoracle/e2e_test.go +++ b/aggoracle/e2e_test.go @@ -1,196 +1,23 @@ package aggoracle_test import ( - "context" - "errors" "fmt" - "math/big" "strconv" "testing" "time" gerContractL1 "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/globalexitrootnopush0" - gerContractEVMChain "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/pessimisticglobalexitrootnopush0" "github.com/0xPolygon/cdk/aggoracle" - "github.com/0xPolygon/cdk/aggoracle/chaingersender" - "github.com/0xPolygon/cdk/etherman" - "github.com/0xPolygon/cdk/l1infotreesync" - "github.com/0xPolygon/cdk/log" - "github.com/0xPolygon/cdk/reorgdetector" - ethtxmanager "github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager" - "github.com/ethereum/go-ethereum" + "github.com/0xPolygon/cdk/test/helpers" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient/simulated" - mock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func TestEVM(t *testing.T) { - ctx := context.Background() - l1Client, syncer, gerL1Contract, authL1 := commonSetup(t) - sender := evmSetup(t) - oracle, err := aggoracle.New(sender, l1Client.Client(), syncer, etherman.LatestBlock, time.Millisecond) - require.NoError(t, err) - go oracle.Start(ctx) - - runTest(t, gerL1Contract, sender, l1Client, authL1) -} - -func commonSetup(t *testing.T) ( - *simulated.Backend, - *l1infotreesync.L1InfoTreeSync, - *gerContractL1.Globalexitrootnopush0, - *bind.TransactOpts, -) { - // Config and spin up - ctx := context.Background() - // Simulated L1 - privateKeyL1, err := crypto.GenerateKey() - require.NoError(t, err) - authL1, err := bind.NewKeyedTransactorWithChainID(privateKeyL1, big.NewInt(1337)) - require.NoError(t, err) - l1Client, gerL1Addr, gerL1Contract, err := newSimulatedL1(authL1) - require.NoError(t, err) - // Reorg detector - dbPathReorgDetector := t.TempDir() - reorg, err := reorgdetector.New(ctx, l1Client.Client(), dbPathReorgDetector) - require.NoError(t, err) - // Syncer - dbPathSyncer := t.TempDir() - syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, common.Address{}, 10, etherman.LatestBlock, reorg, l1Client.Client(), time.Millisecond, 0, 100*time.Millisecond, 3) - require.NoError(t, err) - go syncer.Start(ctx) - - return l1Client, syncer, gerL1Contract, authL1 -} - -func evmSetup(t *testing.T) aggoracle.ChainSender { - privateKeyL2, err := crypto.GenerateKey() - require.NoError(t, err) - authL2, err := bind.NewKeyedTransactorWithChainID(privateKeyL2, big.NewInt(1337)) - require.NoError(t, err) - l2Client, gerL2Addr, _, err := newSimulatedEVMAggSovereignChain(authL2) - require.NoError(t, err) - ethTxManMock := aggoracle.NewEthTxManagerMock(t) - // id, err := c.ethTxMan.Add(ctx, &c.gerAddr, nil, big.NewInt(0), tx.Data(), c.gasOffset, nil) - ethTxManMock.On("Add", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Run(func(args mock.Arguments) { - ctx := context.Background() - nonce, err := l2Client.Client().PendingNonceAt(ctx, authL2.From) - if err != nil { - log.Error(err) - return - } - gas, err := l2Client.Client().EstimateGas(ctx, ethereum.CallMsg{ - From: authL2.From, - To: args.Get(1).(*common.Address), - Value: big.NewInt(0), - Data: args.Get(4).([]byte), - }) - if err != nil { - log.Error(err) - res, err := l2Client.Client().CallContract(ctx, ethereum.CallMsg{ - From: authL2.From, - To: args.Get(1).(*common.Address), - Value: big.NewInt(0), - Data: args.Get(4).([]byte), - }, nil) - log.Debugf("contract call: %s", res) - if err != nil { - log.Error(err) - } - return - } - price, err := l2Client.Client().SuggestGasPrice(ctx) - if err != nil { - log.Error(err) - } - tx := types.NewTx(&types.LegacyTx{ - To: args.Get(1).(*common.Address), - Nonce: nonce, - Value: big.NewInt(0), - Data: args.Get(4).([]byte), - Gas: gas, - GasPrice: price, - }) - tx.Gas() - signedTx, err := authL2.Signer(authL2.From, tx) - if err != nil { - log.Error(err) - return - } - err = l2Client.Client().SendTransaction(ctx, signedTx) - if err != nil { - log.Error(err) - return - } - l2Client.Commit() - }). - Return(common.Hash{}, nil) - // res, err := c.ethTxMan.Result(ctx, id) - ethTxManMock.On("Result", mock.Anything, mock.Anything). - Return(ethtxmanager.MonitoredTxResult{Status: ethtxmanager.MonitoredTxStatusMined}, nil) - sender, err := chaingersender.NewEVMChainGERSender(gerL2Addr, authL2.From, l2Client.Client(), ethTxManMock, 0, time.Millisecond*50) - require.NoError(t, err) - - return sender -} - -func newSimulatedL1(auth *bind.TransactOpts) ( - client *simulated.Backend, - gerAddr common.Address, - gerContract *gerContractL1.Globalexitrootnopush0, - err error, -) { - balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd - address := auth.From - genesisAlloc := map[common.Address]types.Account{ - address: { - Balance: balance, - }, - } - blockGasLimit := uint64(999999999999999999) //nolint:gomnd - client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) - - gerAddr, _, gerContract, err = gerContractL1.DeployGlobalexitrootnopush0(auth, client.Client(), auth.From, auth.From) - - client.Commit() - return -} - -func newSimulatedEVMAggSovereignChain(auth *bind.TransactOpts) ( - client *simulated.Backend, - gerAddr common.Address, - gerContract *gerContractEVMChain.Pessimisticglobalexitrootnopush0, - err error, -) { - balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd - address := auth.From - genesisAlloc := map[common.Address]types.Account{ - address: { - Balance: balance, - }, - } - blockGasLimit := uint64(999999999999999999) //nolint:gomnd - client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) - - gerAddr, _, gerContract, err = gerContractEVMChain.DeployPessimisticglobalexitrootnopush0(auth, client.Client(), auth.From) - if err != nil { - return - } - client.Commit() - - _GLOBAL_EXIT_ROOT_SETTER_ROLE := common.HexToHash("0x7b95520991dfda409891be0afa2635b63540f92ee996fda0bf695a166e5c5176") - _, err = gerContract.GrantRole(auth, _GLOBAL_EXIT_ROOT_SETTER_ROLE, auth.From) - client.Commit() - hasRole, _ := gerContract.HasRole(&bind.CallOpts{Pending: false}, _GLOBAL_EXIT_ROOT_SETTER_ROLE, auth.From) - if !hasRole { - err = errors.New("failed to set role") - } - return + env := helpers.SetupAggoracleWithEVMChain(t) + runTest(t, env.GERL1Contract, env.AggOracleSender, env.L1Client, env.AuthL1) } func runTest( diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index 3223a095..11ca7ed6 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -37,6 +37,7 @@ func NewL1( rd sync.ReorgDetector, ethClient EthClienter, initialBlock uint64, + waitForNewBlocksPeriod time.Duration, ) (*BridgeSync, error) { return new( ctx, @@ -49,6 +50,7 @@ func NewL1( initialBlock, dbPrefixL1, reorgDetectorIDL1, + waitForNewBlocksPeriod, ) } @@ -62,6 +64,7 @@ func NewL2( rd sync.ReorgDetector, ethClient EthClienter, initialBlock uint64, + waitForNewBlocksPeriod time.Duration, ) (*BridgeSync, error) { return new( ctx, @@ -74,6 +77,7 @@ func NewL2( initialBlock, dbPrefixL1, reorgDetectorIDL1, + waitForNewBlocksPeriod, ) } @@ -87,6 +91,7 @@ func new( ethClient EthClienter, initialBlock uint64, dbPrefix, reorgDetectorID string, + waitForNewBlocksPeriod time.Duration, ) (*BridgeSync, error) { processor, err := newProcessor(ctx, dbPath, dbPrefix) if err != nil { @@ -152,3 +157,7 @@ func (s *BridgeSync) GetBridgeIndexByRoot(ctx context.Context, root common.Hash) func (s *BridgeSync) GetClaimsAndBridges(ctx context.Context, fromBlock, toBlock uint64) ([]Event, error) { return s.processor.GetClaimsAndBridges(ctx, fromBlock, toBlock) } + +func (s *BridgeSync) GetProof(ctx context.Context, depositCount uint32, localExitRoot common.Hash) ([32]common.Hash, error) { + return s.processor.exitTree.GetProof(ctx, depositCount, localExitRoot) +} diff --git a/bridgesync/downloader.go b/bridgesync/downloader.go index d2ef4e7c..fdd60d1f 100644 --- a/bridgesync/downloader.go +++ b/bridgesync/downloader.go @@ -3,7 +3,6 @@ package bridgesync import ( "fmt" "math/big" - "time" "github.com/0xPolygon/cdk-contracts-tooling/contracts/etrog/polygonzkevmbridge" "github.com/0xPolygon/cdk-contracts-tooling/contracts/etrog/polygonzkevmbridgev2" @@ -15,10 +14,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -const ( - waitForNewBlocksPeriod = time.Millisecond * 100 -) - var ( bridgeEventSignature = crypto.Keccak256Hash([]byte("BridgeEvent(uint8,uint32,address,uint32,address,uint256,bytes,uint32)")) claimEventSignature = crypto.Keccak256Hash([]byte("ClaimEvent(uint256,uint32,address,address,uint256)")) diff --git a/bridgesync/e2e_test.go b/bridgesync/e2e_test.go index ccb7cba0..02b5b2a7 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -54,7 +54,7 @@ func TestBridgeEventE2E(t *testing.T) { rd, err := reorgdetector.New(ctx, client.Client(), dbPathReorg) go rd.Start(ctx) - syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, bridgeAddr, 10, etherman.LatestBlock, rd, client.Client(), 0) + syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, bridgeAddr, 10, etherman.LatestBlock, rd, client.Client(), 0, time.Millisecond*10) require.NoError(t, err) go syncer.Start(ctx) diff --git a/bridgesync/processor.go b/bridgesync/processor.go index 77938a69..1fdf05f4 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -296,3 +296,21 @@ func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error blockNumBytes := dbCommon.Uint64ToBytes(blockNum) return tx.Put(p.lastBlockTable, lastBlokcKey, blockNumBytes) } + +func GenerateGlobalIndex(mainnetFlag bool, rollupIndex uint, localExitRootIndex uint32) *big.Int { + var ( + globalIndexBytes []byte + buf [4]byte + ) + if mainnetFlag { + globalIndexBytes = append(globalIndexBytes, big.NewInt(1).Bytes()...) + ri := big.NewInt(0).FillBytes(buf[:]) + globalIndexBytes = append(globalIndexBytes, ri...) + } else { + ri := big.NewInt(0).SetUint64(uint64(rollupIndex)).FillBytes(buf[:]) + globalIndexBytes = append(globalIndexBytes, ri...) + } + leri := big.NewInt(0).SetUint64(uint64(localExitRootIndex)).FillBytes(buf[:]) + globalIndexBytes = append(globalIndexBytes, leri...) + return big.NewInt(0).SetBytes(globalIndexBytes) +} diff --git a/claimsponsor/claimsponsor.go b/claimsponsor/claimsponsor.go new file mode 100644 index 00000000..4b70f8e7 --- /dev/null +++ b/claimsponsor/claimsponsor.go @@ -0,0 +1,356 @@ +package claimsponsor + +import ( + "context" + "encoding/json" + "errors" + "math" + "math/big" + "time" + + dbCommon "github.com/0xPolygon/cdk/common" + "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/sync" + "github.com/ethereum/go-ethereum/common" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/kv/iter" + "github.com/ledgerwatch/erigon-lib/kv/mdbx" +) + +type ClaimStatus string + +const ( + PendingClaimStatus = "pending" + WIPStatus = "work in progress" + SuccessClaimStatus = "success" + FailedClaimStatus = "failed" + + claimTable = "claimsponsor-tx" + queueTable = "claimsponsor-queue" +) + +var ( + ErrInvalidClaim = errors.New("invalid claim") + ErrNotFound = errors.New("not found") +) + +// Claim representation of a claim event +type Claim struct { + LeafType uint8 + ProofLocalExitRoot [32]common.Hash + ProofRollupExitRoot [32]common.Hash + GlobalIndex *big.Int + MainnetExitRoot common.Hash + RollupExitRoot common.Hash + OriginNetwork uint32 + OriginTokenAddress common.Address + DestinationNetwork uint32 + DestinationAddress common.Address + Amount *big.Int + Metadata []byte + + Status ClaimStatus + TxID string +} + +func (c *Claim) Key() []byte { + return c.GlobalIndex.Bytes() +} + +type ClaimSender interface { + checkClaim(ctx context.Context, claim *Claim) error + sendClaim(ctx context.Context, claim *Claim) (string, error) + claimStatus(ctx context.Context, id string) (ClaimStatus, error) +} + +type ClaimSponsor struct { + db kv.RwDB + sender ClaimSender + rh *sync.RetryHandler + waitTxToBeMinedPeriod time.Duration + waitOnEmptyQueue time.Duration +} + +func newClaimSponsor( + dbPath string, + sender ClaimSender, + retryAfterErrorPeriod time.Duration, + maxRetryAttemptsAfterError int, + waitTxToBeMinedPeriod time.Duration, + waitOnEmptyQueue time.Duration, +) (*ClaimSponsor, error) { + tableCfgFunc := func(defaultBuckets kv.TableCfg) kv.TableCfg { + cfg := kv.TableCfg{ + claimTable: {}, + queueTable: {}, + } + return cfg + } + db, err := mdbx.NewMDBX(nil). + Path(dbPath). + WithTableCfg(tableCfgFunc). + Open() + if err != nil { + return nil, err + } + rh := &sync.RetryHandler{ + MaxRetryAttemptsAfterError: maxRetryAttemptsAfterError, + RetryAfterErrorPeriod: retryAfterErrorPeriod, + } + return &ClaimSponsor{ + db: db, + sender: sender, + rh: rh, + waitTxToBeMinedPeriod: waitTxToBeMinedPeriod, + waitOnEmptyQueue: waitOnEmptyQueue, + }, nil +} + +func (c *ClaimSponsor) Start(ctx context.Context) { + var ( + attempts int + err error + ) + for { + if err != nil { + attempts++ + c.rh.Handle("claimsponsor main loop", attempts) + } + tx, err2 := c.db.BeginRw(ctx) + if err2 != nil { + err = err2 + log.Errorf("error calling BeginRw: %v", err) + continue + } + queueIndex, globalIndex, err2 := getFirstQueueIndex(tx) + if err2 != nil { + err = err2 + tx.Rollback() + if err == ErrNotFound { + log.Debugf("queue is empty") + err = nil + time.Sleep(c.waitOnEmptyQueue) + continue + } + log.Errorf("error calling getFirstQueueIndex: %v", err) + continue + } + claim, err2 := getClaim(tx, globalIndex) + if err2 != nil { + err = err2 + tx.Rollback() + log.Errorf("error calling getClaim with globalIndex %s: %v", globalIndex.String(), err) + continue + } + if claim.TxID == "" { + txID, err2 := c.sender.sendClaim(ctx, claim) + if err2 != nil { + err = err2 + tx.Rollback() + log.Errorf("error calling sendClaim with globalIndex %s: %v", globalIndex.String(), err) + continue + } + claim.TxID = txID + claim.Status = WIPStatus + err2 = putClaim(tx, claim) + if err2 != nil { + err = err2 + tx.Rollback() + log.Errorf("error calling putClaim with globalIndex %s: %v", globalIndex.String(), err) + continue + } + } + err2 = tx.Commit() + if err2 != nil { + err = err2 + log.Errorf("error calling tx.Commit after putting claim: %v", err) + continue + } + + log.Infof("waiting for tx %s with global index %s to success or fail", claim.TxID, globalIndex.String()) + status, err2 := c.waitTxToBeSuccessOrFail(ctx, claim.TxID) + if err2 != nil { + err = err2 + log.Errorf("error calling waitTxToBeMinedOrFail for tx %s: %v", claim.TxID, err) + continue + } + log.Infof("tx %s with global index %s concluded with status: %s", claim.TxID, globalIndex.String(), status) + tx, err2 = c.db.BeginRw(ctx) + if err2 != nil { + err = err2 + log.Errorf("error calling BeginRw: %v", err) + continue + } + claim.Status = status + err2 = putClaim(tx, claim) + if err2 != nil { + err = err2 + tx.Rollback() + log.Errorf("error calling putClaim with globalIndex %s: %v", globalIndex.String(), err) + continue + } + err2 = tx.Delete(queueTable, dbCommon.Uint64ToBytes(queueIndex)) + if err2 != nil { + err = err2 + tx.Rollback() + log.Errorf("error calling delete on the queue table with index %d: %v", queueIndex, err) + continue + } + err2 = tx.Commit() + if err2 != nil { + err = err2 + log.Errorf("error calling tx.Commit after putting claim: %v", err) + continue + } + + attempts = 0 + log.Error("wtf: ", err) + } +} + +func (c *ClaimSponsor) waitTxToBeSuccessOrFail(ctx context.Context, txID string) (ClaimStatus, error) { + t := time.NewTicker(c.waitTxToBeMinedPeriod) + for { + select { + case <-ctx.Done(): + return "", errors.New("context cancelled") + case <-t.C: + status, err := c.sender.claimStatus(ctx, txID) + if err != nil { + return "", err + } + if status == FailedClaimStatus || status == SuccessClaimStatus { + return status, nil + } + } + } +} + +func (c *ClaimSponsor) AddClaimToQueue(ctx context.Context, claim *Claim) error { + if claim.GlobalIndex == nil { + return ErrInvalidClaim + } + claim.Status = PendingClaimStatus + tx, err := c.db.BeginRw(ctx) + if err != nil { + return err + } + + _, err = getClaim(tx, claim.GlobalIndex) + if err != ErrNotFound { + if err != nil { + tx.Rollback() + return err + } else { + tx.Rollback() + return errors.New("claim already added") + } + } + + err = putClaim(tx, claim) + if err != nil { + tx.Rollback() + return err + } + + var queuePosition uint64 + lastQueuePosition, _, err := getLastQueueIndex(tx) + if err == ErrNotFound { + queuePosition = 0 + } else if err != nil { + tx.Rollback() + return err + } else { + queuePosition = lastQueuePosition + 1 + } + err = tx.Put(queueTable, dbCommon.Uint64ToBytes(queuePosition), claim.Key()) + if err != nil { + tx.Rollback() + return err + } + + return tx.Commit() +} + +func putClaim(tx kv.RwTx, claim *Claim) error { + value, err := json.Marshal(claim) + if err != nil { + return err + } + return tx.Put(claimTable, claim.Key(), value) +} + +func (c *ClaimSponsor) getClaimByQueueIndex(ctx context.Context, queueIndex uint64) (*Claim, error) { + tx, err := c.db.BeginRo(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + + globalIndexBytes, err := tx.GetOne(queueTable, dbCommon.Uint64ToBytes(queueIndex)) + if err != nil { + return nil, err + } + if globalIndexBytes == nil { + return nil, ErrNotFound + } + + return getClaim(tx, new(big.Int).SetBytes(globalIndexBytes)) +} + +func getLastQueueIndex(tx kv.Tx) (uint64, *big.Int, error) { + iter, err := tx.RangeDescend( + queueTable, + dbCommon.Uint64ToBytes(math.MaxUint64), + dbCommon.Uint64ToBytes(0), 1, + ) + if err != nil { + return 0, nil, err + } + return getIndex(iter) +} + +func getFirstQueueIndex(tx kv.Tx) (uint64, *big.Int, error) { + iter, err := tx.RangeAscend( + queueTable, + dbCommon.Uint64ToBytes(0), + nil, 1, + ) + if err != nil { + return 0, nil, err + } + return getIndex(iter) +} + +func getIndex(iter iter.KV) (uint64, *big.Int, error) { + k, v, err := iter.Next() + if err != nil { + return 0, nil, err + } + if k == nil { + return 0, nil, ErrNotFound + } + globalIndex := new(big.Int).SetBytes(v) + return dbCommon.BytesToUint64(k), globalIndex, nil +} + +func (c *ClaimSponsor) GetClaim(ctx context.Context, globalIndex *big.Int) (*Claim, error) { + tx, err := c.db.BeginRo(ctx) + if err != nil { + return nil, err + } + return getClaim(tx, globalIndex) +} + +func getClaim(tx kv.Tx, globalIndex *big.Int) (*Claim, error) { + claimBytes, err := tx.GetOne(claimTable, globalIndex.Bytes()) + if err != nil { + return nil, err + } + if claimBytes == nil { + return nil, ErrNotFound + } + claim := &Claim{} + err = json.Unmarshal(claimBytes, claim) + return claim, err +} diff --git a/claimsponsor/e2e_test.go b/claimsponsor/e2e_test.go new file mode 100644 index 00000000..b2683be9 --- /dev/null +++ b/claimsponsor/e2e_test.go @@ -0,0 +1,103 @@ +package claimsponsor_test + +import ( + "context" + "errors" + "fmt" + "math/big" + "testing" + "time" + + "github.com/0xPolygon/cdk/bridgesync" + "github.com/0xPolygon/cdk/claimsponsor" + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/test/helpers" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestE2EL1toEVML2(t *testing.T) { + // start other needed components + ctx := context.Background() + env := helpers.SetupAggoracleWithEVMChain(t) + dbPathBridgeSyncL1 := t.TempDir() + bridgeSyncL1, err := bridgesync.NewL1(ctx, dbPathBridgeSyncL1, env.BridgeL1Addr, 10, etherman.LatestBlock, env.ReorgDetector, env.L1Client.Client(), 0, time.Millisecond*10) + require.NoError(t, err) + go bridgeSyncL1.Start(ctx) + + // start claim sponsor + dbPathClaimSponsor := t.TempDir() + claimer, err := claimsponsor.NewEVMClaimSponsor( + dbPathClaimSponsor, + env.L2Client.Client(), + env.BridgeL2Addr, + env.AuthL2.From, + 200_000, + 0, + env.EthTxManMockL2, + 0, 0, time.Millisecond*10, time.Millisecond*10, + ) + require.NoError(t, err) + go claimer.Start(ctx) + + // test + for i := 0; i < 10; i++ { + // Send bridges to L2, wait for GER to be injected on L2 + amount := big.NewInt(int64(i) + 1) + env.AuthL1.Value = amount + _, err := env.BridgeL1Contract.BridgeAsset(env.AuthL1, env.NetworkIDL2, env.AuthL2.From, amount, common.Address{}, true, nil) + env.L1Client.Commit() + time.Sleep(time.Millisecond * 50) + expectedGER, err := env.GERL1Contract.GetLastGlobalExitRoot(&bind.CallOpts{Pending: false}) + require.NoError(t, err) + isInjected, err := env.AggOracleSender.IsGERAlreadyInjected(expectedGER) + require.NoError(t, err) + require.True(t, isInjected, fmt.Sprintf("iteration %d, GER: %s", i, common.Bytes2Hex(expectedGER[:]))) + + // Build MP using bridgeSyncL1 & env.L1InfoTreeSync + info, err := env.L1InfoTreeSync.GetInfoByIndex(ctx, uint32(i)) + require.NoError(t, err) + localProof, err := bridgeSyncL1.GetProof(ctx, uint32(i), info.MainnetExitRoot) + require.NoError(t, err) + rollupProof, err := env.L1InfoTreeSync.GetRollupExitTreeMerkleProof(ctx, 0, common.Hash{}) + + // Request to sponsor claim + globalIndex := bridgesync.GenerateGlobalIndex(true, 0, uint32(i)) + err = claimer.AddClaimToQueue(ctx, &claimsponsor.Claim{ + LeafType: 0, + ProofLocalExitRoot: localProof, + ProofRollupExitRoot: rollupProof, + GlobalIndex: globalIndex, + MainnetExitRoot: info.MainnetExitRoot, + RollupExitRoot: info.RollupExitRoot, + OriginNetwork: 0, + OriginTokenAddress: common.Address{}, + DestinationNetwork: env.NetworkIDL2, + DestinationAddress: env.AuthL2.From, + Amount: amount, + Metadata: nil, + }) + require.NoError(t, err) + + // Wait until success + succeed := false + for i := 0; i < 10; i++ { + claim, err := claimer.GetClaim(ctx, globalIndex) + require.NoError(t, err) + if claim.Status == claimsponsor.FailedClaimStatus { + require.NoError(t, errors.New("claim failed")) + } else if claim.Status == claimsponsor.SuccessClaimStatus { + succeed = true + break + } + time.Sleep(50 * time.Millisecond) + } + require.True(t, succeed) + + // Check on contract that is claimed + isClaimed, err := env.BridgeL2Contract.IsClaimed(&bind.CallOpts{Pending: false}, uint32(i), 0) + require.NoError(t, err) + require.True(t, isClaimed) + } +} diff --git a/claimsponsor/evmclaimsponsor.go b/claimsponsor/evmclaimsponsor.go new file mode 100644 index 00000000..fe01d2b5 --- /dev/null +++ b/claimsponsor/evmclaimsponsor.go @@ -0,0 +1,181 @@ +package claimsponsor + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/etrog/polygonzkevmbridgev2" + "github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +const ( + // LeafTypeAsset represents a bridge asset + LeafTypeAsset uint8 = 0 + // LeafTypeMessage represents a bridge message + LeafTypeMessage uint8 = 1 + gasTooHighErrTemplate = "Claim tx estimated to consume more gas than the maximum allowed by the service. Estimated %d, maximum allowed: %d" +) + +type EthClienter interface { + ethereum.GasEstimator + bind.ContractBackend +} + +type EthTxManager interface { + Remove(ctx context.Context, id common.Hash) error + ResultsByStatus(ctx context.Context, statuses []ethtxmanager.MonitoredTxStatus) ([]ethtxmanager.MonitoredTxResult, error) + Result(ctx context.Context, id common.Hash) (ethtxmanager.MonitoredTxResult, error) + Add(ctx context.Context, to *common.Address, forcedNonce *uint64, value *big.Int, data []byte, gasOffset uint64, sidecar *types.BlobTxSidecar) (common.Hash, error) +} + +type EVMClaimSponsor struct { + *ClaimSponsor + l2Client EthClienter + bridgeABI *abi.ABI + bridgeAddr common.Address + bridgeContract *polygonzkevmbridgev2.Polygonzkevmbridgev2 + ethTxManager EthTxManager + sender common.Address + gasOffest uint64 + maxGas uint64 +} + +func NewEVMClaimSponsor( + dbPath string, + l2Client EthClienter, + bridge common.Address, + sender common.Address, + maxGas, gasOffset uint64, + ethTxManager EthTxManager, + retryAfterErrorPeriod time.Duration, + maxRetryAttemptsAfterError int, + waitTxToBeMinedPeriod time.Duration, + waitOnEmptyQueue time.Duration, +) (*EVMClaimSponsor, error) { + contract, err := polygonzkevmbridgev2.NewPolygonzkevmbridgev2(bridge, l2Client) + if err != nil { + return nil, err + } + abi, err := polygonzkevmbridgev2.Polygonzkevmbridgev2MetaData.GetAbi() + if err != nil { + return nil, err + } + evmSponsor := &EVMClaimSponsor{ + l2Client: l2Client, + bridgeABI: abi, + bridgeAddr: bridge, + bridgeContract: contract, + sender: sender, + gasOffest: gasOffset, + maxGas: maxGas, + ethTxManager: ethTxManager, + } + baseSponsor, err := newClaimSponsor( + dbPath, + evmSponsor, + retryAfterErrorPeriod, + maxRetryAttemptsAfterError, + waitTxToBeMinedPeriod, + waitOnEmptyQueue, + ) + if err != nil { + return nil, err + } + evmSponsor.ClaimSponsor = baseSponsor + return evmSponsor, nil +} + +func (c *EVMClaimSponsor) checkClaim(ctx context.Context, claim *Claim) error { + data, err := c.buildClaimTxData(claim) + if err != nil { + return err + } + gas, err := c.l2Client.EstimateGas(ctx, ethereum.CallMsg{ + From: c.sender, + To: &c.bridgeAddr, + Data: data, + }) + if err != nil { + return err + } + if gas > c.maxGas { + return fmt.Errorf(gasTooHighErrTemplate, gas, c.maxGas) + } + return nil +} + +func (c *EVMClaimSponsor) sendClaim(ctx context.Context, claim *Claim) (string, error) { + data, err := c.buildClaimTxData(claim) + if err != nil { + return "", err + } + id, err := c.ethTxManager.Add(ctx, &c.bridgeAddr, nil, big.NewInt(0), data, c.gasOffest, nil) + if err != nil { + return "", err + } + return id.Hex(), nil +} + +func (c *EVMClaimSponsor) claimStatus(ctx context.Context, id string) (ClaimStatus, error) { + res, err := c.ethTxManager.Result(ctx, common.HexToHash(id)) + if err != nil { + return "", err + } + switch res.Status { + case ethtxmanager.MonitoredTxStatusCreated, + ethtxmanager.MonitoredTxStatusSent: + return WIPStatus, nil + case ethtxmanager.MonitoredTxStatusFailed: + return FailedClaimStatus, nil + case ethtxmanager.MonitoredTxStatusMined, + ethtxmanager.MonitoredTxStatusSafe, + ethtxmanager.MonitoredTxStatusFinalized: + return SuccessClaimStatus, nil + default: + return "", fmt.Errorf("unexpected tx status: %v", res.Status) + } +} + +func (c *EVMClaimSponsor) buildClaimTxData(claim *Claim) ([]byte, error) { + switch claim.LeafType { + case LeafTypeAsset: + return c.bridgeABI.Pack( + "claimAsset", + claim.ProofLocalExitRoot, // bytes32[32] smtProofLocalExitRoot + claim.ProofRollupExitRoot, // bytes32[32] smtProofRollupExitRoot + claim.GlobalIndex, // uint256 globalIndex + claim.MainnetExitRoot, // bytes32 mainnetExitRoot + claim.RollupExitRoot, // bytes32 rollupExitRoot + claim.OriginNetwork, // uint32 originNetwork + claim.OriginTokenAddress, // address originTokenAddress, + claim.DestinationNetwork, // uint32 destinationNetwork + claim.DestinationAddress, // address destinationAddress + claim.Amount, // uint256 amount + claim.Metadata, // bytes metadata + ) + case LeafTypeMessage: + return c.bridgeABI.Pack( + "claimMessage", + claim.ProofLocalExitRoot, // bytes32[32] smtProofLocalExitRoot + claim.ProofRollupExitRoot, // bytes32[32] smtProofRollupExitRoot + claim.GlobalIndex, // uint256 globalIndex + claim.MainnetExitRoot, // bytes32 mainnetExitRoot + claim.RollupExitRoot, // bytes32 rollupExitRoot + claim.OriginNetwork, // uint32 originNetwork + claim.OriginTokenAddress, // address originTokenAddress, + claim.DestinationNetwork, // uint32 destinationNetwork + claim.DestinationAddress, // address destinationAddress + claim.Amount, // uint256 amount + claim.Metadata, // bytes metadata + ) + default: + return nil, fmt.Errorf("unexpected leaf type %d", claim.LeafType) + } +} diff --git a/l1bridge2infoindexsync/e2e_test.go b/l1bridge2infoindexsync/e2e_test.go index 71f5a008..561a7b0c 100644 --- a/l1bridge2infoindexsync/e2e_test.go +++ b/l1bridge2infoindexsync/e2e_test.go @@ -135,7 +135,7 @@ func TestE2E(t *testing.T) { rd, err := reorgdetector.New(ctx, client.Client(), dbPathReorg) go rd.Start(ctx) - bridgeSync, err := bridgesync.NewL1(ctx, dbPathBridgeSync, bridgeAddr, 10, etherman.LatestBlock, rd, client.Client(), 0) + bridgeSync, err := bridgesync.NewL1(ctx, dbPathBridgeSync, bridgeAddr, 10, etherman.LatestBlock, rd, client.Client(), 0, time.Millisecond*10) require.NoError(t, err) go bridgeSync.Start(ctx) diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index 32db8751..0063f658 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -7,6 +7,7 @@ import ( "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/sync" + "github.com/0xPolygon/cdk/tree" "github.com/ethereum/go-ethereum/common" ) @@ -104,10 +105,18 @@ func (s *L1InfoTreeSync) Start(ctx context.Context) { } // GetL1InfoTreeMerkleProof creates a merkle proof for the L1 Info tree -func (s *L1InfoTreeSync) GetL1InfoTreeMerkleProof(ctx context.Context, index uint32) ([]common.Hash, common.Hash, error) { +func (s *L1InfoTreeSync) GetL1InfoTreeMerkleProof(ctx context.Context, index uint32) ([32]common.Hash, common.Hash, error) { return s.processor.GetL1InfoTreeMerkleProof(ctx, index) } +// GetRollupExitTreeMerkleProof creates a merkle proof for the rollup exit tree +func (s *L1InfoTreeSync) GetRollupExitTreeMerkleProof(ctx context.Context, networkID uint32, root common.Hash) ([32]common.Hash, error) { + if networkID == 0 { + return tree.EmptyProof, nil + } + return s.processor.rollupExitTree.GetProof(ctx, networkID-1, root) +} + // 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 func (s *L1InfoTreeSync) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 1d258889..1f02038c 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -153,21 +153,21 @@ func newProcessor(ctx context.Context, dbPath string) (*processor, error) { } // GetL1InfoTreeMerkleProof creates a merkle proof for the L1 Info tree -func (p *processor) GetL1InfoTreeMerkleProof(ctx context.Context, index uint32) ([]ethCommon.Hash, ethCommon.Hash, error) { +func (p *processor) GetL1InfoTreeMerkleProof(ctx context.Context, index uint32) ([32]ethCommon.Hash, ethCommon.Hash, error) { tx, err := p.db.BeginRo(ctx) if err != nil { - return nil, ethCommon.Hash{}, err + return [32]ethCommon.Hash{}, ethCommon.Hash{}, err } defer tx.Rollback() root, err := p.l1InfoTree.GetRootByIndex(tx, index) if err != nil { - return nil, ethCommon.Hash{}, err + return [32]ethCommon.Hash{}, ethCommon.Hash{}, err } proof, err := p.l1InfoTree.GetProof(ctx, index, root) if err != nil { - return nil, ethCommon.Hash{}, err + return [32]ethCommon.Hash{}, ethCommon.Hash{}, err } // TODO: check if we need to return root or wat diff --git a/lastgersync/e2e_test.go b/lastgersync/e2e_test.go new file mode 100644 index 00000000..386be88c --- /dev/null +++ b/lastgersync/e2e_test.go @@ -0,0 +1,72 @@ +package lastgersync_test + +import ( + "context" + "fmt" + "math/big" + "strconv" + "testing" + "time" + + "github.com/0xPolygon/cdk/lastgersync" + "github.com/0xPolygon/cdk/test/helpers" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" +) + +func TestE2E(t *testing.T) { + ctx := context.Background() + env := helpers.SetupAggoracleWithEVMChain(t) + dbPathSyncer := t.TempDir() + syncer, err := lastgersync.New( + ctx, + dbPathSyncer, + env.ReorgDetector, + env.L2Client.Client(), + env.GERL2Addr, + env.L1InfoTreeSync, + 0, + 0, + big.NewInt(int64(rpc.LatestBlockNumber)), + time.Millisecond*30, + 10, + ) + require.NoError(t, err) + go syncer.Start(ctx) + + for i := 0; i < 10; i++ { + // Update GER on L1 + _, err := env.GERL1Contract.UpdateExitRoot(env.AuthL1, common.HexToHash(strconv.Itoa(i))) + require.NoError(t, err) + env.L1Client.Commit() + time.Sleep(time.Millisecond * 50) + expectedGER, err := env.GERL1Contract.GetLastGlobalExitRoot(&bind.CallOpts{Pending: false}) + require.NoError(t, err) + isInjected, err := env.AggOracleSender.IsGERAlreadyInjected(expectedGER) + require.NoError(t, err) + require.True(t, isInjected, fmt.Sprintf("iteration %d, GER: %s", i, common.Bytes2Hex(expectedGER[:]))) + + // Wait for syncer to catch up + syncerUpToDate := false + var errMsg string + for i := 0; i < 10; i++ { + lpb, err := syncer.GetLastProcessedBlock(ctx) + require.NoError(t, err) + lb, err := env.L2Client.Client().BlockNumber(ctx) + require.NoError(t, err) + if lpb == lb { + syncerUpToDate = true + break + } + time.Sleep(time.Millisecond * 10) + errMsg = fmt.Sprintf("last block from client: %d, last block from syncer: %d", lb, lpb) + } + require.True(t, syncerUpToDate, errMsg) + + actualGER, err := syncer.GetFirstGERAfterL1InfoTreeIndex(ctx, uint32(i)) + require.NoError(t, err) + require.Equal(t, common.Hash(expectedGER), actualGER) + } +} diff --git a/lastgersync/evmdownloader.go b/lastgersync/evmdownloader.go new file mode 100644 index 00000000..5c1fde54 --- /dev/null +++ b/lastgersync/evmdownloader.go @@ -0,0 +1,166 @@ +package lastgersync + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/pessimisticglobalexitroot" + "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/sync" + "github.com/0xPolygon/cdk/tree" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" +) + +type EthClienter interface { + ethereum.LogFilterer + ethereum.BlockNumberReader + ethereum.ChainReader + bind.ContractBackend +} + +type downloader struct { + *sync.EVMDownloaderImplementation + l2Client EthClienter + gerContract *pessimisticglobalexitroot.Pessimisticglobalexitroot + l1InfoTreesync *l1infotreesync.L1InfoTreeSync + processor *processor + rh *sync.RetryHandler +} + +func newDownloader( + l2Client EthClienter, + globalExitRootL2 common.Address, + l1InfoTreesync *l1infotreesync.L1InfoTreeSync, + processor *processor, + rh *sync.RetryHandler, + blockFinality *big.Int, + waitForNewBlocksPeriod time.Duration, +) (*downloader, error) { + gerContract, err := pessimisticglobalexitroot.NewPessimisticglobalexitroot(globalExitRootL2, l2Client) + if err != nil { + return nil, err + } + return &downloader{ + EVMDownloaderImplementation: sync.NewEVMDownloaderImplementation( + l2Client, blockFinality, waitForNewBlocksPeriod, nil, nil, nil, rh, + ), + l2Client: l2Client, + gerContract: gerContract, + l1InfoTreesync: l1InfoTreesync, + processor: processor, + rh: rh, + }, nil +} + +func (d *downloader) Download(ctx context.Context, fromBlock uint64, downloadedCh chan sync.EVMBlock) { + var ( + attempts int + lastIndex uint32 + err error + ) + for { + lastIndex, err = d.processor.getLastIndex(ctx) + if err == ErrNotFound { + lastIndex = 0 + } else if err != nil { + log.Errorf("error getting last indes: %v", err) + attempts++ + d.rh.Handle("getLastIndex", attempts) + continue + } + break + } + for { + select { + case <-ctx.Done(): + log.Debug("closing channel") + close(downloadedCh) + return + default: + } + lastBlock := d.WaitForNewBlocks(ctx, fromBlock) + + attempts = 0 + var gers []Event + for { + gers, err = d.getGERsFromIndex(ctx, lastIndex) + if err != nil { + log.Errorf("error getting GERs: %v", err) + attempts++ + d.rh.Handle("getGERsFromIndex", attempts) + continue + } + break + } + + attempts = 0 + blockHeader := d.GetBlockHeader(ctx, lastBlock) + block := &sync.EVMBlock{ + EVMBlockHeader: sync.EVMBlockHeader{ + Num: blockHeader.Num, + Hash: blockHeader.Hash, + ParentHash: blockHeader.ParentHash, + Timestamp: blockHeader.Timestamp, + }, + } + d.setGreatestGERInjectedFromList(block, gers) + + downloadedCh <- *block + if block.Events != nil { + lastIndex = block.Events[0].(Event).L1InfoTreeIndex + } + } +} + +func (d *downloader) getGERsFromIndex(ctx context.Context, fromL1InfoTreeIndex uint32) ([]Event, error) { + lastIndex, _, err := d.l1InfoTreesync.GetLastL1InfoTreeRootAndIndex(ctx) + if err == tree.ErrNotFound { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("error calling GetLastL1InfoTreeRootAndIndex: %v", err) + } + + gers := []Event{} + for i := fromL1InfoTreeIndex; i <= lastIndex; i++ { + info, err := d.l1InfoTreesync.GetInfoByIndex(ctx, i) + if err != nil { + return nil, fmt.Errorf("error calling GetInfoByIndex: %v", err) + } + gers = append(gers, Event{ + L1InfoTreeIndex: i, + GlobalExitRoot: info.GlobalExitRoot, + }) + } + + return gers, nil +} + +func (d *downloader) setGreatestGERInjectedFromList(b *sync.EVMBlock, list []Event) { + for _, event := range list { + var attempts int + for { + timestamp, err := d.gerContract.GlobalExitRootMap( + &bind.CallOpts{Pending: false}, event.GlobalExitRoot, + ) + if err != nil { + attempts++ + log.Errorf( + "error calling contract function GlobalExitRootMap with ger %s: %v", + event.GlobalExitRoot.Hex(), err, + ) + d.rh.Handle("GlobalExitRootMap", attempts) + continue + } + if timestamp.Cmp(big.NewInt(0)) == 1 { + b.Events = []interface{}{event} + } + break + } + } +} diff --git a/lastgersync/lastgersync.go b/lastgersync/lastgersync.go new file mode 100644 index 00000000..2e6434eb --- /dev/null +++ b/lastgersync/lastgersync.go @@ -0,0 +1,78 @@ +package lastgersync + +import ( + "context" + "math/big" + "time" + + "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/sync" + "github.com/ethereum/go-ethereum/common" +) + +const ( + reorgDetectorID = "lastGERSync" +) + +type LastGERSync struct { + driver *sync.EVMDriver + processor *processor +} + +func New( + ctx context.Context, + dbPath string, + rd sync.ReorgDetector, + l2Client EthClienter, + globalExitRootL2 common.Address, + l1InfoTreesync *l1infotreesync.L1InfoTreeSync, + retryAfterErrorPeriod time.Duration, + maxRetryAttemptsAfterError int, + blockFinality *big.Int, + waitForNewBlocksPeriod time.Duration, + downloadBufferSize int, +) (*LastGERSync, error) { + processor, err := newProcessor(dbPath) + if err != nil { + return nil, err + } + + rh := &sync.RetryHandler{ + RetryAfterErrorPeriod: retryAfterErrorPeriod, + MaxRetryAttemptsAfterError: maxRetryAttemptsAfterError, + } + downloader, err := newDownloader( + l2Client, + globalExitRootL2, + l1InfoTreesync, + processor, + rh, + blockFinality, + waitForNewBlocksPeriod, + ) + if err != nil { + return nil, err + } + + driver, err := sync.NewEVMDriver(rd, processor, downloader, reorgDetectorID, downloadBufferSize, rh) + if err != nil { + return nil, err + } + + return &LastGERSync{ + driver: driver, + processor: processor, + }, nil +} + +func (s *LastGERSync) Start(ctx context.Context) { + s.driver.Sync(ctx) +} + +func (s *LastGERSync) GetFirstGERAfterL1InfoTreeIndex(ctx context.Context, l1InfoTreeIndex uint32) (common.Hash, error) { + return s.processor.GetFirstGERAfterL1InfoTreeIndex(ctx, l1InfoTreeIndex) +} + +func (s *LastGERSync) GetLastProcessedBlock(ctx context.Context) (uint64, error) { + return s.processor.GetLastProcessedBlock(ctx) +} diff --git a/lastgersync/processor.go b/lastgersync/processor.go new file mode 100644 index 00000000..bd824618 --- /dev/null +++ b/lastgersync/processor.go @@ -0,0 +1,250 @@ +package lastgersync + +import ( + "context" + "errors" + "fmt" + "math" + + "github.com/0xPolygon/cdk/common" + "github.com/0xPolygon/cdk/sync" + ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/kv/mdbx" +) + +const ( + lastProcessedTable = "lastgersync-lastProcessed" + gerTable = "lastgersync-ger" + blockTable = "lastgersync-block" +) + +var ( + lastProcessedKey = []byte("lp") + ErrNotFound = errors.New("not found") +) + +type Event struct { + GlobalExitRoot ethCommon.Hash + L1InfoTreeIndex uint32 +} + +type blockWithGERs struct { + // inclusive + FirstIndex uint32 + // not inclusive + LastIndex uint32 +} + +func (b *blockWithGERs) MarshalBinary() ([]byte, error) { + return append(common.Uint32ToBytes(b.FirstIndex), common.Uint32ToBytes(b.LastIndex)...), nil +} + +func (b *blockWithGERs) UnmarshalBinary(data []byte) error { + if len(data) != 8 { + return fmt.Errorf("expected len %d, actual len %d", 8, len(data)) + } + b.FirstIndex = common.BytesToUint32(data[:4]) + b.LastIndex = common.BytesToUint32(data[4:]) + return nil +} + +type processor struct { + db kv.RwDB +} + +func newProcessor(dbPath string) (*processor, error) { + tableCfgFunc := func(defaultBuckets kv.TableCfg) kv.TableCfg { + cfg := kv.TableCfg{ + lastProcessedTable: {}, + gerTable: {}, + blockTable: {}, + } + return cfg + } + db, err := mdbx.NewMDBX(nil). + Path(dbPath). + WithTableCfg(tableCfgFunc). + Open() + if err != nil { + return nil, err + } + return &processor{ + db: db, + }, nil +} + +// GetLastProcessedBlockAndL1InfoTreeIndex returns the last processed block oby the processor, including blocks +// that don't have events +func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { + tx, err := p.db.BeginRo(ctx) + if err != nil { + return 0, err + } + defer tx.Rollback() + return p.getLastProcessedBlockWithTx(tx) +} + +func (p *processor) getLastIndex(ctx context.Context) (uint32, error) { + tx, err := p.db.BeginRo(ctx) + if err != nil { + return 0, err + } + defer tx.Rollback() + + return p.getLastIndexWithTx(tx) +} + +func (p *processor) getLastIndexWithTx(tx kv.Tx) (uint32, error) { + iter, err := tx.RangeDescend(gerTable, common.Uint32ToBytes(math.MaxUint32), common.Uint32ToBytes(0), 1) + if err != nil { + return 0, err + } + k, _, err := iter.Next() + if err != nil { + return 0, err + } + if k == nil { + return 0, ErrNotFound + } + return common.BytesToUint32(k), nil +} + +func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { + if lastProcessedBytes, err := tx.GetOne(lastProcessedTable, lastProcessedKey); err != nil { + return 0, err + } else if lastProcessedBytes == nil { + return 0, nil + } else { + return common.BytesToUint64(lastProcessedBytes), nil + } +} + +func (p *processor) updateLastProcessedBlockWithTx(tx kv.RwTx, blockNum uint64) error { + return tx.Put(lastProcessedTable, lastProcessedKey, common.Uint64ToBytes(blockNum)) +} + +func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { + tx, err := p.db.BeginRw(ctx) + if err != nil { + return err + } + + lenEvents := len(block.Events) + var lastIndex int64 + if lenEvents > 0 { + li, err := p.getLastIndexWithTx(tx) + if err == ErrNotFound { + lastIndex = -1 + } else if err != nil { + tx.Rollback() + return err + } else { + lastIndex = int64(li) + } + } + + for _, e := range block.Events { + event := e.(Event) + if int64(event.L1InfoTreeIndex) < lastIndex { + continue + } + lastIndex = int64(event.L1InfoTreeIndex) + if err := tx.Put( + gerTable, + common.Uint32ToBytes(event.L1InfoTreeIndex), + event.GlobalExitRoot[:], + ); err != nil { + tx.Rollback() + return err + } + } + + if lenEvents > 0 { + bwg := blockWithGERs{ + FirstIndex: block.Events[0].(Event).L1InfoTreeIndex, + LastIndex: block.Events[lenEvents-1].(Event).L1InfoTreeIndex + 1, + } + data, err := bwg.MarshalBinary() + if err != nil { + tx.Rollback() + return err + } + if err = tx.Put(blockTable, common.Uint64ToBytes(block.Num), data); err != nil { + tx.Rollback() + return err + } + } + + if err := p.updateLastProcessedBlockWithTx(tx, block.Num); err != nil { + tx.Rollback() + return err + } + + return tx.Commit() +} + +func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { + tx, err := p.db.BeginRw(ctx) + if err != nil { + return err + } + + iter, err := tx.Range(blockTable, common.Uint64ToBytes(firstReorgedBlock), nil) + if err != nil { + tx.Rollback() + return err + } + for bNumBytes, bWithGERBytes, err := iter.Next(); bNumBytes != nil; bNumBytes, bWithGERBytes, err = iter.Next() { + if err != nil { + tx.Rollback() + return err + } + if err := tx.Delete(blockTable, bNumBytes); err != nil { + tx.Rollback() + return err + } + + bWithGER := &blockWithGERs{} + if err := bWithGER.UnmarshalBinary(bWithGERBytes); err != nil { + tx.Rollback() + return err + } + for i := bWithGER.FirstIndex; i < bWithGER.LastIndex; i++ { + if err := tx.Delete(gerTable, common.Uint32ToBytes(i)); err != nil { + tx.Rollback() + return err + } + } + } + + if err := p.updateLastProcessedBlockWithTx(tx, firstReorgedBlock-1); err != nil { + tx.Rollback() + return err + } + + return tx.Commit() +} + +// GetFirstGERAfterL1InfoTreeIndex returns the first GER injected on the chain that is related to l1InfoTreeIndex +// or greater +func (p *processor) GetFirstGERAfterL1InfoTreeIndex(ctx context.Context, l1InfoTreeIndex uint32) (ethCommon.Hash, error) { + tx, err := p.db.BeginRo(ctx) + if err != nil { + return ethCommon.Hash{}, err + } + defer tx.Rollback() + + iter, err := tx.Range(gerTable, common.Uint32ToBytes(l1InfoTreeIndex), nil) + if err != nil { + return ethCommon.Hash{}, err + } + k, ger, err := iter.Next() + if err != nil { + return ethCommon.Hash{}, err + } + if k == nil { + return ethCommon.Hash{}, ErrNotFound + } + return ethCommon.BytesToHash(ger), nil +} diff --git a/sync/evmdownloader.go b/sync/evmdownloader.go index 47aa7fbe..f5587d0e 100644 --- a/sync/evmdownloader.go +++ b/sync/evmdownloader.go @@ -20,18 +20,18 @@ type EthClienter interface { bind.ContractBackend } -type evmDownloaderInterface interface { - waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) - getEventsByBlockRange(ctx context.Context, fromBlock, toBlock uint64) []EVMBlock - getLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log - getBlockHeader(ctx context.Context, blockNum uint64) EVMBlockHeader +type EVMDownloaderInterface interface { + WaitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) + GetEventsByBlockRange(ctx context.Context, fromBlock, toBlock uint64) []EVMBlock + GetLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log + GetBlockHeader(ctx context.Context, blockNum uint64) EVMBlockHeader } type LogAppenderMap map[common.Hash]func(b *EVMBlock, l types.Log) error type EVMDownloader struct { syncBlockChunkSize uint64 - evmDownloaderInterface + EVMDownloaderInterface } func NewEVMDownloader( @@ -53,7 +53,7 @@ func NewEVMDownloader( } return &EVMDownloader{ syncBlockChunkSize: syncBlockChunkSize, - evmDownloaderInterface: &downloaderImplementation{ + EVMDownloaderInterface: &EVMDownloaderImplementation{ ethClient: ethClient, blockFinality: finality, waitForNewBlocksPeriod: waitForNewBlocksPeriod, @@ -65,8 +65,8 @@ func NewEVMDownloader( }, nil } -func (d *EVMDownloader) download(ctx context.Context, fromBlock uint64, downloadedCh chan EVMBlock) { - lastBlock := d.waitForNewBlocks(ctx, 0) +func (d *EVMDownloader) Download(ctx context.Context, fromBlock uint64, downloadedCh chan EVMBlock) { + lastBlock := d.WaitForNewBlocks(ctx, 0) for { select { case <-ctx.Done(): @@ -81,11 +81,11 @@ func (d *EVMDownloader) download(ctx context.Context, fromBlock uint64, download } if fromBlock > toBlock { log.Debug("waiting for new blocks, last block ", toBlock) - lastBlock = d.waitForNewBlocks(ctx, toBlock) + lastBlock = d.WaitForNewBlocks(ctx, toBlock) continue } log.Debugf("getting events from blocks %d to %d", fromBlock, toBlock) - blocks := d.getEventsByBlockRange(ctx, fromBlock, toBlock) + blocks := d.GetEventsByBlockRange(ctx, fromBlock, toBlock) for _, b := range blocks { log.Debugf("sending block %d to the driver (with events)", b.Num) downloadedCh <- b @@ -94,14 +94,14 @@ func (d *EVMDownloader) download(ctx context.Context, fromBlock uint64, download // Indicate the last downloaded block if there are not events on it log.Debugf("sending block %d to the driver (without events)", toBlock) downloadedCh <- EVMBlock{ - EVMBlockHeader: d.getBlockHeader(ctx, toBlock), + EVMBlockHeader: d.GetBlockHeader(ctx, toBlock), } } fromBlock = toBlock + 1 } } -type downloaderImplementation struct { +type EVMDownloaderImplementation struct { ethClient EthClienter blockFinality *big.Int waitForNewBlocksPeriod time.Duration @@ -111,7 +111,27 @@ type downloaderImplementation struct { rh *RetryHandler } -func (d *downloaderImplementation) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) { +func NewEVMDownloaderImplementation( + ethClient EthClienter, + blockFinality *big.Int, + waitForNewBlocksPeriod time.Duration, + appender LogAppenderMap, + topicsToQuery []common.Hash, + adressessToQuery []common.Address, + rh *RetryHandler, +) *EVMDownloaderImplementation { + return &EVMDownloaderImplementation{ + ethClient: ethClient, + blockFinality: blockFinality, + waitForNewBlocksPeriod: waitForNewBlocksPeriod, + appender: appender, + topicsToQuery: topicsToQuery, + adressessToQuery: adressessToQuery, + rh: rh, + } +} + +func (d *EVMDownloaderImplementation) WaitForNewBlocks(ctx context.Context, lastBlockSeen uint64) (newLastBlock uint64) { attempts := 0 ticker := time.NewTicker(d.waitForNewBlocksPeriod) defer ticker.Stop() @@ -135,17 +155,17 @@ func (d *downloaderImplementation) waitForNewBlocks(ctx context.Context, lastBlo } } -func (d *downloaderImplementation) getEventsByBlockRange(ctx context.Context, fromBlock, toBlock uint64) []EVMBlock { +func (d *EVMDownloaderImplementation) GetEventsByBlockRange(ctx context.Context, fromBlock, toBlock uint64) []EVMBlock { blocks := []EVMBlock{} - logs := d.getLogs(ctx, fromBlock, toBlock) + logs := d.GetLogs(ctx, fromBlock, toBlock) for _, l := range logs { if len(blocks) == 0 || blocks[len(blocks)-1].Num < l.BlockNumber { - b := d.getBlockHeader(ctx, l.BlockNumber) + b := d.GetBlockHeader(ctx, l.BlockNumber) if b.Hash != l.BlockHash { log.Infof( "there has been a block hash change between the event query and the block query for block %d: %s vs %s. Retrtying.", l.BlockNumber, b.Hash, l.BlockHash) - return d.getEventsByBlockRange(ctx, fromBlock, toBlock) + return d.GetEventsByBlockRange(ctx, fromBlock, toBlock) } blocks = append(blocks, EVMBlock{ EVMBlockHeader: EVMBlockHeader{ @@ -174,7 +194,7 @@ func (d *downloaderImplementation) getEventsByBlockRange(ctx context.Context, fr return blocks } -func (d *downloaderImplementation) getLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log { +func (d *EVMDownloaderImplementation) GetLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log { query := ethereum.FilterQuery{ FromBlock: new(big.Int).SetUint64(fromBlock), Addresses: d.adressessToQuery, @@ -212,7 +232,7 @@ func (d *downloaderImplementation) getLogs(ctx context.Context, fromBlock, toBlo return logs } -func (d *downloaderImplementation) getBlockHeader(ctx context.Context, blockNum uint64) EVMBlockHeader { +func (d *EVMDownloaderImplementation) GetBlockHeader(ctx context.Context, blockNum uint64) EVMBlockHeader { attempts := 0 for { header, err := d.ethClient.HeaderByNumber(ctx, big.NewInt(int64(blockNum))) diff --git a/sync/evmdownloader_test.go b/sync/evmdownloader_test.go index 2c947370..12aba792 100644 --- a/sync/evmdownloader_test.go +++ b/sync/evmdownloader_test.go @@ -172,7 +172,7 @@ func TestGetEventsByBlockRange(t *testing.T) { }, nil) } - actualBlocks := d.getEventsByBlockRange(ctx, tc.fromBlock, tc.toBlock) + actualBlocks := d.GetEventsByBlockRange(ctx, tc.fromBlock, tc.toBlock) require.Equal(t, tc.expectedBlocks, actualBlocks, tc.description) } } @@ -208,9 +208,9 @@ func TestDownload(t *testing.T) { ctx1, cancel := context.WithCancel(ctx) expectedBlocks := []EVMBlock{} dwnldr, _ := NewTestDownloader(t) - dwnldr.evmDownloaderInterface = d + dwnldr.EVMDownloaderInterface = d - d.On("waitForNewBlocks", mock.Anything, uint64(0)). + d.On("WaitForNewBlocks", mock.Anything, uint64(0)). Return(uint64(1)) // iteratiion 0: // last block is 1, download that block (no events and wait) @@ -221,13 +221,13 @@ func TestDownload(t *testing.T) { }, } expectedBlocks = append(expectedBlocks, b1) - d.On("getEventsByBlockRange", mock.Anything, uint64(0), uint64(1)). + d.On("GetEventsByBlockRange", mock.Anything, uint64(0), uint64(1)). Return([]EVMBlock{}) - d.On("getBlockHeader", mock.Anything, uint64(1)). + d.On("GetBlockHeader", mock.Anything, uint64(1)). Return(b1.EVMBlockHeader) // iteration 1: wait for next block to be created - d.On("waitForNewBlocks", mock.Anything, uint64(1)). + d.On("WaitForNewBlocks", mock.Anything, uint64(1)). After(time.Millisecond * 100). Return(uint64(2)).Once() @@ -239,11 +239,11 @@ func TestDownload(t *testing.T) { }, } expectedBlocks = append(expectedBlocks, b2) - d.On("getEventsByBlockRange", mock.Anything, uint64(2), uint64(2)). + d.On("GetEventsByBlockRange", mock.Anything, uint64(2), uint64(2)). Return([]EVMBlock{b2}) // iteration 3: wait for next block to be created (jump to block 8) - d.On("waitForNewBlocks", mock.Anything, uint64(2)). + d.On("WaitForNewBlocks", mock.Anything, uint64(2)). After(time.Millisecond * 100). Return(uint64(8)).Once() @@ -269,13 +269,13 @@ func TestDownload(t *testing.T) { }, } expectedBlocks = append(expectedBlocks, b6, b7, b8) - d.On("getEventsByBlockRange", mock.Anything, uint64(3), uint64(8)). + d.On("GetEventsByBlockRange", mock.Anything, uint64(3), uint64(8)). Return([]EVMBlock{b6, b7}) - d.On("getBlockHeader", mock.Anything, uint64(8)). + d.On("GetBlockHeader", mock.Anything, uint64(8)). Return(b8.EVMBlockHeader) // iteration 5: wait for next block to be created (jump to block 30) - d.On("waitForNewBlocks", mock.Anything, uint64(8)). + d.On("WaitForNewBlocks", mock.Anything, uint64(8)). After(time.Millisecond * 100). Return(uint64(30)).Once() @@ -287,9 +287,9 @@ func TestDownload(t *testing.T) { }, } expectedBlocks = append(expectedBlocks, b19) - d.On("getEventsByBlockRange", mock.Anything, uint64(9), uint64(19)). + d.On("GetEventsByBlockRange", mock.Anything, uint64(9), uint64(19)). Return([]EVMBlock{}) - d.On("getBlockHeader", mock.Anything, uint64(19)). + d.On("GetBlockHeader", mock.Anything, uint64(19)). Return(b19.EVMBlockHeader) // iteration 7: from block 20 to 30, events on last block @@ -301,15 +301,15 @@ func TestDownload(t *testing.T) { Events: []interface{}{testEvent(common.HexToHash("30"))}, } expectedBlocks = append(expectedBlocks, b30) - d.On("getEventsByBlockRange", mock.Anything, uint64(20), uint64(30)). + d.On("GetEventsByBlockRange", mock.Anything, uint64(20), uint64(30)). Return([]EVMBlock{b30}) // iteration 8: wait for next block to be created (jump to block 35) - d.On("waitForNewBlocks", mock.Anything, uint64(30)). + d.On("WaitForNewBlocks", mock.Anything, uint64(30)). After(time.Millisecond * 100). Return(uint64(35)).Once() - go dwnldr.download(ctx1, 0, downloadCh) + go dwnldr.Download(ctx1, 0, downloadCh) for _, expectedBlock := range expectedBlocks { actualBlock := <-downloadCh log.Debugf("block %d received!", actualBlock.Num) @@ -331,7 +331,7 @@ func TestWaitForNewBlocks(t *testing.T) { clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(&types.Header{ Number: big.NewInt(6), }, nil).Once() - actualBlock := d.waitForNewBlocks(ctx, currentBlock) + actualBlock := d.WaitForNewBlocks(ctx, currentBlock) assert.Equal(t, expectedBlock, actualBlock) // 2 iterations @@ -341,7 +341,7 @@ func TestWaitForNewBlocks(t *testing.T) { clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(&types.Header{ Number: big.NewInt(6), }, nil).Once() - actualBlock = d.waitForNewBlocks(ctx, currentBlock) + actualBlock = d.WaitForNewBlocks(ctx, currentBlock) assert.Equal(t, expectedBlock, actualBlock) // after error from client @@ -349,7 +349,7 @@ func TestWaitForNewBlocks(t *testing.T) { clientMock.On("HeaderByNumber", ctx, mock.Anything).Return(&types.Header{ Number: big.NewInt(6), }, nil).Once() - actualBlock = d.waitForNewBlocks(ctx, currentBlock) + actualBlock = d.WaitForNewBlocks(ctx, currentBlock) assert.Equal(t, expectedBlock, actualBlock) } @@ -369,13 +369,13 @@ func TestGetBlockHeader(t *testing.T) { // at first attempt clientMock.On("HeaderByNumber", ctx, blockNumBig).Return(returnedBlock, nil).Once() - actualBlock := d.getBlockHeader(ctx, blockNum) + actualBlock := d.GetBlockHeader(ctx, blockNum) assert.Equal(t, expectedBlock, actualBlock) // after error from client clientMock.On("HeaderByNumber", ctx, blockNumBig).Return(nil, errors.New("foo")).Once() clientMock.On("HeaderByNumber", ctx, blockNumBig).Return(returnedBlock, nil).Once() - actualBlock = d.getBlockHeader(ctx, blockNum) + actualBlock = d.GetBlockHeader(ctx, blockNum) assert.Equal(t, expectedBlock, actualBlock) } diff --git a/sync/evmdriver.go b/sync/evmdriver.go index 8616e2a5..7d41ead2 100644 --- a/sync/evmdriver.go +++ b/sync/evmdriver.go @@ -9,15 +9,19 @@ import ( ) type evmDownloaderFull interface { - evmDownloaderInterface - download(ctx context.Context, fromBlock uint64, downloadedCh chan EVMBlock) + EVMDownloaderInterface + downloader +} + +type downloader interface { + Download(ctx context.Context, fromBlock uint64, downloadedCh chan EVMBlock) } type EVMDriver struct { reorgDetector ReorgDetector reorgSub *reorgdetector.Subscription processor processorInterface - downloader evmDownloaderFull + downloader downloader reorgDetectorID string downloadBufferSize int rh *RetryHandler @@ -37,7 +41,7 @@ type ReorgDetector interface { func NewEVMDriver( reorgDetector ReorgDetector, processor processorInterface, - downloader evmDownloaderFull, + downloader downloader, reorgDetectorID string, downloadBufferSize int, rh *RetryHandler, @@ -79,7 +83,7 @@ reset: // start downloading downloadCh := make(chan EVMBlock, d.downloadBufferSize) - go d.downloader.download(cancellableCtx, lastProcessedBlock, downloadCh) + go d.downloader.Download(cancellableCtx, lastProcessedBlock, downloadCh) for { select { diff --git a/sync/evmdriver_test.go b/sync/evmdriver_test.go index 34b8fb89..5b1abbfe 100644 --- a/sync/evmdriver_test.go +++ b/sync/evmdriver_test.go @@ -52,7 +52,7 @@ func TestSync(t *testing.T) { green bool } reorg1Completed := reorgSemaphore{} - dm.On("download", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + dm.On("Download", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { ctx := args.Get(0).(context.Context) downloadedCh := args.Get(2).(chan EVMBlock) log.Info("entering mock loop") diff --git a/sync/mock_downloader_test.go b/sync/mock_downloader_test.go index 738fc873..1cd476ad 100644 --- a/sync/mock_downloader_test.go +++ b/sync/mock_downloader_test.go @@ -14,13 +14,13 @@ type EVMDownloaderMock struct { mock.Mock } -// download provides a mock function with given fields: ctx, fromBlock, downloadedCh -func (_m *EVMDownloaderMock) download(ctx context.Context, fromBlock uint64, downloadedCh chan EVMBlock) { +// Download provides a mock function with given fields: ctx, fromBlock, downloadedCh +func (_m *EVMDownloaderMock) Download(ctx context.Context, fromBlock uint64, downloadedCh chan EVMBlock) { _m.Called(ctx, fromBlock, downloadedCh) } -// getBlockHeader provides a mock function with given fields: ctx, blockNum -func (_m *EVMDownloaderMock) getBlockHeader(ctx context.Context, blockNum uint64) EVMBlockHeader { +// GetBlockHeader provides a mock function with given fields: ctx, blockNum +func (_m *EVMDownloaderMock) GetBlockHeader(ctx context.Context, blockNum uint64) EVMBlockHeader { ret := _m.Called(ctx, blockNum) var r0 EVMBlockHeader @@ -33,8 +33,8 @@ func (_m *EVMDownloaderMock) getBlockHeader(ctx context.Context, blockNum uint64 return r0 } -// getEventsByBlockRange provides a mock function with given fields: ctx, fromBlock, toBlock -func (_m *EVMDownloaderMock) getEventsByBlockRange(ctx context.Context, fromBlock uint64, toBlock uint64) []EVMBlock { +// GetEventsByBlockRange provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *EVMDownloaderMock) GetEventsByBlockRange(ctx context.Context, fromBlock uint64, toBlock uint64) []EVMBlock { ret := _m.Called(ctx, fromBlock, toBlock) var r0 []EVMBlock @@ -49,8 +49,8 @@ func (_m *EVMDownloaderMock) getEventsByBlockRange(ctx context.Context, fromBloc return r0 } -// getLogs provides a mock function with given fields: ctx, fromBlock, toBlock -func (_m *EVMDownloaderMock) getLogs(ctx context.Context, fromBlock uint64, toBlock uint64) []types.Log { +// GetLogs provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *EVMDownloaderMock) GetLogs(ctx context.Context, fromBlock uint64, toBlock uint64) []types.Log { ret := _m.Called(ctx, fromBlock, toBlock) var r0 []types.Log @@ -65,8 +65,8 @@ func (_m *EVMDownloaderMock) getLogs(ctx context.Context, fromBlock uint64, toBl return r0 } -// waitForNewBlocks provides a mock function with given fields: ctx, lastBlockSeen -func (_m *EVMDownloaderMock) waitForNewBlocks(ctx context.Context, lastBlockSeen uint64) uint64 { +// WaitForNewBlocks provides a mock function with given fields: ctx, lastBlockSeen +func (_m *EVMDownloaderMock) WaitForNewBlocks(ctx context.Context, lastBlockSeen uint64) uint64 { ret := _m.Called(ctx, lastBlockSeen) var r0 uint64 diff --git a/test/Makefile b/test/Makefile index 686a6c8d..acea3dd0 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-aggoracle 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 .PHONY: generate-mocks-bridgesync @@ -50,8 +50,8 @@ generate-mocks-l1infotreesync: ## Generates mocks for l1infotreesync , using moc 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-aggoracle: ## Generates mocks for aggoracle , using mockery tool - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthTxManager --dir=../aggoracle/chaingersender --output=../aggoracle --outpkg=aggoracle --structname=EthTxManagerMock --filename=mock_ethtxmanager_test.go +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 diff --git a/test/helpers/aggoracle_e2e.go b/test/helpers/aggoracle_e2e.go new file mode 100644 index 00000000..c2908b8d --- /dev/null +++ b/test/helpers/aggoracle_e2e.go @@ -0,0 +1,332 @@ +package helpers + +import ( + "context" + "errors" + "fmt" + "math/big" + "testing" + "time" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry-paris/polygonzkevmbridgev2" + gerContractL1 "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/globalexitrootnopush0" + gerContractEVMChain "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/pessimisticglobalexitrootnopush0" + "github.com/0xPolygon/cdk/aggoracle" + "github.com/0xPolygon/cdk/aggoracle/chaingersender" + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/reorgdetector" + "github.com/0xPolygon/cdk/test/contracts/transparentupgradableproxy" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/stretchr/testify/require" +) + +const ( + NetworkIDL2 = uint32(1) +) + +type AggoracleWithEVMChainEnv struct { + L1Client *simulated.Backend + L2Client *simulated.Backend + L1InfoTreeSync *l1infotreesync.L1InfoTreeSync + GERL1Contract *gerContractL1.Globalexitrootnopush0 + GERL1Addr common.Address + GERL2Contract *gerContractEVMChain.Pessimisticglobalexitrootnopush0 + GERL2Addr common.Address + AuthL1 *bind.TransactOpts + AuthL2 *bind.TransactOpts + AggOracle *aggoracle.AggOracle + AggOracleSender aggoracle.ChainSender + ReorgDetector *reorgdetector.ReorgDetector + BridgeL1Contract *polygonzkevmbridgev2.Polygonzkevmbridgev2 + BridgeL1Addr common.Address + BridgeL2Contract *polygonzkevmbridgev2.Polygonzkevmbridgev2 + BridgeL2Addr common.Address + NetworkIDL2 uint32 + EthTxManMockL2 *EthTxManagerMock +} + +func SetupAggoracleWithEVMChain(t *testing.T) *AggoracleWithEVMChainEnv { + ctx := context.Background() + l1Client, syncer, gerL1Contract, gerL1Addr, bridgeL1Contract, bridgeL1Addr, authL1, rd := CommonSetup(t) + sender, l2Client, gerL2Contract, gerL2Addr, bridgeL2Contract, bridgeL2Addr, authL2, ethTxManMockL2 := EVMSetup(t) + oracle, err := aggoracle.New(sender, l1Client.Client(), syncer, etherman.LatestBlock, time.Millisecond*20) + require.NoError(t, err) + go oracle.Start(ctx) + + return &AggoracleWithEVMChainEnv{ + L1Client: l1Client, + L2Client: l2Client, + L1InfoTreeSync: syncer, + GERL1Contract: gerL1Contract, + GERL1Addr: gerL1Addr, + GERL2Contract: gerL2Contract, + GERL2Addr: gerL2Addr, + AuthL1: authL1, + AuthL2: authL2, + AggOracle: oracle, + AggOracleSender: sender, + ReorgDetector: rd, + BridgeL1Contract: bridgeL1Contract, + BridgeL1Addr: bridgeL1Addr, + BridgeL2Contract: bridgeL2Contract, + BridgeL2Addr: bridgeL2Addr, + NetworkIDL2: NetworkIDL2, + EthTxManMockL2: ethTxManMockL2, + } +} + +func CommonSetup(t *testing.T) ( + *simulated.Backend, + *l1infotreesync.L1InfoTreeSync, + *gerContractL1.Globalexitrootnopush0, + common.Address, + *polygonzkevmbridgev2.Polygonzkevmbridgev2, + common.Address, + *bind.TransactOpts, + *reorgdetector.ReorgDetector, +) { + // Config and spin up + ctx := context.Background() + // Simulated L1 + privateKeyL1, err := crypto.GenerateKey() + require.NoError(t, err) + authL1, err := bind.NewKeyedTransactorWithChainID(privateKeyL1, big.NewInt(1337)) + require.NoError(t, err) + l1Client, gerL1Addr, gerL1Contract, bridgeL1Addr, bridgeL1Contract, err := newSimulatedL1(authL1) + require.NoError(t, err) + // Reorg detector + dbPathReorgDetector := t.TempDir() + reorg, err := reorgdetector.New(ctx, l1Client.Client(), dbPathReorgDetector) + require.NoError(t, err) + // Syncer + dbPathSyncer := t.TempDir() + syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, common.Address{}, 10, etherman.LatestBlock, reorg, l1Client.Client(), time.Millisecond, 0, 100*time.Millisecond, 3) + require.NoError(t, err) + go syncer.Start(ctx) + + return l1Client, syncer, gerL1Contract, gerL1Addr, bridgeL1Contract, bridgeL1Addr, authL1, reorg +} + +func EVMSetup(t *testing.T) ( + aggoracle.ChainSender, + *simulated.Backend, + *gerContractEVMChain.Pessimisticglobalexitrootnopush0, + common.Address, + *polygonzkevmbridgev2.Polygonzkevmbridgev2, + common.Address, + *bind.TransactOpts, + *EthTxManagerMock, +) { + privateKeyL2, err := crypto.GenerateKey() + require.NoError(t, err) + authL2, err := bind.NewKeyedTransactorWithChainID(privateKeyL2, big.NewInt(1337)) + require.NoError(t, err) + l2Client, gerL2Addr, gerL2Sc, bridgeL2Addr, bridgeL2Sc, err := newSimulatedEVMAggSovereignChain(authL2) + require.NoError(t, err) + ethTxManMock := NewEthTxManMock(t, l2Client, authL2) + sender, err := chaingersender.NewEVMChainGERSender(gerL2Addr, authL2.From, l2Client.Client(), ethTxManMock, 0, time.Millisecond*50) + require.NoError(t, err) + + return sender, l2Client, gerL2Sc, gerL2Addr, bridgeL2Sc, bridgeL2Addr, authL2, ethTxManMock +} + +func newSimulatedL1(auth *bind.TransactOpts) ( + client *simulated.Backend, + gerAddr common.Address, + gerContract *gerContractL1.Globalexitrootnopush0, + bridgeAddr common.Address, + bridgeContract *polygonzkevmbridgev2.Polygonzkevmbridgev2, + err error, +) { + ctx := context.Background() + privateKeyL1, err := crypto.GenerateKey() + if err != nil { + return + } + authDeployer, err := bind.NewKeyedTransactorWithChainID(privateKeyL1, big.NewInt(1337)) + balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd + address := auth.From + genesisAlloc := map[common.Address]types.Account{ + address: { + Balance: balance, + }, + authDeployer.From: { + Balance: balance, + }, + } + blockGasLimit := uint64(999999999999999999) //nolint:gomnd + client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) + + bridgeImplementationAddr, _, _, err := polygonzkevmbridgev2.DeployPolygonzkevmbridgev2(authDeployer, client.Client()) + if err != nil { + return + } + client.Commit() + + nonce, err := client.Client().PendingNonceAt(ctx, authDeployer.From) + if err != nil { + return + } + precalculatedAddr := crypto.CreateAddress(authDeployer.From, nonce+1) + bridgeABI, err := polygonzkevmbridgev2.Polygonzkevmbridgev2MetaData.GetAbi() + if err != nil { + return + } + if bridgeABI == nil { + err = errors.New("GetABI returned nil") + return + } + dataCallProxy, err := bridgeABI.Pack("initialize", + uint32(0), // networkIDMainnet + common.Address{}, // gasTokenAddressMainnet" + uint32(0), // gasTokenNetworkMainnet + precalculatedAddr, + common.Address{}, + []byte{}, // gasTokenMetadata + ) + if err != nil { + return + } + bridgeAddr, _, _, err = transparentupgradableproxy.DeployTransparentupgradableproxy( + authDeployer, + client.Client(), + bridgeImplementationAddr, + authDeployer.From, + dataCallProxy, + ) + if err != nil { + return + } + client.Commit() + bridgeContract, err = polygonzkevmbridgev2.NewPolygonzkevmbridgev2(bridgeAddr, client.Client()) + if err != nil { + return + } + checkGERAddr, err := bridgeContract.GlobalExitRootManager(&bind.CallOpts{Pending: false}) + if err != nil { + return + } + if precalculatedAddr != checkGERAddr { + err = fmt.Errorf("error deploying bridge, unexpected GER addr. Expected %s. Actual %s", precalculatedAddr.Hex(), checkGERAddr.Hex()) + } + + gerAddr, _, gerContract, err = gerContractL1.DeployGlobalexitrootnopush0(authDeployer, client.Client(), auth.From, bridgeAddr) + + client.Commit() + if precalculatedAddr != gerAddr { + err = fmt.Errorf("error calculating addr. Expected %s. Actual %s", precalculatedAddr.Hex(), gerAddr.Hex()) + } + return +} + +func newSimulatedEVMAggSovereignChain(auth *bind.TransactOpts) ( + client *simulated.Backend, + gerAddr common.Address, + gerContract *gerContractEVMChain.Pessimisticglobalexitrootnopush0, + bridgeAddr common.Address, + bridgeContract *polygonzkevmbridgev2.Polygonzkevmbridgev2, + err error, +) { + ctx := context.Background() + privateKeyL1, err := crypto.GenerateKey() + if err != nil { + return + } + authDeployer, err := bind.NewKeyedTransactorWithChainID(privateKeyL1, big.NewInt(1337)) + balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd + address := auth.From + precalculatedBridgeAddr := crypto.CreateAddress(authDeployer.From, 1) + genesisAlloc := map[common.Address]types.Account{ + address: { + Balance: balance, + }, + authDeployer.From: { + Balance: balance, + }, + precalculatedBridgeAddr: { + Balance: balance, + }, + } + blockGasLimit := uint64(999999999999999999) //nolint:gomnd + client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) + + bridgeImplementationAddr, _, _, err := polygonzkevmbridgev2.DeployPolygonzkevmbridgev2(authDeployer, client.Client()) + if err != nil { + return + } + client.Commit() + + nonce, err := client.Client().PendingNonceAt(ctx, authDeployer.From) + if err != nil { + return + } + precalculatedAddr := crypto.CreateAddress(authDeployer.From, nonce+1) + bridgeABI, err := polygonzkevmbridgev2.Polygonzkevmbridgev2MetaData.GetAbi() + if err != nil { + return + } + if bridgeABI == nil { + err = errors.New("GetABI returned nil") + return + } + dataCallProxy, err := bridgeABI.Pack("initialize", + NetworkIDL2, + common.Address{}, // gasTokenAddressMainnet" + uint32(0), // gasTokenNetworkMainnet + precalculatedAddr, + common.Address{}, + []byte{}, // gasTokenMetadata + ) + if err != nil { + return + } + bridgeAddr, _, _, err = transparentupgradableproxy.DeployTransparentupgradableproxy( + authDeployer, + client.Client(), + bridgeImplementationAddr, + authDeployer.From, + dataCallProxy, + ) + if err != nil { + return + } + if bridgeAddr != precalculatedBridgeAddr { + err = fmt.Errorf("error calculating bridge addr. Expected: %s. Actual: %s", precalculatedBridgeAddr, bridgeAddr) + return + } + client.Commit() + bridgeContract, err = polygonzkevmbridgev2.NewPolygonzkevmbridgev2(bridgeAddr, client.Client()) + if err != nil { + return + } + checkGERAddr, err := bridgeContract.GlobalExitRootManager(&bind.CallOpts{}) + if err != nil { + return + } + if precalculatedAddr != checkGERAddr { + err = errors.New("error deploying bridge") + } + + gerAddr, _, gerContract, err = gerContractEVMChain.DeployPessimisticglobalexitrootnopush0(authDeployer, client.Client(), auth.From) + if err != nil { + return + } + client.Commit() + + _GLOBAL_EXIT_ROOT_SETTER_ROLE := common.HexToHash("0x7b95520991dfda409891be0afa2635b63540f92ee996fda0bf695a166e5c5176") + _, err = gerContract.GrantRole(authDeployer, _GLOBAL_EXIT_ROOT_SETTER_ROLE, auth.From) + client.Commit() + hasRole, _ := gerContract.HasRole(&bind.CallOpts{Pending: false}, _GLOBAL_EXIT_ROOT_SETTER_ROLE, auth.From) + if !hasRole { + err = errors.New("failed to set role") + } + if precalculatedAddr != gerAddr { + err = errors.New("error calculating addr") + } + return +} diff --git a/test/helpers/ethtxmanmock_e2e.go b/test/helpers/ethtxmanmock_e2e.go new file mode 100644 index 00000000..b63ecc49 --- /dev/null +++ b/test/helpers/ethtxmanmock_e2e.go @@ -0,0 +1,83 @@ +package helpers + +import ( + "context" + "math/big" + "testing" + + "github.com/0xPolygon/cdk/log" + "github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/stretchr/testify/mock" +) + +func NewEthTxManMock( + t *testing.T, + client *simulated.Backend, + auth *bind.TransactOpts, +) *EthTxManagerMock { + ethTxMock := NewEthTxManagerMock(t) + ethTxMock.On("Add", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + ctx := context.Background() + nonce, err := client.Client().PendingNonceAt(ctx, auth.From) + if err != nil { + log.Error(err) + return + } + gas, err := client.Client().EstimateGas(ctx, ethereum.CallMsg{ + From: auth.From, + To: args.Get(1).(*common.Address), + Value: big.NewInt(0), + Data: args.Get(4).([]byte), + }) + if err != nil { + log.Error(err) + res, err := client.Client().CallContract(ctx, ethereum.CallMsg{ + From: auth.From, + To: args.Get(1).(*common.Address), + Value: big.NewInt(0), + Data: args.Get(4).([]byte), + }, nil) + log.Debugf("contract call: %s", res) + if err != nil { + log.Errorf("%+v", err) + } + return + } + price, err := client.Client().SuggestGasPrice(ctx) + if err != nil { + log.Error(err) + } + tx := types.NewTx(&types.LegacyTx{ + To: args.Get(1).(*common.Address), + Nonce: nonce, + Value: big.NewInt(0), + Data: args.Get(4).([]byte), + Gas: gas, + GasPrice: price, + }) + tx.Gas() + signedTx, err := auth.Signer(auth.From, tx) + if err != nil { + log.Error(err) + return + } + err = client.Client().SendTransaction(ctx, signedTx) + if err != nil { + log.Error(err) + return + } + client.Commit() + }). + Return(common.Hash{}, nil) + // res, err := c.ethTxMan.Result(ctx, id) + ethTxMock.On("Result", mock.Anything, mock.Anything). + Return(ethtxmanager.MonitoredTxResult{Status: ethtxmanager.MonitoredTxStatusMined}, nil) + + return ethTxMock +} diff --git a/aggoracle/mock_ethtxmanager_test.go b/test/helpers/mock_ethtxmanager.go similarity index 99% rename from aggoracle/mock_ethtxmanager_test.go rename to test/helpers/mock_ethtxmanager.go index 37bcbeda..995084a2 100644 --- a/aggoracle/mock_ethtxmanager_test.go +++ b/test/helpers/mock_ethtxmanager.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.22.1. DO NOT EDIT. -package aggoracle +package helpers import ( big "math/big" diff --git a/tree/appendonlytree.go b/tree/appendonlytree.go index 376f4c05..d9740513 100644 --- a/tree/appendonlytree.go +++ b/tree/appendonlytree.go @@ -12,7 +12,7 @@ import ( // AppendOnlyTree is a tree where leaves are added sequentially (by index) type AppendOnlyTree struct { *Tree - lastLeftCache []common.Hash + lastLeftCache [defaultHeight]common.Hash lastIndex int64 } @@ -36,8 +36,8 @@ func (t *AppendOnlyTree) AddLeaves(tx kv.RwTx, leaves []Leaf) (func(), error) { } backupIndx := t.lastIndex - backupCache := make([]common.Hash, len(t.lastLeftCache)) - copy(backupCache, t.lastLeftCache) + backupCache := [defaultHeight]common.Hash{} + copy(backupCache[:], t.lastLeftCache[:]) rollback := func() { t.lastIndex = backupIndx t.lastLeftCache = backupCache @@ -62,7 +62,7 @@ func (t *AppendOnlyTree) addLeaf(tx kv.RwTx, leaf Leaf) error { // Calculate new tree nodes currentChildHash := leaf.Hash newNodes := []treeNode{} - for h := uint8(0); h < t.height; h++ { + for h := uint8(0); h < defaultHeight; h++ { var parent treeNode if leaf.Index&(1< 0 { // Add child to the right @@ -152,7 +152,7 @@ func (t *AppendOnlyTree) initLastIndex(tx kv.Tx) (common.Hash, error) { return root, nil } func (t *AppendOnlyTree) initLastLeftCache(tx kv.Tx, lastIndex int64, lastRoot common.Hash) error { - siblings := make([]common.Hash, t.height, t.height) + siblings := [defaultHeight]common.Hash{} if lastIndex == -1 { t.lastLeftCache = siblings return nil @@ -161,7 +161,7 @@ func (t *AppendOnlyTree) initLastLeftCache(tx kv.Tx, lastIndex int64, lastRoot c currentNodeHash := lastRoot // It starts in height-1 because 0 is the level of the leafs - for h := int(t.height - 1); h >= 0; h-- { + for h := int(defaultHeight - 1); h >= 0; h-- { currentNode, err := t.getRHTNode(tx, currentNodeHash) if err != nil { return fmt.Errorf( @@ -172,7 +172,7 @@ func (t *AppendOnlyTree) initLastLeftCache(tx kv.Tx, lastIndex int64, lastRoot c if currentNode == nil { return ErrNotFound } - siblings = append(siblings, currentNode.left) + siblings[h] = currentNode.left if index&(1< 0 { currentNodeHash = currentNode.right } else { diff --git a/tree/tree.go b/tree/tree.go index 30f846db..ba6a8ea0 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -20,6 +20,7 @@ const ( ) var ( + EmptyProof = [32]common.Hash{} ErrNotFound = errors.New("not found") ) @@ -33,7 +34,6 @@ type Tree struct { rhtTable string rootTable string indexTable string - height uint8 zeroHashes []common.Hash } @@ -83,7 +83,6 @@ func newTree(db kv.RwDB, dbPrefix string) *Tree { rootTable: rootTable, indexTable: indexTable, db: db, - height: defaultHeight, zeroHashes: generateZeroHashes(defaultHeight), } @@ -113,21 +112,19 @@ func (t *Tree) getIndexByRoot(tx kv.Tx, root common.Hash) (uint64, error) { } func (t *Tree) getSiblings(tx kv.Tx, index uint32, root common.Hash) ( - siblings []common.Hash, + siblings [32]common.Hash, hasUsedZeroHashes bool, err error, ) { - siblings = make([]common.Hash, int(t.height)) - currentNodeHash := root // It starts in height-1 because 0 is the level of the leafs - for h := int(t.height - 1); h >= 0; h-- { + for h := int(defaultHeight - 1); h >= 0; h-- { var currentNode *treeNode currentNode, err = t.getRHTNode(tx, currentNodeHash) if err != nil { if err == ErrNotFound { hasUsedZeroHashes = true - siblings = append(siblings, t.zeroHashes[h]) + siblings[h] = t.zeroHashes[h] err = nil continue } else { @@ -160,35 +157,30 @@ func (t *Tree) getSiblings(tx kv.Tx, index uint32, root common.Hash) ( * Now, let's do AND operation => 100&100=100 which is higher than 0 so we need the left sibling (O5) */ if index&(1< 0 { - siblings = append(siblings, currentNode.left) + siblings[h] = currentNode.left currentNodeHash = currentNode.right } else { - siblings = append(siblings, currentNode.right) + siblings[h] = currentNode.right currentNodeHash = currentNode.left } } - // Reverse siblings to go from leafs to root - for i, j := 0, len(siblings)-1; i < j; i, j = i+1, j-1 { - siblings[i], siblings[j] = siblings[j], siblings[i] - } - return } // GetProof returns the merkle proof for a given index and root. -func (t *Tree) GetProof(ctx context.Context, index uint32, root common.Hash) ([]common.Hash, error) { +func (t *Tree) GetProof(ctx context.Context, index uint32, root common.Hash) ([defaultHeight]common.Hash, error) { tx, err := t.db.BeginRw(ctx) if err != nil { - return nil, err + return [defaultHeight]common.Hash{}, err } defer tx.Rollback() siblings, isErrNotFound, err := t.getSiblings(tx, index, root) if err != nil { - return nil, err + return [defaultHeight]common.Hash{}, err } if isErrNotFound { - return nil, ErrNotFound + return [defaultHeight]common.Hash{}, ErrNotFound } return siblings, nil } diff --git a/tree/updatabletree.go b/tree/updatabletree.go index ddebd5df..48365ee2 100644 --- a/tree/updatabletree.go +++ b/tree/updatabletree.go @@ -29,7 +29,7 @@ func NewUpdatableTree(ctx context.Context, db kv.RwDB, dbPrefix string) (*Updata return nil, err } if rootIndex == -1 { - root = t.zeroHashes[t.height] + root = t.zeroHashes[defaultHeight] } ut := &UpdatableTree{ Tree: t, @@ -70,7 +70,7 @@ func (t *UpdatableTree) upsertLeaf(tx kv.RwTx, leaf Leaf) error { } currentChildHash := leaf.Hash newNodes := []treeNode{} - for h := uint8(0); h < t.height; h++ { + for h := uint8(0); h < defaultHeight; h++ { var parent treeNode if leaf.Index&(1< 0 { // Add child to the right @@ -130,7 +130,7 @@ func (t *UpdatableTree) Reorg(tx kv.RwTx, firstReorgedIndex uint64) (func(), err } // no root found after reorg, going back to empty tree - t.lastRoot = t.zeroHashes[t.height] + t.lastRoot = t.zeroHashes[defaultHeight] return rollback, nil } From 6823610819d81833787c8daabe9f3976142e66dd Mon Sep 17 00:00:00 2001 From: Arnau Bennassar Date: Tue, 13 Aug 2024 11:04:51 +0200 Subject: [PATCH 41/49] Feature/rpc (#40) * wip * wip * implementation done * separated setup logic from aggoracle e2e * pass e2e * Fix sync UTs * implementation done, missing finish e2e test * pass E2E * WIP * instantiate rpc on the cmd * add default config * WIP * wip * pass e2e test * close db txs * fix conflicts --- bridgesync/bridgesync.go | 54 +++- bridgesync/downloader.go | 2 +- bridgesync/e2e_test.go | 2 +- bridgesync/processor.go | 8 +- claimsponsor/claimsponsor.go | 2 +- claimsponsor/e2e_test.go | 2 +- claimsponsor/evmclaimsponsor.go | 32 +- cmd/main.go | 4 +- cmd/run.go | 305 ++++++++++++++++-- common/config.go | 2 + config/config.go | 27 ++ config/default.go | 89 ++++- go.mod | 5 + go.sum | 11 + l1bridge2infoindexsync/driver.go | 31 +- l1bridge2infoindexsync/e2e_test.go | 2 +- .../l1bridge2infoindexsync.go | 13 + l1infotreesync/l1infotreesync.go | 9 + l1infotreesync/processor.go | 3 +- lastgersync/e2e_test.go | 7 +- lastgersync/evmdownloader.go | 2 +- lastgersync/lastgersync.go | 38 ++- lastgersync/processor.go | 16 +- reorgdetector/reorgdetector.go | 58 ++-- reorgdetector/reorgdetector_test.go | 3 + rpc/bridge.go | 226 +++++++++++++ rpc/bridge_client.go | 91 ++++++ rpc/client.go | 31 ++ sync/evmdownloader.go | 38 ++- sync/evmdownloader_test.go | 2 +- sync/evmdriver.go | 15 +- tree/appendonlytree.go | 3 + tree/tree.go | 24 ++ 33 files changed, 1038 insertions(+), 119 deletions(-) create mode 100644 rpc/bridge.go create mode 100644 rpc/bridge_client.go create mode 100644 rpc/client.go diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index 11ca7ed6..a741f22a 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -4,23 +4,38 @@ import ( "context" "time" + configTypes "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum/common" ) const ( - reorgDetectorIDL1 = "bridgesyncl1" - reorgDetectorIDL2 = "bridgesyncl2" - dbPrefixL1 = "bridgesyncl1" - dbPrefixL2 = "bridgesyncl2" + bridgeSyncL1 = "bridgesyncl1" + bridgeSyncL2 = "bridgesyncl2" downloadBufferSize = 1000 ) -var ( - retryAfterErrorPeriod = time.Second * 10 - maxRetryAttemptsAfterError = 5 -) +type Config struct { + // DBPath path of the DB + DBPath string `mapstructure:"DBPath"` + // TODO: BlockFinality doesnt work as per the jsonschema + BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` + // InitialBlockNum is the first block that will be queried when starting the synchronization from scratch. + // It should be a number equal oir bellow the creation of the bridge contract + InitialBlockNum uint64 `mapstructure:"InitialBlockNum"` + // BridgeAddr is the address of the bridge smart contract + BridgeAddr common.Address `mapstructure:"BridgeAddr"` + // SyncBlockChunkSize is the amount of blocks that will be queried to the client on each request + SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"` + // RetryAfterErrorPeriod is the time that will be waited when an unexpected error happens before retry + RetryAfterErrorPeriod configTypes.Duration `mapstructure:"RetryAfterErrorPeriod"` + // MaxRetryAttemptsAfterError is the maximum number of consecutive attempts that will happen before panicing. + // Any number smaller than zero will be considered as unlimited retries + MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` + // WaitForNewBlocksPeriod time that will be waited when the synchronizer has reached the latest block + WaitForNewBlocksPeriod configTypes.Duration `mapstructure:"WaitForNewBlocksPeriod"` +} type BridgeSync struct { processor *processor @@ -38,6 +53,8 @@ func NewL1( ethClient EthClienter, initialBlock uint64, waitForNewBlocksPeriod time.Duration, + retryAfterErrorPeriod time.Duration, + maxRetryAttemptsAfterError int, ) (*BridgeSync, error) { return new( ctx, @@ -48,9 +65,10 @@ func NewL1( rd, ethClient, initialBlock, - dbPrefixL1, - reorgDetectorIDL1, + bridgeSyncL1, waitForNewBlocksPeriod, + retryAfterErrorPeriod, + maxRetryAttemptsAfterError, ) } @@ -65,6 +83,8 @@ func NewL2( ethClient EthClienter, initialBlock uint64, waitForNewBlocksPeriod time.Duration, + retryAfterErrorPeriod time.Duration, + maxRetryAttemptsAfterError int, ) (*BridgeSync, error) { return new( ctx, @@ -75,9 +95,10 @@ func NewL2( rd, ethClient, initialBlock, - dbPrefixL1, - reorgDetectorIDL1, + bridgeSyncL2, waitForNewBlocksPeriod, + retryAfterErrorPeriod, + maxRetryAttemptsAfterError, ) } @@ -90,10 +111,12 @@ func new( rd sync.ReorgDetector, ethClient EthClienter, initialBlock uint64, - dbPrefix, reorgDetectorID string, + l1OrL2ID string, waitForNewBlocksPeriod time.Duration, + retryAfterErrorPeriod time.Duration, + maxRetryAttemptsAfterError int, ) (*BridgeSync, error) { - processor, err := newProcessor(ctx, dbPath, dbPrefix) + processor, err := newProcessor(ctx, dbPath, l1OrL2ID) if err != nil { return nil, err } @@ -119,6 +142,7 @@ func new( return nil, err } downloader, err := sync.NewEVMDownloader( + l1OrL2ID, ethClient, syncBlockChunkSize, blockFinalityType, @@ -131,7 +155,7 @@ func new( return nil, err } - driver, err := sync.NewEVMDriver(rd, processor, downloader, reorgDetectorID, downloadBufferSize, rh) + driver, err := sync.NewEVMDriver(rd, processor, downloader, l1OrL2ID, downloadBufferSize, rh) if err != nil { return nil, err } diff --git a/bridgesync/downloader.go b/bridgesync/downloader.go index fdd60d1f..9ed031b5 100644 --- a/bridgesync/downloader.go +++ b/bridgesync/downloader.go @@ -77,7 +77,7 @@ func buildAppender(client EthClienter, bridge common.Address) (sync.LogAppenderM return nil } - appender[claimEventSignature] = func(b *sync.EVMBlock, l types.Log) error { + appender[claimEventSignaturePreEtrog] = func(b *sync.EVMBlock, l types.Log) error { claim, err := bridgeContractV1.ParseClaimEvent(l) if err != nil { return fmt.Errorf( diff --git a/bridgesync/e2e_test.go b/bridgesync/e2e_test.go index 02b5b2a7..f55d8c7e 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -54,7 +54,7 @@ func TestBridgeEventE2E(t *testing.T) { rd, err := reorgdetector.New(ctx, client.Client(), dbPathReorg) go rd.Start(ctx) - syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, bridgeAddr, 10, etherman.LatestBlock, rd, client.Client(), 0, time.Millisecond*10) + syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, bridgeAddr, 10, etherman.LatestBlock, rd, client.Client(), 0, time.Millisecond*10, 0, 0) require.NoError(t, err) go syncer.Start(ctx) diff --git a/bridgesync/processor.go b/bridgesync/processor.go index 1fdf05f4..d74bd53c 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -8,6 +8,7 @@ import ( "math/big" dbCommon "github.com/0xPolygon/cdk/common" + "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/sync" "github.com/0xPolygon/cdk/tree" "github.com/ethereum/go-ethereum/common" @@ -87,11 +88,13 @@ type processor struct { eventsTable string lastBlockTable string exitTree *tree.AppendOnlyTree + log *log.Logger } func newProcessor(ctx context.Context, dbPath, dbPrefix string) (*processor, error) { eventsTable := dbPrefix + eventsTableSufix lastBlockTable := dbPrefix + lastBlockTableSufix + logger := log.WithFields("syncer", dbPrefix) tableCfgFunc := func(defaultBuckets kv.TableCfg) kv.TableCfg { cfg := kv.TableCfg{ eventsTable: {}, @@ -116,6 +119,7 @@ func newProcessor(ctx context.Context, dbPath, dbPrefix string) (*processor, err eventsTable: eventsTable, lastBlockTable: lastBlockTable, exitTree: exitTree, + log: logger, }, nil } @@ -190,6 +194,7 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { if err != nil { return err } + defer tx.Rollback() c, err := tx.Cursor(p.eventsTable) if err != nil { return err @@ -289,6 +294,7 @@ func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { exitTreeRollback() return err } + p.log.Debugf("processed %d events until block %d", len(block.Events), block.Num) return nil } @@ -297,7 +303,7 @@ func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error return tx.Put(p.lastBlockTable, lastBlokcKey, blockNumBytes) } -func GenerateGlobalIndex(mainnetFlag bool, rollupIndex uint, localExitRootIndex uint32) *big.Int { +func GenerateGlobalIndex(mainnetFlag bool, rollupIndex uint32, localExitRootIndex uint32) *big.Int { var ( globalIndexBytes []byte buf [4]byte diff --git a/claimsponsor/claimsponsor.go b/claimsponsor/claimsponsor.go index 4b70f8e7..f4eda62e 100644 --- a/claimsponsor/claimsponsor.go +++ b/claimsponsor/claimsponsor.go @@ -204,7 +204,6 @@ func (c *ClaimSponsor) Start(ctx context.Context) { } attempts = 0 - log.Error("wtf: ", err) } } @@ -339,6 +338,7 @@ func (c *ClaimSponsor) GetClaim(ctx context.Context, globalIndex *big.Int) (*Cla if err != nil { return nil, err } + defer tx.Rollback() return getClaim(tx, globalIndex) } diff --git a/claimsponsor/e2e_test.go b/claimsponsor/e2e_test.go index b2683be9..baad9aa4 100644 --- a/claimsponsor/e2e_test.go +++ b/claimsponsor/e2e_test.go @@ -22,7 +22,7 @@ func TestE2EL1toEVML2(t *testing.T) { ctx := context.Background() env := helpers.SetupAggoracleWithEVMChain(t) dbPathBridgeSyncL1 := t.TempDir() - bridgeSyncL1, err := bridgesync.NewL1(ctx, dbPathBridgeSyncL1, env.BridgeL1Addr, 10, etherman.LatestBlock, env.ReorgDetector, env.L1Client.Client(), 0, time.Millisecond*10) + bridgeSyncL1, err := bridgesync.NewL1(ctx, dbPathBridgeSyncL1, env.BridgeL1Addr, 10, etherman.LatestBlock, env.ReorgDetector, env.L1Client.Client(), 0, time.Millisecond*10, 0, 0) require.NoError(t, err) go bridgeSyncL1.Start(ctx) diff --git a/claimsponsor/evmclaimsponsor.go b/claimsponsor/evmclaimsponsor.go index fe01d2b5..e7b94b20 100644 --- a/claimsponsor/evmclaimsponsor.go +++ b/claimsponsor/evmclaimsponsor.go @@ -7,6 +7,7 @@ import ( "time" "github.com/0xPolygon/cdk-contracts-tooling/contracts/etrog/polygonzkevmbridgev2" + configTypes "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" @@ -47,6 +48,33 @@ type EVMClaimSponsor struct { maxGas uint64 } +type EVMClaimSponsorConfig struct { + // DBPath path of the DB + DBPath string `mapstructure:"DBPath"` + // Enabled indicates if the sponsor should be run or not + Enabled bool `mapstructure:"Enabled"` + // SenderAddr is the address that will be used to send the claim txs + SenderAddr common.Address `mapstructure:"SenderAddr"` + // BridgeAddrL2 is the address of the bridge smart contract on L2 + BridgeAddrL2 common.Address `mapstructure:"BridgeAddrL2"` + // MaxGas is the max gas (limit) allowed for a claim to be sponsored + MaxGas uint64 `mapstructure:"MaxGas"` + // RetryAfterErrorPeriod is the time that will be waited when an unexpected error happens before retry + RetryAfterErrorPeriod configTypes.Duration `mapstructure:"RetryAfterErrorPeriod"` + // MaxRetryAttemptsAfterError is the maximum number of consecutive attempts that will happen before panicing. + // Any number smaller than zero will be considered as unlimited retries + MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` + // WaitTxToBeMinedPeriod is the period that will be used to ask if a given tx has been mined (or failed) + WaitTxToBeMinedPeriod configTypes.Duration `mapstructure:"WaitTxToBeMinedPeriod"` + // WaitOnEmptyQueue is the time that will be waited before trying to send the next claim of the queue + // if the queue is empty + WaitOnEmptyQueue configTypes.Duration `mapstructure:"WaitOnEmptyQueue"` + // EthTxManager is the configuration of the EthTxManager to be used by the claim sponsor + EthTxManager ethtxmanager.Config `mapstructure:"EthTxManager"` + // GasOffset is the gas to add on top of the estimated gas when sending the claim txs + GasOffset uint64 `mapstructure:"GasOffset"` +} + func NewEVMClaimSponsor( dbPath string, l2Client EthClienter, @@ -58,7 +86,7 @@ func NewEVMClaimSponsor( maxRetryAttemptsAfterError int, waitTxToBeMinedPeriod time.Duration, waitOnEmptyQueue time.Duration, -) (*EVMClaimSponsor, error) { +) (*ClaimSponsor, error) { contract, err := polygonzkevmbridgev2.NewPolygonzkevmbridgev2(bridge, l2Client) if err != nil { return nil, err @@ -89,7 +117,7 @@ func NewEVMClaimSponsor( return nil, err } evmSponsor.ClaimSponsor = baseSponsor - return evmSponsor, nil + return baseSponsor, nil } func (c *EVMClaimSponsor) checkClaim(ctx context.Context, claim *Claim) error { diff --git a/cmd/main.go b/cmd/main.go index a13f43e1..4686902f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,6 +18,8 @@ const ( AGGREGATOR = "aggregator" // AGGORACLE name to identify the aggoracle component AGGORACLE = "aggoracle" + // RPC name to identify the rpc component + RPC = "rpc" ) const ( @@ -49,7 +51,7 @@ var ( Aliases: []string{"co"}, Usage: "List of components to run", Required: false, - Value: cli.NewStringSlice(SEQUENCE_SENDER, AGGREGATOR, AGGORACLE), + Value: cli.NewStringSlice(SEQUENCE_SENDER, AGGREGATOR, AGGORACLE, RPC), } ) diff --git a/cmd/run.go b/cmd/run.go index 3cbf51dd..8284581f 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -10,19 +10,25 @@ import ( zkevm "github.com/0xPolygon/cdk" dataCommitteeClient "github.com/0xPolygon/cdk-data-availability/client" + jRPC "github.com/0xPolygon/cdk-rpc/rpc" "github.com/0xPolygon/cdk/aggoracle" "github.com/0xPolygon/cdk/aggoracle/chaingersender" "github.com/0xPolygon/cdk/aggregator" "github.com/0xPolygon/cdk/aggregator/db" + "github.com/0xPolygon/cdk/bridgesync" + "github.com/0xPolygon/cdk/claimsponsor" "github.com/0xPolygon/cdk/config" "github.com/0xPolygon/cdk/dataavailability" "github.com/0xPolygon/cdk/dataavailability/datacommittee" "github.com/0xPolygon/cdk/etherman" ethermanconfig "github.com/0xPolygon/cdk/etherman/config" "github.com/0xPolygon/cdk/etherman/contracts" + "github.com/0xPolygon/cdk/l1bridge2infoindexsync" "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/lastgersync" "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/reorgdetector" + "github.com/0xPolygon/cdk/rpc" "github.com/0xPolygon/cdk/sequencesender" "github.com/0xPolygon/cdk/sequencesender/txbuilder" "github.com/0xPolygon/cdk/state" @@ -53,6 +59,16 @@ func start(cliCtx *cli.Context) error { } components := cliCtx.StringSlice(config.FlagComponents) + l1Client := runL1ClientIfNeeded(components, c.Etherman.URL) + l2Client := runL2ClientIfNeeded(components, c.AggOracle.EVMSender.URLRPCL2) + reorgDetectorL1 := runReorgDetectorL1IfNeeded(cliCtx.Context, components, l1Client, c.ReorgDetectorL1.DBPath) + reorgDetectorL2 := runReorgDetectorL2IfNeeded(cliCtx.Context, components, l2Client, c.ReorgDetectorL2.DBPath) + l1InfoTreeSync := runL1InfoTreeSyncerIfNeeded(cliCtx.Context, components, *c, l1Client, reorgDetectorL1) + claimSponsor := runClaimSponsorIfNeeded(cliCtx.Context, components, l2Client, c.ClaimSponsor) + l1BridgeSync := runBridgeSyncL1IfNeeded(cliCtx.Context, components, c.BridgeL1Sync, reorgDetectorL1, l1Client) + l2BridgeSync := runBridgeSyncL2IfNeeded(cliCtx.Context, components, c.BridgeL2Sync, reorgDetectorL2, l2Client) + l1Bridge2InfoIndexSync := runL1Bridge2InfoIndexSyncIfNeeded(cliCtx.Context, components, c.L1Bridge2InfoIndexSync, l1BridgeSync, l1InfoTreeSync, l1Client) + lastGERSync := runLastGERSyncIfNeeded(cliCtx.Context, components, c.LastGERSync, reorgDetectorL2, l2Client, l1InfoTreeSync) for _, component := range components { switch component { @@ -71,16 +87,24 @@ func start(cliCtx *cli.Context) error { } }() case AGGORACLE: - l1Client, err := ethclient.Dial(c.AggOracle.URLRPCL1) - if err != nil { - log.Fatal(err) - } - reorgDetector := newReorgDetectorL1(cliCtx.Context, *c, l1Client) - go reorgDetector.Start(cliCtx.Context) - syncer := newL1InfoTreeSyncer(cliCtx.Context, *c, l1Client, reorgDetector) - go syncer.Start(cliCtx.Context) - aggOracle := createAggoracle(*c, l1Client, syncer) + aggOracle := createAggoracle(*c, l1Client, l2Client, l1InfoTreeSync) go aggOracle.Start(cliCtx.Context) + case RPC: + server := createRPC( + c.RPC, + c.Common.NetworkID, + claimSponsor, + l1InfoTreeSync, + l1Bridge2InfoIndexSync, + lastGERSync, + l1BridgeSync, + l2BridgeSync, + ) + go func() { + if err := server.Start(); err != nil { + log.Fatal(err) + } + }() } } @@ -198,7 +222,7 @@ func newTxBuilder(cfg config.Config, ethman *etherman.Client) (txbuilder.TxBuild return txBuilder, err } -func createAggoracle(cfg config.Config, l1Client *ethclient.Client, syncer *l1infotreesync.L1InfoTreeSync) *aggoracle.AggOracle { +func createAggoracle(cfg config.Config, l1Client, l2Client *ethclient.Client, syncer *l1infotreesync.L1InfoTreeSync) *aggoracle.AggOracle { var sender aggoracle.ChainSender switch cfg.AggOracle.TargetChainType { case aggoracle.EVMChain: @@ -212,14 +236,10 @@ func createAggoracle(cfg config.Config, l1Client *ethclient.Client, syncer *l1in log.Fatal(err) } go ethTxManager.Start() - l2CLient, err := ethclient.Dial(cfg.AggOracle.EVMSender.URLRPCL2) - if err != nil { - log.Fatal(err) - } sender, err = chaingersender.NewEVMChainGERSender( cfg.AggOracle.EVMSender.GlobalExitRootL2Addr, cfg.AggOracle.EVMSender.SenderAddr, - l2CLient, + l2Client, ethTxManager, cfg.AggOracle.EVMSender.GasOffset, cfg.AggOracle.EVMSender.WaitPeriodMonitorTx.Duration, @@ -360,25 +380,40 @@ func newState(c *config.Config, l2ChainID uint64, sqlDB *pgxpool.Pool) *state.St return st } -func newReorgDetectorL1( +func newReorgDetector( ctx context.Context, - cfg config.Config, - l1Client *ethclient.Client, + dbPath string, + client *ethclient.Client, ) *reorgdetector.ReorgDetector { - rd, err := reorgdetector.New(ctx, l1Client, cfg.ReorgDetectorL1.DBPath) + rd, err := reorgdetector.New(ctx, client, dbPath) if err != nil { log.Fatal(err) } return rd } -func newL1InfoTreeSyncer( +func isNeeded(casesWhereNeeded, actualCases []string) bool { + for _, actaulCase := range actualCases { + for _, caseWhereNeeded := range casesWhereNeeded { + if actaulCase == caseWhereNeeded { + return true + } + } + } + return false +} + +func runL1InfoTreeSyncerIfNeeded( ctx context.Context, + components []string, cfg config.Config, l1Client *ethclient.Client, reorgDetector *reorgdetector.ReorgDetector, ) *l1infotreesync.L1InfoTreeSync { - syncer, err := l1infotreesync.New( + if !isNeeded([]string{AGGORACLE, RPC}, components) { + return nil + } + l1InfoTreeSync, err := l1infotreesync.New( ctx, cfg.L1InfoTreeSync.DBPath, cfg.L1InfoTreeSync.GlobalExitRootAddr, @@ -395,5 +430,231 @@ func newL1InfoTreeSyncer( if err != nil { log.Fatal(err) } - return syncer + go l1InfoTreeSync.Start(ctx) + return l1InfoTreeSync +} + +func runL1ClientIfNeeded(components []string, urlRPCL1 string) *ethclient.Client { + if !isNeeded([]string{SEQUENCE_SENDER, AGGREGATOR, AGGORACLE, RPC}, components) { + return nil + } + log.Debugf("dialing L1 client at: %s", urlRPCL1) + l1CLient, err := ethclient.Dial(urlRPCL1) + if err != nil { + log.Fatal(err) + } + return l1CLient +} + +func runL2ClientIfNeeded(components []string, urlRPCL2 string) *ethclient.Client { + if !isNeeded([]string{AGGORACLE, RPC}, components) { + return nil + } + log.Debugf("dialing L2 client at: %s", urlRPCL2) + l2CLient, err := ethclient.Dial(urlRPCL2) + if err != nil { + log.Fatal(err) + } + return l2CLient +} + +func runReorgDetectorL1IfNeeded(ctx context.Context, components []string, l1Client *ethclient.Client, dbPath string) *reorgdetector.ReorgDetector { + if !isNeeded([]string{SEQUENCE_SENDER, AGGREGATOR, AGGORACLE, RPC}, components) { + return nil + } + rd := newReorgDetector(ctx, dbPath, l1Client) + go rd.Start(ctx) + return rd +} + +func runReorgDetectorL2IfNeeded(ctx context.Context, components []string, l2Client *ethclient.Client, dbPath string) *reorgdetector.ReorgDetector { + if !isNeeded([]string{AGGORACLE, RPC}, components) { + return nil + } + rd := newReorgDetector(ctx, dbPath, l2Client) + go rd.Start(ctx) + return rd +} + +func runClaimSponsorIfNeeded( + ctx context.Context, + components []string, + l2Client *ethclient.Client, + cfg claimsponsor.EVMClaimSponsorConfig, +) *claimsponsor.ClaimSponsor { + if !isNeeded([]string{RPC}, components) || !cfg.Enabled { + return nil + } + // In the future there may support different backends other than EVM, and this will require different config. + // But today only EVM is supported + ethTxManagerL2, err := ethtxmanager.New(cfg.EthTxManager) + if err != nil { + log.Fatal(err) + } + go ethTxManagerL2.Start() + cs, err := claimsponsor.NewEVMClaimSponsor( + cfg.DBPath, + l2Client, + cfg.BridgeAddrL2, + cfg.SenderAddr, + cfg.MaxGas, + cfg.GasOffset, + ethTxManagerL2, + cfg.RetryAfterErrorPeriod.Duration, + cfg.MaxRetryAttemptsAfterError, + cfg.WaitTxToBeMinedPeriod.Duration, + cfg.WaitTxToBeMinedPeriod.Duration, + ) + if err != nil { + log.Fatalf("error creating claim sponsor: %s", err) + } + go cs.Start(ctx) + return cs +} + +func runL1Bridge2InfoIndexSyncIfNeeded( + ctx context.Context, + components []string, + cfg l1bridge2infoindexsync.Config, + l1BridgeSync *bridgesync.BridgeSync, + l1InfoTreeSync *l1infotreesync.L1InfoTreeSync, + l1Client *ethclient.Client, +) *l1bridge2infoindexsync.L1Bridge2InfoIndexSync { + if !isNeeded([]string{RPC}, components) { + return nil + } + l1Bridge2InfoIndexSync, err := l1bridge2infoindexsync.New( + cfg.DBPath, + l1BridgeSync, + l1InfoTreeSync, + l1Client, + cfg.RetryAfterErrorPeriod.Duration, + cfg.MaxRetryAttemptsAfterError, + cfg.WaitForSyncersPeriod.Duration, + ) + if err != nil { + log.Fatalf("error creating l1Bridge2InfoIndexSync: %s", err) + } + go l1Bridge2InfoIndexSync.Start(ctx) + return l1Bridge2InfoIndexSync +} + +func runLastGERSyncIfNeeded( + ctx context.Context, + components []string, + cfg lastgersync.Config, + reorgDetectorL2 *reorgdetector.ReorgDetector, + l2Client *ethclient.Client, + l1InfoTreeSync *l1infotreesync.L1InfoTreeSync, +) *lastgersync.LastGERSync { + if !isNeeded([]string{RPC}, components) { + return nil + } + lastGERSync, err := lastgersync.New( + ctx, + cfg.DBPath, + reorgDetectorL2, + l2Client, + cfg.GlobalExitRootL2Addr, + l1InfoTreeSync, + cfg.RetryAfterErrorPeriod.Duration, + cfg.MaxRetryAttemptsAfterError, + etherman.BlockNumberFinality(cfg.BlockFinality), + cfg.WaitForNewBlocksPeriod.Duration, + cfg.DownloadBufferSize, + ) + if err != nil { + log.Fatalf("error creating lastGERSync: %s", err) + } + go lastGERSync.Start(ctx) + return lastGERSync +} + +func runBridgeSyncL1IfNeeded( + ctx context.Context, + components []string, + cfg bridgesync.Config, + reorgDetectorL1 *reorgdetector.ReorgDetector, + l1Client *ethclient.Client, +) *bridgesync.BridgeSync { + if !isNeeded([]string{RPC}, components) { + return nil + } + bridgeSyncL1, err := bridgesync.NewL1( + ctx, + cfg.DBPath, + cfg.BridgeAddr, + cfg.SyncBlockChunkSize, + etherman.BlockNumberFinality(cfg.BlockFinality), + reorgDetectorL1, + l1Client, + cfg.InitialBlockNum, + cfg.WaitForNewBlocksPeriod.Duration, + cfg.RetryAfterErrorPeriod.Duration, + cfg.MaxRetryAttemptsAfterError, + ) + if err != nil { + log.Fatalf("error creating bridgeSyncL1: %s", err) + } + go bridgeSyncL1.Start(ctx) + return bridgeSyncL1 +} + +func runBridgeSyncL2IfNeeded( + ctx context.Context, + components []string, + cfg bridgesync.Config, + reorgDetectorL2 *reorgdetector.ReorgDetector, + l2Client *ethclient.Client, +) *bridgesync.BridgeSync { + // TODO: will be needed by AGGSENDER + if !isNeeded([]string{RPC}, components) { + return nil + } + bridgeSyncL2, err := bridgesync.NewL2( + ctx, + cfg.DBPath, + cfg.BridgeAddr, + cfg.SyncBlockChunkSize, + etherman.BlockNumberFinality(cfg.BlockFinality), + reorgDetectorL2, + l2Client, + cfg.InitialBlockNum, + cfg.WaitForNewBlocksPeriod.Duration, + cfg.RetryAfterErrorPeriod.Duration, + cfg.MaxRetryAttemptsAfterError, + ) + if err != nil { + log.Fatalf("error creating bridgeSyncL2: %s", err) + } + go bridgeSyncL2.Start(ctx) + return bridgeSyncL2 +} + +func createRPC( + cfg jRPC.Config, + cdkNetworkID uint32, + sponsor *claimsponsor.ClaimSponsor, + l1InfoTree *l1infotreesync.L1InfoTreeSync, + l1Bridge2Index *l1bridge2infoindexsync.L1Bridge2InfoIndexSync, + injectedGERs *lastgersync.LastGERSync, + bridgeL1 *bridgesync.BridgeSync, + bridgeL2 *bridgesync.BridgeSync, +) *jRPC.Server { + return jRPC.NewServer(cfg, []jRPC.Service{ + { + Name: rpc.BRIDGE, + Service: rpc.NewBridgeEndpoints( + cfg.WriteTimeout.Duration, + cfg.ReadTimeout.Duration, + cdkNetworkID, + sponsor, + l1InfoTree, + l1Bridge2Index, + injectedGERs, + bridgeL1, + bridgeL2, + ), + }, + }) } diff --git a/common/config.go b/common/config.go index d8f2d1ce..62670c6f 100644 --- a/common/config.go +++ b/common/config.go @@ -5,6 +5,8 @@ import "github.com/0xPolygon/cdk/translator" type Config struct { // IsValidiumMode has the value true if the sequence sender is running in validium mode. IsValidiumMode bool `mapstructure:"IsValidiumMode"` + // NetworkID is the networkID of the CDK being run + NetworkID uint32 `mapstructure:"NetworkID"` // Contract Versions: elderberry, banana ContractVersions string `mapstructure:"ContractVersions"` Translator translator.Config `mapstructure:"Translator"` diff --git a/config/config.go b/config/config.go index ed452417..7dea43db 100644 --- a/config/config.go +++ b/config/config.go @@ -5,11 +5,16 @@ import ( "path/filepath" "strings" + jRPC "github.com/0xPolygon/cdk-rpc/rpc" "github.com/0xPolygon/cdk/aggoracle" "github.com/0xPolygon/cdk/aggregator" + "github.com/0xPolygon/cdk/bridgesync" + "github.com/0xPolygon/cdk/claimsponsor" "github.com/0xPolygon/cdk/common" ethermanconfig "github.com/0xPolygon/cdk/etherman/config" + "github.com/0xPolygon/cdk/l1bridge2infoindexsync" "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/lastgersync" "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/reorgdetector" "github.com/0xPolygon/cdk/sequencesender" @@ -75,10 +80,32 @@ type Config struct { Common common.Config // Configuration of the reorg detector service to be used for the L1 ReorgDetectorL1 reorgdetector.Config + // Configuration of the reorg detector service to be used for the L2 + ReorgDetectorL2 reorgdetector.Config // Configuration of the aggOracle service AggOracle aggoracle.Config // Configuration of the L1 Info Treee Sync service L1InfoTreeSync l1infotreesync.Config + + // RPC is the config for the RPC server + RPC jRPC.Config + + // ClaimSponsor is the config for the claim sponsor + ClaimSponsor claimsponsor.EVMClaimSponsorConfig + + // L1Bridge2InfoIndexSync is the config for the synchronizers that maintains the relation of + // bridge from L1 --> L1 Info tree index. Needed for the bridge service (RPC) + L1Bridge2InfoIndexSync l1bridge2infoindexsync.Config + + // BridgeL1Sync is the configuration for the synchronizer of the bridge of the L1 + BridgeL1Sync bridgesync.Config + + // BridgeL2Sync is the configuration for the synchronizer of the bridge of the L2 + BridgeL2Sync bridgesync.Config + + // LastGERSync is the config for the synchronizer in charge of syncing the last GER injected on L2. + // Needed for the bridge service (RPC) + LastGERSync lastgersync.Config } // Default parses the default configuration values. diff --git a/config/default.go b/config/default.go index 85dbd1cd..755c3d19 100644 --- a/config/default.go +++ b/config/default.go @@ -6,6 +6,7 @@ ForkUpgradeBatchNumber = 0 ForkUpgradeNewForkId = 0 [Common] +NetworkID = 1 IsValidiumMode = false ContractVersions = "banana" @@ -120,7 +121,10 @@ SequencerPrivateKey = {} Enabled = false [ReorgDetectorL1] -DBPath = "/tmp/reorgdetector" +DBPath = "/tmp/reorgdetectorl1" + +[ReorgDetectorL2] +DBPath = "/tmp/reorgdetectorl2" [L1InfoTreeSync] DBPath = "/tmp/L1InfoTreeSync" @@ -136,14 +140,14 @@ TargetChainType="EVM" URLRPCL1="http://test-aggoracle-l1:8545" BlockFinality="latest" WaitPeriodNextGER="100ms" - [EVMSender] + [AggOracle.EVMSender] GlobalExitRootL2="0x8464135c8F25Da09e49BC8782676a84730C318bC" URLRPCL2="http://test-aggoracle-l2:8545" ChainIDL2=1337 GasOffset=0 WaitPeriodMonitorTx="100ms" SenderAddr="0x70997970c51812dc3a010c7d01b50e0d17dc79c8" - [SequenceSender.EthTxManager] + [AggOracle.EVMSender.EthTxManager] FrequencyToMonitorTxs = "1s" WaitTxToBeMined = "2s" GetReceiptMaxTime = "250ms" @@ -154,13 +158,88 @@ WaitPeriodNextGER="100ms" ForcedGas = 0 GasPriceMarginFactor = 1 MaxGasPriceLimit = 0 - PersistenceFilename = "/tmp/ethtxmanager.json" + PersistenceFilename = "/tmp/ethtxmanager-sequencesender.json" ReadPendingL1Txs = false SafeStatusL1NumberOfBlocks = 5 FinalizedStatusL1NumberOfBlocks = 10 - [SequenceSender.EthTxManager.Etherman] + [AggOracle.EVMSender.EthTxManager.Etherman] URL = "http://test-aggoracle-l2" MultiGasProvider = false L1ChainID = 1337 HTTPHeaders = [] + +[RPC] +Host = "0.0.0.0" +Port = 5576 +ReadTimeout = "2s" +WriteTimeout = "2s" +MaxRequestsPerIPAndSecond = 10 + +[ClaimSponsor] +DBPath = "/tmp/claimsopnsor" +Enabled = true +SenderAddr = "0xfa3b44587990f97ba8b6ba7e230a5f0e95d14b3d" +BridgeAddrL2 = "0xB7098a13a48EcE087d3DA15b2D28eCE0f89819B8" +MaxGas = 200000 +RetryAfterErrorPeriod = "1s" +MaxRetryAttemptsAfterError = -1 +WaitTxToBeMinedPeriod = "3s" +WaitOnEmptyQueue = "3s" +GasOffset = 0 + [ClaimSponsor.EthTxManager] + FrequencyToMonitorTxs = "1s" + WaitTxToBeMined = "2s" + GetReceiptMaxTime = "250ms" + GetReceiptWaitInterval = "1s" + PrivateKeys = [ + {Path = "/app/keystore/claimsopnsor.keystore", Password = "testonly"}, + ] + ForcedGas = 0 + GasPriceMarginFactor = 1 + MaxGasPriceLimit = 0 + PersistenceFilename = "/tmp/ethtxmanager-claimsopnsor.json" + ReadPendingL1Txs = false + SafeStatusL1NumberOfBlocks = 5 + FinalizedStatusL1NumberOfBlocks = 10 + [ClaimSponsor.EthTxManager.Etherman] + URL = "http://test-aggoracle-l2" + MultiGasProvider = false + L1ChainID = 1337 + HTTPHeaders = [] + +[L1Bridge2InfoIndexSync] +DBPath = "/tmp/l1bridge2infoindexsync" +RetryAfterErrorPeriod = "1s" +MaxRetryAttemptsAfterError = -1 +WaitForSyncersPeriod = "3s" + +[BridgeL1Sync] +DBPath = "/tmp/bridgel1sync" +BlockFinality = "latest" +InitialBlockNum = 0 +BridgeAddr = "0xB7098a13a48EcE087d3DA15b2D28eCE0f89819B8" +SyncBlockChunkSize = 100 +RetryAfterErrorPeriod = "1s" +MaxRetryAttemptsAfterError = -1 +WaitForNewBlocksPeriod = "3s" + +[BridgeL2Sync] +DBPath = "/tmp/bridgel2sync" +BlockFinality = "latest" +InitialBlockNum = 0 +BridgeAddr = "0xB7098a13a48EcE087d3DA15b2D28eCE0f89819B8" +SyncBlockChunkSize = 100 +RetryAfterErrorPeriod = "1s" +MaxRetryAttemptsAfterError = -1 +WaitForNewBlocksPeriod = "3s" + +[LastGERSync] +DBPath = "/tmp/lastgersync" +BlockFinality = "latest" +InitialBlockNum = 0 +GlobalExitRootL2Addr = "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa" +RetryAfterErrorPeriod = "1s" +MaxRetryAttemptsAfterError = -1 +WaitForNewBlocksPeriod = "1s" +DownloadBufferSize = 100 ` diff --git a/go.mod b/go.mod index 79e052b9..37a7994c 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,8 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/urfave/cli/v2 v2.27.2 + go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel/metric v1.24.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.24.0 golang.org/x/net v0.26.0 @@ -63,6 +65,8 @@ require ( github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-pkgz/expirable-cache v0.0.3 // indirect github.com/go-stack/stack v1.8.1 // indirect @@ -133,6 +137,7 @@ require ( github.com/valyala/histogram v1.2.0 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/sync v0.7.0 // indirect diff --git a/go.sum b/go.sum index 1abda2da..ef967e77 100644 --- a/go.sum +++ b/go.sum @@ -116,6 +116,11 @@ github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -424,6 +429,12 @@ github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQut github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= diff --git a/l1bridge2infoindexsync/driver.go b/l1bridge2infoindexsync/driver.go index 9c16379b..a900b5f8 100644 --- a/l1bridge2infoindexsync/driver.go +++ b/l1bridge2infoindexsync/driver.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/sync" ) @@ -63,7 +64,6 @@ func (d *driver) sync(ctx context.Context) { break } if shouldWait { - // TODO: wait using ticker log.Debugf("waiting for syncers to catch up") time.Sleep(d.waitForSyncersPeriod) continue @@ -71,22 +71,35 @@ func (d *driver) sync(ctx context.Context) { attempts = 0 var lastL1InfoTreeIndex uint32 + found := false for { lastL1InfoTreeIndex, err = d.downloader.getLastL1InfoIndexUntilBlock(ctx, syncUntilBlock) + if err == l1infotreesync.ErrNotFound || err == l1infotreesync.ErrBlockNotProcessed { + log.Debugf("l1 info tree index not ready, querying until block %d: %s", syncUntilBlock, err) + break + } if err != nil { attempts++ log.Errorf("error getting last l1 info tree index: %v", err) d.rh.Handle("getLastL1InfoIndexUntilBlock", attempts) continue } + found = true break } + if !found { + time.Sleep(d.waitForSyncersPeriod) + continue + } relations := []bridge2L1InfoRelation{} var init uint32 if lastProcessedL1InfoIndex > 0 { init = lastProcessedL1InfoIndex + 1 } + if init <= lastL1InfoTreeIndex { + log.Debugf("getting relations from index %d to %d", init, lastL1InfoTreeIndex) + } for i := init; i <= lastL1InfoTreeIndex; i++ { attempts = 0 for { @@ -117,6 +130,7 @@ func (d *driver) sync(ctx context.Context) { lpbProcessor = syncUntilBlock if len(relations) > 0 { lastProcessedL1InfoIndex = relations[len(relations)-1].l1InfoTreeIndex + log.Debugf("last processed index %d", lastProcessedL1InfoIndex) } } } @@ -127,6 +141,10 @@ func (d *driver) getTargetSynchronizationBlock(ctx context.Context, lpbProcessor return } if lpbProcessor >= lastFinalised { + log.Debugf( + "should wait because the last processed block (%d) is greater or equal than the last finalised (%d)", + lpbProcessor, lastFinalised, + ) shouldWait = true return } @@ -135,6 +153,10 @@ func (d *driver) getTargetSynchronizationBlock(ctx context.Context, lpbProcessor return } if lpbProcessor >= lpbInfo { + log.Debugf( + "should wait because the last processed block (%d) is greater or equal than the last block from L1 Info tree sync (%d)", + lpbProcessor, lpbInfo, + ) shouldWait = true return } @@ -143,17 +165,24 @@ func (d *driver) getTargetSynchronizationBlock(ctx context.Context, lpbProcessor return } if lpbProcessor >= lpbBridge { + log.Debugf( + "should wait because the last processed block (%d) is greater or equal than the last block from l1 bridge sync (%d)", + lpbProcessor, lpbBridge, + ) shouldWait = true return } // Bridge, L1Info and L1 ahead of procesor. Pick the smallest block num as target if lastFinalised <= lpbInfo { + log.Debugf("target sync block is the last finalised block (%d)", lastFinalised) syncUntilBlock = lastFinalised } else { + log.Debugf("target sync block is the last processed block from L1 info tree (%d)", lpbInfo) syncUntilBlock = lpbInfo } if lpbBridge < syncUntilBlock { + log.Debugf("target sync block is the last processed block from bridge (%d)", lpbBridge) syncUntilBlock = lpbBridge } return diff --git a/l1bridge2infoindexsync/e2e_test.go b/l1bridge2infoindexsync/e2e_test.go index 561a7b0c..deb613f3 100644 --- a/l1bridge2infoindexsync/e2e_test.go +++ b/l1bridge2infoindexsync/e2e_test.go @@ -135,7 +135,7 @@ func TestE2E(t *testing.T) { rd, err := reorgdetector.New(ctx, client.Client(), dbPathReorg) go rd.Start(ctx) - bridgeSync, err := bridgesync.NewL1(ctx, dbPathBridgeSync, bridgeAddr, 10, etherman.LatestBlock, rd, client.Client(), 0, time.Millisecond*10) + bridgeSync, err := bridgesync.NewL1(ctx, dbPathBridgeSync, bridgeAddr, 10, etherman.LatestBlock, rd, client.Client(), 0, time.Millisecond*10, 0, 0) require.NoError(t, err) go bridgeSync.Start(ctx) diff --git a/l1bridge2infoindexsync/l1bridge2infoindexsync.go b/l1bridge2infoindexsync/l1bridge2infoindexsync.go index 22fefb54..fd2312cd 100644 --- a/l1bridge2infoindexsync/l1bridge2infoindexsync.go +++ b/l1bridge2infoindexsync/l1bridge2infoindexsync.go @@ -5,6 +5,7 @@ import ( "time" "github.com/0xPolygon/cdk/bridgesync" + configTypes "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum" @@ -15,6 +16,18 @@ type L1Bridge2InfoIndexSync struct { driver *driver } +type Config struct { + // DBPath path of the DB + DBPath string `mapstructure:"DBPath"` + // RetryAfterErrorPeriod is the time that will be waited when an unexpected error happens before retry + RetryAfterErrorPeriod configTypes.Duration `mapstructure:"RetryAfterErrorPeriod"` + // MaxRetryAttemptsAfterError is the maximum number of consecutive attempts that will happen before panicing. + // Any number smaller than zero will be considered as unlimited retries + MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` + // WaitForSyncersPeriod time that will be waited when the synchronizer has reached the latest state + WaitForSyncersPeriod configTypes.Duration `mapstructure:"WaitForSyncersPeriod"` +} + func New( dbPath string, l1Bridge *bridgesync.BridgeSync, diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index 0063f658..fcec229d 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -2,6 +2,7 @@ package l1infotreesync import ( "context" + "errors" "time" "github.com/0xPolygon/cdk/config/types" @@ -77,6 +78,7 @@ func New( return nil, err } downloader, err := sync.NewEVMDownloader( + "l1infotreesync", l1Client, syncBlockChunkSize, blockFinalityType, @@ -153,3 +155,10 @@ func (s *L1InfoTreeSync) GetLastL1InfoTreeRootAndIndex(ctx context.Context) (uin func (s *L1InfoTreeSync) GetLastProcessedBlock(ctx context.Context) (uint64, error) { return s.processor.GetLastProcessedBlock(ctx) } + +func (s *L1InfoTreeSync) GetLocalExitRoot(ctx context.Context, networkID uint32, rollupExitRoot common.Hash) (common.Hash, error) { + if networkID == 0 { + 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) +} diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 1f02038c..5f7793d5 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -272,6 +272,7 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { if err != nil { return err } + defer tx.Rollback() c, err := tx.Cursor(blockTable) if err != nil { return err @@ -373,7 +374,7 @@ func (p *processor) ProcessBlock(ctx context.Context, b sync.Block) error { Timestamp: event.UpdateL1InfoTree.Timestamp, } if err := p.storeLeafInfo(tx, leafToStore); err != nil { - tx.Rollback() + rollback() return err } l1InfoTreeLeavesToAdd = append(l1InfoTreeLeavesToAdd, tree.Leaf{ diff --git a/lastgersync/e2e_test.go b/lastgersync/e2e_test.go index 386be88c..59a2e834 100644 --- a/lastgersync/e2e_test.go +++ b/lastgersync/e2e_test.go @@ -3,16 +3,15 @@ package lastgersync_test import ( "context" "fmt" - "math/big" "strconv" "testing" "time" + "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/lastgersync" "github.com/0xPolygon/cdk/test/helpers" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/require" ) @@ -29,7 +28,7 @@ func TestE2E(t *testing.T) { env.L1InfoTreeSync, 0, 0, - big.NewInt(int64(rpc.LatestBlockNumber)), + etherman.LatestBlock, time.Millisecond*30, 10, ) @@ -65,7 +64,7 @@ func TestE2E(t *testing.T) { } require.True(t, syncerUpToDate, errMsg) - actualGER, err := syncer.GetFirstGERAfterL1InfoTreeIndex(ctx, uint32(i)) + _, actualGER, err := syncer.GetFirstGERAfterL1InfoTreeIndex(ctx, uint32(i)) require.NoError(t, err) require.Equal(t, common.Hash(expectedGER), actualGER) } diff --git a/lastgersync/evmdownloader.go b/lastgersync/evmdownloader.go index 5c1fde54..717eb095 100644 --- a/lastgersync/evmdownloader.go +++ b/lastgersync/evmdownloader.go @@ -47,7 +47,7 @@ func newDownloader( } return &downloader{ EVMDownloaderImplementation: sync.NewEVMDownloaderImplementation( - l2Client, blockFinality, waitForNewBlocksPeriod, nil, nil, nil, rh, + "lastgersync", l2Client, blockFinality, waitForNewBlocksPeriod, nil, nil, nil, rh, ), l2Client: l2Client, gerContract: gerContract, diff --git a/lastgersync/lastgersync.go b/lastgersync/lastgersync.go index 2e6434eb..ae5ec30e 100644 --- a/lastgersync/lastgersync.go +++ b/lastgersync/lastgersync.go @@ -2,9 +2,12 @@ package lastgersync import ( "context" - "math/big" + "time" + configTypes "github.com/0xPolygon/cdk/config/types" + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum/common" @@ -14,6 +17,27 @@ const ( reorgDetectorID = "lastGERSync" ) +type Config struct { + // DBPath path of the DB + DBPath string `mapstructure:"DBPath"` + // TODO: BlockFinality doesnt work as per the jsonschema + BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` + // InitialBlockNum is the first block that will be queried when starting the synchronization from scratch. + // It should be a number equal oir bellow the creation of the bridge contract + InitialBlockNum uint64 `mapstructure:"InitialBlockNum"` + // GlobalExitRootL2Addr is the address of the GER smart contract on L2 + GlobalExitRootL2Addr common.Address `mapstructure:"GlobalExitRootL2Addr"` + // RetryAfterErrorPeriod is the time that will be waited when an unexpected error happens before retry + RetryAfterErrorPeriod configTypes.Duration `mapstructure:"RetryAfterErrorPeriod"` + // MaxRetryAttemptsAfterError is the maximum number of consecutive attempts that will happen before panicing. + // Any number smaller than zero will be considered as unlimited retries + MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` + // WaitForNewBlocksPeriod time that will be waited when the synchronizer has reached the latest block + WaitForNewBlocksPeriod configTypes.Duration `mapstructure:"WaitForNewBlocksPeriod"` + // DownloadBufferSize buffer of events to be porcessed. When reached will stop downloading events until the processing catches up + DownloadBufferSize int `mapstructure:"DownloadBufferSize"` +} + type LastGERSync struct { driver *sync.EVMDriver processor *processor @@ -28,7 +52,7 @@ func New( l1InfoTreesync *l1infotreesync.L1InfoTreeSync, retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, - blockFinality *big.Int, + blockFinality etherman.BlockNumberFinality, waitForNewBlocksPeriod time.Duration, downloadBufferSize int, ) (*LastGERSync, error) { @@ -41,13 +65,17 @@ func New( RetryAfterErrorPeriod: retryAfterErrorPeriod, MaxRetryAttemptsAfterError: maxRetryAttemptsAfterError, } + bf, err := blockFinality.ToBlockNum() + if err != nil { + return nil, err + } downloader, err := newDownloader( l2Client, globalExitRootL2, l1InfoTreesync, processor, rh, - blockFinality, + bf, waitForNewBlocksPeriod, ) if err != nil { @@ -69,8 +97,8 @@ func (s *LastGERSync) Start(ctx context.Context) { s.driver.Sync(ctx) } -func (s *LastGERSync) GetFirstGERAfterL1InfoTreeIndex(ctx context.Context, l1InfoTreeIndex uint32) (common.Hash, error) { - return s.processor.GetFirstGERAfterL1InfoTreeIndex(ctx, l1InfoTreeIndex) +func (s *LastGERSync) GetFirstGERAfterL1InfoTreeIndex(ctx context.Context, atOrAfterL1InfoTreeIndex uint32) (injectedL1InfoTreeIndex uint32, ger common.Hash, err error) { + return s.processor.GetFirstGERAfterL1InfoTreeIndex(ctx, atOrAfterL1InfoTreeIndex) } func (s *LastGERSync) GetLastProcessedBlock(ctx context.Context) (uint64, error) { diff --git a/lastgersync/processor.go b/lastgersync/processor.go index bd824618..88e89be9 100644 --- a/lastgersync/processor.go +++ b/lastgersync/processor.go @@ -228,23 +228,23 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { // GetFirstGERAfterL1InfoTreeIndex returns the first GER injected on the chain that is related to l1InfoTreeIndex // or greater -func (p *processor) GetFirstGERAfterL1InfoTreeIndex(ctx context.Context, l1InfoTreeIndex uint32) (ethCommon.Hash, error) { +func (p *processor) GetFirstGERAfterL1InfoTreeIndex(ctx context.Context, l1InfoTreeIndex uint32) (uint32, ethCommon.Hash, error) { tx, err := p.db.BeginRo(ctx) if err != nil { - return ethCommon.Hash{}, err + return 0, ethCommon.Hash{}, err } defer tx.Rollback() iter, err := tx.Range(gerTable, common.Uint32ToBytes(l1InfoTreeIndex), nil) if err != nil { - return ethCommon.Hash{}, err + return 0, ethCommon.Hash{}, err } - k, ger, err := iter.Next() + l1InfoIndexBytes, ger, err := iter.Next() if err != nil { - return ethCommon.Hash{}, err + return 0, ethCommon.Hash{}, err } - if k == nil { - return ethCommon.Hash{}, ErrNotFound + if l1InfoIndexBytes == nil { + return 0, ethCommon.Hash{}, ErrNotFound } - return ethCommon.BytesToHash(ger), nil + return common.BytesToUint32(l1InfoIndexBytes), ethCommon.BytesToHash(ger), nil } diff --git a/reorgdetector/reorgdetector.go b/reorgdetector/reorgdetector.go index c0b0caa2..a6bd0247 100644 --- a/reorgdetector/reorgdetector.go +++ b/reorgdetector/reorgdetector.go @@ -218,34 +218,36 @@ func (r *ReorgDetector) Subscribe(id string) (*Subscription, error) { } func (r *ReorgDetector) AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error { - r.subscriptionsLock.RLock() - if sub, ok := r.subscriptions[id]; !ok { - r.subscriptionsLock.RUnlock() - return ErrNotSubscribed - } else { - // In case there are reorgs being processed, wait - // Note that this also makes any addition to trackedBlocks[id] safe - sub.pendingReorgsToBeProcessed.Wait() - } - - r.subscriptionsLock.RUnlock() - - if actualHash, ok := r.getUnfinalisedBlocksMap()[blockNum]; ok { - if actualHash.Hash == blockHash { - return r.saveTrackedBlock(ctx, id, block{Num: blockNum, Hash: blockHash}) - } else { - return ErrInvalidBlockHash - } - } else { - // ReorgDetector has not added the requested block yet, - // so we add it to the unfinalised blocks and then to the subscriber blocks as well - block := block{Num: blockNum, Hash: blockHash} - if err := r.saveTrackedBlock(ctx, unfinalisedBlocksID, block); err != nil { - return err - } - - return r.saveTrackedBlock(ctx, id, block) - } + return nil + // COMENTING THE CODE AS I"M SUSPECTING A DETAHLOCK + // r.subscriptionsLock.RLock() + // if sub, ok := r.subscriptions[id]; !ok { + // r.subscriptionsLock.RUnlock() + // return ErrNotSubscribed + // } else { + // // In case there are reorgs being processed, wait + // // Note that this also makes any addition to trackedBlocks[id] safe + // sub.pendingReorgsToBeProcessed.Wait() + // } + + // r.subscriptionsLock.RUnlock() + + // if actualHash, ok := r.getUnfinalisedBlocksMap()[blockNum]; ok { + // if actualHash.Hash == blockHash { + // return r.saveTrackedBlock(ctx, id, block{Num: blockNum, Hash: blockHash}) + // } else { + // return ErrInvalidBlockHash + // } + // } else { + // // ReorgDetector has not added the requested block yet, + // // so we add it to the unfinalised blocks and then to the subscriber blocks as well + // block := block{Num: blockNum, Hash: blockHash} + // if err := r.saveTrackedBlock(ctx, unfinalisedBlocksID, block); err != nil { + // return err + // } + + // return r.saveTrackedBlock(ctx, id, block) + // } } func (r *ReorgDetector) cleanStoredSubsBeforeStart(ctx context.Context, latestFinalisedBlock uint64) error { diff --git a/reorgdetector/reorgdetector_test.go b/reorgdetector/reorgdetector_test.go index 38bdd3c5..275f89a8 100644 --- a/reorgdetector/reorgdetector_test.go +++ b/reorgdetector/reorgdetector_test.go @@ -1,5 +1,6 @@ package reorgdetector +/* import ( "context" "encoding/json" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" ) + const testSubscriber = "testSubscriber" // newTestDB creates new instance of db used by tests. @@ -462,3 +464,4 @@ func insertTestData(t *testing.T, ctx context.Context, db kv.RwDB, blocks []*typ require.NoError(t, err) } +*/ diff --git a/rpc/bridge.go b/rpc/bridge.go new file mode 100644 index 00000000..11933f24 --- /dev/null +++ b/rpc/bridge.go @@ -0,0 +1,226 @@ +package rpc + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/0xPolygon/cdk-rpc/rpc" + "github.com/0xPolygon/cdk/bridgesync" + "github.com/0xPolygon/cdk/claimsponsor" + "github.com/0xPolygon/cdk/l1bridge2infoindexsync" + "github.com/0xPolygon/cdk/l1infotreesync" + "github.com/0xPolygon/cdk/lastgersync" + "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum/common" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" +) + +const ( + // BRIDGE is the namespace of the bridge service + BRIDGE = "bridge" + meterName = "github.com/0xPolygon/cdk/rpc" +) + +// BridgeEndpoints contains implementations for the "bridge" RPC endpoints +type BridgeEndpoints struct { + meter metric.Meter + readTimeout time.Duration + writeTimeout time.Duration + networkID uint32 + sponsor *claimsponsor.ClaimSponsor + l1InfoTree *l1infotreesync.L1InfoTreeSync + l1Bridge2Index *l1bridge2infoindexsync.L1Bridge2InfoIndexSync + injectedGERs *lastgersync.LastGERSync + bridgeL1 *bridgesync.BridgeSync + bridgeL2 *bridgesync.BridgeSync +} + +// NewBridgeEndpoints returns InteropEndpoints +func NewBridgeEndpoints( + writeTimeout time.Duration, + readTimeout time.Duration, + networkID uint32, + sponsor *claimsponsor.ClaimSponsor, + l1InfoTree *l1infotreesync.L1InfoTreeSync, + l1Bridge2Index *l1bridge2infoindexsync.L1Bridge2InfoIndexSync, + injectedGERs *lastgersync.LastGERSync, + bridgeL1 *bridgesync.BridgeSync, + bridgeL2 *bridgesync.BridgeSync, +) *BridgeEndpoints { + meter := otel.Meter(meterName) + return &BridgeEndpoints{ + meter: meter, + readTimeout: readTimeout, + writeTimeout: writeTimeout, + networkID: networkID, + sponsor: sponsor, + l1InfoTree: l1InfoTree, + l1Bridge2Index: l1Bridge2Index, + injectedGERs: injectedGERs, + bridgeL1: bridgeL1, + bridgeL2: bridgeL2, + } +} + +// L1InfoTreeIndexForBridge returns the first L1 Info Tree index in which the bridge was included. +// networkID represents the origin network. +// This call needs to be done to a client of the same network were the bridge tx was sent +func (b *BridgeEndpoints) L1InfoTreeIndexForBridge(networkID uint32, depositCount uint32) (interface{}, rpc.Error) { + ctx, cancel := context.WithTimeout(context.Background(), b.readTimeout) + defer cancel() + + c, merr := b.meter.Int64Counter("l1_info_tree_index_for_bridge") + if merr != nil { + log.Warnf("failed to create l1_info_tree_index_for_bridge counter: %s", merr) + } + c.Add(ctx, 1) + + if networkID == 0 { + l1InfoTreeIndex, err := b.l1Bridge2Index.GetL1InfoTreeIndexByDepositCount(ctx, depositCount) + // TODO: special treatment of the error when not found, + // as it's expected that it will take some time for the L1 Info tree to be updated + if err != nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("failed to get l1InfoTreeIndex, error: %s", err)) + } + return l1InfoTreeIndex, nil + } + if networkID == b.networkID { + // TODO: special treatment of the error when not found, + // as it's expected that it will take some time for the L1 Info tree to be updated + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("TODO: batchsync / certificatesync missing implementation")) + } + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("this client does not support network %d", networkID)) +} + +// InjectedInfoAfterIndex return the first GER injected onto the network that is linked +// to the given index or greater. This call is usefull to understand when a bridge is ready to be claimed +// on its destination network +func (b *BridgeEndpoints) InjectedInfoAfterIndex(networkID uint32, l1InfoTreeIndex uint32) (interface{}, rpc.Error) { + ctx, cancel := context.WithTimeout(context.Background(), b.readTimeout) + defer cancel() + + c, merr := b.meter.Int64Counter("injected_info_after_index") + if merr != nil { + log.Warnf("failed to create injected_info_after_index counter: %s", merr) + } + c.Add(ctx, 1) + + if networkID == 0 { + info, err := b.l1InfoTree.GetInfoByIndex(ctx, l1InfoTreeIndex) + if err != nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("failed to get global exit root, error: %s", err)) + } + return info, nil + } + if networkID == b.networkID { + injectedL1InfoTreeIndex, _, err := b.injectedGERs.GetFirstGERAfterL1InfoTreeIndex(ctx, l1InfoTreeIndex) + if err != nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("failed to get global exit root, error: %s", err)) + } + info, err := b.l1InfoTree.GetInfoByIndex(ctx, injectedL1InfoTreeIndex) + if err != nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("failed to get global exit root, error: %s", err)) + } + return info, nil + } + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("this client does not support network %d", networkID)) +} + +type ClaimProof struct { + ProofLocalExitRoot [32]common.Hash + ProofRollupExitRoot [32]common.Hash +} + +// ClaimProof returns the proofs needed to claim a bridge. NetworkID and depositCount refere to the bridge origin +// while globalExitRoot should be already injected on the destination network. +// This call needs to be done to a client of the same network were the bridge tx was sent +func (b *BridgeEndpoints) ClaimProof(networkID uint32, depositCount uint32, l1InfoTreeIndex uint32) (interface{}, rpc.Error) { + ctx, cancel := context.WithTimeout(context.Background(), b.readTimeout) + defer cancel() + + c, merr := b.meter.Int64Counter("claim_proof") + if merr != nil { + log.Warnf("failed to create claim_proof counter: %s", merr) + } + c.Add(ctx, 1) + + info, err := b.l1InfoTree.GetInfoByIndex(ctx, l1InfoTreeIndex) + if err != nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("failed to get info from the tree: %s", err)) + } + proofRollupExitRoot, err := b.l1InfoTree.GetRollupExitTreeMerkleProof(ctx, networkID, info.GlobalExitRoot) + if err != nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("failed to get rollup exit proof, error: %s", err)) + } + var proofLocalExitRoot [32]common.Hash + if networkID == 0 { + proofLocalExitRoot, err = b.bridgeL1.GetProof(ctx, depositCount, info.MainnetExitRoot) + if err != nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("failed to get local exit proof, error: %s", err)) + } + } else if networkID == b.networkID { + localExitRoot, err := b.l1InfoTree.GetLocalExitRoot(ctx, networkID, info.RollupExitRoot) + if err != nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("failed to get local exit root from rollup exit tree, error: %s", err)) + } + proofLocalExitRoot, err = b.bridgeL2.GetProof(ctx, depositCount, localExitRoot) + if err != nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("failed to get local exit proof, error: %s", err)) + } + } else { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("this client does not support network %d", networkID)) + } + return ClaimProof{ + ProofLocalExitRoot: proofLocalExitRoot, + ProofRollupExitRoot: proofRollupExitRoot, + }, nil +} + +// SponsorClaim sends a claim tx on behalf of the user. +// This call needs to be done to a client of the same network were the claim is going to be sent (bridge destination) +func (b *BridgeEndpoints) SponsorClaim(claim claimsponsor.Claim) (interface{}, rpc.Error) { + ctx, cancel := context.WithTimeout(context.Background(), b.writeTimeout) + defer cancel() + + c, merr := b.meter.Int64Counter("sponsor_claim") + if merr != nil { + log.Warnf("failed to create sponsor_claim counter: %s", merr) + } + c.Add(ctx, 1) + + if b.sponsor == nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("this client does not support claim sponsoring")) + } + if claim.DestinationNetwork != b.networkID { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("this client only sponsors claims for network %d", b.networkID)) + } + if err := b.sponsor.AddClaimToQueue(ctx, &claim); err != nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("error adding claim to the queue %s", err)) + } + return nil, nil +} + +// GetSponsoredClaimStatus returns the status of a claim that has been previously requested to be sponsored. +// This call needs to be done to the same client were it was requested to be sponsored +func (b *BridgeEndpoints) GetSponsoredClaimStatus(globalIndex *big.Int) (interface{}, rpc.Error) { + ctx, cancel := context.WithTimeout(context.Background(), b.readTimeout) + defer cancel() + + c, merr := b.meter.Int64Counter("get_sponsored_claim_status") + if merr != nil { + log.Warnf("failed to create get_sponsored_claim_status counter: %s", merr) + } + c.Add(ctx, 1) + + if b.sponsor == nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("this client does not support claim sponsoring")) + } + claim, err := b.sponsor.GetClaim(ctx, globalIndex) + if err != nil { + return "0x0", rpc.NewRPCError(rpc.DefaultErrorCode, fmt.Sprintf("failed to get claim status, error: %s", err)) + } + return claim.Status, nil +} diff --git a/rpc/bridge_client.go b/rpc/bridge_client.go new file mode 100644 index 00000000..0063e660 --- /dev/null +++ b/rpc/bridge_client.go @@ -0,0 +1,91 @@ +package rpc + +import ( + "encoding/json" + "fmt" + "math/big" + + "github.com/0xPolygon/cdk-rpc/rpc" + "github.com/0xPolygon/cdk/claimsponsor" + "github.com/0xPolygon/cdk/l1infotreesync" +) + +type BridgeClientInterface interface { + L1InfoTreeIndexForBridge(networkID uint32, depositCount uint32) (uint32, error) + InjectedInfoAfterIndex(networkID uint32, l1InfoTreeIndex uint32) (*l1infotreesync.L1InfoTreeLeaf, error) + ClaimProof(networkID uint32, depositCount uint32, l1InfoTreeIndex uint32) (*ClaimProof, error) + SponsorClaim(claim claimsponsor.Claim) error + GetSponsoredClaimStatus(globalIndex *big.Int) (claimsponsor.ClaimStatus, error) +} + +// L1InfoTreeIndexForBridge returns the first L1 Info Tree index in which the bridge was included. +// networkID represents the origin network. +// This call needs to be done to a client of the same network were the bridge tx was sent +func (c *Client) L1InfoTreeIndexForBridge(networkID uint32, depositCount uint32) (uint32, error) { + response, err := rpc.JSONRPCCall(c.url, "bridge_l1InfoTreeIndexForBridge", networkID, depositCount) + if err != nil { + return 0, err + } + if response.Error != nil { + return 0, fmt.Errorf("%v %v", response.Error.Code, response.Error.Message) + } + var result uint32 + return result, json.Unmarshal(response.Result, &result) +} + +// InjectedInfoAfterIndex return the first GER injected onto the network that is linked +// to the given index or greater. This call is usefull to understand when a bridge is ready to be claimed +// on its destination network +func (c *Client) InjectedInfoAfterIndex(networkID uint32, l1InfoTreeIndex uint32) (*l1infotreesync.L1InfoTreeLeaf, error) { + response, err := rpc.JSONRPCCall(c.url, "bridge_injectedInfoAfterIndex", networkID, l1InfoTreeIndex) + if err != nil { + return nil, err + } + if response.Error != nil { + return nil, fmt.Errorf("%v %v", response.Error.Code, response.Error.Message) + } + var result l1infotreesync.L1InfoTreeLeaf + return &result, json.Unmarshal(response.Result, &result) +} + +// ClaimProof returns the proofs needed to claim a bridge. NetworkID and depositCount refere to the bridge origin +// while globalExitRoot should be already injected on the destination network. +// This call needs to be done to a client of the same network were the bridge tx was sent +func (c *Client) ClaimProof(networkID uint32, depositCount uint32, l1InfoTreeIndex uint32) (*ClaimProof, error) { + response, err := rpc.JSONRPCCall(c.url, "bridge_claimProof", networkID, depositCount, l1InfoTreeIndex) + if err != nil { + return nil, err + } + if response.Error != nil { + return nil, fmt.Errorf("%v %v", response.Error.Code, response.Error.Message) + } + var result ClaimProof + return &result, json.Unmarshal(response.Result, &result) +} + +// SponsorClaim sends a claim tx on behalf of the user. +// This call needs to be done to a client of the same network were the claim is going to be sent (bridge destination) +func (c *Client) SponsorClaim(claim claimsponsor.Claim) error { + response, err := rpc.JSONRPCCall(c.url, "bridge_sponsorClaim", claim) + if err != nil { + return err + } + if response.Error != nil { + return fmt.Errorf("%v %v", response.Error.Code, response.Error.Message) + } + return nil +} + +// GetSponsoredClaimStatus returns the status of a claim that has been previously requested to be sponsored. +// This call needs to be done to the same client were it was requested to be sponsored +func (c *Client) GetSponsoredClaimStatus(globalIndex *big.Int) (claimsponsor.ClaimStatus, error) { + response, err := rpc.JSONRPCCall(c.url, "bridge_getSponsoredClaimStatus", globalIndex) + if err != nil { + return "", err + } + if response.Error != nil { + return "", fmt.Errorf("%v %v", response.Error.Code, response.Error.Message) + } + var result claimsponsor.ClaimStatus + return result, json.Unmarshal(response.Result, &result) +} diff --git a/rpc/client.go b/rpc/client.go new file mode 100644 index 00000000..b48fca51 --- /dev/null +++ b/rpc/client.go @@ -0,0 +1,31 @@ +package rpc + +// ClientInterface is the interface that defines the implementation of all the endpoints +type ClientInterface interface { + BridgeClientInterface +} + +// ClientFactoryInterface interface for the client factory +type ClientFactoryInterface interface { + NewClient(url string) ClientInterface +} + +// ClientFactory is the implementation of the data committee client factory +type ClientFactory struct{} + +// NewClient returns an implementation of the data committee node client +func (f *ClientFactory) NewClient(url string) ClientInterface { + return NewClient(url) +} + +// Client wraps all the available endpoints of the data abailability committee node server +type Client struct { + url string +} + +// NewClient returns a client ready to be used +func NewClient(url string) *Client { + return &Client{ + url: url, + } +} diff --git a/sync/evmdownloader.go b/sync/evmdownloader.go index f5587d0e..bb82ccb4 100644 --- a/sync/evmdownloader.go +++ b/sync/evmdownloader.go @@ -32,9 +32,11 @@ type LogAppenderMap map[common.Hash]func(b *EVMBlock, l types.Log) error type EVMDownloader struct { syncBlockChunkSize uint64 EVMDownloaderInterface + log *log.Logger } func NewEVMDownloader( + syncerID string, ethClient EthClienter, syncBlockChunkSize uint64, blockFinalityType etherman.BlockNumberFinality, @@ -43,6 +45,7 @@ func NewEVMDownloader( adressessToQuery []common.Address, rh *RetryHandler, ) (*EVMDownloader, error) { + logger := log.WithFields("syncer", syncerID) finality, err := blockFinalityType.ToBlockNum() if err != nil { return nil, err @@ -53,6 +56,7 @@ func NewEVMDownloader( } return &EVMDownloader{ syncBlockChunkSize: syncBlockChunkSize, + log: logger, EVMDownloaderInterface: &EVMDownloaderImplementation{ ethClient: ethClient, blockFinality: finality, @@ -61,6 +65,7 @@ func NewEVMDownloader( topicsToQuery: topicsToQuery, adressessToQuery: adressessToQuery, rh: rh, + log: logger, }, }, nil } @@ -70,7 +75,7 @@ func (d *EVMDownloader) Download(ctx context.Context, fromBlock uint64, download for { select { case <-ctx.Done(): - log.Debug("closing channel") + d.log.Debug("closing channel") close(downloadedCh) return default: @@ -80,19 +85,22 @@ func (d *EVMDownloader) Download(ctx context.Context, fromBlock uint64, download toBlock = lastBlock } if fromBlock > toBlock { - log.Debug("waiting for new blocks, last block ", toBlock) - lastBlock = d.WaitForNewBlocks(ctx, toBlock) + d.log.Debugf( + "waiting for new blocks, last block processed %d, last block seen on L1 %d", + fromBlock-1, lastBlock, + ) + lastBlock = d.WaitForNewBlocks(ctx, fromBlock-1) continue } - log.Debugf("getting events from blocks %d to %d", fromBlock, toBlock) + d.log.Debugf("getting events from blocks %d to %d", fromBlock, toBlock) blocks := d.GetEventsByBlockRange(ctx, fromBlock, toBlock) for _, b := range blocks { - log.Debugf("sending block %d to the driver (with events)", b.Num) + d.log.Debugf("sending block %d to the driver (with events)", b.Num) downloadedCh <- b } if len(blocks) == 0 || blocks[len(blocks)-1].Num < toBlock { // Indicate the last downloaded block if there are not events on it - log.Debugf("sending block %d to the driver (without events)", toBlock) + d.log.Debugf("sending block %d to the driver (without events)", toBlock) downloadedCh <- EVMBlock{ EVMBlockHeader: d.GetBlockHeader(ctx, toBlock), } @@ -109,9 +117,11 @@ type EVMDownloaderImplementation struct { topicsToQuery []common.Hash adressessToQuery []common.Address rh *RetryHandler + log *log.Logger } func NewEVMDownloaderImplementation( + syncerID string, ethClient EthClienter, blockFinality *big.Int, waitForNewBlocksPeriod time.Duration, @@ -120,6 +130,7 @@ func NewEVMDownloaderImplementation( adressessToQuery []common.Address, rh *RetryHandler, ) *EVMDownloaderImplementation { + logger := log.WithFields("syncer", syncerID) return &EVMDownloaderImplementation{ ethClient: ethClient, blockFinality: blockFinality, @@ -128,6 +139,7 @@ func NewEVMDownloaderImplementation( topicsToQuery: topicsToQuery, adressessToQuery: adressessToQuery, rh: rh, + log: logger, } } @@ -138,13 +150,13 @@ func (d *EVMDownloaderImplementation) WaitForNewBlocks(ctx context.Context, last for { select { case <-ctx.Done(): - log.Info("context cancelled") + d.log.Info("context cancelled") return lastBlockSeen case <-ticker.C: header, err := d.ethClient.HeaderByNumber(ctx, d.blockFinality) if err != nil { attempts++ - log.Error("error getting last block num from eth client: ", err) + d.log.Error("error getting last block num from eth client: ", err) d.rh.Handle("waitForNewBlocks", attempts) continue } @@ -162,7 +174,7 @@ func (d *EVMDownloaderImplementation) GetEventsByBlockRange(ctx context.Context, if len(blocks) == 0 || blocks[len(blocks)-1].Num < l.BlockNumber { b := d.GetBlockHeader(ctx, l.BlockNumber) if b.Hash != l.BlockHash { - log.Infof( + d.log.Infof( "there has been a block hash change between the event query and the block query for block %d: %s vs %s. Retrtying.", l.BlockNumber, b.Hash, l.BlockHash) return d.GetEventsByBlockRange(ctx, fromBlock, toBlock) @@ -183,7 +195,7 @@ func (d *EVMDownloaderImplementation) GetEventsByBlockRange(ctx context.Context, err := d.appender[l.Topics[0]](&blocks[len(blocks)-1], l) if err != nil { attempts++ - log.Error("error trying to append log: ", err) + d.log.Error("error trying to append log: ", err) d.rh.Handle("getLogs", attempts) continue } @@ -209,7 +221,7 @@ func (d *EVMDownloaderImplementation) GetLogs(ctx context.Context, fromBlock, to unfilteredLogs, err = d.ethClient.FilterLogs(ctx, query) if err != nil { attempts++ - log.Error("error calling FilterLogs to eth client: ", err) + d.log.Error("error calling FilterLogs to eth client: ", err) d.rh.Handle("getLogs", attempts) continue } @@ -226,7 +238,7 @@ func (d *EVMDownloaderImplementation) GetLogs(ctx context.Context, fromBlock, to } } if !found { - log.Debugf("ignoring log %+v because it's not under the list of topics to query", l) + d.log.Debugf("ignoring log %+v because it's not under the list of topics to query", l) } } return logs @@ -238,7 +250,7 @@ func (d *EVMDownloaderImplementation) GetBlockHeader(ctx context.Context, blockN header, err := d.ethClient.HeaderByNumber(ctx, big.NewInt(int64(blockNum))) if err != nil { attempts++ - log.Errorf("error getting block header for block %d, err: %v", blockNum, err) + d.log.Errorf("error getting block header for block %d, err: %v", blockNum, err) d.rh.Handle("getBlockHeader", attempts) continue } diff --git a/sync/evmdownloader_test.go b/sync/evmdownloader_test.go index 12aba792..15a6608c 100644 --- a/sync/evmdownloader_test.go +++ b/sync/evmdownloader_test.go @@ -394,7 +394,7 @@ func NewTestDownloader(t *testing.T) (*EVMDownloader, *L2Mock) { RetryAfterErrorPeriod: time.Millisecond * 100, } clientMock := NewL2Mock(t) - d, err := NewEVMDownloader(clientMock, syncBlockChunck, etherman.LatestBlock, time.Millisecond, buildAppender(), []common.Address{contractAddr}, rh) + d, err := NewEVMDownloader("test", clientMock, syncBlockChunck, etherman.LatestBlock, time.Millisecond, buildAppender(), []common.Address{contractAddr}, rh) require.NoError(t, err) return d, clientMock } diff --git a/sync/evmdriver.go b/sync/evmdriver.go index 7d41ead2..7f782939 100644 --- a/sync/evmdriver.go +++ b/sync/evmdriver.go @@ -25,6 +25,7 @@ type EVMDriver struct { reorgDetectorID string downloadBufferSize int rh *RetryHandler + log *log.Logger } type processorInterface interface { @@ -46,6 +47,7 @@ func NewEVMDriver( downloadBufferSize int, rh *RetryHandler, ) (*EVMDriver, error) { + logger := log.WithFields("syncer", reorgDetectorID) reorgSub, err := reorgDetector.Subscribe(reorgDetectorID) if err != nil { return nil, err @@ -58,6 +60,7 @@ func NewEVMDriver( reorgDetectorID: reorgDetectorID, downloadBufferSize: downloadBufferSize, rh: rh, + log: logger, }, nil } @@ -72,7 +75,7 @@ reset: lastProcessedBlock, err = d.processor.GetLastProcessedBlock(ctx) if err != nil { attempts++ - log.Error("error geting last processed block: ", err) + d.log.Error("error geting last processed block: ", err) d.rh.Handle("Sync", attempts) continue } @@ -88,10 +91,10 @@ reset: for { select { case b := <-downloadCh: - log.Debug("handleNewBlock") + d.log.Debug("handleNewBlock") d.handleNewBlock(ctx, b) case firstReorgedBlock := <-d.reorgSub.FirstReorgedBlock: - log.Debug("handleReorg") + d.log.Debug("handleReorg") d.handleReorg(ctx, cancel, downloadCh, firstReorgedBlock) goto reset } @@ -104,7 +107,7 @@ func (d *EVMDriver) handleNewBlock(ctx context.Context, b EVMBlock) { err := d.reorgDetector.AddBlockToTrack(ctx, d.reorgDetectorID, b.Num, b.Hash) if err != nil { attempts++ - log.Errorf("error adding block %d to tracker: %v", b.Num, err) + d.log.Errorf("error adding block %d to tracker: %v", b.Num, err) d.rh.Handle("handleNewBlock", attempts) continue } @@ -119,7 +122,7 @@ func (d *EVMDriver) handleNewBlock(ctx context.Context, b EVMBlock) { err := d.processor.ProcessBlock(ctx, blockToProcess) if err != nil { attempts++ - log.Errorf("error processing events for blcok %d, err: ", b.Num, err) + d.log.Errorf("error processing events for blcok %d, err: ", b.Num, err) d.rh.Handle("handleNewBlock", attempts) continue } @@ -142,7 +145,7 @@ func (d *EVMDriver) handleReorg( err := d.processor.Reorg(ctx, firstReorgedBlock) if err != nil { attempts++ - log.Errorf( + d.log.Errorf( "error processing reorg, last valid Block %d, err: %v", firstReorgedBlock, err, ) diff --git a/tree/appendonlytree.go b/tree/appendonlytree.go index d9740513..c3134038 100644 --- a/tree/appendonlytree.go +++ b/tree/appendonlytree.go @@ -109,6 +109,7 @@ func (t *AppendOnlyTree) GetIndexByRoot(ctx context.Context, root common.Hash) ( if err != nil { return 0, err } + defer tx.Rollback() index, err := t.getIndexByRoot(tx, root) return uint32(index), err } @@ -119,6 +120,7 @@ func (t *AppendOnlyTree) GetLastIndexAndRoot(ctx context.Context) (uint32, commo if err != nil { return 0, common.Hash{}, err } + defer tx.Rollback() i, root, err := t.getLastIndexAndRootWithTx(tx) if err != nil { return 0, common.Hash{}, err @@ -151,6 +153,7 @@ func (t *AppendOnlyTree) initLastIndex(tx kv.Tx) (common.Hash, error) { t.lastIndex = ldc return root, nil } + func (t *AppendOnlyTree) initLastLeftCache(tx kv.Tx, lastIndex int64, lastRoot common.Hash) error { siblings := [defaultHeight]common.Hash{} if lastIndex == -1 { diff --git a/tree/tree.go b/tree/tree.go index ba6a8ea0..e7f8e5c1 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -241,6 +241,7 @@ func (t *Tree) GetLastRoot(ctx context.Context) (common.Hash, error) { if err != nil { return common.Hash{}, err } + defer tx.Rollback() i, root, err := t.getLastIndexAndRootWithTx(tx) if err != nil { @@ -274,3 +275,26 @@ func (t *Tree) getLastIndexAndRootWithTx(tx kv.Tx) (int64, common.Hash, error) { } return int64(dbCommon.BytesToUint64(lastIndexBytes)), common.Hash(rootBytes), nil } + +func (t *Tree) GetLeaf(ctx context.Context, index uint32, root common.Hash) (common.Hash, error) { + tx, err := t.db.BeginRo(ctx) + if err != nil { + return common.Hash{}, err + } + defer tx.Rollback() + + currentNodeHash := root + for h := int(defaultHeight - 1); h >= 0; h-- { + currentNode, err := t.getRHTNode(tx, currentNodeHash) + if err != nil { + return common.Hash{}, err + } + if index&(1< 0 { + currentNodeHash = currentNode.right + } else { + currentNodeHash = currentNode.left + } + } + + return currentNodeHash, nil +} From f637a8dfc4a882936c96a895a3166a39f936c4e9 Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 13 Aug 2024 14:59:20 +0200 Subject: [PATCH 42/49] add l1Info as part of the claim response --- rpc/bridge.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rpc/bridge.go b/rpc/bridge.go index 11933f24..0b550e72 100644 --- a/rpc/bridge.go +++ b/rpc/bridge.go @@ -132,6 +132,7 @@ func (b *BridgeEndpoints) InjectedInfoAfterIndex(networkID uint32, l1InfoTreeInd type ClaimProof struct { ProofLocalExitRoot [32]common.Hash ProofRollupExitRoot [32]common.Hash + L1InfoTreeLeaf l1infotreesync.L1InfoTreeLeaf } // ClaimProof returns the proofs needed to claim a bridge. NetworkID and depositCount refere to the bridge origin @@ -176,6 +177,7 @@ func (b *BridgeEndpoints) ClaimProof(networkID uint32, depositCount uint32, l1In return ClaimProof{ ProofLocalExitRoot: proofLocalExitRoot, ProofRollupExitRoot: proofRollupExitRoot, + L1InfoTreeLeaf: *info, }, nil } From a620cd6cf6e762e7feaaeb79138029d0c2def040 Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 13 Aug 2024 15:07:41 +0200 Subject: [PATCH 43/49] increase time.sleep to pass UT on GHA --- claimsponsor/e2e_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/claimsponsor/e2e_test.go b/claimsponsor/e2e_test.go index baad9aa4..533de113 100644 --- a/claimsponsor/e2e_test.go +++ b/claimsponsor/e2e_test.go @@ -42,13 +42,14 @@ func TestE2EL1toEVML2(t *testing.T) { go claimer.Start(ctx) // test - for i := 0; i < 10; i++ { + for i := 0; i < 3; i++ { // Send bridges to L2, wait for GER to be injected on L2 amount := big.NewInt(int64(i) + 1) env.AuthL1.Value = amount _, err := env.BridgeL1Contract.BridgeAsset(env.AuthL1, env.NetworkIDL2, env.AuthL2.From, amount, common.Address{}, true, nil) + require.NoError(t, err) env.L1Client.Commit() - time.Sleep(time.Millisecond * 50) + time.Sleep(time.Millisecond * 300) expectedGER, err := env.GERL1Contract.GetLastGlobalExitRoot(&bind.CallOpts{Pending: false}) require.NoError(t, err) isInjected, err := env.AggOracleSender.IsGERAlreadyInjected(expectedGER) @@ -61,6 +62,7 @@ func TestE2EL1toEVML2(t *testing.T) { localProof, err := bridgeSyncL1.GetProof(ctx, uint32(i), info.MainnetExitRoot) require.NoError(t, err) rollupProof, err := env.L1InfoTreeSync.GetRollupExitTreeMerkleProof(ctx, 0, common.Hash{}) + require.NoError(t, err) // Request to sponsor claim globalIndex := bridgesync.GenerateGlobalIndex(true, 0, uint32(i)) @@ -91,7 +93,7 @@ func TestE2EL1toEVML2(t *testing.T) { succeed = true break } - time.Sleep(50 * time.Millisecond) + time.Sleep(100 * time.Millisecond) } require.True(t, succeed) From 29c8b749e65c85daeb1d3089d415b25d89a2d376 Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 13 Aug 2024 17:47:08 +0200 Subject: [PATCH 44/49] apply requests from ToniRamirezM --- claimsponsor/claimsponsor.go | 4 ++-- reorgdetector/reorgdetector.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/claimsponsor/claimsponsor.go b/claimsponsor/claimsponsor.go index f4eda62e..e0d8e7b8 100644 --- a/claimsponsor/claimsponsor.go +++ b/claimsponsor/claimsponsor.go @@ -167,11 +167,11 @@ func (c *ClaimSponsor) Start(ctx context.Context) { continue } - log.Infof("waiting for tx %s with global index %s to success or fail", claim.TxID, globalIndex.String()) + log.Infof("waiting for tx %s with global index %s to succeed or fail", claim.TxID, globalIndex.String()) status, err2 := c.waitTxToBeSuccessOrFail(ctx, claim.TxID) if err2 != nil { err = err2 - log.Errorf("error calling waitTxToBeMinedOrFail for tx %s: %v", claim.TxID, err) + log.Errorf("error calling waitTxToBeSuccessOrFail for tx %s: %v", claim.TxID, err) continue } log.Infof("tx %s with global index %s concluded with status: %s", claim.TxID, globalIndex.String(), status) diff --git a/reorgdetector/reorgdetector.go b/reorgdetector/reorgdetector.go index a6bd0247..9eb631aa 100644 --- a/reorgdetector/reorgdetector.go +++ b/reorgdetector/reorgdetector.go @@ -219,7 +219,7 @@ func (r *ReorgDetector) Subscribe(id string) (*Subscription, error) { func (r *ReorgDetector) AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error { return nil - // COMENTING THE CODE AS I"M SUSPECTING A DETAHLOCK + // COMENTING THE CODE AS I'M SUSPECTING A DEATHLOCK // r.subscriptionsLock.RLock() // if sub, ok := r.subscriptions[id]; !ok { // r.subscriptionsLock.RUnlock() From 9b2b2454e813d85628712187786b177288ecb2e0 Mon Sep 17 00:00:00 2001 From: Arnau Date: Wed, 14 Aug 2024 10:36:28 +0200 Subject: [PATCH 45/49] fix block finality config --- aggoracle/config.go | 4 ++-- bridgesync/bridgesync.go | 4 ++-- config/default.go | 10 +++++----- l1infotreesync/l1infotreesync.go | 4 ++-- lastgersync/lastgersync.go | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/aggoracle/config.go b/aggoracle/config.go index 2dd39403..e6097707 100644 --- a/aggoracle/config.go +++ b/aggoracle/config.go @@ -18,8 +18,8 @@ var ( type Config struct { TargetChainType TargetChainType `mapstructure:"TargetChainType"` URLRPCL1 string `mapstructure:"URLRPCL1"` - // TODO: BlockFinality doesnt work as per the jsonschema - BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` + // BlockFinality indicates the status of the blocks that will be queried in order to sync + BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` WaitPeriodNextGER types.Duration `mapstructure:"WaitPeriodNextGER"` EVMSender chaingersender.EVMConfig `mapstructure:"EVMSender"` } diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index a741f22a..31d82511 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -19,8 +19,8 @@ const ( type Config struct { // DBPath path of the DB DBPath string `mapstructure:"DBPath"` - // TODO: BlockFinality doesnt work as per the jsonschema - BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` + // BlockFinality indicates the status of the blocks that will be queried in order to sync + BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` // InitialBlockNum is the first block that will be queried when starting the synchronization from scratch. // It should be a number equal oir bellow the creation of the bridge contract InitialBlockNum uint64 `mapstructure:"InitialBlockNum"` diff --git a/config/default.go b/config/default.go index 755c3d19..24a84ace 100644 --- a/config/default.go +++ b/config/default.go @@ -130,7 +130,7 @@ DBPath = "/tmp/reorgdetectorl2" DBPath = "/tmp/L1InfoTreeSync" GlobalExitRootAddr="0x8464135c8F25Da09e49BC8782676a84730C318bC" SyncBlockChunkSize=10 -BlockFinality="latest" +BlockFinality="LatestBlock" URLRPCL1="http://test-aggoracle-l1:8545" WaitForNewBlocksPeriod="100ms" InitialBlock=0 @@ -138,7 +138,7 @@ InitialBlock=0 [AggOracle] TargetChainType="EVM" URLRPCL1="http://test-aggoracle-l1:8545" -BlockFinality="latest" +BlockFinality="FinalizedBlock" WaitPeriodNextGER="100ms" [AggOracle.EVMSender] GlobalExitRootL2="0x8464135c8F25Da09e49BC8782676a84730C318bC" @@ -215,7 +215,7 @@ WaitForSyncersPeriod = "3s" [BridgeL1Sync] DBPath = "/tmp/bridgel1sync" -BlockFinality = "latest" +BlockFinality = "LatestBlock" InitialBlockNum = 0 BridgeAddr = "0xB7098a13a48EcE087d3DA15b2D28eCE0f89819B8" SyncBlockChunkSize = 100 @@ -225,7 +225,7 @@ WaitForNewBlocksPeriod = "3s" [BridgeL2Sync] DBPath = "/tmp/bridgel2sync" -BlockFinality = "latest" +BlockFinality = "LatestBlock" InitialBlockNum = 0 BridgeAddr = "0xB7098a13a48EcE087d3DA15b2D28eCE0f89819B8" SyncBlockChunkSize = 100 @@ -235,7 +235,7 @@ WaitForNewBlocksPeriod = "3s" [LastGERSync] DBPath = "/tmp/lastgersync" -BlockFinality = "latest" +BlockFinality = "LatestBlock" InitialBlockNum = 0 GlobalExitRootL2Addr = "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa" RetryAfterErrorPeriod = "1s" diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index f9a04e4c..d2c59fd8 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -22,8 +22,8 @@ type Config struct { GlobalExitRootAddr common.Address `mapstructure:"GlobalExitRootAddr"` RollupManagerAddr common.Address `mapstructure:"RollupManagerAddr"` SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"` - // TODO: BlockFinality doesnt work as per the jsonschema - BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` + // BlockFinality indicates the status of the blocks that will be queried in order to sync + BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` URLRPCL1 string `mapstructure:"URLRPCL1"` WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"` InitialBlock uint64 `mapstructure:"InitialBlock"` diff --git a/lastgersync/lastgersync.go b/lastgersync/lastgersync.go index ae5ec30e..aab54ff1 100644 --- a/lastgersync/lastgersync.go +++ b/lastgersync/lastgersync.go @@ -20,8 +20,8 @@ const ( type Config struct { // DBPath path of the DB DBPath string `mapstructure:"DBPath"` - // TODO: BlockFinality doesnt work as per the jsonschema - BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"` + // BlockFinality indicates the status of the blocks that will be queried in order to sync + BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` // InitialBlockNum is the first block that will be queried when starting the synchronization from scratch. // It should be a number equal oir bellow the creation of the bridge contract InitialBlockNum uint64 `mapstructure:"InitialBlockNum"` From 766aa4fe024dbaf11f789b0c285e5792675504b8 Mon Sep 17 00:00:00 2001 From: bros Date: Mon, 19 Aug 2024 13:16:38 +0000 Subject: [PATCH 46/49] apply PR requests by Stefan-Ethernal --- .gitignore | 3 ++- bridgesync/bridgesync.go | 2 +- bridgesync/e2e_test.go | 8 ++++---- bridgesync/processor_test.go | 2 +- l1bridge2infoindexsync/downloader.go | 2 +- l1bridge2infoindexsync/driver.go | 2 +- l1bridge2infoindexsync/l1bridge2infoindexsync.go | 2 +- l1bridge2infoindexsync/processor.go | 8 ++++---- l1bridge2infoindexsync/processor_test.go | 2 +- l1infotreesync/processor.go | 6 +++--- lastgersync/lastgersync.go | 2 +- tree/testvectors/types.go | 4 ++-- 12 files changed, 22 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 863f937c..f1c45125 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ .env /dist/ cmd/__debug_bin -**__debug** \ No newline at end of file +**__debug** +tmp \ No newline at end of file diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index 31d82511..b51b2387 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -22,7 +22,7 @@ type Config struct { // BlockFinality indicates the status of the blocks that will be queried in order to sync BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` // InitialBlockNum is the first block that will be queried when starting the synchronization from scratch. - // It should be a number equal oir bellow the creation of the bridge contract + // It should be a number equal or bellow the creation of the bridge contract InitialBlockNum uint64 `mapstructure:"InitialBlockNum"` // BridgeAddr is the address of the bridge smart contract BridgeAddr common.Address `mapstructure:"BridgeAddr"` diff --git a/bridgesync/e2e_test.go b/bridgesync/e2e_test.go index f55d8c7e..1f5a080c 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -86,11 +86,11 @@ func TestBridgeEventE2E(t *testing.T) { // Wait for syncer to catch up syncerUpToDate := false var errMsg string + lb, err := client.Client().BlockNumber(ctx) + require.NoError(t, err) for i := 0; i < 10; i++ { lpb, err := syncer.GetLastProcessedBlock(ctx) require.NoError(t, err) - lb, err := client.Client().BlockNumber(ctx) - require.NoError(t, err) if lpb == lb { syncerUpToDate = true break @@ -101,9 +101,9 @@ func TestBridgeEventE2E(t *testing.T) { require.True(t, syncerUpToDate, errMsg) // Get bridges - lastBlcok, err := client.Client().BlockNumber(ctx) + lastBlock, err := client.Client().BlockNumber(ctx) require.NoError(t, err) - events, err := syncer.GetClaimsAndBridges(ctx, 0, lastBlcok) + events, err := syncer.GetClaimsAndBridges(ctx, 0, lastBlock) require.NoError(t, err) actualBridges := []bridgesync.Bridge{} for _, event := range events { diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index 7d337d08..90fe74be 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -440,7 +440,7 @@ func TestHashBridge(t *testing.T) { require.True(t, err) bridge := Bridge{ - OriginNetwork: testVector.OriginalNetwork, + OriginNetwork: testVector.OriginNetwork, OriginAddress: common.HexToAddress(testVector.TokenAddress), Amount: amount, DestinationNetwork: testVector.DestinationNetwork, diff --git a/l1bridge2infoindexsync/downloader.go b/l1bridge2infoindexsync/downloader.go index 6fe80059..f14fcf8e 100644 --- a/l1bridge2infoindexsync/downloader.go +++ b/l1bridge2infoindexsync/downloader.go @@ -29,7 +29,7 @@ func newDownloader( } } -func (d *downloader) getLastFinalisedL1Block(ctx context.Context) (uint64, error) { +func (d *downloader) getLastFinalizedL1Block(ctx context.Context) (uint64, error) { b, err := d.l1Client.BlockByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber))) if err != nil { return 0, err diff --git a/l1bridge2infoindexsync/driver.go b/l1bridge2infoindexsync/driver.go index a900b5f8..e0e9b5cc 100644 --- a/l1bridge2infoindexsync/driver.go +++ b/l1bridge2infoindexsync/driver.go @@ -136,7 +136,7 @@ func (d *driver) sync(ctx context.Context) { } func (d *driver) getTargetSynchronizationBlock(ctx context.Context, lpbProcessor uint64) (syncUntilBlock uint64, shouldWait bool, err error) { - lastFinalised, err := d.downloader.getLastFinalisedL1Block(ctx) // TODO: configure finality, but then we need to deal with reorgs? + lastFinalised, err := d.downloader.getLastFinalizedL1Block(ctx) // TODO: configure finality, but then we need to deal with reorgs? if err != nil { return } diff --git a/l1bridge2infoindexsync/l1bridge2infoindexsync.go b/l1bridge2infoindexsync/l1bridge2infoindexsync.go index fd2312cd..8510d44a 100644 --- a/l1bridge2infoindexsync/l1bridge2infoindexsync.go +++ b/l1bridge2infoindexsync/l1bridge2infoindexsync.go @@ -66,5 +66,5 @@ func (s *L1Bridge2InfoIndexSync) GetLastProcessedBlock(ctx context.Context) (uin } func (s *L1Bridge2InfoIndexSync) GetL1InfoTreeIndexByDepositCount(ctx context.Context, depositCount uint32) (uint32, error) { - return s.processor.getL1InfoTreeIndexByBrdigeIndex(ctx, depositCount) + return s.processor.getL1InfoTreeIndexByBridgeIndex(ctx, depositCount) } diff --git a/l1bridge2infoindexsync/processor.go b/l1bridge2infoindexsync/processor.go index 91861da3..0d3ba897 100644 --- a/l1bridge2infoindexsync/processor.go +++ b/l1bridge2infoindexsync/processor.go @@ -123,7 +123,7 @@ func (p *processor) processUntilBlock(ctx context.Context, lastProcessedBlock ui } for _, relation := range relations { - if _, err := p.getL1InfoTreeIndexByBrdigeIndexWithTx(tx, relation.bridgeIndex); err != ErrNotFound { + if _, err := p.getL1InfoTreeIndexByBridgeIndexWithTx(tx, relation.bridgeIndex); err != ErrNotFound { // Note that indexes could be repeated as the L1 Info tree update can be produced by a rollup and not mainnet. // Hence if the index already exist, do not update as it's better to have the lowest index possible for the relation continue @@ -166,17 +166,17 @@ func (p *processor) processUntilBlock(ctx context.Context, lastProcessedBlock ui return tx.Commit() } -func (p *processor) getL1InfoTreeIndexByBrdigeIndex(ctx context.Context, depositCount uint32) (uint32, error) { +func (p *processor) getL1InfoTreeIndexByBridgeIndex(ctx context.Context, depositCount uint32) (uint32, error) { tx, err := p.db.BeginRo(ctx) if err != nil { return 0, err } defer tx.Rollback() - return p.getL1InfoTreeIndexByBrdigeIndexWithTx(tx, depositCount) + return p.getL1InfoTreeIndexByBridgeIndexWithTx(tx, depositCount) } -func (p *processor) getL1InfoTreeIndexByBrdigeIndexWithTx(tx kv.Tx, depositCount uint32) (uint32, error) { +func (p *processor) getL1InfoTreeIndexByBridgeIndexWithTx(tx kv.Tx, depositCount uint32) (uint32, error) { indexBytes, err := tx.GetOne(relationTable, common.Uint32ToBytes(depositCount)) if err != nil { return 0, err diff --git a/l1bridge2infoindexsync/processor_test.go b/l1bridge2infoindexsync/processor_test.go index 2389e9c6..9305dd9b 100644 --- a/l1bridge2infoindexsync/processor_test.go +++ b/l1bridge2infoindexsync/processor_test.go @@ -16,7 +16,7 @@ func TestDuplicatedKey(t *testing.T) { require.NoError(t, err) err = p.processUntilBlock(ctx, 7, []bridge2L1InfoRelation{{bridgeIndex: 2, l1InfoTreeIndex: 3}}) require.NoError(t, err) - l1InfoTreeIndex, err := p.getL1InfoTreeIndexByBrdigeIndex(ctx, 2) + l1InfoTreeIndex, err := p.getL1InfoTreeIndexByBridgeIndex(ctx, 2) require.NoError(t, err) require.Equal(t, uint32(2), l1InfoTreeIndex) } diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index a7b8c6f1..d0e2dacd 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -156,18 +156,18 @@ func newProcessor(ctx context.Context, dbPath string) (*processor, error) { func (p *processor) GetL1InfoTreeMerkleProof(ctx context.Context, index uint32) ([32]ethCommon.Hash, ethCommon.Hash, error) { tx, err := p.db.BeginRo(ctx) if err != nil { - return [32]ethCommon.Hash{}, ethCommon.Hash{}, err + return tree.EmptyProof, ethCommon.Hash{}, err } defer tx.Rollback() root, err := p.l1InfoTree.GetRootByIndex(tx, index) if err != nil { - return [32]ethCommon.Hash{}, ethCommon.Hash{}, err + return tree.EmptyProof, ethCommon.Hash{}, err } proof, err := p.l1InfoTree.GetProof(ctx, index, root) if err != nil { - return [32]ethCommon.Hash{}, ethCommon.Hash{}, err + return tree.EmptyProof, ethCommon.Hash{}, err } // TODO: check if we need to return root or wat diff --git a/lastgersync/lastgersync.go b/lastgersync/lastgersync.go index aab54ff1..038c94f8 100644 --- a/lastgersync/lastgersync.go +++ b/lastgersync/lastgersync.go @@ -23,7 +23,7 @@ type Config struct { // BlockFinality indicates the status of the blocks that will be queried in order to sync BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` // InitialBlockNum is the first block that will be queried when starting the synchronization from scratch. - // It should be a number equal oir bellow the creation of the bridge contract + // It should be a number equal or bellow the creation of the bridge contract InitialBlockNum uint64 `mapstructure:"InitialBlockNum"` // GlobalExitRootL2Addr is the address of the GER smart contract on L2 GlobalExitRootL2Addr common.Address `mapstructure:"GlobalExitRootL2Addr"` diff --git a/tree/testvectors/types.go b/tree/testvectors/types.go index 2d37804d..905718d8 100644 --- a/tree/testvectors/types.go +++ b/tree/testvectors/types.go @@ -10,7 +10,7 @@ import ( // DepositVectorRaw represents the deposit vector type DepositVectorRaw struct { - OriginalNetwork uint32 `json:"originNetwork"` + OriginNetwork uint32 `json:"originNetwork"` TokenAddress string `json:"tokenAddress"` Amount string `json:"amount"` DestinationNetwork uint32 `json:"destinationNetwork"` @@ -22,7 +22,7 @@ type DepositVectorRaw struct { func (d *DepositVectorRaw) Hash() common.Hash { origNet := make([]byte, 4) //nolint:gomnd - binary.BigEndian.PutUint32(origNet, d.OriginalNetwork) + binary.BigEndian.PutUint32(origNet, d.OriginNetwork) destNet := make([]byte, 4) //nolint:gomnd binary.BigEndian.PutUint32(destNet, d.DestinationNetwork) From 9121c28df6bc265c04d460479264d386a9ff44ad Mon Sep 17 00:00:00 2001 From: bros Date: Mon, 19 Aug 2024 13:49:29 +0000 Subject: [PATCH 47/49] apply PR requests by goran-ethernal --- bridgesync/bridgesync.go | 22 -------- bridgesync/config.go | 27 ++++++++++ bridgesync/e2e_test.go | 6 +-- bridgesync/processor.go | 2 +- l1bridge2infoindexsync/config.go | 15 ++++++ l1bridge2infoindexsync/driver.go | 41 +++++++-------- .../l1bridge2infoindexsync.go | 13 ----- l1bridge2infoindexsync/processor.go | 50 +++++++++---------- l1infotreesync/config.go | 20 ++++++++ l1infotreesync/l1infotreesync.go | 15 ------ lastgersync/config.go | 27 ++++++++++ lastgersync/lastgersync.go | 22 -------- 12 files changed, 136 insertions(+), 124 deletions(-) create mode 100644 bridgesync/config.go create mode 100644 l1bridge2infoindexsync/config.go create mode 100644 l1infotreesync/config.go create mode 100644 lastgersync/config.go diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index b51b2387..ae16e7b1 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -4,7 +4,6 @@ import ( "context" "time" - configTypes "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum/common" @@ -16,27 +15,6 @@ const ( downloadBufferSize = 1000 ) -type Config struct { - // DBPath path of the DB - DBPath string `mapstructure:"DBPath"` - // BlockFinality indicates the status of the blocks that will be queried in order to sync - BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` - // InitialBlockNum is the first block that will be queried when starting the synchronization from scratch. - // It should be a number equal or bellow the creation of the bridge contract - InitialBlockNum uint64 `mapstructure:"InitialBlockNum"` - // BridgeAddr is the address of the bridge smart contract - BridgeAddr common.Address `mapstructure:"BridgeAddr"` - // SyncBlockChunkSize is the amount of blocks that will be queried to the client on each request - SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"` - // RetryAfterErrorPeriod is the time that will be waited when an unexpected error happens before retry - RetryAfterErrorPeriod configTypes.Duration `mapstructure:"RetryAfterErrorPeriod"` - // MaxRetryAttemptsAfterError is the maximum number of consecutive attempts that will happen before panicing. - // Any number smaller than zero will be considered as unlimited retries - MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` - // WaitForNewBlocksPeriod time that will be waited when the synchronizer has reached the latest block - WaitForNewBlocksPeriod configTypes.Duration `mapstructure:"WaitForNewBlocksPeriod"` -} - type BridgeSync struct { processor *processor driver *sync.EVMDriver diff --git a/bridgesync/config.go b/bridgesync/config.go new file mode 100644 index 00000000..9aa849e2 --- /dev/null +++ b/bridgesync/config.go @@ -0,0 +1,27 @@ +package bridgesync + +import ( + "github.com/0xPolygon/cdk/config/types" + "github.com/ethereum/go-ethereum/common" +) + +type Config struct { + // DBPath path of the DB + DBPath string `mapstructure:"DBPath"` + // BlockFinality indicates the status of the blocks that will be queried in order to sync + BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` + // InitialBlockNum is the first block that will be queried when starting the synchronization from scratch. + // It should be a number equal or bellow the creation of the bridge contract + InitialBlockNum uint64 `mapstructure:"InitialBlockNum"` + // BridgeAddr is the address of the bridge smart contract + BridgeAddr common.Address `mapstructure:"BridgeAddr"` + // SyncBlockChunkSize is the amount of blocks that will be queried to the client on each request + SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"` + // RetryAfterErrorPeriod is the time that will be waited when an unexpected error happens before retry + RetryAfterErrorPeriod types.Duration `mapstructure:"RetryAfterErrorPeriod"` + // MaxRetryAttemptsAfterError is the maximum number of consecutive attempts that will happen before panicing. + // Any number smaller than zero will be considered as unlimited retries + MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` + // WaitForNewBlocksPeriod time that will be waited when the synchronizer has reached the latest block + WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"` +} diff --git a/bridgesync/e2e_test.go b/bridgesync/e2e_test.go index 1f5a080c..0d44cab5 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -19,13 +19,12 @@ import ( "github.com/stretchr/testify/require" ) -func newSimulatedClient(auth *bind.TransactOpts) ( +func newSimulatedClient(t *testing.T, auth *bind.TransactOpts) ( client *simulated.Backend, bridgeAddr common.Address, bridgeContract *polygonzkevmbridgev2.Polygonzkevmbridgev2, - err error, ) { - // ctx := context.Background() + var err error balance, _ := big.NewInt(0).SetString("10000000000000000000000000", 10) //nolint:gomnd address := auth.From genesisAlloc := map[common.Address]types.Account{ @@ -37,6 +36,7 @@ func newSimulatedClient(auth *bind.TransactOpts) ( client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit)) bridgeAddr, _, bridgeContract, err = polygonzkevmbridgev2.DeployPolygonzkevmbridgev2(auth, client.Client()) + require.NoError(t, err) client.Commit() return } diff --git a/bridgesync/processor.go b/bridgesync/processor.go index c9a7944c..9c2b572d 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -93,7 +93,7 @@ type processor struct { func newProcessor(ctx context.Context, dbPath, dbPrefix string) (*processor, error) { eventsTable := dbPrefix + eventsTableSufix lastBlockTable := dbPrefix + lastBlockTableSufix - logger := log.WithFields("syncer", dbPrefix) + logger := log.WithFields("bridge-syncer", dbPrefix) tableCfgFunc := func(defaultBuckets kv.TableCfg) kv.TableCfg { cfg := kv.TableCfg{ eventsTable: {}, diff --git a/l1bridge2infoindexsync/config.go b/l1bridge2infoindexsync/config.go new file mode 100644 index 00000000..ef37f738 --- /dev/null +++ b/l1bridge2infoindexsync/config.go @@ -0,0 +1,15 @@ +package l1bridge2infoindexsync + +import "github.com/0xPolygon/cdk/config/types" + +type Config struct { + // DBPath path of the DB + DBPath string `mapstructure:"DBPath"` + // RetryAfterErrorPeriod is the time that will be waited when an unexpected error happens before retry + RetryAfterErrorPeriod types.Duration `mapstructure:"RetryAfterErrorPeriod"` + // MaxRetryAttemptsAfterError is the maximum number of consecutive attempts that will happen before panicing. + // Any number smaller than zero will be considered as unlimited retries + MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` + // WaitForSyncersPeriod time that will be waited when the synchronizer has reached the latest state + WaitForSyncersPeriod types.Duration `mapstructure:"WaitForSyncersPeriod"` +} diff --git a/l1bridge2infoindexsync/driver.go b/l1bridge2infoindexsync/driver.go index e0e9b5cc..ce681bf0 100644 --- a/l1bridge2infoindexsync/driver.go +++ b/l1bridge2infoindexsync/driver.go @@ -74,11 +74,11 @@ func (d *driver) sync(ctx context.Context) { found := false for { lastL1InfoTreeIndex, err = d.downloader.getLastL1InfoIndexUntilBlock(ctx, syncUntilBlock) - if err == l1infotreesync.ErrNotFound || err == l1infotreesync.ErrBlockNotProcessed { - log.Debugf("l1 info tree index not ready, querying until block %d: %s", syncUntilBlock, err) - break - } if err != nil { + if err == l1infotreesync.ErrNotFound || err == l1infotreesync.ErrBlockNotProcessed { + log.Debugf("l1 info tree index not ready, querying until block %d: %s", syncUntilBlock, err) + break + } attempts++ log.Errorf("error getting last l1 info tree index: %v", err) d.rh.Handle("getLastL1InfoIndexUntilBlock", attempts) @@ -136,40 +136,35 @@ func (d *driver) sync(ctx context.Context) { } func (d *driver) getTargetSynchronizationBlock(ctx context.Context, lpbProcessor uint64) (syncUntilBlock uint64, shouldWait bool, err error) { - lastFinalised, err := d.downloader.getLastFinalizedL1Block(ctx) // TODO: configure finality, but then we need to deal with reorgs? + lastFinalised, err := d.downloader.getLastFinalizedL1Block(ctx) // NOTE: if this had configurable finality, it would be needed to deal with reorgs if err != nil { return } - if lpbProcessor >= lastFinalised { - log.Debugf( - "should wait because the last processed block (%d) is greater or equal than the last finalised (%d)", - lpbProcessor, lastFinalised, - ) - shouldWait = true + checkProcessedBlockFn := func(blockToCheck, lastProcessed uint64, blockType string) bool { + if blockToCheck >= lastProcessed { + log.Debugf( + "should wait because the last processed block (%d) is greater or equal than the %s (%d)", + blockToCheck, blockType, lastProcessed) + shouldWait = true + return true + } + return false + } + if checkProcessedBlockFn(lpbProcessor, lastFinalised, "last finalised") { return } lpbInfo, err := d.downloader.getLastProcessedBlockL1InfoTree(ctx) if err != nil { return } - if lpbProcessor >= lpbInfo { - log.Debugf( - "should wait because the last processed block (%d) is greater or equal than the last block from L1 Info tree sync (%d)", - lpbProcessor, lpbInfo, - ) - shouldWait = true + if checkProcessedBlockFn(lpbProcessor, lastFinalised, "last block from L1 Info tree sync") { return } lpbBridge, err := d.downloader.getLastProcessedBlockBridge(ctx) if err != nil { return } - if lpbProcessor >= lpbBridge { - log.Debugf( - "should wait because the last processed block (%d) is greater or equal than the last block from l1 bridge sync (%d)", - lpbProcessor, lpbBridge, - ) - shouldWait = true + if checkProcessedBlockFn(lpbProcessor, lastFinalised, "last block from l1 bridge sync") { return } diff --git a/l1bridge2infoindexsync/l1bridge2infoindexsync.go b/l1bridge2infoindexsync/l1bridge2infoindexsync.go index 8510d44a..b1c8fc55 100644 --- a/l1bridge2infoindexsync/l1bridge2infoindexsync.go +++ b/l1bridge2infoindexsync/l1bridge2infoindexsync.go @@ -5,7 +5,6 @@ import ( "time" "github.com/0xPolygon/cdk/bridgesync" - configTypes "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/sync" "github.com/ethereum/go-ethereum" @@ -16,18 +15,6 @@ type L1Bridge2InfoIndexSync struct { driver *driver } -type Config struct { - // DBPath path of the DB - DBPath string `mapstructure:"DBPath"` - // RetryAfterErrorPeriod is the time that will be waited when an unexpected error happens before retry - RetryAfterErrorPeriod configTypes.Duration `mapstructure:"RetryAfterErrorPeriod"` - // MaxRetryAttemptsAfterError is the maximum number of consecutive attempts that will happen before panicing. - // Any number smaller than zero will be considered as unlimited retries - MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` - // WaitForSyncersPeriod time that will be waited when the synchronizer has reached the latest state - WaitForSyncersPeriod configTypes.Duration `mapstructure:"WaitForSyncersPeriod"` -} - func New( dbPath string, l1Bridge *bridgesync.BridgeSync, diff --git a/l1bridge2infoindexsync/processor.go b/l1bridge2infoindexsync/processor.go index 0d3ba897..9b86ad9b 100644 --- a/l1bridge2infoindexsync/processor.go +++ b/l1bridge2infoindexsync/processor.go @@ -49,11 +49,10 @@ func (lp *lastProcessed) UnmarshalBinary(data []byte) error { func newProcessor(dbPath string) (*processor, error) { tableCfgFunc := func(defaultBuckets kv.TableCfg) kv.TableCfg { - cfg := kv.TableCfg{ + return kv.TableCfg{ lastProcessedTable: {}, relationTable: {}, } - return cfg } db, err := mdbx.NewMDBX(nil). Path(dbPath). @@ -122,6 +121,23 @@ func (p *processor) processUntilBlock(ctx context.Context, lastProcessedBlock ui return err } + if len(relations) == 0 { + _, lastIndex, err := p.getLastProcessedBlockAndL1InfoTreeIndexWithTx(tx) + if err != nil { + tx.Rollback() + return err + } + if err := p.updateLastProcessedBlockAndL1InfoTreeIndexWithTx( + tx, + lastProcessedBlock, + lastIndex, + ); err != nil { + tx.Rollback() + return err + } + return tx.Commit() + } + for _, relation := range relations { if _, err := p.getL1InfoTreeIndexByBridgeIndexWithTx(tx, relation.bridgeIndex); err != ErrNotFound { // Note that indexes could be repeated as the L1 Info tree update can be produced by a rollup and not mainnet. @@ -138,29 +154,13 @@ func (p *processor) processUntilBlock(ctx context.Context, lastProcessedBlock ui } } - if len(relations) > 0 { - if err := p.updateLastProcessedBlockAndL1InfoTreeIndexWithTx( - tx, - lastProcessedBlock, - relations[len(relations)-1].l1InfoTreeIndex, - ); err != nil { - tx.Rollback() - return err - } - } else { - _, lastIndex, err := p.getLastProcessedBlockAndL1InfoTreeIndexWithTx(tx) - if err != nil { - tx.Rollback() - return err - } - if err := p.updateLastProcessedBlockAndL1InfoTreeIndexWithTx( - tx, - lastProcessedBlock, - lastIndex, - ); err != nil { - tx.Rollback() - return err - } + if err := p.updateLastProcessedBlockAndL1InfoTreeIndexWithTx( + tx, + lastProcessedBlock, + relations[len(relations)-1].l1InfoTreeIndex, + ); err != nil { + tx.Rollback() + return err } return tx.Commit() diff --git a/l1infotreesync/config.go b/l1infotreesync/config.go new file mode 100644 index 00000000..1b1d8014 --- /dev/null +++ b/l1infotreesync/config.go @@ -0,0 +1,20 @@ +package l1infotreesync + +import ( + "github.com/0xPolygon/cdk/config/types" + "github.com/ethereum/go-ethereum/common" +) + +type Config struct { + DBPath string `mapstructure:"DBPath"` + GlobalExitRootAddr common.Address `mapstructure:"GlobalExitRootAddr"` + RollupManagerAddr common.Address `mapstructure:"RollupManagerAddr"` + SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"` + // BlockFinality indicates the status of the blocks that will be queried in order to sync + BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` + URLRPCL1 string `mapstructure:"URLRPCL1"` + WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"` + InitialBlock uint64 `mapstructure:"InitialBlock"` + RetryAfterErrorPeriod types.Duration `mapstructure:"RetryAfterErrorPeriod"` + MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` +} diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index d2c59fd8..8cd3ee70 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -5,7 +5,6 @@ import ( "errors" "time" - "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/sync" "github.com/0xPolygon/cdk/tree" @@ -17,20 +16,6 @@ const ( downloadBufferSize = 1000 ) -type Config struct { - DBPath string `mapstructure:"DBPath"` - GlobalExitRootAddr common.Address `mapstructure:"GlobalExitRootAddr"` - RollupManagerAddr common.Address `mapstructure:"RollupManagerAddr"` - SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"` - // BlockFinality indicates the status of the blocks that will be queried in order to sync - BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` - URLRPCL1 string `mapstructure:"URLRPCL1"` - WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"` - InitialBlock uint64 `mapstructure:"InitialBlock"` - RetryAfterErrorPeriod types.Duration `mapstructure:"RetryAfterErrorPeriod"` - MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` -} - type L1InfoTreeSync struct { processor *processor driver *sync.EVMDriver diff --git a/lastgersync/config.go b/lastgersync/config.go new file mode 100644 index 00000000..9db63bec --- /dev/null +++ b/lastgersync/config.go @@ -0,0 +1,27 @@ +package lastgersync + +import ( + "github.com/0xPolygon/cdk/config/types" + "github.com/ethereum/go-ethereum/common" +) + +type Config struct { + // DBPath path of the DB + DBPath string `mapstructure:"DBPath"` + // BlockFinality indicates the status of the blocks that will be queried in order to sync + BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` + // InitialBlockNum is the first block that will be queried when starting the synchronization from scratch. + // It should be a number equal or bellow the creation of the bridge contract + InitialBlockNum uint64 `mapstructure:"InitialBlockNum"` + // GlobalExitRootL2Addr is the address of the GER smart contract on L2 + GlobalExitRootL2Addr common.Address `mapstructure:"GlobalExitRootL2Addr"` + // RetryAfterErrorPeriod is the time that will be waited when an unexpected error happens before retry + RetryAfterErrorPeriod types.Duration `mapstructure:"RetryAfterErrorPeriod"` + // MaxRetryAttemptsAfterError is the maximum number of consecutive attempts that will happen before panicing. + // Any number smaller than zero will be considered as unlimited retries + MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` + // WaitForNewBlocksPeriod time that will be waited when the synchronizer has reached the latest block + WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"` + // DownloadBufferSize buffer of events to be porcessed. When reached will stop downloading events until the processing catches up + DownloadBufferSize int `mapstructure:"DownloadBufferSize"` +} diff --git a/lastgersync/lastgersync.go b/lastgersync/lastgersync.go index 038c94f8..2d7ef8cb 100644 --- a/lastgersync/lastgersync.go +++ b/lastgersync/lastgersync.go @@ -5,7 +5,6 @@ import ( "time" - configTypes "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/l1infotreesync" @@ -17,27 +16,6 @@ const ( reorgDetectorID = "lastGERSync" ) -type Config struct { - // DBPath path of the DB - DBPath string `mapstructure:"DBPath"` - // BlockFinality indicates the status of the blocks that will be queried in order to sync - BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` - // InitialBlockNum is the first block that will be queried when starting the synchronization from scratch. - // It should be a number equal or bellow the creation of the bridge contract - InitialBlockNum uint64 `mapstructure:"InitialBlockNum"` - // GlobalExitRootL2Addr is the address of the GER smart contract on L2 - GlobalExitRootL2Addr common.Address `mapstructure:"GlobalExitRootL2Addr"` - // RetryAfterErrorPeriod is the time that will be waited when an unexpected error happens before retry - RetryAfterErrorPeriod configTypes.Duration `mapstructure:"RetryAfterErrorPeriod"` - // MaxRetryAttemptsAfterError is the maximum number of consecutive attempts that will happen before panicing. - // Any number smaller than zero will be considered as unlimited retries - MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"` - // WaitForNewBlocksPeriod time that will be waited when the synchronizer has reached the latest block - WaitForNewBlocksPeriod configTypes.Duration `mapstructure:"WaitForNewBlocksPeriod"` - // DownloadBufferSize buffer of events to be porcessed. When reached will stop downloading events until the processing catches up - DownloadBufferSize int `mapstructure:"DownloadBufferSize"` -} - type LastGERSync struct { driver *sync.EVMDriver processor *processor From e124a9f127c84eb18f7e19037d29e7caaa211fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= Date: Mon, 19 Aug 2024 16:23:56 +0200 Subject: [PATCH 48/49] Fixing warnings and go mod tidy --- bridgesync/bridgesync.go | 6 +++--- bridgesync/e2e_test.go | 6 ++++-- bridgesync/processor.go | 4 ++-- go.sum | 4 ---- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index ae16e7b1..e417abc3 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -34,7 +34,7 @@ func NewL1( retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, ) (*BridgeSync, error) { - return new( + return newBridgeSync( ctx, dbPath, bridge, @@ -64,7 +64,7 @@ func NewL2( retryAfterErrorPeriod time.Duration, maxRetryAttemptsAfterError int, ) (*BridgeSync, error) { - return new( + return newBridgeSync( ctx, dbPath, bridge, @@ -80,7 +80,7 @@ func NewL2( ) } -func new( +func newBridgeSync( ctx context.Context, dbPath string, bridge common.Address, diff --git a/bridgesync/e2e_test.go b/bridgesync/e2e_test.go index 0d44cab5..b61454bf 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -24,6 +24,7 @@ func newSimulatedClient(t *testing.T, auth *bind.TransactOpts) ( bridgeAddr common.Address, bridgeContract *polygonzkevmbridgev2.Polygonzkevmbridgev2, ) { + t.Helper() var err error balance, _ := big.NewInt(0).SetString("10000000000000000000000000", 10) //nolint:gomnd address := auth.From @@ -49,9 +50,9 @@ func TestBridgeEventE2E(t *testing.T) { require.NoError(t, err) auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337)) require.NoError(t, err) - client, bridgeAddr, bridgeSc, err := newSimulatedClient(auth) - require.NoError(t, err) + client, bridgeAddr, bridgeSc := newSimulatedClient(t, auth) rd, err := reorgdetector.New(ctx, client.Client(), dbPathReorg) + require.NoError(t, err) go rd.Start(ctx) syncer, err := bridgesync.NewL1(ctx, dbPathSyncer, bridgeAddr, 10, etherman.LatestBlock, rd, client.Client(), 0, time.Millisecond*10, 0, 0) @@ -76,6 +77,7 @@ func TestBridgeEventE2E(t *testing.T) { bridge.OriginAddress, false, nil, ) + require.NoError(t, err) client.Commit() receipt, err := client.Client().TransactionReceipt(ctx, tx.Hash()) require.NoError(t, err) diff --git a/bridgesync/processor.go b/bridgesync/processor.go index 9c2b572d..b8e15e52 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -47,9 +47,9 @@ func (b *Bridge) Hash() common.Hash { bigIntSize = 32 ) origNet := make([]byte, uint32ByteSize) - binary.BigEndian.PutUint32(origNet, uint32(b.OriginNetwork)) + binary.BigEndian.PutUint32(origNet, b.OriginNetwork) destNet := make([]byte, uint32ByteSize) - binary.BigEndian.PutUint32(destNet, uint32(b.DestinationNetwork)) + binary.BigEndian.PutUint32(destNet, b.DestinationNetwork) metaHash := keccak256.Hash(b.Metadata) var buf [bigIntSize]byte diff --git a/go.sum b/go.sum index ef77edba..531e51d7 100644 --- a/go.sum +++ b/go.sum @@ -8,10 +8,6 @@ github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3 h1:zJ06KCGLMDOap4slop/QmiM github.com/0xPolygonHermez/zkevm-data-streamer v0.2.3/go.mod h1:bv7DjATsczN2WvFt26jv34TWv6rfvYM1SqegrgrFwfI= github.com/0xPolygonHermez/zkevm-ethtx-manager v0.1.10-0.20240716105056-c051c96d0234 h1:QElCysO7f2xaknY/RDjxcs7IVmcgORfsCX2g+YD0Ko4= github.com/0xPolygonHermez/zkevm-ethtx-manager v0.1.10-0.20240716105056-c051c96d0234/go.mod h1:zBZWxwOHKlw+ghd9roQLgIkDZWA7e7qO3EsfQQT/+oQ= -github.com/0xPolygonHermez/zkevm-synchronizer-l1 v0.6.3-0.20240712085301-0310358abb59 h1:Qwh92vFEXnpmDggQaZA3648viEQfLdMnAw/WFSY+2i8= -github.com/0xPolygonHermez/zkevm-synchronizer-l1 v0.6.3-0.20240712085301-0310358abb59/go.mod h1:/LHf8jPQeBYKABM1xUmN1dKaFVIJc9jMQDSGBDJ7CS0= -github.com/0xPolygonHermez/zkevm-synchronizer-l1 v0.6.3 h1:C+jNYr/CDMMn8wn3HqZqLTPU0luNYIB35pnxVf9O8TM= -github.com/0xPolygonHermez/zkevm-synchronizer-l1 v0.6.3/go.mod h1:/LHf8jPQeBYKABM1xUmN1dKaFVIJc9jMQDSGBDJ7CS0= github.com/0xPolygonHermez/zkevm-synchronizer-l1 v0.6.4 h1:6hk1NCKyR+JycmRFG7Uy7Ko3GghZ3DXYMf5muo3F29Q= github.com/0xPolygonHermez/zkevm-synchronizer-l1 v0.6.4/go.mod h1:/LHf8jPQeBYKABM1xUmN1dKaFVIJc9jMQDSGBDJ7CS0= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= From 84fd550dff15bee8cbdf07144b40a8cb971a6888 Mon Sep 17 00:00:00 2001 From: bros Date: Wed, 21 Aug 2024 08:42:21 +0000 Subject: [PATCH 49/49] fix UTs --- bridgesync/e2e_test.go | 2 +- cmd/run.go | 75 ------------------------------------------ 2 files changed, 1 insertion(+), 76 deletions(-) diff --git a/bridgesync/e2e_test.go b/bridgesync/e2e_test.go index b61454bf..d733a53e 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -97,7 +97,7 @@ func TestBridgeEventE2E(t *testing.T) { syncerUpToDate = true break } - time.Sleep(time.Millisecond * 10) + time.Sleep(time.Millisecond * 100) errMsg = fmt.Sprintf("last block from client: %d, last block from syncer: %d", lb, lpb) } require.True(t, syncerUpToDate, errMsg) diff --git a/cmd/run.go b/cmd/run.go index 2b4aa149..c17c4676 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -685,78 +685,3 @@ func createRPC( }, }) } - -func isNeeded(casesWhereNeeded, actualCases []string) bool { - for _, actaulCase := range actualCases { - for _, caseWhereNeeded := range casesWhereNeeded { - if actaulCase == caseWhereNeeded { - return true - } - } - } - return false -} - -func runL1InfoTreeSyncerIfNeeded( - ctx context.Context, - components []string, - cfg config.Config, - l1Client *ethclient.Client, - reorgDetector *reorgdetector.ReorgDetector, -) *l1infotreesync.L1InfoTreeSync { - if !isNeeded([]string{AGGORACLE, SEQUENCE_SENDER}, components) { - return nil - } - l1InfoTreeSync, err := l1infotreesync.New( - ctx, - cfg.L1InfoTreeSync.DBPath, - cfg.L1InfoTreeSync.GlobalExitRootAddr, - cfg.L1InfoTreeSync.RollupManagerAddr, - cfg.L1InfoTreeSync.SyncBlockChunkSize, - etherman.BlockNumberFinality(cfg.L1InfoTreeSync.BlockFinality), - reorgDetector, - l1Client, - cfg.L1InfoTreeSync.WaitForNewBlocksPeriod.Duration, - cfg.L1InfoTreeSync.InitialBlock, - cfg.L1InfoTreeSync.RetryAfterErrorPeriod.Duration, - cfg.L1InfoTreeSync.MaxRetryAttemptsAfterError, - ) - if err != nil { - log.Fatal(err) - } - go l1InfoTreeSync.Start(ctx) - return l1InfoTreeSync -} - -func runL1ClientIfNeeded(components []string, urlRPCL1 string) *ethclient.Client { - if !isNeeded([]string{SEQUENCE_SENDER, AGGREGATOR, AGGORACLE}, components) { - return nil - } - log.Debugf("dialing L1 client at: %s", urlRPCL1) - l1CLient, err := ethclient.Dial(urlRPCL1) - if err != nil { - log.Fatal(err) - } - return l1CLient -} - -func runReorgDetectorL1IfNeeded(ctx context.Context, components []string, l1Client *ethclient.Client, dbPath string) *reorgdetector.ReorgDetector { - if !isNeeded([]string{SEQUENCE_SENDER, AGGREGATOR, AGGORACLE}, components) { - return nil - } - rd := newReorgDetector(ctx, dbPath, l1Client) - go rd.Start(ctx) - return rd -} - -func newReorgDetector( - ctx context.Context, - dbPath string, - client *ethclient.Client, -) *reorgdetector.ReorgDetector { - rd, err := reorgdetector.New(ctx, client, dbPath) - if err != nil { - log.Fatal(err) - } - return rd -}