diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index d9fb515d..e3b50932 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -60,7 +60,8 @@ func New( aggLayerClient agglayer.AgglayerClientInterface, l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, l2Syncer types.L2BridgeSyncer, - epochNotifier types.EpochNotifier) (*AggSender, error) { + epochNotifier types.EpochNotifier, + l1Client types.EthClient) (*AggSender, error) { storageConfig := db.AggSenderSQLStorageConfig{ DBPath: cfg.StoragePath, KeepCertificatesHistory: cfg.KeepCertificatesHistory, @@ -90,7 +91,8 @@ func New( return nil, fmt.Errorf("error creating aggkit prover client: %w", err) } - flowManager = newAggchainProverFlow(logger, cfg, aggchainProofClient, storage, l1InfoTreeSyncer, l2Syncer) + flowManager = newAggchainProverFlow(logger, cfg, aggchainProofClient, storage, + l1InfoTreeSyncer, l2Syncer, l1Client) } else { flowManager = newPPFlow(logger, cfg, storage, l1InfoTreeSyncer, l2Syncer) } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 541c3aff..7b18755d 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -82,7 +82,7 @@ func TestAggSenderStart(t *testing.T) { aggLayerMock, nil, bridgeL2SyncerMock, - epochNotifierMock) + epochNotifierMock, nil) require.NoError(t, err) require.NotNil(t, aggSender) ch := make(chan aggsendertypes.EpochEvent) @@ -114,7 +114,7 @@ func TestAggSenderSendCertificates(t *testing.T) { AggLayerMock, nil, bridgeL2SyncerMock, - epochNotifierMock) + epochNotifierMock, nil) require.NoError(t, err) require.NotNil(t, aggSender) ch := make(chan aggsendertypes.EpochEvent, 2) diff --git a/aggsender/flow_aggchain_prover.go b/aggsender/flow_aggchain_prover.go index e04e07e1..f18b2bf5 100644 --- a/aggsender/flow_aggchain_prover.go +++ b/aggsender/flow_aggchain_prover.go @@ -3,18 +3,24 @@ package aggsender import ( "context" "fmt" + "math/big" "github.com/agglayer/aggkit/agglayer" "github.com/agglayer/aggkit/aggsender/db" "github.com/agglayer/aggkit/aggsender/grpc" "github.com/agglayer/aggkit/aggsender/types" + "github.com/agglayer/aggkit/etherman" + treeTypes "github.com/agglayer/aggkit/tree/types" "github.com/ethereum/go-ethereum/common" ) +var finalizedBlockBigInt = big.NewInt(int64(etherman.Finalized)) + // aggchainProverFlow is a struct that holds the logic for the AggchainProver prover type flow type aggchainProverFlow struct { *baseFlow + l1Client types.EthClient aggchainProofClient grpc.AggchainProofClientInterface } @@ -24,8 +30,10 @@ func newAggchainProverFlow(log types.Logger, aggkitProverClient grpc.AggchainProofClientInterface, storage db.AggSenderStorage, l1InfoTreeSyncer types.L1InfoTreeSyncer, - l2Syncer types.L2BridgeSyncer) *aggchainProverFlow { + l2Syncer types.L2BridgeSyncer, + l1Client types.EthClient) *aggchainProverFlow { return &aggchainProverFlow{ + l1Client: l1Client, aggchainProofClient: aggkitProverClient, baseFlow: &baseFlow{ log: log, @@ -49,7 +57,6 @@ func (a *aggchainProverFlow) GetCertificateBuildParams(ctx context.Context) (*ty if lastSentCertificateInfo != nil && lastSentCertificateInfo.Status == agglayer.InError { // if the last certificate was in error, we need to resend it - a.log.Infof("resending the same InError certificate: %s", lastSentCertificateInfo.String()) bridges, claims, err := a.getBridgesAndClaims(ctx, lastSentCertificateInfo.FromBlock, lastSentCertificateInfo.ToBlock) @@ -65,12 +72,17 @@ func (a *aggchainProverFlow) GetCertificateBuildParams(ctx context.Context) (*ty "but no bridges to resend the same certificate", lastSentCertificateInfo.String()) } - proof := lastSentCertificateInfo.AggchainProof + aggProof := lastSentCertificateInfo.AggchainProof toBlock := lastSentCertificateInfo.ToBlock - if len(proof) == 0 { + if len(aggProof) == 0 { + proof, leaf, root, err := a.getFinalizedL1InfoTreeData(ctx) + if err != nil { + return nil, fmt.Errorf("aggchainProverFlow - error getting finalized L1 Info tree data: %w", err) + } + aggchainProof, err := a.aggchainProofClient.GenerateAggchainProof(lastSentCertificateInfo.FromBlock, - lastSentCertificateInfo.ToBlock, common.Hash{}, common.Hash{}, [32]common.Hash{}) + lastSentCertificateInfo.ToBlock, root, leaf, proof) if err != nil { return nil, fmt.Errorf("aggchainProverFlow - error fetching aggchain proof for block range %d : %d : %w", lastSentCertificateInfo.FromBlock, lastSentCertificateInfo.ToBlock, err) @@ -81,7 +93,7 @@ func (a *aggchainProverFlow) GetCertificateBuildParams(ctx context.Context) (*ty aggchainProof.StartBlock, aggchainProof.EndBlock, aggchainProof.Proof, lastSentCertificateInfo.FromBlock, lastSentCertificateInfo.ToBlock) - proof = aggchainProof.Proof + aggProof = aggchainProof.Proof if aggchainProof.EndBlock < lastSentCertificateInfo.ToBlock { // aggchain prover can return a proof for a smaller range than requested @@ -99,7 +111,7 @@ func (a *aggchainProverFlow) GetCertificateBuildParams(ctx context.Context) (*ty Claims: claims, LastSentCertificate: lastSentCertificateInfo, CreatedAt: lastSentCertificateInfo.CreatedAt, - AggchainProof: proof, + AggchainProof: aggProof, } buildParams, err = adjustBlockRange(buildParams, lastSentCertificateInfo.ToBlock, toBlock) @@ -116,8 +128,13 @@ func (a *aggchainProverFlow) GetCertificateBuildParams(ctx context.Context) (*ty return nil, err } + proof, leaf, root, err := a.getFinalizedL1InfoTreeData(ctx) + if err != nil { + return nil, fmt.Errorf("aggchainProverFlow - error getting finalized L1 Info tree data: %w", err) + } + aggchainProof, err := a.aggchainProofClient.GenerateAggchainProof( - buildParams.FromBlock, buildParams.ToBlock, common.Hash{}, common.Hash{}, [32]common.Hash{}) + buildParams.FromBlock, buildParams.ToBlock, root, leaf, proof) if err != nil { return nil, fmt.Errorf("aggchainProverFlow - error fetching aggchain proof for block range %d : %d : %w", buildParams.FromBlock, buildParams.ToBlock, err) @@ -137,6 +154,80 @@ func (a *aggchainProverFlow) GetCertificateBuildParams(ctx context.Context) (*ty return buildParams, nil } +// getFinalizedL1InfoTreeData returns the L1 Info tree data for the last finalized processed block +// l1InfoTreeData is: +// - the leaf data of the highest index leaf on that block and root +// - merkle proof of given l1 info tree leaf +// - the root of the l1 info tree on that block +func (a *aggchainProverFlow) getFinalizedL1InfoTreeData(ctx context.Context, +) (treeTypes.Proof, common.Hash, common.Hash, error) { + lastFinalizedProcessedBlock, err := a.getLatestProcessedFinalizedBlock(ctx) + if err != nil { + return treeTypes.Proof{}, common.Hash{}, common.Hash{}, + fmt.Errorf("aggchainProverFlow - error getting latest processed finalized block: %w", err) + } + + root, err := a.l1InfoTreeSyncer.GetLastL1InfoTreeRootByBlockNum(ctx, lastFinalizedProcessedBlock) + if err != nil { + return treeTypes.Proof{}, common.Hash{}, common.Hash{}, + fmt.Errorf("aggchainProverFlow - error getting last L1 Info tree root by block num %d: %w", + lastFinalizedProcessedBlock, err) + } + + leaf, err := a.l1InfoTreeSyncer.GetInfoByIndex(ctx, root.Index) + if err != nil { + return treeTypes.Proof{}, common.Hash{}, common.Hash{}, + fmt.Errorf("aggchainProverFlow - error getting L1 Info tree leaf by index %d: %w", root.Index, err) + } + + proof, err := a.l1InfoTreeSyncer.GetL1InfoTreeMerkleProofFromIndexToRoot(ctx, root.Index, root.Hash) + if err != nil { + return treeTypes.Proof{}, common.Hash{}, common.Hash{}, + fmt.Errorf("aggchainProverFlow - error getting L1 Info tree merkle proof from index %d to root %s: %w", + root.Index, root.Hash.String(), err) + } + + return proof, leaf.Hash, root.Hash, nil +} + +// getLatestProcessedFinalizedBlock returns the latest processed finalized block from the l1infotreesyncer +func (a *aggchainProverFlow) getLatestProcessedFinalizedBlock(ctx context.Context) (uint64, error) { + lastFinalizedL1Block, err := a.l1Client.HeaderByNumber(ctx, finalizedBlockBigInt) + if err != nil { + return 0, fmt.Errorf("aggchainProverFlow - error getting latest finalized L1 block: %w", err) + } + + lastProcessedBlockNum, lastProcessedBlockHash, err := a.l1InfoTreeSyncer.GetProcessedBlockUntil(ctx, + lastFinalizedL1Block.Number.Uint64()) + if err != nil { + return 0, fmt.Errorf("aggchainProverFlow - error getting latest processed block from l1infotreesyncer: %w", err) + } + + if lastProcessedBlockNum == 0 { + return 0, fmt.Errorf("aggchainProverFlow - l1infotreesyncer did not process any block yet") + } + + if lastFinalizedL1Block.Number.Uint64() > lastProcessedBlockNum { + // syncer has a lower block than the finalized block, so we need to get that block from the l1 node + lastFinalizedL1Block, err = a.l1Client.HeaderByNumber(ctx, new(big.Int).SetUint64(lastProcessedBlockNum)) + if err != nil { + return 0, fmt.Errorf("aggchainProverFlow - error getting latest processed finalized block: %d: %w", + lastProcessedBlockNum, err) + } + } + + if (lastProcessedBlockHash == common.Hash{}) || (lastProcessedBlockHash == lastFinalizedL1Block.Hash()) { + // if the hash is empty it means that this is an old block that was processed before this + // feature was added, so we will consider it finalized + return lastFinalizedL1Block.Number.Uint64(), nil + } + + return 0, fmt.Errorf("aggchainProverFlow - l1infotreesyncer returned a different hash for "+ + "the latest finalized block: %d. Might be that syncer did not process a reorg yet. "+ + "Expected hash: %s, got: %s", lastProcessedBlockNum, + lastFinalizedL1Block.Hash().String(), lastProcessedBlockHash.String()) +} + // adjustBlockRange adjusts the block range of the certificate to match the range returned by the aggchain prover func adjustBlockRange(buildParams *types.CertificateBuildParams, requestedToBlock, aggchainProverToBlock uint64) (*types.CertificateBuildParams, error) { @@ -152,26 +243,3 @@ func adjustBlockRange(buildParams *types.CertificateBuildParams, return buildParams, nil } - -// getLastSentBlockAndRetryCount returns the last sent block of the last sent certificate -// if there is no previosly sent certificate, it returns 0 and 0 -func getLastSentBlockAndRetryCount(lastSentCertificateInfo *types.CertificateInfo) (uint64, int) { - if lastSentCertificateInfo == nil { - return 0, 0 - } - - retryCount := 0 - lastSentBlock := lastSentCertificateInfo.ToBlock - - if lastSentCertificateInfo.Status == agglayer.InError { - // if the last certificate was in error, we need to resend it - // from the block before the error - if lastSentCertificateInfo.FromBlock > 0 { - lastSentBlock = lastSentCertificateInfo.FromBlock - 1 - } - - retryCount = lastSentCertificateInfo.RetryCount + 1 - } - - return lastSentBlock, retryCount -} diff --git a/aggsender/flow_aggchain_prover_test.go b/aggsender/flow_aggchain_prover_test.go index 56b836bc..8cbde30a 100644 --- a/aggsender/flow_aggchain_prover_test.go +++ b/aggsender/flow_aggchain_prover_test.go @@ -3,6 +3,7 @@ package aggsender import ( "context" "errors" + "math/big" "testing" "time" @@ -10,9 +11,11 @@ import ( "github.com/agglayer/aggkit/aggsender/mocks" "github.com/agglayer/aggkit/aggsender/types" "github.com/agglayer/aggkit/bridgesync" + "github.com/agglayer/aggkit/l1infotreesync" "github.com/agglayer/aggkit/log" treeTypes "github.com/agglayer/aggkit/tree/types" "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" ) @@ -22,8 +25,13 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { ctx := context.Background() testCases := []struct { - name string - mockFn func(*mocks.AggSenderStorage, *mocks.L2BridgeSyncer, *mocks.AggchainProofClientInterface) + name string + mockFn func(*mocks.AggSenderStorage, + *mocks.L2BridgeSyncer, + *mocks.AggchainProofClientInterface, + *mocks.EthClient, + *mocks.L1InfoTreeSyncer, + ) expectedParams *types.CertificateBuildParams expectedError string }{ @@ -31,7 +39,9 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { name: "error getting last sent certificate", mockFn: func(mockStorage *mocks.AggSenderStorage, mockL2Syncer *mocks.L2BridgeSyncer, - mockClient *mocks.AggchainProofClientInterface) { + mockProverClient *mocks.AggchainProofClientInterface, + mockL1Client *mocks.EthClient, + mockL1InfTreeSyncer *mocks.L1InfoTreeSyncer) { mockStorage.On("GetLastSentCertificate").Return(nil, errors.New("some error")) }, expectedError: "some error", @@ -40,7 +50,9 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { name: "resend InError certificate with no bridges", mockFn: func(mockStorage *mocks.AggSenderStorage, mockL2Syncer *mocks.L2BridgeSyncer, - mockClient *mocks.AggchainProofClientInterface) { + mockProverClient *mocks.AggchainProofClientInterface, + mockL1Client *mocks.EthClient, + mockL1InfTreeSyncer *mocks.L1InfoTreeSyncer) { mockStorage.On("GetLastSentCertificate").Return(&types.CertificateInfo{ FromBlock: 1, ToBlock: 10, @@ -54,7 +66,10 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { name: "resend InError certificate with no auth proof", mockFn: func(mockStorage *mocks.AggSenderStorage, mockL2Syncer *mocks.L2BridgeSyncer, - mockClient *mocks.AggchainProofClientInterface) { + mockProverClient *mocks.AggchainProofClientInterface, + mockL1Client *mocks.EthClient, + mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} mockStorage.On("GetLastSentCertificate").Return(&types.CertificateInfo{ FromBlock: 1, ToBlock: 10, @@ -62,7 +77,16 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { }, nil) mockL2Syncer.On("GetBridgesPublished", ctx, uint64(1), uint64(10)).Return([]bridgesync.Bridge{{}}, nil) mockL2Syncer.On("GetClaims", ctx, uint64(1), uint64(10)).Return([]bridgesync.Claim{{}}, nil) - mockClient.On("GenerateAggchainProof", uint64(1), uint64(10), common.Hash{}, common.Hash{}, treeTypes.Proof{}).Return(&types.AggchainProof{ + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return(l1Header.Number.Uint64(), l1Header.Hash(), nil) + mockL1InfoTreeSyncer.On("GetLastL1InfoTreeRootByBlockNum", ctx, l1Header.Number.Uint64()).Return( + &treeTypes.Root{Hash: common.HexToHash("0x1"), Index: 0}, nil) + mockL1InfoTreeSyncer.On("GetInfoByIndex", ctx, uint32(0)).Return(&l1infotreesync.L1InfoTreeLeaf{ + BlockNumber: l1Header.Number.Uint64(), Hash: common.HexToHash("0x2")}, nil) + mockL1InfoTreeSyncer.On("GetL1InfoTreeMerkleProofFromIndexToRoot", ctx, uint32(0), common.HexToHash("0x1")).Return( + treeTypes.Proof{}, nil) + mockProverClient.On("GenerateAggchainProof", uint64(1), uint64(10), + common.HexToHash("0x1"), common.HexToHash("0x2"), treeTypes.Proof{}).Return(&types.AggchainProof{ Proof: []byte("some-proof"), StartBlock: 1, EndBlock: 10}, nil) }, expectedParams: &types.CertificateBuildParams{ @@ -83,7 +107,10 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { name: "resend InError certificate with no auth proof - aggchain prover returned smaller range", mockFn: func(mockStorage *mocks.AggSenderStorage, mockL2Syncer *mocks.L2BridgeSyncer, - mockClient *mocks.AggchainProofClientInterface) { + mockProverClient *mocks.AggchainProofClientInterface, + mockL1Client *mocks.EthClient, + mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} mockStorage.On("GetLastSentCertificate").Return(&types.CertificateInfo{ FromBlock: 1, ToBlock: 10, @@ -93,7 +120,16 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { {BlockNum: 5}, {BlockNum: 10}}, nil) mockL2Syncer.On("GetClaims", ctx, uint64(1), uint64(10)).Return([]bridgesync.Claim{ {BlockNum: 6}, {BlockNum: 9}}, nil) - mockClient.On("GenerateAggchainProof", uint64(1), uint64(10), common.Hash{}, common.Hash{}, treeTypes.Proof{}).Return(&types.AggchainProof{ + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return(l1Header.Number.Uint64(), l1Header.Hash(), nil) + mockL1InfoTreeSyncer.On("GetLastL1InfoTreeRootByBlockNum", ctx, l1Header.Number.Uint64()).Return( + &treeTypes.Root{Hash: common.HexToHash("0x1"), Index: 0}, nil) + mockL1InfoTreeSyncer.On("GetInfoByIndex", ctx, uint32(0)).Return(&l1infotreesync.L1InfoTreeLeaf{ + BlockNumber: l1Header.Number.Uint64(), Hash: common.HexToHash("0x2")}, nil) + mockL1InfoTreeSyncer.On("GetL1InfoTreeMerkleProofFromIndexToRoot", ctx, uint32(0), common.HexToHash("0x1")).Return( + treeTypes.Proof{}, nil) + mockProverClient.On("GenerateAggchainProof", uint64(1), uint64(10), + common.HexToHash("0x1"), common.HexToHash("0x2"), treeTypes.Proof{}).Return(&types.AggchainProof{ Proof: []byte("some-proof"), StartBlock: 1, EndBlock: 8}, nil) }, expectedParams: &types.CertificateBuildParams{ @@ -114,7 +150,9 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { name: "resend InError certificate with auth proof", mockFn: func(mockStorage *mocks.AggSenderStorage, mockL2Syncer *mocks.L2BridgeSyncer, - mockClient *mocks.AggchainProofClientInterface) { + mockProverClient *mocks.AggchainProofClientInterface, + mockL1Client *mocks.EthClient, + mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer) { mockStorage.On("GetLastSentCertificate").Return(&types.CertificateInfo{ FromBlock: 1, ToBlock: 10, @@ -143,13 +181,24 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { name: "error fetching aggchain proof for new certificate", mockFn: func(mockStorage *mocks.AggSenderStorage, mockL2Syncer *mocks.L2BridgeSyncer, - mockClient *mocks.AggchainProofClientInterface) { + mockProverClient *mocks.AggchainProofClientInterface, + mockL1Client *mocks.EthClient, + mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} mockStorage.On("GetLastSentCertificate").Return(nil, nil).Twice() mockL2Syncer.On("GetLastProcessedBlock", ctx).Return(uint64(10), nil) mockL2Syncer.On("GetBridgesPublished", ctx, uint64(1), uint64(10)).Return([]bridgesync.Bridge{{}}, nil) mockL2Syncer.On("GetClaims", ctx, uint64(1), uint64(10)).Return([]bridgesync.Claim{{}}, nil) - mockClient.On("GenerateAggchainProof", uint64(1), uint64(10), - common.Hash{}, common.Hash{}, treeTypes.Proof{}).Return(nil, errors.New("some error")) + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return(l1Header.Number.Uint64(), l1Header.Hash(), nil) + mockL1InfoTreeSyncer.On("GetLastL1InfoTreeRootByBlockNum", ctx, l1Header.Number.Uint64()).Return( + &treeTypes.Root{Hash: common.HexToHash("0x1"), Index: 0}, nil) + mockL1InfoTreeSyncer.On("GetInfoByIndex", ctx, uint32(0)).Return(&l1infotreesync.L1InfoTreeLeaf{ + BlockNumber: l1Header.Number.Uint64(), Hash: common.HexToHash("0x2")}, nil) + mockL1InfoTreeSyncer.On("GetL1InfoTreeMerkleProofFromIndexToRoot", ctx, uint32(0), common.HexToHash("0x1")).Return( + treeTypes.Proof{}, nil) + mockProverClient.On("GenerateAggchainProof", uint64(1), uint64(10), + common.HexToHash("0x1"), common.HexToHash("0x2"), treeTypes.Proof{}).Return(nil, errors.New("some error")) }, expectedError: "error fetching aggchain proof for block range 1 : 10 : some error", }, @@ -157,12 +206,24 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { name: "success fetching aggchain proof for new certificate", mockFn: func(mockStorage *mocks.AggSenderStorage, mockL2Syncer *mocks.L2BridgeSyncer, - mockClient *mocks.AggchainProofClientInterface) { + mockProverClient *mocks.AggchainProofClientInterface, + mockL1Client *mocks.EthClient, + mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} mockStorage.On("GetLastSentCertificate").Return(&types.CertificateInfo{ToBlock: 5}, nil).Twice() mockL2Syncer.On("GetLastProcessedBlock", ctx).Return(uint64(10), nil) mockL2Syncer.On("GetBridgesPublished", ctx, uint64(6), uint64(10)).Return([]bridgesync.Bridge{{}}, nil) mockL2Syncer.On("GetClaims", ctx, uint64(6), uint64(10)).Return([]bridgesync.Claim{{}}, nil) - mockClient.On("GenerateAggchainProof", uint64(6), uint64(10), common.Hash{}, common.Hash{}, treeTypes.Proof{}).Return(&types.AggchainProof{ + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return(l1Header.Number.Uint64(), l1Header.Hash(), nil) + mockL1InfoTreeSyncer.On("GetLastL1InfoTreeRootByBlockNum", ctx, l1Header.Number.Uint64()).Return( + &treeTypes.Root{Hash: common.HexToHash("0x1"), Index: 0}, nil) + mockL1InfoTreeSyncer.On("GetInfoByIndex", ctx, uint32(0)).Return(&l1infotreesync.L1InfoTreeLeaf{ + BlockNumber: l1Header.Number.Uint64(), Hash: common.HexToHash("0x2")}, nil) + mockL1InfoTreeSyncer.On("GetL1InfoTreeMerkleProofFromIndexToRoot", ctx, uint32(0), common.HexToHash("0x1")).Return( + treeTypes.Proof{}, nil) + mockProverClient.On("GenerateAggchainProof", uint64(6), uint64(10), + common.HexToHash("0x1"), common.HexToHash("0x2"), treeTypes.Proof{}).Return(&types.AggchainProof{ Proof: []byte("some-proof"), StartBlock: 6, EndBlock: 10}, nil) }, expectedParams: &types.CertificateBuildParams{ @@ -180,14 +241,26 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { name: "success fetching aggchain proof for new certificate - aggchain prover returns smaller range", mockFn: func(mockStorage *mocks.AggSenderStorage, mockL2Syncer *mocks.L2BridgeSyncer, - mockClient *mocks.AggchainProofClientInterface) { + mockProverClient *mocks.AggchainProofClientInterface, + mockL1Client *mocks.EthClient, + mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} mockStorage.On("GetLastSentCertificate").Return(&types.CertificateInfo{ToBlock: 5}, nil).Twice() mockL2Syncer.On("GetLastProcessedBlock", ctx).Return(uint64(10), nil) mockL2Syncer.On("GetBridgesPublished", ctx, uint64(6), uint64(10)).Return([]bridgesync.Bridge{ {BlockNum: 6}, {BlockNum: 10}}, nil) mockL2Syncer.On("GetClaims", ctx, uint64(6), uint64(10)).Return([]bridgesync.Claim{ {BlockNum: 8}, {BlockNum: 9}}, nil) - mockClient.On("GenerateAggchainProof", uint64(6), uint64(10), common.Hash{}, common.Hash{}, treeTypes.Proof{}).Return(&types.AggchainProof{ + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return(l1Header.Number.Uint64(), l1Header.Hash(), nil) + mockL1InfoTreeSyncer.On("GetLastL1InfoTreeRootByBlockNum", ctx, l1Header.Number.Uint64()).Return( + &treeTypes.Root{Hash: common.HexToHash("0x1"), Index: 0}, nil) + mockL1InfoTreeSyncer.On("GetInfoByIndex", ctx, uint32(0)).Return(&l1infotreesync.L1InfoTreeLeaf{ + BlockNumber: l1Header.Number.Uint64(), Hash: common.HexToHash("0x2")}, nil) + mockL1InfoTreeSyncer.On("GetL1InfoTreeMerkleProofFromIndexToRoot", ctx, uint32(0), common.HexToHash("0x1")).Return( + treeTypes.Proof{}, nil) + mockProverClient.On("GenerateAggchainProof", uint64(6), uint64(10), + common.HexToHash("0x1"), common.HexToHash("0x2"), treeTypes.Proof{}).Return(&types.AggchainProof{ Proof: []byte("some-proof"), StartBlock: 6, EndBlock: 8}, nil) }, expectedParams: &types.CertificateBuildParams{ @@ -208,13 +281,15 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() + mockAggchainProofClient := mocks.NewAggchainProofClientInterface(t) + mockL1InfTreeSyncer := mocks.NewL1InfoTreeSyncer(t) mockStorage := mocks.NewAggSenderStorage(t) mockL2Syncer := mocks.NewL2BridgeSyncer(t) - mockAggchainProofClient := mocks.NewAggchainProofClientInterface(t) + mockL1Client := mocks.NewEthClient(t) aggchainFlow := newAggchainProverFlow(log.WithFields("flowManager", "Test_AggchainProverFlow_GetCertificateBuildParams"), - Config{}, mockAggchainProofClient, mockStorage, nil, mockL2Syncer) + Config{}, mockAggchainProofClient, mockStorage, mockL1InfTreeSyncer, mockL2Syncer, mockL1Client) - tc.mockFn(mockStorage, mockL2Syncer, mockAggchainProofClient) + tc.mockFn(mockStorage, mockL2Syncer, mockAggchainProofClient, mockL1Client, mockL1InfTreeSyncer) params, err := aggchainFlow.GetCertificateBuildParams(ctx) if tc.expectedError != "" { @@ -223,6 +298,204 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { require.NoError(t, err) require.Equal(t, tc.expectedParams, params) } + + mockStorage.AssertExpectations(t) + mockL2Syncer.AssertExpectations(t) + mockL1Client.AssertExpectations(t) + mockL1InfTreeSyncer.AssertExpectations(t) + mockAggchainProofClient.AssertExpectations(t) + }) + } +} + +func Test_AggchainProverFlow_GetFinalizedL1InfoTreeData(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + testCases := []struct { + name string + mockFn func(*mocks.L1InfoTreeSyncer, *mocks.EthClient) + expectedProof treeTypes.Proof + expectedLeaf common.Hash + expectedRoot common.Hash + expectedError string + }{ + { + name: "error getting latest processed finalized block", + mockFn: func(mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer, mockL1Client *mocks.EthClient) { + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(nil, errors.New("some error")) + }, + expectedError: "error getting latest processed finalized block", + }, + { + name: "error getting last L1 Info tree root by block num", + mockFn: func(mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer, mockL1Client *mocks.EthClient) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return(l1Header.Number.Uint64(), l1Header.Hash(), nil) + mockL1InfoTreeSyncer.On("GetLastL1InfoTreeRootByBlockNum", ctx, l1Header.Number.Uint64()).Return(nil, errors.New("some error")) + }, + expectedError: "error getting last L1 Info tree root by block num 10: some error", + }, + { + name: "error getting L1 Info tree leaf by index", + mockFn: func(mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer, mockL1Client *mocks.EthClient) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return(l1Header.Number.Uint64(), l1Header.Hash(), nil) + mockL1InfoTreeSyncer.On("GetLastL1InfoTreeRootByBlockNum", ctx, l1Header.Number.Uint64()).Return(&treeTypes.Root{Index: 0}, nil) + mockL1InfoTreeSyncer.On("GetInfoByIndex", ctx, uint32(0)).Return(nil, errors.New("some error")) + }, + expectedError: "error getting L1 Info tree leaf by index 0: some error", + }, + { + name: "error getting L1 Info tree merkle proof from index to root", + mockFn: func(mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer, mockL1Client *mocks.EthClient) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return(l1Header.Number.Uint64(), l1Header.Hash(), nil) + mockL1InfoTreeSyncer.On("GetLastL1InfoTreeRootByBlockNum", ctx, l1Header.Number.Uint64()).Return(&treeTypes.Root{Index: 0, Hash: common.HexToHash("0x1")}, nil) + mockL1InfoTreeSyncer.On("GetInfoByIndex", ctx, uint32(0)).Return(&l1infotreesync.L1InfoTreeLeaf{Hash: common.HexToHash("0x2")}, nil) + mockL1InfoTreeSyncer.On("GetL1InfoTreeMerkleProofFromIndexToRoot", ctx, uint32(0), common.HexToHash("0x1")).Return(treeTypes.Proof{}, errors.New("some error")) + }, + expectedError: "error getting L1 Info tree merkle proof from index 0 to root", + }, + { + name: "success", + mockFn: func(mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer, mockL1Client *mocks.EthClient) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return(l1Header.Number.Uint64(), l1Header.Hash(), nil) + mockL1InfoTreeSyncer.On("GetLastL1InfoTreeRootByBlockNum", ctx, l1Header.Number.Uint64()).Return(&treeTypes.Root{Index: 0, Hash: common.HexToHash("0x1")}, nil) + mockL1InfoTreeSyncer.On("GetInfoByIndex", ctx, uint32(0)).Return(&l1infotreesync.L1InfoTreeLeaf{Hash: common.HexToHash("0x2")}, nil) + mockL1InfoTreeSyncer.On("GetL1InfoTreeMerkleProofFromIndexToRoot", ctx, uint32(0), common.HexToHash("0x1")).Return(treeTypes.Proof{}, nil) + }, + expectedProof: treeTypes.Proof{}, + expectedLeaf: common.HexToHash("0x2"), + expectedRoot: common.HexToHash("0x1"), + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncer(t) + mockL1Client := mocks.NewEthClient(t) + aggchainFlow := newAggchainProverFlow(log.WithFields("flowManager", "Test_AggchainProverFlow_GetFinalizedL1InfoTreeData"), + Config{}, nil, nil, mockL1InfoTreeSyncer, nil, mockL1Client) + + tc.mockFn(mockL1InfoTreeSyncer, mockL1Client) + + proof, leaf, root, err := aggchainFlow.getFinalizedL1InfoTreeData(ctx) + if tc.expectedError != "" { + require.ErrorContains(t, err, tc.expectedError) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectedProof, proof) + require.Equal(t, tc.expectedLeaf, leaf) + require.Equal(t, tc.expectedRoot, root) + } + + mockL1InfoTreeSyncer.AssertExpectations(t) + mockL1Client.AssertExpectations(t) + }) + } +} + +func Test_AggchainProverFlow_GetLatestProcessedFinalizedBlock(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + testCases := []struct { + name string + mockFn func(*mocks.L1InfoTreeSyncer, *mocks.EthClient) + expectedBlock uint64 + expectedError string + }{ + { + name: "error getting latest finalized L1 block", + mockFn: func(mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer, mockL1Client *mocks.EthClient) { + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(nil, errors.New("some error")) + }, + expectedError: "error getting latest finalized L1 block: some error", + }, + { + name: "error getting latest processed block from l1infotreesyncer", + mockFn: func(mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer, mockL1Client *mocks.EthClient) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return(uint64(0), common.Hash{}, errors.New("some error")) + }, + expectedError: "error getting latest processed block from l1infotreesyncer: some error", + }, + { + name: "l1infotreesyncer did not process any block yet", + mockFn: func(mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer, mockL1Client *mocks.EthClient) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return(uint64(0), common.Hash{}, nil) + }, + expectedError: "l1infotreesyncer did not process any block yet", + }, + { + name: "error getting latest processed finalized block", + mockFn: func(mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer, mockL1Client *mocks.EthClient) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return(uint64(9), common.Hash{}, nil) + mockL1Client.On("HeaderByNumber", ctx, big.NewInt(9)).Return(nil, errors.New("some error")) + }, + expectedError: "error getting latest processed finalized block: 9: some error", + }, + { + name: "l1infotreesyncer returned a different hash for the latest finalized block", + mockFn: func(mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer, mockL1Client *mocks.EthClient) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return( + l1Header.Number.Uint64(), common.HexToHash("0x2"), nil) + }, + expectedError: "l1infotreesyncer returned a different hash for the latest finalized block: 10. " + + "Might be that syncer did not process a reorg yet.", + }, + { + name: "success", + mockFn: func(mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer, mockL1Client *mocks.EthClient) { + l1Header := &gethTypes.Header{Number: big.NewInt(10)} + mockL1Client.On("HeaderByNumber", ctx, finalizedBlockBigInt).Return(l1Header, nil) + mockL1InfoTreeSyncer.On("GetProcessedBlockUntil", ctx, l1Header.Number.Uint64()).Return( + l1Header.Number.Uint64(), l1Header.Hash(), nil) + }, + expectedBlock: 10, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncer(t) + mockL1Client := mocks.NewEthClient(t) + aggchainFlow := newAggchainProverFlow(log.WithFields("flowManager", "Test_AggchainProverFlow_GetLatestProcessedFinalizedBlock"), + Config{}, nil, nil, mockL1InfoTreeSyncer, nil, mockL1Client) + + tc.mockFn(mockL1InfoTreeSyncer, mockL1Client) + + block, err := aggchainFlow.getLatestProcessedFinalizedBlock(ctx) + if tc.expectedError != "" { + require.ErrorContains(t, err, tc.expectedError) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectedBlock, block) + } + + mockL1InfoTreeSyncer.AssertExpectations(t) + mockL1Client.AssertExpectations(t) }) } } diff --git a/aggsender/flow_pp.go b/aggsender/flow_pp.go index db174692..8b7e0835 100644 --- a/aggsender/flow_pp.go +++ b/aggsender/flow_pp.go @@ -78,7 +78,7 @@ func (f *baseFlow) GetCertificateBuildParams(ctx context.Context) (*types.Certif return nil, err } - return &types.CertificateBuildParams{ + buildParams := &types.CertificateBuildParams{ FromBlock: fromBlock, ToBlock: toBlock, RetryCount: retryCount, @@ -86,22 +86,24 @@ func (f *baseFlow) GetCertificateBuildParams(ctx context.Context) (*types.Certif Bridges: bridges, Claims: claims, CreatedAt: uint32(time.Now().UTC().Unix()), - }, nil + } + + buildParams, err = f.limitCertSize(buildParams) + if err != nil { + return nil, fmt.Errorf("error limitCertSize: %w", err) + } + + return buildParams, nil } // BuildCertificate builds a certificate based on the buildParams // this function is the implementation of the FlowManager interface func (f *baseFlow) BuildCertificate(ctx context.Context, buildParams *types.CertificateBuildParams) (*agglayer.Certificate, error) { - certificateParams, err := f.limitCertSize(buildParams) - if err != nil { - return nil, fmt.Errorf("error limitCertSize: %w", err) - } - f.log.Infof("building certificate for %s estimatedSize=%d", - certificateParams.String(), certificateParams.EstimatedSize()) + buildParams.String(), buildParams.EstimatedSize()) - return f.buildCertificate(ctx, certificateParams, buildParams.LastSentCertificate) + return f.buildCertificate(ctx, buildParams, buildParams.LastSentCertificate) } // limitCertSize limits certificate size based on the max size configuration parameter @@ -410,6 +412,29 @@ func (f *baseFlow) getNextHeightAndPreviousLER( lastSentCertificateInfo.ID(), lastSentCertificateInfo.Status.String()) } +// getLastSentBlockAndRetryCount returns the last sent block of the last sent certificate +// if there is no previosly sent certificate, it returns 0 and 0 +func getLastSentBlockAndRetryCount(lastSentCertificateInfo *types.CertificateInfo) (uint64, int) { + if lastSentCertificateInfo == nil { + return 0, 0 + } + + retryCount := 0 + lastSentBlock := lastSentCertificateInfo.ToBlock + + if lastSentCertificateInfo.Status == agglayer.InError { + // if the last certificate was in error, we need to resend it + // from the block before the error + if lastSentCertificateInfo.FromBlock > 0 { + lastSentBlock = lastSentCertificateInfo.FromBlock - 1 + } + + retryCount = lastSentCertificateInfo.RetryCount + 1 + } + + return lastSentBlock, retryCount +} + // ppFlow is a struct that holds the logic for the regular pessimistic proof flow type ppFlow struct { *baseFlow diff --git a/aggsender/grpc/aggchain_proof_client.go b/aggsender/grpc/aggchain_proof_client.go index 997cd286..9391b1fe 100644 --- a/aggsender/grpc/aggchain_proof_client.go +++ b/aggsender/grpc/aggchain_proof_client.go @@ -16,9 +16,9 @@ type AggchainProofClientInterface interface { GenerateAggchainProof( startBlock uint64, maxEndBlock uint64, - l1InfoTreeRoot common.Hash, - l1InfoTreeLeaf common.Hash, - l1InfoTreeProof treeTypes.Proof, + l1InfoTreeRootHash common.Hash, + l1InfoTreeLeafHash common.Hash, + l1InfoTreeMerkleProof treeTypes.Proof, ) (*types.AggchainProof, error) } @@ -41,24 +41,24 @@ func NewAggchainProofClient(serverAddr string) (*AggchainProofClient, error) { func (c *AggchainProofClient) GenerateAggchainProof( startBlock uint64, maxEndBlock uint64, - l1InfoTreeRoot common.Hash, - l1InfoTreeLeaf common.Hash, - l1InfoTreeProof treeTypes.Proof, + l1InfoTreeRootHash common.Hash, + l1InfoTreeLeafHash common.Hash, + l1InfoTreeMerkleProof treeTypes.Proof, ) (*types.AggchainProof, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute*TIMEOUT) defer cancel() convertedProof := make([][]byte, treeTypes.DefaultHeight) for i := 0; i < int(treeTypes.DefaultHeight); i++ { - convertedProof[i] = l1InfoTreeProof[i].Bytes() + convertedProof[i] = l1InfoTreeMerkleProof[i].Bytes() } resp, err := c.client.GenerateAggchainProof(ctx, &types.GenerateAggchainProofRequest{ - StartBlock: startBlock, - MaxEndBlock: maxEndBlock, - L1InfoTreeRoot: l1InfoTreeRoot.Bytes(), - L1InfoTreeLeaf: l1InfoTreeLeaf.Bytes(), - L1InfoTreeProof: convertedProof, + StartBlock: startBlock, + MaxEndBlock: maxEndBlock, + L1InfoTreeRootHash: l1InfoTreeRootHash.Bytes(), + L1InfoTreeLeafHash: l1InfoTreeLeafHash.Bytes(), + L1InfoTreeMerkleProof: convertedProof, }) if err != nil { return nil, err diff --git a/aggsender/grpc/aggchain_proof_client_test.go b/aggsender/grpc/aggchain_proof_client_test.go index 4911e82d..ff00197a 100644 --- a/aggsender/grpc/aggchain_proof_client_test.go +++ b/aggsender/grpc/aggchain_proof_client_test.go @@ -33,11 +33,11 @@ func TestGenerateAggchainProof_Success(t *testing.T) { } mockClient.On("GenerateAggchainProof", mock.Anything, &types.GenerateAggchainProofRequest{ - StartBlock: 100, - MaxEndBlock: 200, - L1InfoTreeRoot: common.Hash{}.Bytes(), - L1InfoTreeLeaf: common.Hash{}.Bytes(), - L1InfoTreeProof: convertedProof, + StartBlock: 100, + MaxEndBlock: 200, + L1InfoTreeRootHash: common.Hash{}.Bytes(), + L1InfoTreeLeafHash: common.Hash{}.Bytes(), + L1InfoTreeMerkleProof: convertedProof, }).Return(expectedResponse, nil) result, err := client.GenerateAggchainProof(100, 200, common.Hash{}, common.Hash{}, [32]common.Hash{}) @@ -61,11 +61,11 @@ func TestGenerateAggchainProof_Error(t *testing.T) { } mockClient.On("GenerateAggchainProof", mock.Anything, &types.GenerateAggchainProofRequest{ - StartBlock: 300, - MaxEndBlock: 400, - L1InfoTreeRoot: common.Hash{}.Bytes(), - L1InfoTreeLeaf: common.Hash{}.Bytes(), - L1InfoTreeProof: convertedProof, + StartBlock: 300, + MaxEndBlock: 400, + L1InfoTreeRootHash: common.Hash{}.Bytes(), + L1InfoTreeLeafHash: common.Hash{}.Bytes(), + L1InfoTreeMerkleProof: convertedProof, }).Return((*types.GenerateAggchainProofResponse)(nil), expectedError) result, err := client.GenerateAggchainProof(300, 400, common.Hash{}, common.Hash{}, [32]common.Hash{}) diff --git a/aggsender/mocks/mock_aggchain_proof_client_interface.go b/aggsender/mocks/mock_aggchain_proof_client_interface.go index 934bd481..0f994474 100644 --- a/aggsender/mocks/mock_aggchain_proof_client_interface.go +++ b/aggsender/mocks/mock_aggchain_proof_client_interface.go @@ -24,9 +24,9 @@ func (_m *AggchainProofClientInterface) EXPECT() *AggchainProofClientInterface_E return &AggchainProofClientInterface_Expecter{mock: &_m.Mock} } -// GenerateAggchainProof provides a mock function with given fields: startBlock, maxEndBlock, l1InfoTreeRoot, l1InfoTreeLeaf, l1InfoTreeProof -func (_m *AggchainProofClientInterface) GenerateAggchainProof(startBlock uint64, maxEndBlock uint64, l1InfoTreeRoot common.Hash, l1InfoTreeLeaf common.Hash, l1InfoTreeProof types.Proof) (*aggsendertypes.AggchainProof, error) { - ret := _m.Called(startBlock, maxEndBlock, l1InfoTreeRoot, l1InfoTreeLeaf, l1InfoTreeProof) +// GenerateAggchainProof provides a mock function with given fields: startBlock, maxEndBlock, l1InfoTreeRootHash, l1InfoTreeLeafHash, l1InfoTreeMerkleProof +func (_m *AggchainProofClientInterface) GenerateAggchainProof(startBlock uint64, maxEndBlock uint64, l1InfoTreeRootHash common.Hash, l1InfoTreeLeafHash common.Hash, l1InfoTreeMerkleProof types.Proof) (*aggsendertypes.AggchainProof, error) { + ret := _m.Called(startBlock, maxEndBlock, l1InfoTreeRootHash, l1InfoTreeLeafHash, l1InfoTreeMerkleProof) if len(ret) == 0 { panic("no return value specified for GenerateAggchainProof") @@ -35,10 +35,10 @@ func (_m *AggchainProofClientInterface) GenerateAggchainProof(startBlock uint64, var r0 *aggsendertypes.AggchainProof var r1 error if rf, ok := ret.Get(0).(func(uint64, uint64, common.Hash, common.Hash, types.Proof) (*aggsendertypes.AggchainProof, error)); ok { - return rf(startBlock, maxEndBlock, l1InfoTreeRoot, l1InfoTreeLeaf, l1InfoTreeProof) + return rf(startBlock, maxEndBlock, l1InfoTreeRootHash, l1InfoTreeLeafHash, l1InfoTreeMerkleProof) } if rf, ok := ret.Get(0).(func(uint64, uint64, common.Hash, common.Hash, types.Proof) *aggsendertypes.AggchainProof); ok { - r0 = rf(startBlock, maxEndBlock, l1InfoTreeRoot, l1InfoTreeLeaf, l1InfoTreeProof) + r0 = rf(startBlock, maxEndBlock, l1InfoTreeRootHash, l1InfoTreeLeafHash, l1InfoTreeMerkleProof) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*aggsendertypes.AggchainProof) @@ -46,7 +46,7 @@ func (_m *AggchainProofClientInterface) GenerateAggchainProof(startBlock uint64, } if rf, ok := ret.Get(1).(func(uint64, uint64, common.Hash, common.Hash, types.Proof) error); ok { - r1 = rf(startBlock, maxEndBlock, l1InfoTreeRoot, l1InfoTreeLeaf, l1InfoTreeProof) + r1 = rf(startBlock, maxEndBlock, l1InfoTreeRootHash, l1InfoTreeLeafHash, l1InfoTreeMerkleProof) } else { r1 = ret.Error(1) } @@ -62,14 +62,14 @@ type AggchainProofClientInterface_GenerateAggchainProof_Call struct { // GenerateAggchainProof is a helper method to define mock.On call // - startBlock uint64 // - maxEndBlock uint64 -// - l1InfoTreeRoot common.Hash -// - l1InfoTreeLeaf common.Hash -// - l1InfoTreeProof types.Proof -func (_e *AggchainProofClientInterface_Expecter) GenerateAggchainProof(startBlock interface{}, maxEndBlock interface{}, l1InfoTreeRoot interface{}, l1InfoTreeLeaf interface{}, l1InfoTreeProof interface{}) *AggchainProofClientInterface_GenerateAggchainProof_Call { - return &AggchainProofClientInterface_GenerateAggchainProof_Call{Call: _e.mock.On("GenerateAggchainProof", startBlock, maxEndBlock, l1InfoTreeRoot, l1InfoTreeLeaf, l1InfoTreeProof)} +// - l1InfoTreeRootHash common.Hash +// - l1InfoTreeLeafHash common.Hash +// - l1InfoTreeMerkleProof types.Proof +func (_e *AggchainProofClientInterface_Expecter) GenerateAggchainProof(startBlock interface{}, maxEndBlock interface{}, l1InfoTreeRootHash interface{}, l1InfoTreeLeafHash interface{}, l1InfoTreeMerkleProof interface{}) *AggchainProofClientInterface_GenerateAggchainProof_Call { + return &AggchainProofClientInterface_GenerateAggchainProof_Call{Call: _e.mock.On("GenerateAggchainProof", startBlock, maxEndBlock, l1InfoTreeRootHash, l1InfoTreeLeafHash, l1InfoTreeMerkleProof)} } -func (_c *AggchainProofClientInterface_GenerateAggchainProof_Call) Run(run func(startBlock uint64, maxEndBlock uint64, l1InfoTreeRoot common.Hash, l1InfoTreeLeaf common.Hash, l1InfoTreeProof types.Proof)) *AggchainProofClientInterface_GenerateAggchainProof_Call { +func (_c *AggchainProofClientInterface_GenerateAggchainProof_Call) Run(run func(startBlock uint64, maxEndBlock uint64, l1InfoTreeRootHash common.Hash, l1InfoTreeLeafHash common.Hash, l1InfoTreeMerkleProof types.Proof)) *AggchainProofClientInterface_GenerateAggchainProof_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(uint64), args[1].(uint64), args[2].(common.Hash), args[3].(common.Hash), args[4].(types.Proof)) }) diff --git a/aggsender/mocks/mock_l1_info_tree_syncer.go b/aggsender/mocks/mock_l1_info_tree_syncer.go index 85c17bc6..58beaa98 100644 --- a/aggsender/mocks/mock_l1_info_tree_syncer.go +++ b/aggsender/mocks/mock_l1_info_tree_syncer.go @@ -85,6 +85,65 @@ func (_c *L1InfoTreeSyncer_GetInfoByGlobalExitRoot_Call) RunAndReturn(run func(c return _c } +// GetInfoByIndex provides a mock function with given fields: ctx, index +func (_m *L1InfoTreeSyncer) GetInfoByIndex(ctx context.Context, index uint32) (*l1infotreesync.L1InfoTreeLeaf, error) { + ret := _m.Called(ctx, index) + + if len(ret) == 0 { + panic("no return value specified for GetInfoByIndex") + } + + var r0 *l1infotreesync.L1InfoTreeLeaf + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint32) (*l1infotreesync.L1InfoTreeLeaf, error)); ok { + return rf(ctx, index) + } + if rf, ok := ret.Get(0).(func(context.Context, uint32) *l1infotreesync.L1InfoTreeLeaf); ok { + r0 = rf(ctx, index) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*l1infotreesync.L1InfoTreeLeaf) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { + r1 = rf(ctx, index) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L1InfoTreeSyncer_GetInfoByIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetInfoByIndex' +type L1InfoTreeSyncer_GetInfoByIndex_Call struct { + *mock.Call +} + +// GetInfoByIndex is a helper method to define mock.On call +// - ctx context.Context +// - index uint32 +func (_e *L1InfoTreeSyncer_Expecter) GetInfoByIndex(ctx interface{}, index interface{}) *L1InfoTreeSyncer_GetInfoByIndex_Call { + return &L1InfoTreeSyncer_GetInfoByIndex_Call{Call: _e.mock.On("GetInfoByIndex", ctx, index)} +} + +func (_c *L1InfoTreeSyncer_GetInfoByIndex_Call) Run(run func(ctx context.Context, index uint32)) *L1InfoTreeSyncer_GetInfoByIndex_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32)) + }) + return _c +} + +func (_c *L1InfoTreeSyncer_GetInfoByIndex_Call) Return(_a0 *l1infotreesync.L1InfoTreeLeaf, _a1 error) *L1InfoTreeSyncer_GetInfoByIndex_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L1InfoTreeSyncer_GetInfoByIndex_Call) RunAndReturn(run func(context.Context, uint32) (*l1infotreesync.L1InfoTreeLeaf, error)) *L1InfoTreeSyncer_GetInfoByIndex_Call { + _c.Call.Return(run) + return _c +} + // GetL1InfoTreeMerkleProofFromIndexToRoot provides a mock function with given fields: ctx, index, root func (_m *L1InfoTreeSyncer) GetL1InfoTreeMerkleProofFromIndexToRoot(ctx context.Context, index uint32, root common.Hash) (treetypes.Proof, error) { ret := _m.Called(ctx, index, root) @@ -202,6 +261,131 @@ func (_c *L1InfoTreeSyncer_GetL1InfoTreeRootByIndex_Call) RunAndReturn(run func( return _c } +// GetLastL1InfoTreeRootByBlockNum provides a mock function with given fields: ctx, blockNum +func (_m *L1InfoTreeSyncer) GetLastL1InfoTreeRootByBlockNum(ctx context.Context, blockNum uint64) (*treetypes.Root, error) { + ret := _m.Called(ctx, blockNum) + + if len(ret) == 0 { + panic("no return value specified for GetLastL1InfoTreeRootByBlockNum") + } + + var r0 *treetypes.Root + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64) (*treetypes.Root, error)); ok { + return rf(ctx, blockNum) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64) *treetypes.Root); ok { + r0 = rf(ctx, blockNum) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*treetypes.Root) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64) error); ok { + r1 = rf(ctx, blockNum) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L1InfoTreeSyncer_GetLastL1InfoTreeRootByBlockNum_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLastL1InfoTreeRootByBlockNum' +type L1InfoTreeSyncer_GetLastL1InfoTreeRootByBlockNum_Call struct { + *mock.Call +} + +// GetLastL1InfoTreeRootByBlockNum is a helper method to define mock.On call +// - ctx context.Context +// - blockNum uint64 +func (_e *L1InfoTreeSyncer_Expecter) GetLastL1InfoTreeRootByBlockNum(ctx interface{}, blockNum interface{}) *L1InfoTreeSyncer_GetLastL1InfoTreeRootByBlockNum_Call { + return &L1InfoTreeSyncer_GetLastL1InfoTreeRootByBlockNum_Call{Call: _e.mock.On("GetLastL1InfoTreeRootByBlockNum", ctx, blockNum)} +} + +func (_c *L1InfoTreeSyncer_GetLastL1InfoTreeRootByBlockNum_Call) Run(run func(ctx context.Context, blockNum uint64)) *L1InfoTreeSyncer_GetLastL1InfoTreeRootByBlockNum_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64)) + }) + return _c +} + +func (_c *L1InfoTreeSyncer_GetLastL1InfoTreeRootByBlockNum_Call) Return(_a0 *treetypes.Root, _a1 error) *L1InfoTreeSyncer_GetLastL1InfoTreeRootByBlockNum_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L1InfoTreeSyncer_GetLastL1InfoTreeRootByBlockNum_Call) RunAndReturn(run func(context.Context, uint64) (*treetypes.Root, error)) *L1InfoTreeSyncer_GetLastL1InfoTreeRootByBlockNum_Call { + _c.Call.Return(run) + return _c +} + +// GetProcessedBlockUntil provides a mock function with given fields: ctx, blockNumber +func (_m *L1InfoTreeSyncer) GetProcessedBlockUntil(ctx context.Context, blockNumber uint64) (uint64, common.Hash, error) { + ret := _m.Called(ctx, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for GetProcessedBlockUntil") + } + + var r0 uint64 + var r1 common.Hash + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, uint64) (uint64, common.Hash, error)); ok { + return rf(ctx, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64) uint64); ok { + r0 = rf(ctx, blockNumber) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64) common.Hash); ok { + r1 = rf(ctx, blockNumber) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(common.Hash) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, uint64) error); ok { + r2 = rf(ctx, blockNumber) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// L1InfoTreeSyncer_GetProcessedBlockUntil_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProcessedBlockUntil' +type L1InfoTreeSyncer_GetProcessedBlockUntil_Call struct { + *mock.Call +} + +// GetProcessedBlockUntil is a helper method to define mock.On call +// - ctx context.Context +// - blockNumber uint64 +func (_e *L1InfoTreeSyncer_Expecter) GetProcessedBlockUntil(ctx interface{}, blockNumber interface{}) *L1InfoTreeSyncer_GetProcessedBlockUntil_Call { + return &L1InfoTreeSyncer_GetProcessedBlockUntil_Call{Call: _e.mock.On("GetProcessedBlockUntil", ctx, blockNumber)} +} + +func (_c *L1InfoTreeSyncer_GetProcessedBlockUntil_Call) Run(run func(ctx context.Context, blockNumber uint64)) *L1InfoTreeSyncer_GetProcessedBlockUntil_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64)) + }) + return _c +} + +func (_c *L1InfoTreeSyncer_GetProcessedBlockUntil_Call) Return(_a0 uint64, _a1 common.Hash, _a2 error) *L1InfoTreeSyncer_GetProcessedBlockUntil_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *L1InfoTreeSyncer_GetProcessedBlockUntil_Call) RunAndReturn(run func(context.Context, uint64) (uint64, common.Hash, error)) *L1InfoTreeSyncer_GetProcessedBlockUntil_Call { + _c.Call.Return(run) + return _c +} + // NewL1InfoTreeSyncer creates a new instance of L1InfoTreeSyncer. 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 NewL1InfoTreeSyncer(t interface { diff --git a/aggsender/proto/aggchain_proof_generation.proto b/aggsender/proto/aggchain_proof_generation.proto index a0dde5dc..c0b71bc7 100644 --- a/aggsender/proto/aggchain_proof_generation.proto +++ b/aggsender/proto/aggchain_proof_generation.proto @@ -16,12 +16,12 @@ message GenerateAggchainProofRequest { uint64 start_block = 1; // The max end block for which the aggchain proof is requested. uint64 max_end_block = 2; - // L1 Info tree root. - bytes l1_info_tree_root = 3; - // L1 Info tree leaf. - bytes l1_info_tree_leaf = 4; - // L1 Info tree proof. - repeated bytes l1_info_tree_proof = 5; + // L1 Info tree root. (hash) + bytes l1_info_tree_root_hash = 3; + // L1 Info tree leaf. (hash) + bytes l1_info_tree_leaf_hash = 4; + // L1 Info tree proof. ([32]hash) + repeated bytes l1_info_tree_merkle_proof = 5; } // The aggchain proof response message. diff --git a/aggsender/types/aggchain_proof_generation.pb.go b/aggsender/types/aggchain_proof_generation.pb.go index c1e8048d..3f638b69 100644 --- a/aggsender/types/aggchain_proof_generation.pb.go +++ b/aggsender/types/aggchain_proof_generation.pb.go @@ -29,14 +29,14 @@ type GenerateAggchainProofRequest struct { StartBlock uint64 `protobuf:"varint,1,opt,name=start_block,json=startBlock,proto3" json:"start_block,omitempty"` // The max end block for which the aggchain proof is requested. MaxEndBlock uint64 `protobuf:"varint,2,opt,name=max_end_block,json=maxEndBlock,proto3" json:"max_end_block,omitempty"` - // L1 Info tree root. - L1InfoTreeRoot []byte `protobuf:"bytes,3,opt,name=l1_info_tree_root,json=l1InfoTreeRoot,proto3" json:"l1_info_tree_root,omitempty"` - // L1 Info tree leaf. - L1InfoTreeLeaf []byte `protobuf:"bytes,4,opt,name=l1_info_tree_leaf,json=l1InfoTreeLeaf,proto3" json:"l1_info_tree_leaf,omitempty"` - // L1 Info tree proof. - L1InfoTreeProof [][]byte `protobuf:"bytes,5,rep,name=l1_info_tree_proof,json=l1InfoTreeProof,proto3" json:"l1_info_tree_proof,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // L1 Info tree root. (hash) + L1InfoTreeRootHash []byte `protobuf:"bytes,3,opt,name=l1_info_tree_root_hash,json=l1InfoTreeRootHash,proto3" json:"l1_info_tree_root_hash,omitempty"` + // L1 Info tree leaf. (hash) + L1InfoTreeLeafHash []byte `protobuf:"bytes,4,opt,name=l1_info_tree_leaf_hash,json=l1InfoTreeLeafHash,proto3" json:"l1_info_tree_leaf_hash,omitempty"` + // L1 Info tree proof. ([32]hash) + L1InfoTreeMerkleProof [][]byte `protobuf:"bytes,5,rep,name=l1_info_tree_merkle_proof,json=l1InfoTreeMerkleProof,proto3" json:"l1_info_tree_merkle_proof,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GenerateAggchainProofRequest) Reset() { @@ -83,23 +83,23 @@ func (x *GenerateAggchainProofRequest) GetMaxEndBlock() uint64 { return 0 } -func (x *GenerateAggchainProofRequest) GetL1InfoTreeRoot() []byte { +func (x *GenerateAggchainProofRequest) GetL1InfoTreeRootHash() []byte { if x != nil { - return x.L1InfoTreeRoot + return x.L1InfoTreeRootHash } return nil } -func (x *GenerateAggchainProofRequest) GetL1InfoTreeLeaf() []byte { +func (x *GenerateAggchainProofRequest) GetL1InfoTreeLeafHash() []byte { if x != nil { - return x.L1InfoTreeLeaf + return x.L1InfoTreeLeafHash } return nil } -func (x *GenerateAggchainProofRequest) GetL1InfoTreeProof() [][]byte { +func (x *GenerateAggchainProofRequest) GetL1InfoTreeMerkleProof() [][]byte { if x != nil { - return x.L1InfoTreeProof + return x.L1InfoTreeMerkleProof } return nil } @@ -174,41 +174,43 @@ var File_aggchain_proof_generation_proto protoreflect.FileDescriptor var file_aggchain_proof_generation_proto_rawDesc = []byte{ 0x0a, 0x1f, 0x61, 0x67, 0x67, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0xe6, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x6e, + 0x6f, 0x12, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0x85, 0x02, 0x0a, 0x1c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x67, 0x67, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x6e, 0x64, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x45, 0x6e, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x29, - 0x0a, 0x11, 0x6c, 0x31, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x72, - 0x6f, 0x6f, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x6c, 0x31, 0x49, 0x6e, 0x66, - 0x6f, 0x54, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x29, 0x0a, 0x11, 0x6c, 0x31, 0x5f, - 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x6c, 0x31, 0x49, 0x6e, 0x66, 0x6f, 0x54, 0x72, 0x65, 0x65, - 0x4c, 0x65, 0x61, 0x66, 0x12, 0x2b, 0x0a, 0x12, 0x6c, 0x31, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x5f, - 0x74, 0x72, 0x65, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0c, - 0x52, 0x0f, 0x6c, 0x31, 0x49, 0x6e, 0x66, 0x6f, 0x54, 0x72, 0x65, 0x65, 0x50, 0x72, 0x6f, 0x6f, - 0x66, 0x22, 0x84, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x67, + 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x45, 0x6e, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x32, + 0x0a, 0x16, 0x6c, 0x31, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x72, + 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, + 0x6c, 0x31, 0x49, 0x6e, 0x66, 0x6f, 0x54, 0x72, 0x65, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x32, 0x0a, 0x16, 0x6c, 0x31, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x74, 0x72, + 0x65, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x12, 0x6c, 0x31, 0x49, 0x6e, 0x66, 0x6f, 0x54, 0x72, 0x65, 0x65, 0x4c, 0x65, + 0x61, 0x66, 0x48, 0x61, 0x73, 0x68, 0x12, 0x38, 0x0a, 0x19, 0x6c, 0x31, 0x5f, 0x69, 0x6e, 0x66, + 0x6f, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x5f, 0x70, 0x72, + 0x6f, 0x6f, 0x66, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x15, 0x6c, 0x31, 0x49, 0x6e, 0x66, + 0x6f, 0x54, 0x72, 0x65, 0x65, 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, + 0x22, 0x84, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x67, 0x67, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x67, 0x67, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x70, + 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x61, 0x67, 0x67, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x6e, + 0x64, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x65, + 0x6e, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x32, 0x7a, 0x0a, 0x14, 0x41, 0x67, 0x67, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x62, 0x0a, 0x15, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x67, 0x67, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x23, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x67, 0x67, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x67, 0x67, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x67, 0x67, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, - 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x61, 0x67, 0x67, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x65, - 0x6e, 0x64, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, - 0x65, 0x6e, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x32, 0x7a, 0x0a, 0x14, 0x41, 0x67, 0x67, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x62, 0x0a, 0x15, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x67, 0x67, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x23, 0x2e, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x67, 0x67, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, - 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, - 0x67, 0x67, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x61, 0x67, 0x67, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x67, 0x6b, - 0x69, 0x74, 0x2f, 0x61, 0x67, 0x67, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x2f, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x73, 0x65, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x61, 0x67, 0x67, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x67, 0x6b, 0x69, + 0x74, 0x2f, 0x61, 0x67, 0x67, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x2f, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/aggsender/types/types.go b/aggsender/types/types.go index 6bdb1cd1..81994b0e 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -39,6 +39,9 @@ type L1InfoTreeSyncer interface { ctx context.Context, index uint32, root common.Hash, ) (treeTypes.Proof, error) GetL1InfoTreeRootByIndex(ctx context.Context, index uint32) (treeTypes.Root, error) + GetProcessedBlockUntil(ctx context.Context, blockNumber uint64) (uint64, common.Hash, error) + GetInfoByIndex(ctx context.Context, index uint32) (*l1infotreesync.L1InfoTreeLeaf, error) + GetLastL1InfoTreeRootByBlockNum(ctx context.Context, blockNum uint64) (*treeTypes.Root, error) } // L2BridgeSyncer is an interface defining functions that an L2BridgeSyncer should implement diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index d69acb3b..b5a15a34 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -2,6 +2,8 @@ package bridgesync import ( "context" + "fmt" + "math/big" "time" "github.com/agglayer/aggkit/etherman" @@ -131,8 +133,14 @@ func newBridgeSync( } if lastProcessedBlock < initialBlock { + block, err := ethClient.BlockByNumber(ctx, new(big.Int).SetUint64(initialBlock)) + if err != nil { + return nil, fmt.Errorf("failed to get initial block %d: %w", initialBlock, err) + } + err = processor.ProcessBlock(ctx, sync.Block{ - Num: initialBlock, + Num: initialBlock, + Hash: block.Hash(), }) if err != nil { return nil, err diff --git a/bridgesync/migrations/bridgesync0002.sql b/bridgesync/migrations/bridgesync0002.sql new file mode 100644 index 00000000..0453081d --- /dev/null +++ b/bridgesync/migrations/bridgesync0002.sql @@ -0,0 +1,5 @@ +-- +migrate Down +ALTER TABLE block DROP COLUMN hash; + +-- +migrate Up +ALTER TABLE block ADD COLUMN hash VARCHAR; \ No newline at end of file diff --git a/bridgesync/migrations/migrations.go b/bridgesync/migrations/migrations.go index b504ed7e..a94d2005 100644 --- a/bridgesync/migrations/migrations.go +++ b/bridgesync/migrations/migrations.go @@ -11,12 +11,19 @@ import ( //go:embed bridgesync0001.sql var mig001 string +//go:embed bridgesync0002.sql +var mig002 string + func RunMigrations(dbPath string) error { migrations := []types.Migration{ { ID: "bridgesync0001", SQL: mig001, }, + { + ID: "bridgesync0002", + SQL: mig002, + }, } migrations = append(migrations, treeMigrations.Migrations...) return db.RunMigrations(dbPath, migrations) diff --git a/bridgesync/migrations/bridgesync0001_test.go b/bridgesync/migrations/migrations_test.go similarity index 72% rename from bridgesync/migrations/bridgesync0001_test.go rename to bridgesync/migrations/migrations_test.go index c234abbe..4e9700c9 100644 --- a/bridgesync/migrations/bridgesync0001_test.go +++ b/bridgesync/migrations/migrations_test.go @@ -10,6 +10,8 @@ import ( ) func Test001(t *testing.T) { + t.Parallel() + dbPath := path.Join(t.TempDir(), "bridgesyncTest001.sqlite") err := RunMigrations(dbPath) @@ -56,6 +58,24 @@ func Test001(t *testing.T) { ) VALUES (1, 0, 0, 0, '0x0000', '0x0000', 0, '0x000,0x000', '0x000,0x000', '0x000', '0x000', '0x0', 0, NULL, FALSE); `) require.NoError(t, err) - err = tx.Commit() + require.NoError(t, tx.Commit()) +} + +func Test002(t *testing.T) { + t.Parallel() + + dbPath := path.Join(t.TempDir(), "bridgesyncTest002.sqlite") + + err := RunMigrations(dbPath) + require.NoError(t, err) + db, err := db.NewSQLiteDB(dbPath) + require.NoError(t, err) + + ctx := context.Background() + tx, err := db.BeginTx(ctx, nil) + require.NoError(t, err) + + _, err = tx.Exec(`INSERT INTO block (num, hash) VALUES (1, '0x0000');`) require.NoError(t, err) + require.NoError(t, tx.Commit()) } diff --git a/bridgesync/processor.go b/bridgesync/processor.go index a3eb6dd0..3926625d 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -222,13 +222,14 @@ func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { } func (p *processor) getLastProcessedBlockWithTx(tx db.Querier) (uint64, error) { - var lastProcessedBlock uint64 + var lastProcessedBlockNum uint64 + row := tx.QueryRow("SELECT num FROM block ORDER BY num DESC LIMIT 1;") - err := row.Scan(&lastProcessedBlock) + err := row.Scan(&lastProcessedBlockNum) if errors.Is(err, sql.ErrNoRows) { return 0, nil } - return lastProcessedBlock, err + return lastProcessedBlockNum, err } // Reorg triggers a purge and reset process on the processor to leaf it on a state @@ -285,7 +286,7 @@ func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { } }() - if _, err := tx.Exec(`INSERT INTO block (num) VALUES ($1)`, block.Num); err != nil { + if _, err := tx.Exec(`INSERT INTO block (num, hash) VALUES ($1, $2)`, block.Num, block.Hash.String()); err != nil { return err } for _, e := range block.Events { diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index ab1e8e54..8a034496 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -759,7 +759,7 @@ func TestInsertAndGetClaim(t *testing.T) { IsMessage: false, } - _, err = tx.Exec(`INSERT INTO block (num) VALUES ($1)`, testClaim.BlockNum) + _, err = tx.Exec(`INSERT INTO block (num, hash) VALUES ($1, $2)`, testClaim.BlockNum, fmt.Sprintf("0x%x", testClaim.BlockNum)) require.NoError(t, err) require.NoError(t, meddler.Insert(tx, "claim", testClaim)) @@ -826,7 +826,7 @@ func TestGetBridgesPublished(t *testing.T) { require.NoError(t, err) for i := tc.fromBlock; i <= tc.toBlock; i++ { - _, err = tx.Exec(`INSERT INTO block (num) VALUES ($1)`, i) + _, err = tx.Exec(`INSERT INTO block (num, hash) VALUES ($1, $2)`, i, fmt.Sprintf("0x%x", i)) require.NoError(t, err) } diff --git a/cmd/run.go b/cmd/run.go index 879149a3..137b7542 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -150,7 +150,7 @@ func createAggSender( go blockNotifier.Start(ctx) log.Infof("Starting epochNotifier: %s", epochNotifier.String()) go epochNotifier.Start(ctx) - return aggsender.New(ctx, logger, cfg, agglayerClient, l1InfoTreeSync, l2Syncer, epochNotifier) + return aggsender.New(ctx, logger, cfg, agglayerClient, l1InfoTreeSync, l2Syncer, epochNotifier, l1EthClient) } func createAggoracle( diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index 05be8df0..1079b46e 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -3,6 +3,8 @@ package l1infotreesync import ( "context" "errors" + "fmt" + "math/big" "time" "github.com/agglayer/aggkit/db" @@ -61,8 +63,14 @@ func New( return nil, err } if initialBlock > 0 && lastProcessedBlock < initialBlock-1 { + block, err := l1Client.BlockByNumber(ctx, new(big.Int).SetUint64(initialBlock-1)) + if err != nil { + return nil, fmt.Errorf("failed to get initial block %d: %w", initialBlock-1, err) + } + err = processor.ProcessBlock(ctx, sync.Block{ - Num: initialBlock - 1, + Num: initialBlock - 1, + Hash: block.Hash(), }) if err != nil { return nil, err @@ -278,3 +286,21 @@ func (s *L1InfoTreeSync) GetInitL1InfoRootMap(ctx context.Context) (*L1InfoTreeI } return s.processor.GetInitL1InfoRootMap(nil) } + +// GetProcessedBlockUntil returns the last block processed before the given block number or +// the exact block num and hash associated to given block if it was processed +func (s *L1InfoTreeSync) GetProcessedBlockUntil(ctx context.Context, blockNum uint64) (uint64, common.Hash, error) { + if s.processor.isHalted() { + return 0, common.Hash{}, sync.ErrInconsistentState + } + return s.processor.GetProcessedBlockUntil(ctx, blockNum) +} + +// GetLastL1InfoTreeRootByBlockNum returns the last root processed before the given block number or +// the root associated to the block number if it exists +func (s *L1InfoTreeSync) GetLastL1InfoTreeRootByBlockNum(ctx context.Context, blockNum uint64) (*types.Root, error) { + if s.processor.isHalted() { + return nil, sync.ErrInconsistentState + } + return s.processor.l1InfoTree.GetLastRootByBlockNum(ctx, blockNum) +} diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 141c63e6..764f13b5 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -164,6 +164,7 @@ func (p *processor) GetL1InfoTreeMerkleProof( if err != nil { return treeTypes.Proof{}, treeTypes.Root{}, err } + proof, err := p.l1InfoTree.GetProof(ctx, root.Index, root.Hash) return proof, root, err } @@ -225,13 +226,35 @@ func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { } func (p *processor) getLastProcessedBlockWithTx(tx db.Querier) (uint64, error) { - var lastProcessedBlock uint64 + var lastProcessedBlockNum uint64 + row := tx.QueryRow("SELECT num FROM BLOCK ORDER BY num DESC LIMIT 1;") - err := row.Scan(&lastProcessedBlock) + err := row.Scan(&lastProcessedBlockNum) if errors.Is(err, sql.ErrNoRows) { return 0, nil } - return lastProcessedBlock, err + return lastProcessedBlockNum, err +} + +// GetProcessedBlockUntil returns the last processed block until the given blockNum +// Returns the given blockNum if it has been processed, or the last processed block before it +func (p *processor) GetProcessedBlockUntil(ctx context.Context, blockNum uint64) (uint64, common.Hash, error) { + var ( + processedBlockNum uint64 + processedBlockHash *string + ) + + row := p.db.QueryRow("SELECT num, hash FROM block WHERE num <= $1 ORDER BY num DESC LIMIT 1;", blockNum) + if err := row.Scan(&processedBlockNum, &processedBlockHash); err != nil { + return 0, common.Hash{}, err + } + + hash := common.Hash{} + if processedBlockHash != nil { + hash = common.HexToHash(*processedBlockHash) + } + + return processedBlockNum, hash, nil } // Reorg triggers a purge and reset process on the processor to leaf it on a state diff --git a/l1infotreesync/processor_test.go b/l1infotreesync/processor_test.go index 087ea8a3..9b238339 100644 --- a/l1infotreesync/processor_test.go +++ b/l1infotreesync/processor_test.go @@ -1,6 +1,7 @@ package l1infotreesync import ( + "database/sql" "fmt" "path" "testing" @@ -122,7 +123,7 @@ func TestGetLatestInfoUntilBlockIfNotFoundReturnsErrNotFound(t *testing.T) { require.NoError(t, err) ctx := context.Background() // Fake block 1 - _, err = sut.db.Exec(`INSERT INTO block (num) VALUES ($1)`, 1) + _, err = sut.db.Exec(`INSERT INTO block (num, hash) VALUES ($1, $2)`, 1, "0x1") require.NoError(t, err) _, err = sut.GetLatestInfoUntilBlock(ctx, 1) @@ -382,3 +383,59 @@ func TestProcessBlockUpdateL1InfoTreeV2DontMatchTree(t *testing.T) { require.ErrorIs(t, err, sync.ErrInconsistentState) require.True(t, sut.halted) } + +func TestGetProcessedBlockUntil(t *testing.T) { + dbPath := path.Join(t.TempDir(), "l1infotreesyncTestGetProcessedBlockUntil.sqlite") + p, err := newProcessor(dbPath) + require.NoError(t, err) + ctx := context.Background() + + // Test when no blocks are present + _, _, err = p.GetProcessedBlockUntil(ctx, 1) + require.Error(t, err) + + // Insert some blocks + _, err = p.db.Exec(`INSERT INTO block (num, hash) VALUES ($1, $2)`, 1, "0x1") + require.NoError(t, err) + _, err = p.db.Exec(`INSERT INTO block (num, hash) VALUES ($1, $2)`, 2, "0x2") + require.NoError(t, err) + _, err = p.db.Exec(`INSERT INTO block (num, hash) VALUES ($1, $2)`, 3, "0x3") + require.NoError(t, err) + + // Test when blockNum is less than the first block + _, _, err = p.GetProcessedBlockUntil(ctx, 0) + require.ErrorIs(t, err, sql.ErrNoRows) + + // Test when blockNum is exactly the first block + blockNum, blockHash, err := p.GetProcessedBlockUntil(ctx, 1) + require.NoError(t, err) + require.Equal(t, uint64(1), blockNum) + require.Equal(t, common.HexToHash("0x1"), blockHash) + + // Test when blockNum is between two blocks + blockNum, blockHash, err = p.GetProcessedBlockUntil(ctx, 2) + require.NoError(t, err) + require.Equal(t, uint64(2), blockNum) + require.Equal(t, common.HexToHash("0x2"), blockHash) + + // Test when blockNum is exactly the last block + blockNum, blockHash, err = p.GetProcessedBlockUntil(ctx, 3) + require.NoError(t, err) + require.Equal(t, uint64(3), blockNum) + require.Equal(t, common.HexToHash("0x3"), blockHash) + + // Test when blockNum is greater than the last block + blockNum, blockHash, err = p.GetProcessedBlockUntil(ctx, 4) + require.NoError(t, err) + require.Equal(t, uint64(3), blockNum) + require.Equal(t, common.HexToHash("0x3"), blockHash) + + // Test when hash is nil + _, err = p.db.Exec(`INSERT INTO block (num) VALUES ($1)`, 4) + require.NoError(t, err) + + blockNum, blockHash, err = p.GetProcessedBlockUntil(ctx, 4) + require.NoError(t, err) + require.Equal(t, uint64(4), blockNum) + require.Equal(t, common.Hash{}, blockHash) +} diff --git a/lastgersync/migrations/lastgersync0002.sql b/lastgersync/migrations/lastgersync0002.sql new file mode 100644 index 00000000..0453081d --- /dev/null +++ b/lastgersync/migrations/lastgersync0002.sql @@ -0,0 +1,5 @@ +-- +migrate Down +ALTER TABLE block DROP COLUMN hash; + +-- +migrate Up +ALTER TABLE block ADD COLUMN hash VARCHAR; \ No newline at end of file diff --git a/lastgersync/migrations/migrations.go b/lastgersync/migrations/migrations.go index e0c13341..1ef991a1 100644 --- a/lastgersync/migrations/migrations.go +++ b/lastgersync/migrations/migrations.go @@ -10,12 +10,19 @@ import ( //go:embed lastgersync0001.sql var mig001 string +//go:embed lastgersync0002.sql +var mig002 string + func RunMigrations(dbPath string) error { migrations := []types.Migration{ { ID: "lastgersync0001", SQL: mig001, }, + { + ID: "lastgersync0002", + SQL: mig002, + }, } return db.RunMigrations(dbPath, migrations) } diff --git a/lastgersync/processor.go b/lastgersync/processor.go index 88b572b5..6ea675cd 100644 --- a/lastgersync/processor.go +++ b/lastgersync/processor.go @@ -49,13 +49,14 @@ func newProcessor(dbPath string, loggerPrefix string) (*processor, error) { // GetLastProcessedBlock returns the last processed block by the processor, including blocks // that don't have events func (p *processor) GetLastProcessedBlock(ctx context.Context) (uint64, error) { - var lastProcessedBlock uint64 + var lastProcessedBlockNum uint64 + row := p.db.QueryRow("SELECT num FROM BLOCK ORDER BY num DESC LIMIT 1;") - err := row.Scan(&lastProcessedBlock) + err := row.Scan(&lastProcessedBlockNum) if errors.Is(err, sql.ErrNoRows) { return 0, nil } - return lastProcessedBlock, err + return lastProcessedBlockNum, err } func (p *processor) getLastIndex() (uint32, error) { @@ -86,7 +87,7 @@ func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { } }() - if _, err := tx.Exec(`INSERT INTO block (num) VALUES ($1)`, block.Num); err != nil { + if _, err := tx.Exec(`INSERT INTO block (num, hash) VALUES ($1, $2)`, block.Num, block.Hash.String()); err != nil { return err } for _, e := range block.Events { diff --git a/sync/evmdownloader_test.go b/sync/evmdownloader_test.go index 30157faa..082e6eb3 100644 --- a/sync/evmdownloader_test.go +++ b/sync/evmdownloader_test.go @@ -363,9 +363,7 @@ func TestDownloadBeforeFinalized(t *testing.T) { } mockEthDownloader.On("GetEventsByBlockRange", mock.Anything, step.fromBlock, step.toBlock). Return(step.eventsReponse, false).Once() - for _, eventBlock := range step.eventsReponse { - expectedBlocks = append(expectedBlocks, eventBlock) - } + expectedBlocks = append(expectedBlocks, step.eventsReponse...) if step.getBlockHeader != nil { log.Infof("iteration:%d : GetBlockHeader(%d) ", i, step.getBlockHeader.Num) mockEthDownloader.On("GetBlockHeader", mock.Anything, step.getBlockHeader.Num).Return(*step.getBlockHeader, false).Once() diff --git a/tree/tree.go b/tree/tree.go index 3844ec2e..13bc52c9 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -230,6 +230,24 @@ func (t *Tree) GetRootByHash(ctx context.Context, hash common.Hash) (*types.Root return &root, nil } +// GetLastRootByBlockNum returns the last root processed before the given block number or +// the root associated to the block number if it exists +func (t *Tree) GetLastRootByBlockNum(ctx context.Context, blockNum uint64) (*types.Root, error) { + var root types.Root + if err := meddler.QueryRow( + t.db, &root, + fmt.Sprintf(`SELECT * FROM %s WHERE block_num <= $1 ORDER BY block_num DESC LIMIT 1;`, t.rootTable), + blockNum, + ); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, db.ErrNotFound + } + return nil, err + } + + return &root, nil +} + func (t *Tree) GetLeaf(tx db.Querier, index uint32, root common.Hash) (common.Hash, error) { currentNodeHash := root for h := int(types.DefaultHeight - 1); h >= 0; h-- { diff --git a/tree/tree_test.go b/tree/tree_test.go index bca78654..5753a74f 100644 --- a/tree/tree_test.go +++ b/tree/tree_test.go @@ -99,6 +99,38 @@ func TestCheckExpectedRoot(t *testing.T) { require.Equal(t, expectedRoot.Hash, root2.Hash) require.Equal(t, expectedRoot.Index, root2.Index) }) + + t.Run("GetLastRootByBlockNum - have exact block", func(t *testing.T) { + numOfLeavesToAdd := 10 + treeDB := createTreeDB() + merkleTree := tree.NewAppendOnlyTree(treeDB, "") + + addLeaves(merkleTree, treeDB, numOfLeavesToAdd, 0) + + root2, err := merkleTree.GetLastRootByBlockNum(context.Background(), 1) + require.NoError(t, err) + require.Equal(t, uint64(1), root2.BlockNum) + }) + + t.Run("GetLastRootByBlockNum - have lower block", func(t *testing.T) { + numOfLeavesToAdd := 4 + treeDB := createTreeDB() + merkleTree := tree.NewAppendOnlyTree(treeDB, "") + + addLeaves(merkleTree, treeDB, numOfLeavesToAdd, 0) + + root2, err := merkleTree.GetLastRootByBlockNum(context.Background(), 4) + require.NoError(t, err) + require.Equal(t, uint64(3), root2.BlockNum) + }) + + t.Run("GetLastRootByBlockNum - don't have a block", func(t *testing.T) { + treeDB := createTreeDB() + merkleTree := tree.NewAppendOnlyTree(treeDB, "") + + _, err := merkleTree.GetLastRootByBlockNum(context.Background(), 4) + require.ErrorIs(t, err, db.ErrNotFound) + }) } func TestMTAddLeaf(t *testing.T) {