diff --git a/.github/test_e2e_cdk_erigon_args_base.json b/.github/test_e2e_cdk_erigon_args_base.json index 7cb3b3f5e..61f71c824 100644 --- a/.github/test_e2e_cdk_erigon_args_base.json +++ b/.github/test_e2e_cdk_erigon_args_base.json @@ -25,4 +25,4 @@ "gas_token_enabled": false, "additional_services": [] } -} \ No newline at end of file +} diff --git a/.github/test_e2e_cdk_erigon_custom_gas_token.json b/.github/test_e2e_cdk_erigon_custom_gas_token.json index 46fde845f..d4970cdf0 100644 --- a/.github/test_e2e_cdk_erigon_custom_gas_token.json +++ b/.github/test_e2e_cdk_erigon_custom_gas_token.json @@ -3,4 +3,4 @@ "gas_token_enabled": true, "gas_token_address": "" } -} \ No newline at end of file +} diff --git a/.github/test_e2e_op_args_base.json b/.github/test_e2e_op_args_base.json index ab5d55c06..567de1722 100644 --- a/.github/test_e2e_op_args_base.json +++ b/.github/test_e2e_op_args_base.json @@ -10,13 +10,10 @@ "agg_sender_multisig_threshold": 2, "agg_sender_validator_total_number": 3, "verbosity": "debug", - "agglayer_image": "ghcr.io/agglayer/agglayer:0.4.0-rc.18", - "additional_services": [ - "bridge_spammer" - ], + "additional_services": [], "binary_name": "aggkit", "aggkit_components": "aggsender,aggoracle", "zkevm_rollup_chain_id": 20201, "zkevm_rollup_id": 1 } -} \ No newline at end of file +} diff --git a/.github/test_e2e_op_succinct_args_base.json b/.github/test_e2e_op_succinct_args_base.json index c6d1586d4..c16f57836 100644 --- a/.github/test_e2e_op_succinct_args_base.json +++ b/.github/test_e2e_op_succinct_args_base.json @@ -9,6 +9,7 @@ "op_succinct_submission_interval": "1", "op_succinct_max_concurrent_proof_requests": "1", "op_succinct_max_concurrent_witness_gen": "1", - "op_succinct_range_proof_interval": "60" + "op_succinct_range_proof_interval": "60", + "additional_services": [] } -} \ No newline at end of file +} diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 1af54718f..99ceacf36 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -54,7 +54,7 @@ jobs: fi else # For push/workflow_dispatch, use the fixed commit - COMMIT="c32d3805ad1eb45a8b3ef91ec41610c6197c6379" + COMMIT="5406ffc7ebf55ee41d432d206b84e045f6922985" echo "Using fixed kurtosis-cdk commit: ${COMMIT}" fi echo "commit=${COMMIT}" >> $GITHUB_OUTPUT @@ -173,19 +173,21 @@ jobs: name: Single L2 chain tests (OP, pessimistic) needs: - build-aggkit-image + - build-tools - read-aggkit-args - get-kurtosis-cdk-commit - uses: agglayer/e2e/.github/workflows/aggkit-e2e-single-chain.yml@145cec24f0b94e92572139f63bd2652fd9be5bdb + uses: agglayer/e2e/.github/workflows/aggkit-e2e-single-chain.yml@cd674a24950b1d8a890fe2639b5aa882252092de secrets: inherit with: kurtosis-cdk-ref: ${{ needs.get-kurtosis-cdk-commit.outputs.kurtosis-commit }} - agglayer-e2e-ref: 145cec24f0b94e92572139f63bd2652fd9be5bdb - kurtosis-cdk-enclave-name: aggkit + agglayer-e2e-ref: cd674a24950b1d8a890fe2639b5aa882252092de + kurtosis-cdk-enclave-name: op kurtosis-cdk-args: ${{ needs.read-aggkit-args.outputs.kurtosis-cdk-args-single-op-pessimistic }} test-name: "test-single-l2-network-op-pessimistic" event-name: ${{ github.event_name }} + aggsender-find-imported-bridge-artifact: aggsender_find_imported_bridge - check-single-l2-network-pessimistic-tests-result: + check-single-l2-network-op-pessimistic-tests-result: name: Check results for single l2 network (OP, pessimistic) needs: - test-single-l2-network-op-pessimistic @@ -198,15 +200,17 @@ jobs: name: Single L2 chain tests (op-succinct) needs: - build-aggkit-image + - build-tools - read-aggkit-args - get-kurtosis-cdk-commit - uses: agglayer/e2e/.github/workflows/aggkit-e2e-single-chain.yml@145cec24f0b94e92572139f63bd2652fd9be5bdb + uses: agglayer/e2e/.github/workflows/aggkit-e2e-single-chain.yml@cd674a24950b1d8a890fe2639b5aa882252092de secrets: inherit with: kurtosis-cdk-ref: ${{ needs.get-kurtosis-cdk-commit.outputs.kurtosis-commit }} - agglayer-e2e-ref: 145cec24f0b94e92572139f63bd2652fd9be5bdb + agglayer-e2e-ref: cd674a24950b1d8a890fe2639b5aa882252092de # main kurtosis-cdk-enclave-name: op kurtosis-cdk-args: ${{ needs.read-aggkit-args.outputs.kurtosis-cdk-args-single-op-succinct }} + aggsender-find-imported-bridge-artifact: aggsender_find_imported_bridge test-name: "test-single-l2-network-op-succinct" event-name: ${{ github.event_name }} @@ -223,17 +227,19 @@ jobs: name: Single L2 chain tests (op-succinct with aggoracle committee) needs: - build-aggkit-image + - build-tools - read-aggkit-args - get-kurtosis-cdk-commit - uses: agglayer/e2e/.github/workflows/aggkit-e2e-single-chain.yml@145cec24f0b94e92572139f63bd2652fd9be5bdb + uses: agglayer/e2e/.github/workflows/aggkit-e2e-single-chain.yml@cd674a24950b1d8a890fe2639b5aa882252092de secrets: inherit with: kurtosis-cdk-ref: ${{ needs.get-kurtosis-cdk-commit.outputs.kurtosis-commit }} - agglayer-e2e-ref: 145cec24f0b94e92572139f63bd2652fd9be5bdb + agglayer-e2e-ref: cd674a24950b1d8a890fe2639b5aa882252092de kurtosis-cdk-enclave-name: op kurtosis-cdk-args: ${{ needs.read-aggkit-args.outputs.kurtosis-cdk-args-single-op-succinct-aggoracle-committee }} test-name: "test-single-l2-network-op-succinct-aggoracle-committee" event-name: ${{ github.event_name }} + aggsender-find-imported-bridge-artifact: aggsender_find_imported_bridge check-single-l2-network-op-succinct-aggoracle-committee-tests-result: name: Check results for single l2 network (op-succinct with aggoracle committee) @@ -251,12 +257,12 @@ jobs: - build-tools - read-aggkit-args - get-kurtosis-cdk-commit - uses: agglayer/e2e/.github/workflows/aggkit-e2e-multi-chains.yml@145cec24f0b94e92572139f63bd2652fd9be5bdb + uses: agglayer/e2e/.github/workflows/aggkit-e2e-multi-chains.yml@cd674a24950b1d8a890fe2639b5aa882252092de secrets: inherit with: kurtosis-cdk-ref: ${{ needs.get-kurtosis-cdk-commit.outputs.kurtosis-commit }} - agglayer-e2e-ref: 145cec24f0b94e92572139f63bd2652fd9be5bdb - kurtosis-cdk-enclave-name: aggkit + agglayer-e2e-ref: cd674a24950b1d8a890fe2639b5aa882252092de + kurtosis-cdk-enclave-name: op aggsender-find-imported-bridge-artifact: aggsender_find_imported_bridge kurtosis-cdk-args-1: ${{ needs.read-aggkit-args.outputs.kurtosis-cdk-args-1 }} kurtosis-cdk-args-2: ${{ needs.read-aggkit-args.outputs.kurtosis-cdk-args-2 }} @@ -277,11 +283,11 @@ jobs: - build-tools - read-aggkit-args - get-kurtosis-cdk-commit - uses: agglayer/e2e/.github/workflows/aggkit-e2e-multi-chains.yml@145cec24f0b94e92572139f63bd2652fd9be5bdb + uses: agglayer/e2e/.github/workflows/aggkit-e2e-multi-chains.yml@cd674a24950b1d8a890fe2639b5aa882252092de secrets: inherit with: kurtosis-cdk-ref: ${{ needs.get-kurtosis-cdk-commit.outputs.kurtosis-commit }} - agglayer-e2e-ref: 145cec24f0b94e92572139f63bd2652fd9be5bdb + agglayer-e2e-ref: cd674a24950b1d8a890fe2639b5aa882252092de kurtosis-cdk-enclave-name: aggkit aggsender-find-imported-bridge-artifact: aggsender_find_imported_bridge kurtosis-cdk-args-1: ${{ needs.read-aggkit-args.outputs.kurtosis-cdk-args-3 }} diff --git a/agglayer/types/types.go b/agglayer/types/types.go index 1c2b45bcb..f8f68f4e1 100644 --- a/agglayer/types/types.go +++ b/agglayer/types/types.go @@ -862,10 +862,22 @@ func (l *L1InfoTreeLeaf) Validate() error { return nil } +type RemovedGER struct { + GlobalExitRoot common.Hash `json:"global_exit_root"` + BlockNumber uint64 `json:"block_number"` + LogIndex uint64 `json:"log_index"` +} + +type Unclaim struct { + GlobalIndex *GlobalIndex `json:"global_index"` + BlockNumber uint64 `json:"block_number"` + LogIndex uint64 `json:"log_index"` +} + type ProvenInsertedGERWithBlockNumber struct { BlockNumber uint64 `json:"block_number"` ProvenInsertedGERLeaf ProvenInsertedGER `json:"inserted_ger_leaf"` - BlockIndex uint `json:"block_index"` + LogIndex uint64 `json:"log_index"` } type ProvenInsertedGER struct { @@ -875,6 +887,7 @@ type ProvenInsertedGER struct { type ImportedBridgeExitWithBlockNumber struct { BlockNumber uint64 `json:"block_number"` + LogIndex uint64 `json:"log_index"` ImportedBridgeExit *ImportedBridgeExit `json:"imported_bridge_exit"` } diff --git a/aggsender/aggchainproofclient/aggchain_proof_client.go b/aggsender/aggchainproofclient/aggchain_proof_client.go index 3a0f21dcd..b3914440e 100644 --- a/aggsender/aggchainproofclient/aggchain_proof_client.go +++ b/aggsender/aggchainproofclient/aggchain_proof_client.go @@ -134,7 +134,7 @@ func convertAggchainProofRequestToGrpcRequest( } convertedGerLeaves[k.String()] = &aggkitProverV1Proto.ProvenInsertedGERWithBlockNumber{ BlockNumber: v.BlockNumber, - BlockIndex: uint64(v.BlockIndex), + LogIndex: v.LogIndex, ProvenInsertedGer: &aggkitProverV1Proto.ProvenInsertedGER{ ProofGerL1Root: &agglayerInteropTypesV1Proto.MerkleProof{ Root: &agglayerInteropTypesV1Proto.FixedBytes32{Value: v.ProvenInsertedGERLeaf.ProofGERToL1Root.Root[:]}, @@ -167,6 +167,7 @@ func convertAggchainProofRequestToGrpcRequest( for i, importedBridgeExitWithBlockNumber := range req.ImportedBridgeExitsWithBlockNumber { convertedImportedBridgeExitsWithBlockNumber[i] = &aggkitProverV1Proto.ImportedBridgeExitWithBlockNumber{ BlockNumber: importedBridgeExitWithBlockNumber.BlockNumber, + LogIndex: importedBridgeExitWithBlockNumber.LogIndex, GlobalIndex: &agglayerInteropTypesV1Proto.FixedBytes32{ Value: common.BigToHash(bridgesync.GenerateGlobalIndex( importedBridgeExitWithBlockNumber.ImportedBridgeExit.GlobalIndex.MainnetFlag, @@ -180,6 +181,32 @@ func convertAggchainProofRequestToGrpcRequest( } } + convertedRemovedGers := make([]*aggkitProverV1Proto.RemovedGER, len(req.RemovedGERs)) + for i, removedGER := range req.RemovedGERs { + convertedRemovedGers[i] = &aggkitProverV1Proto.RemovedGER{ + GlobalExitRoot: &agglayerInteropTypesV1Proto.FixedBytes32{ + Value: removedGER.GlobalExitRoot[:], + }, + BlockNumber: removedGER.BlockNumber, + LogIndex: removedGER.LogIndex, + } + } + + convertedUnclaims := make([]*aggkitProverV1Proto.Unclaim, len(req.Unclaims)) + for i, unclaim := range req.Unclaims { + convertedUnclaims[i] = &aggkitProverV1Proto.Unclaim{ + GlobalIndex: &agglayerInteropTypesV1Proto.FixedBytes32{ + Value: common.BigToHash(bridgesync.GenerateGlobalIndex( + unclaim.GlobalIndex.MainnetFlag, + unclaim.GlobalIndex.RollupIndex, + unclaim.GlobalIndex.LeafIndex, + )).Bytes(), + }, + BlockNumber: unclaim.BlockNumber, + LogIndex: unclaim.LogIndex, + } + } + request := &aggkitProverV1Proto.GenerateAggchainProofRequest{ LastProvenBlock: req.LastProvenBlock, RequestedEndBlock: req.RequestedEndBlock, @@ -188,6 +215,8 @@ func convertAggchainProofRequestToGrpcRequest( L1InfoTreeMerkleProof: convertedMerkleProof, GerLeaves: convertedGerLeaves, ImportedBridgeExits: convertedImportedBridgeExitsWithBlockNumber, + RemovedGers: convertedRemovedGers, + Unclaims: convertedUnclaims, } return request diff --git a/aggsender/aggchainproofclient/aggchain_proof_client_test.go b/aggsender/aggchainproofclient/aggchain_proof_client_test.go index 35f8e651e..4894770cb 100644 --- a/aggsender/aggchainproofclient/aggchain_proof_client_test.go +++ b/aggsender/aggchainproofclient/aggchain_proof_client_test.go @@ -60,6 +60,38 @@ func TestGenerateAggchainProof_Success(t *testing.T) { }, GERLeavesWithBlockNumber: nil, ImportedBridgeExitsWithBlockNumber: nil, + RemovedGERs: []*agglayer.RemovedGER{ + { + GlobalExitRoot: common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111"), + BlockNumber: 150, + LogIndex: 1, + }, + { + GlobalExitRoot: common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222"), + BlockNumber: 175, + LogIndex: 2, + }, + }, + Unclaims: []*agglayer.Unclaim{ + { + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: true, + RollupIndex: 1, + LeafIndex: 1, + }, + BlockNumber: 160, + LogIndex: 3, + }, + { + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 2, + LeafIndex: 2, + }, + BlockNumber: 185, + LogIndex: 4, + }, + }, } result, err := client.GenerateAggchainProof(context.Background(), request) @@ -207,6 +239,38 @@ func TestGenerateAggchainProof_Error(t *testing.T) { }, }, }, + RemovedGERs: []*agglayer.RemovedGER{ + { + GlobalExitRoot: common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"), + BlockNumber: 350, + LogIndex: 5, + }, + { + GlobalExitRoot: common.HexToHash("0x6666666666666666666666666666666666666666666666666666666666666666"), + BlockNumber: 375, + LogIndex: 6, + }, + }, + Unclaims: []*agglayer.Unclaim{ + { + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: true, + RollupIndex: 3, + LeafIndex: 3, + }, + BlockNumber: 360, + LogIndex: 7, + }, + { + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 4, + LeafIndex: 4, + }, + BlockNumber: 385, + LogIndex: 8, + }, + }, } result, err := client.GenerateAggchainProof(context.Background(), request) diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 07c534067..17327b7e4 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -23,6 +23,7 @@ import ( "github.com/agglayer/aggkit/aggsender/mocks" aggsendertypes "github.com/agglayer/aggkit/aggsender/types" "github.com/agglayer/aggkit/bridgesync" + bridgesynctypes "github.com/agglayer/aggkit/bridgesync/types" aggkitcommon "github.com/agglayer/aggkit/common" "github.com/agglayer/aggkit/config/types" mocksdb "github.com/agglayer/aggkit/db/compatibility/mocks" @@ -258,6 +259,7 @@ func TestSendCertificate_NoClaims(t *testing.T) { DepositCount: 1, }, }, []bridgesync.Claim{}, nil).Once() + mockL2BridgeQuerier.EXPECT().GetUnsetClaimsForBlockRange(mock.Anything, uint64(11), uint64(50)).Return([]bridgesynctypes.Unclaim{}, nil).Once() mockL1Querier.EXPECT().GetLatestFinalizedL1InfoRoot(ctx).Return(&treetypes.Root{}, nil, nil).Once() mockL2BridgeQuerier.EXPECT().GetExitRootByIndex(mock.Anything, uint32(1)).Return(common.Hash{}, nil).Once() mockL2BridgeQuerier.EXPECT().OriginNetwork().Return(uint32(1)).Once() diff --git a/aggsender/config/config.go b/aggsender/config/config.go index dd348189a..d871ddab0 100644 --- a/aggsender/config/config.go +++ b/aggsender/config/config.go @@ -91,6 +91,8 @@ type Config struct { RequireCommitteeMembershipCheck bool `mapstructure:"RequireCommitteeMembershipCheck"` // It allow to change committee URL for testing purposes CommitteeOverride query.CommitteeOverride `mapstructure:"CommitteeOverride"` + // AgglayerBridgeL2Addr is the address of the bridge L2 sovereign contract on L2 sovereign chain + AgglayerBridgeL2Addr ethCommon.Address `mapstructure:"AgglayerBridgeL2Addr"` } func (c Config) CheckCertConfigBriefString() string { diff --git a/aggsender/flows/builder_flow_aggchain_prover.go b/aggsender/flows/builder_flow_aggchain_prover.go index 2d8b15c3d..c46f5e036 100644 --- a/aggsender/flows/builder_flow_aggchain_prover.go +++ b/aggsender/flows/builder_flow_aggchain_prover.go @@ -199,6 +199,12 @@ func (a *AggchainProverBuilderFlow) GetCertificateBuildParams( return nil, fmt.Errorf("aggchainProverFlow - error getting bridges and claims: %w", err) } + unclaims, err := a.l2BridgeQuerier.GetUnsetClaimsForBlockRange(ctx, + fromBlock, toBlock) + if err != nil { + return nil, fmt.Errorf("error getting unset claims for block range: %w", err) + } + buildParams := &types.CertificateBuildParams{ FromBlock: fromBlock, ToBlock: toBlock, @@ -208,6 +214,7 @@ func (a *AggchainProverBuilderFlow) GetCertificateBuildParams( LastSentCertificate: lastSentCert, CreatedAt: lastSentCert.CreatedAt, CertificateType: typeCert, + Unclaims: unclaims, } if a.featureMaxL2Block != nil { // If the feature is enabled, we need to adapt the build params diff --git a/aggsender/flows/builder_flow_aggchain_prover_test.go b/aggsender/flows/builder_flow_aggchain_prover_test.go index 03a552db7..a4eb114b7 100644 --- a/aggsender/flows/builder_flow_aggchain_prover_test.go +++ b/aggsender/flows/builder_flow_aggchain_prover_test.go @@ -15,6 +15,7 @@ import ( "github.com/agglayer/aggkit/aggsender/query" "github.com/agglayer/aggkit/aggsender/types" "github.com/agglayer/aggkit/bridgesync" + bridgesynctypes "github.com/agglayer/aggkit/bridgesync/types" "github.com/agglayer/aggkit/l1infotreesync" "github.com/agglayer/aggkit/log" treetypes "github.com/agglayer/aggkit/tree/types" @@ -82,6 +83,7 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { MainnetExitRoot: mer, RollupExitRoot: rer, }}, nil) + mockL2BridgeQuerier.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(1), uint64(10)).Return([]bridgesynctypes.Unclaim{}, nil) }, expectedParams: &types.CertificateBuildParams{ FromBlock: 1, @@ -94,6 +96,7 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { MainnetExitRoot: common.HexToHash("0x2"), GlobalExitRoot: l1infotreesync.CalculateGER(common.HexToHash("0x2"), common.HexToHash("0x1")), }}, + Unclaims: []bridgesynctypes.Unclaim{}, L1InfoTreeRootFromWhichToProve: common.HexToHash("0x1"), AggchainProof: &types.AggchainProof{ SP1StarkProof: &types.SP1StarkProof{Proof: []byte("some-proof")}, @@ -135,6 +138,7 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { MainnetExitRoot: mer, RollupExitRoot: rer, }}, nil) + mockL2BridgeQuerier.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(1), uint64(10)).Return([]bridgesynctypes.Unclaim{}, nil) mockAggchainProofQuerier.EXPECT().GenerateAggchainProof(context.Background(), uint64(0), uint64(10), mock.Anything). Return(&types.AggchainProof{ SP1StarkProof: &types.SP1StarkProof{Proof: []byte("some-proof")}, @@ -162,6 +166,7 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { MainnetExitRoot: common.HexToHash("0x2"), GlobalExitRoot: l1infotreesync.CalculateGER(common.HexToHash("0x2"), common.HexToHash("0x1")), }}, + Unclaims: []bridgesynctypes.Unclaim{}, L1InfoTreeRootFromWhichToProve: common.HexToHash("0x1"), AggchainProof: &types.AggchainProof{ SP1StarkProof: &types.SP1StarkProof{Proof: []byte("some-proof")}, @@ -191,6 +196,7 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { MainnetExitRoot: mer, RollupExitRoot: rer, }}, nil) + mockL2BridgeQuerier.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(1), uint64(10)).Return([]bridgesynctypes.Unclaim{}, nil) mockAggchainProofQuerier.EXPECT().GenerateAggchainProof(context.Background(), uint64(0), uint64(10), mock.Anything). Return(nil, nil, errors.New("some error")) }, @@ -208,6 +214,7 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { mockL1InfoDataQuery.EXPECT().GetLatestFinalizedL1InfoRoot(mock.Anything).Return( &treetypes.Root{Hash: common.HexToHash("0x123"), BlockNum: 10}, nil, nil) mockL2BridgeQuerier.EXPECT().GetBridgesAndClaims(ctx, uint64(1), uint64(10)).Return([]bridgesync.Bridge{}, []bridgesync.Claim{}, nil) + mockL2BridgeQuerier.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(1), uint64(10)).Return([]bridgesynctypes.Unclaim{}, nil) wrappedErr := fmt.Errorf("wrapped error: %w", query.ErrNoProofBuiltYet) mockAggchainProofQuerier.EXPECT().GenerateAggchainProof(context.Background(), uint64(0), uint64(10), mock.Anything). Return(nil, nil, wrappedErr) @@ -235,6 +242,7 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { MainnetExitRoot: mer, RollupExitRoot: rer, }}, nil) + mockL2BridgeQuerier.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(6), uint64(10)).Return([]bridgesynctypes.Unclaim{}, nil) mockAggchainProofQuerier.EXPECT().GenerateAggchainProof(context.Background(), uint64(5), uint64(10), mock.Anything). Return(&types.AggchainProof{ SP1StarkProof: &types.SP1StarkProof{Proof: []byte("some-proof")}, @@ -257,6 +265,7 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { MainnetExitRoot: common.HexToHash("0x2"), GlobalExitRoot: l1infotreesync.CalculateGER(common.HexToHash("0x2"), common.HexToHash("0x1")), }}, + Unclaims: []bridgesynctypes.Unclaim{}, L1InfoTreeRootFromWhichToProve: common.HexToHash("0x1"), AggchainProof: &types.AggchainProof{ SP1StarkProof: &types.SP1StarkProof{Proof: []byte("some-proof")}, @@ -287,6 +296,7 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { {BlockNum: 8, GlobalIndex: big.NewInt(1), GlobalExitRoot: ger, MainnetExitRoot: mer, RollupExitRoot: rer}, {BlockNum: 9, GlobalIndex: big.NewInt(2), GlobalExitRoot: ger, MainnetExitRoot: mer, RollupExitRoot: rer}}, nil) + mockL2BridgeQuerier.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(6), uint64(10)).Return([]bridgesynctypes.Unclaim{}, nil) mockAggchainProofQuerier.EXPECT().GenerateAggchainProof(context.Background(), uint64(5), uint64(10), mock.Anything). Return(&types.AggchainProof{ SP1StarkProof: &types.SP1StarkProof{Proof: []byte("some-proof")}, @@ -310,6 +320,7 @@ func Test_AggchainProverFlow_GetCertificateBuildParams(t *testing.T) { MainnetExitRoot: common.HexToHash("0x2"), GlobalExitRoot: l1infotreesync.CalculateGER(common.HexToHash("0x2"), common.HexToHash("0x1")), }}, + Unclaims: []bridgesynctypes.Unclaim{}, L1InfoTreeRootFromWhichToProve: common.HexToHash("0x1"), AggchainProof: &types.AggchainProof{ SP1StarkProof: &types.SP1StarkProof{Proof: []byte("some-proof")}, diff --git a/aggsender/flows/builder_flow_factory.go b/aggsender/flows/builder_flow_factory.go index 119e2502a..ef759477d 100644 --- a/aggsender/flows/builder_flow_factory.go +++ b/aggsender/flows/builder_flow_factory.go @@ -12,12 +12,14 @@ import ( "github.com/agglayer/aggkit/aggsender/optimistic" "github.com/agglayer/aggkit/aggsender/query" "github.com/agglayer/aggkit/aggsender/types" + "github.com/agglayer/aggkit/bridgesync" aggkitcommon "github.com/agglayer/aggkit/common" "github.com/agglayer/aggkit/l2gersync" "github.com/agglayer/aggkit/log" aggkittypes "github.com/agglayer/aggkit/types" "github.com/agglayer/go_signer/signer" signertypes "github.com/agglayer/go_signer/signer/types" + ethCommon "github.com/ethereum/go-ethereum/common" ) var ( @@ -41,13 +43,14 @@ func NewBuilderFlow( switch cfg.Mode { case types.PessimisticProofMode: commonFlowComponents, err := CreateCommonFlowComponents( - ctx, logger, storage, l1Client, l1InfoTreeSyncer, l2Syncer, + ctx, logger, storage, l1Client, l2Client, l1InfoTreeSyncer, l2Syncer, rollupDataQuerier, committeeQuerier, 0, false, cfg.MaxCertSize, cfg.RollupCreationBlockL1, cfg.DelayBetweenRetries.Duration, cfg.AggsenderPrivateKey, true, cfg.RequireCommitteeMembershipCheck, + cfg.AgglayerBridgeL2Addr, ) if err != nil { return nil, fmt.Errorf("failed to create common flow components: %w", err) @@ -87,13 +90,14 @@ func NewBuilderFlow( } commonFlowComponents, err := CreateCommonFlowComponents( - ctx, logger, storage, l1Client, l1InfoTreeSyncer, l2Syncer, + ctx, logger, storage, l1Client, l2Client, l1InfoTreeSyncer, l2Syncer, rollupDataQuerier, committeeQuerier, aggchainFEPQuerier.StartL2Block(), cfg.RequireNoFEPBlockGap, cfg.MaxCertSize, cfg.RollupCreationBlockL1, cfg.DelayBetweenRetries.Duration, cfg.AggsenderPrivateKey, true, cfg.RequireCommitteeMembershipCheck, + cfg.AgglayerBridgeL2Addr, ) if err != nil { return nil, fmt.Errorf("failed to create common flow components: %w", err) @@ -111,6 +115,7 @@ func NewBuilderFlow( optimisticSigner, commonFlowComponents.BaseFlow, query.NewGERDataQuerier(commonFlowComponents.L1InfoTreeDataQuerier, l2GERReader), + commonFlowComponents.L2BridgeQuerier, ) return NewAggchainProverBuilderFlow( @@ -144,6 +149,7 @@ func CreateCommonFlowComponents( logger *log.Logger, storage db.AggSenderStorage, l1Client aggkittypes.BaseEthereumClienter, + l2Client aggkittypes.BaseEthereumClienter, l1InfoTreeSyncer types.L1InfoTreeSyncer, l2Syncer types.L2BridgeSyncer, rollupDataQuerier types.RollupDataQuerier, @@ -156,6 +162,7 @@ func CreateCommonFlowComponents( signerCfg signertypes.SignerConfig, fullClaimsRequired bool, requireCommitteeMembershipCheck bool, + agglayerBridgeL2Addr ethCommon.Address, ) (*CommonFlowComponents, error) { l2ChainID, err := rollupDataQuerier.GetRollupChainID() if err != nil { @@ -168,7 +175,12 @@ func CreateCommonFlowComponents( return nil, err } - l2BridgeQuerier := query.NewBridgeDataQuerier(logger, l2Syncer, delayBetweenRetries) + agglayerBridgeL2Reader, err := bridgesync.NewAgglayerBridgeL2Reader(agglayerBridgeL2Addr, l2Client) + if err != nil { + return nil, fmt.Errorf("failed to create bridge L2 sovereign reader: %w", err) + } + + l2BridgeQuerier := query.NewBridgeDataQuerier(logger, l2Syncer, delayBetweenRetries, agglayerBridgeL2Reader) l1InfoTreeQuerier := query.NewL1InfoTreeDataQuerier(l1Client, l1InfoTreeSyncer) lerQuerier := query.NewLERDataQuerier(rollupCreationBlockL1, rollupDataQuerier) diff --git a/aggsender/flows/builder_flow_pp_test.go b/aggsender/flows/builder_flow_pp_test.go index dce87a2da..ad905c9c4 100644 --- a/aggsender/flows/builder_flow_pp_test.go +++ b/aggsender/flows/builder_flow_pp_test.go @@ -11,6 +11,7 @@ import ( "github.com/agglayer/aggkit/aggsender/mocks" "github.com/agglayer/aggkit/aggsender/types" "github.com/agglayer/aggkit/bridgesync" + bridgesynctypes "github.com/agglayer/aggkit/bridgesync/types" "github.com/agglayer/aggkit/l1infotreesync" "github.com/agglayer/aggkit/log" treetypes "github.com/agglayer/aggkit/tree/types" @@ -324,6 +325,7 @@ func Test_PPFlow_GetCertificateBuildParams(t *testing.T) { mockL2BridgeQuerier.EXPECT().GetLastProcessedBlock(ctx).Return(uint64(10), nil) mockStorage.EXPECT().GetLastSentCertificateHeader().Return(&types.CertificateHeader{ToBlock: 5}, nil) mockL2BridgeQuerier.EXPECT().GetBridgesAndClaims(ctx, uint64(6), uint64(10)).Return([]bridgesync.Bridge{}, []bridgesync.Claim{}, nil) + mockL2BridgeQuerier.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(6), uint64(10)).Return([]bridgesynctypes.Unclaim{}, nil) }, expectedParams: nil, }, @@ -338,6 +340,7 @@ func Test_PPFlow_GetCertificateBuildParams(t *testing.T) { mockL2BridgeQuerier.EXPECT().GetLastProcessedBlock(ctx).Return(uint64(10), nil) mockStorage.EXPECT().GetLastSentCertificateHeader().Return(&types.CertificateHeader{ToBlock: 5}, nil) mockL2BridgeQuerier.EXPECT().GetBridgesAndClaims(ctx, uint64(6), uint64(10)).Return([]bridgesync.Bridge{}, []bridgesync.Claim{{}}, nil) + mockL2BridgeQuerier.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(6), uint64(10)).Return([]bridgesynctypes.Unclaim{}, nil) }, expectedParams: nil, }, @@ -359,6 +362,7 @@ func Test_PPFlow_GetCertificateBuildParams(t *testing.T) { RollupExitRoot: rer, MainnetExitRoot: mer, }}, nil) + mockL2BridgeQuerier.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(6), uint64(10)).Return([]bridgesynctypes.Unclaim{}, nil) mockL1InfoTreeQuerier.EXPECT().GetLatestFinalizedL1InfoRoot(ctx).Return( &treetypes.Root{Hash: common.HexToHash("0x123"), BlockNum: 1}, nil, nil) }, @@ -377,6 +381,7 @@ func Test_PPFlow_GetCertificateBuildParams(t *testing.T) { MainnetExitRoot: common.HexToHash("0x2"), GlobalExitRoot: l1infotreesync.CalculateGER(common.HexToHash("0x2"), common.HexToHash("0x1")), }}, + Unclaims: []bridgesynctypes.Unclaim{}, CreatedAt: timeNowUTCForTest(), L1InfoTreeRootFromWhichToProve: common.HexToHash("0x123"), }, @@ -392,6 +397,7 @@ func Test_PPFlow_GetCertificateBuildParams(t *testing.T) { mockStorage.EXPECT().GetLastSentCertificateHeader().Return(&types.CertificateHeader{ToBlock: 5}, nil) mockL2BridgeQuerier.EXPECT().GetBridgesAndClaims(ctx, uint64(6), uint64(10)).Return( []bridgesync.Bridge{{}}, []bridgesync.Claim{{GlobalExitRoot: common.HexToHash("0x1")}}, nil) + mockL2BridgeQuerier.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(6), uint64(10)).Return([]bridgesynctypes.Unclaim{}, nil) }, expectedError: "GER mismatch", }, @@ -422,6 +428,7 @@ func Test_PPFlow_GetCertificateBuildParams(t *testing.T) { RollupExitRoot: rer, MainnetExitRoot: mer, }}, nil) + mockL2BridgeQuerier.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(6), uint64(10)).Return([]bridgesynctypes.Unclaim{}, nil) mockL1InfoTreeQuerier.EXPECT().GetLatestFinalizedL1InfoRoot(ctx).Return( &treetypes.Root{Hash: common.HexToHash("0x123"), BlockNum: 10}, nil, nil) }, @@ -439,6 +446,7 @@ func Test_PPFlow_GetCertificateBuildParams(t *testing.T) { MainnetExitRoot: common.HexToHash("0x2"), GlobalExitRoot: l1infotreesync.CalculateGER(common.HexToHash("0x2"), common.HexToHash("0x1")), }}, + Unclaims: []bridgesynctypes.Unclaim{}, CreatedAt: timeNowUTCForTest(), L1InfoTreeRootFromWhichToProve: common.HexToHash("0x123"), }, diff --git a/aggsender/flows/flow_base.go b/aggsender/flows/flow_base.go index 77098bc76..b1370e856 100644 --- a/aggsender/flows/flow_base.go +++ b/aggsender/flows/flow_base.go @@ -11,6 +11,7 @@ import ( "github.com/agglayer/aggkit/aggsender/db" "github.com/agglayer/aggkit/aggsender/types" "github.com/agglayer/aggkit/bridgesync" + bridgesynctypes "github.com/agglayer/aggkit/bridgesync/types" aggkitcommon "github.com/agglayer/aggkit/common" "github.com/agglayer/aggkit/l1infotreesync" "github.com/ethereum/go-ethereum/common" @@ -171,7 +172,13 @@ func (f *baseFlow) GenerateBuildParams(ctx context.Context, bridges, claims, err := f.l2BridgeQuerier.GetBridgesAndClaims(ctx, preParams.BlockRange.FromBlock, preParams.BlockRange.ToBlock) if err != nil { - return nil, fmt.Errorf("generateBulidParams fails getting bridges and claims. Err: %w", err) + return nil, fmt.Errorf("generateBuildParams fails getting bridges and claims. Err: %w", err) + } + + unclaims, err := f.l2BridgeQuerier.GetUnsetClaimsForBlockRange(ctx, + preParams.BlockRange.FromBlock, preParams.BlockRange.ToBlock) + if err != nil { + return nil, fmt.Errorf("error getting unset claims for block range: %w", err) } buildParams := &types.CertificateBuildParams{ @@ -185,6 +192,7 @@ func (f *baseFlow) GenerateBuildParams(ctx context.Context, CertificateType: preParams.CertificateType, L1InfoTreeRootFromWhichToProve: preParams.L1InfoTreeToProve.L1InfoTreeRootToProve, L1InfoTreeLeafCount: preParams.L1InfoTreeToProve.L1InfoTreeLeafCount, + Unclaims: unclaims, } return buildParams, nil } @@ -274,7 +282,8 @@ func (f *baseFlow) BuildCertificate(ctx context.Context, } bridgeExits := f.getBridgeExits(certParams.Bridges) - importedBridgeExits, err := f.getImportedBridgeExits(ctx, certParams.Claims, certParams.L1InfoTreeRootFromWhichToProve) + importedBridgeExits, err := f.getImportedBridgeExits( + ctx, certParams.Claims, certParams.Unclaims, certParams.L1InfoTreeRootFromWhichToProve) if err != nil { return nil, fmt.Errorf("error getting imported bridge exits: %w", err) } @@ -333,15 +342,56 @@ func (f *baseFlow) getBridgeExits(bridges []bridgesync.Bridge) []*agglayertypes. // getImportedBridgeExits converts claims to agglayertypes.ImportedBridgeExit objects and calculates necessary proofs func (f *baseFlow) getImportedBridgeExits( - ctx context.Context, claims []bridgesync.Claim, + ctx context.Context, + claims []bridgesync.Claim, + unclaims []bridgesynctypes.Unclaim, rootFromWhichToProve common.Hash, ) ([]*agglayertypes.ImportedBridgeExit, error) { + // Build unclaim counts by GlobalIndex + unclaimCnt := make(map[string]int) + for _, u := range unclaims { + // Adjust accessor as needed: + // - if GlobalIndex is uint64: key := strconv.FormatUint(u.GlobalIndex, 10) + // - if it's *big.Int: key := u.GlobalIndex.String() + // - if it's a struct method: key := u.GlobalIndex().String() + key := u.GlobalIndex.String() + unclaimCnt[key]++ + } + + // Build claim counts by GlobalIndex + claimCnt := make(map[string]int) + for _, c := range claims { + key := c.GlobalIndex.String() + claimCnt[key]++ + } + + // Compute how many claims should remain per index after cancelling unclaims + remaining := make(map[string]int, len(claimCnt)) + for k, c := range claimCnt { + u := unclaimCnt[k] + if c > u { + remaining[k] = c - u + } else { + remaining[k] = 0 + } + } + + // Filter claims: keep in original order, but only as many as remaining[idx] + filteredClaims := make([]bridgesync.Claim, 0, len(claims)) + for _, c := range claims { + key := c.GlobalIndex.String() + if remaining[key] > 0 { + filteredClaims = append(filteredClaims, c) + remaining[key]-- + } + } + if f.cfg.FullClaimsNeeded { return converters.ConvertToImportedBridgeExits( - ctx, claims, rootFromWhichToProve, f.l1InfoTreeDataQuerier) + ctx, filteredClaims, rootFromWhichToProve, f.l1InfoTreeDataQuerier, + ) } - - return converters.ConvertToImportedBridgeExitsWithoutClaimData(claims) + return converters.ConvertToImportedBridgeExitsWithoutClaimData(filteredClaims) } // getNextHeightAndPreviousLER returns the height and previous LER for the new certificate diff --git a/aggsender/flows/flow_base_test.go b/aggsender/flows/flow_base_test.go index 3e7285859..e1f39721e 100644 --- a/aggsender/flows/flow_base_test.go +++ b/aggsender/flows/flow_base_test.go @@ -3,15 +3,20 @@ package flows import ( "context" "errors" + "fmt" + "math/big" "testing" agglayertypes "github.com/agglayer/aggkit/agglayer/types" "github.com/agglayer/aggkit/aggsender/mocks" "github.com/agglayer/aggkit/aggsender/types" "github.com/agglayer/aggkit/bridgesync" + bridgesynctypes "github.com/agglayer/aggkit/bridgesync/types" aggkitcommon "github.com/agglayer/aggkit/common" "github.com/agglayer/aggkit/db" + "github.com/agglayer/aggkit/l1infotreesync" "github.com/agglayer/aggkit/log" + treetypes "github.com/agglayer/aggkit/tree/types" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -52,6 +57,7 @@ func Test_baseFlow_limitCertSize(t *testing.T) { ToBlock: 9, Bridges: []bridgesync.Bridge{{BlockNum: 9}}, Claims: []bridgesync.Claim{}, + Unclaims: []bridgesynctypes.Unclaim{}, }, }, { @@ -67,6 +73,7 @@ func Test_baseFlow_limitCertSize(t *testing.T) { ToBlock: 9, Bridges: []bridgesync.Bridge{}, Claims: []bridgesync.Claim{}, + Unclaims: []bridgesynctypes.Unclaim{}, }, }, { @@ -755,3 +762,314 @@ func Test_baseFlow_VerifyBlockRangeGaps(t *testing.T) { }) } } + +func Test_baseFlow_getImportedBridgeExits(t *testing.T) { + t.Parallel() + + ctx := context.Background() + rootFromWhichToProve := common.HexToHash("0x1234567890123456789012345678901234567890123456789012345678901234") + + mockProof := func() treetypes.Proof { + proof := treetypes.Proof{} + for i := 0; i < int(treetypes.DefaultHeight) && i < 10; i++ { + proof[i] = common.HexToHash(fmt.Sprintf("0x%02x", i)) + } + return proof + }() + + tests := []struct { + name string + claims []bridgesync.Claim + unclaims []bridgesynctypes.Unclaim + fullClaimsNeeded bool + mockFn func(*mocks.L1InfoTreeDataQuerier) + expectedCount int + expectedError string + }{ + { + name: "no claims, no unclaims", + claims: []bridgesync.Claim{}, + unclaims: []bridgesynctypes.Unclaim{}, + fullClaimsNeeded: false, + expectedCount: 0, + expectedError: "", + }, + { + name: "claims without unclaims - FullClaimsNeeded false", + claims: []bridgesync.Claim{ + { + BlockNum: 1, + BlockPos: 0, + GlobalIndex: big.NewInt(100), + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x123"), + Amount: big.NewInt(1000), + }, + { + BlockNum: 2, + BlockPos: 1, + GlobalIndex: big.NewInt(200), + OriginNetwork: 2, + OriginAddress: common.HexToAddress("0x456"), + Amount: big.NewInt(2000), + }, + }, + unclaims: []bridgesynctypes.Unclaim{}, + fullClaimsNeeded: false, + expectedCount: 2, + expectedError: "", + }, + { + name: "claims without unclaims - FullClaimsNeeded true", + claims: []bridgesync.Claim{ + { + BlockNum: 1, + BlockPos: 0, + GlobalIndex: big.NewInt(100), + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x123"), + Amount: big.NewInt(1000), + GlobalExitRoot: common.HexToHash("0xger1"), + RollupExitRoot: common.HexToHash("0xrer1"), + MainnetExitRoot: common.HexToHash("0xmer1"), + ProofLocalExitRoot: mockProof, + ProofRollupExitRoot: mockProof, + }, + }, + unclaims: []bridgesynctypes.Unclaim{}, + fullClaimsNeeded: true, + mockFn: func(mockL1InfoTreeQuery *mocks.L1InfoTreeDataQuerier) { + mockL1InfoTreeQuery.EXPECT().GetProofForGER(ctx, common.HexToHash("0xger1"), rootFromWhichToProve). + Return( + &l1infotreesync.L1InfoTreeLeaf{ + L1InfoTreeIndex: 1, + Timestamp: 123456789, + PreviousBlockHash: common.HexToHash("0xabc"), + GlobalExitRoot: common.HexToHash("0xger1"), + }, mockProof, nil) + }, + expectedCount: 1, + expectedError: "", + }, + { + name: "claims with unclaims canceling some claims", + claims: []bridgesync.Claim{ + { + BlockNum: 1, + BlockPos: 0, + GlobalIndex: big.NewInt(100), + OriginNetwork: 1, + Amount: big.NewInt(1000), + }, + { + BlockNum: 2, + BlockPos: 1, + GlobalIndex: big.NewInt(100), + OriginNetwork: 2, + Amount: big.NewInt(2000), + }, + { + BlockNum: 3, + BlockPos: 2, + GlobalIndex: big.NewInt(200), + OriginNetwork: 3, + Amount: big.NewInt(3000), + }, + }, + unclaims: []bridgesynctypes.Unclaim{ + {GlobalIndex: big.NewInt(100), BlockNumber: 10, LogIndex: 0}, + }, + fullClaimsNeeded: false, + expectedCount: 2, // one claim with GlobalIndex 100 remains, plus the one with 200 + expectedError: "", + }, + { + name: "claims with unclaims canceling all claims of same GlobalIndex", + claims: []bridgesync.Claim{ + { + BlockNum: 1, + BlockPos: 0, + GlobalIndex: big.NewInt(100), + OriginNetwork: 1, + Amount: big.NewInt(1000), + }, + { + BlockNum: 2, + BlockPos: 1, + GlobalIndex: big.NewInt(100), + OriginNetwork: 2, + Amount: big.NewInt(2000), + }, + { + BlockNum: 3, + BlockPos: 2, + GlobalIndex: big.NewInt(200), + OriginNetwork: 3, + Amount: big.NewInt(3000), + }, + }, + unclaims: []bridgesynctypes.Unclaim{ + {GlobalIndex: big.NewInt(100), BlockNumber: 10, LogIndex: 0}, + {GlobalIndex: big.NewInt(100), BlockNumber: 11, LogIndex: 1}, + }, + fullClaimsNeeded: false, + expectedCount: 1, // only the claim with GlobalIndex 200 remains + expectedError: "", + }, + { + name: "more unclaims than claims for a GlobalIndex", + claims: []bridgesync.Claim{ + { + BlockNum: 1, + BlockPos: 0, + GlobalIndex: big.NewInt(100), + OriginNetwork: 1, + Amount: big.NewInt(1000), + }, + }, + unclaims: []bridgesynctypes.Unclaim{ + {GlobalIndex: big.NewInt(100), BlockNumber: 10, LogIndex: 0}, + {GlobalIndex: big.NewInt(100), BlockNumber: 11, LogIndex: 1}, + {GlobalIndex: big.NewInt(100), BlockNumber: 12, LogIndex: 2}, + }, + fullClaimsNeeded: false, + expectedCount: 0, + expectedError: "", + }, + { + name: "multiple GlobalIndices with mixed cancellation", + claims: []bridgesync.Claim{ + { + BlockNum: 1, + BlockPos: 0, + GlobalIndex: big.NewInt(100), + OriginNetwork: 1, + Amount: big.NewInt(1000), + }, + { + BlockNum: 2, + BlockPos: 1, + GlobalIndex: big.NewInt(100), + OriginNetwork: 2, + Amount: big.NewInt(2000), + }, + { + BlockNum: 3, + BlockPos: 2, + GlobalIndex: big.NewInt(200), + OriginNetwork: 3, + Amount: big.NewInt(3000), + }, + { + BlockNum: 4, + BlockPos: 3, + GlobalIndex: big.NewInt(200), + OriginNetwork: 4, + Amount: big.NewInt(4000), + }, + }, + unclaims: []bridgesynctypes.Unclaim{ + {GlobalIndex: big.NewInt(100), BlockNumber: 10, LogIndex: 0}, + {GlobalIndex: big.NewInt(200), BlockNumber: 11, LogIndex: 1}, + }, + fullClaimsNeeded: false, + expectedCount: 2, // one claim with 100, one claim with 200 + expectedError: "", + }, + { + name: "using GenerateGlobalIndex helper", + claims: []bridgesync.Claim{ + { + BlockNum: 1, + BlockPos: 0, + GlobalIndex: bridgesync.GenerateGlobalIndex(false, 1, 1), + OriginNetwork: 1, + Amount: big.NewInt(1000), + }, + { + BlockNum: 2, + BlockPos: 1, + GlobalIndex: bridgesync.GenerateGlobalIndex(false, 1, 1), + OriginNetwork: 2, + Amount: big.NewInt(2000), + }, + }, + unclaims: []bridgesynctypes.Unclaim{ + {GlobalIndex: bridgesync.GenerateGlobalIndex(false, 1, 1), BlockNumber: 10, LogIndex: 0}, + }, + fullClaimsNeeded: false, + expectedCount: 1, // one claim remains after unclaim + expectedError: "", + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + mockL1InfoTreeQuery := mocks.NewL1InfoTreeDataQuerier(t) + if tt.mockFn != nil { + tt.mockFn(mockL1InfoTreeQuery) + } + + f := &baseFlow{ + cfg: BaseFlowConfig{ + FullClaimsNeeded: tt.fullClaimsNeeded, + }, + l1InfoTreeDataQuerier: mockL1InfoTreeQuery, + log: log.WithFields("test", t.Name()), + } + + result, err := f.getImportedBridgeExits(ctx, tt.claims, tt.unclaims, rootFromWhichToProve) + + if tt.expectedError != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.expectedError) + require.Nil(t, result) + } else { + require.NoError(t, err) + require.NotNil(t, result) + require.Equal(t, tt.expectedCount, len(result)) + + // Verify that the filtered claims are correct by checking GlobalIndex + if !tt.fullClaimsNeeded && len(tt.claims) > 0 { + // Count remaining claims by GlobalIndex + remainingByIndex := make(map[string]int) + for _, claim := range tt.claims { + key := claim.GlobalIndex.String() + remainingByIndex[key]++ + } + for _, unclaim := range tt.unclaims { + key := unclaim.GlobalIndex.String() + if remainingByIndex[key] > 0 { + remainingByIndex[key]-- + } + } + + // Verify that result has correct count per GlobalIndex + resultByIndex := make(map[string]int) + for _, ibe := range result { + if ibe != nil && ibe.GlobalIndex != nil { + // Reconstruct the original GlobalIndex string representation + mainnetFlag := ibe.GlobalIndex.MainnetFlag + rollupIndex := ibe.GlobalIndex.RollupIndex + leafIndex := ibe.GlobalIndex.LeafIndex + reconstructed := bridgesync.GenerateGlobalIndex(mainnetFlag, rollupIndex, leafIndex) + key := reconstructed.String() + resultByIndex[key]++ + } + } + + // Verify total count matches + totalExpected := 0 + for _, count := range remainingByIndex { + totalExpected += count + } + require.Equal(t, totalExpected, len(result), "Total filtered claims should match expected count") + } + } + }) + } +} diff --git a/aggsender/flows/verifier_flow_factory.go b/aggsender/flows/verifier_flow_factory.go index 85fda5035..1521db192 100644 --- a/aggsender/flows/verifier_flow_factory.go +++ b/aggsender/flows/verifier_flow_factory.go @@ -21,6 +21,7 @@ func NewVerifierFlow( cfg validator.Config, logger *log.Logger, l1Client aggkittypes.BaseEthereumClienter, + l2Client aggkittypes.BaseEthereumClienter, l1InfoTreeSyncer types.L1InfoTreeSyncer, l2Syncer types.L2BridgeSyncer, rollupDataQuerier types.RollupDataQuerier, @@ -31,10 +32,11 @@ func NewVerifierFlow( commonFlowComponents, err := CreateCommonFlowComponents( ctx, logger, nil, // storage is not used in validator, - l1Client, l1InfoTreeSyncer, l2Syncer, rollupDataQuerier, committeeQuerier, 0, false, + l1Client, l2Client, l1InfoTreeSyncer, l2Syncer, rollupDataQuerier, committeeQuerier, 0, false, cfg.MaxCertSize, cfg.LerQuerier.RollupCreationBlockL1, cfg.DelayBetweenRetries.Duration, cfg.Signer, true, // full claims are (eventually) needed in validator mode cfg.RequireCommitteeMembershipCheck, + cfg.AgglayerBridgeL2Addr, ) if err != nil { return nil, nil, fmt.Errorf("failed to create common flow components: %w", err) @@ -56,12 +58,13 @@ func NewVerifierFlow( commonFlowComponents, err := CreateCommonFlowComponents( ctx, logger, nil, // storage is not used in validator, - l1Client, l1InfoTreeSyncer, l2Syncer, rollupDataQuerier, committeeQuerier, + l1Client, l2Client, l1InfoTreeSyncer, l2Syncer, rollupDataQuerier, committeeQuerier, 0, cfg.FEPConfig.RequireNoBlockGap, cfg.MaxCertSize, cfg.LerQuerier.RollupCreationBlockL1, cfg.DelayBetweenRetries.Duration, cfg.Signer, true, // full claims are (eventually) needed in validator mode cfg.RequireCommitteeMembershipCheck, + cfg.AgglayerBridgeL2Addr, ) if err != nil { return nil, nil, fmt.Errorf("failed to create common flow components: %w", err) diff --git a/aggsender/flows/verifier_flow_factory_test.go b/aggsender/flows/verifier_flow_factory_test.go index 0627152c4..78ba3887b 100644 --- a/aggsender/flows/verifier_flow_factory_test.go +++ b/aggsender/flows/verifier_flow_factory_test.go @@ -119,6 +119,7 @@ func TestNewVerifierFlow(t *testing.T) { tc.cfg, mockLogger, mockL1Client, + nil, mockL1InfoTreeSyncer, mockL2Syncer, mockRollupDataQuerier, diff --git a/aggsender/mocks/mock_agglayer_bridge_l2_reader.go b/aggsender/mocks/mock_agglayer_bridge_l2_reader.go new file mode 100644 index 000000000..a97430c9b --- /dev/null +++ b/aggsender/mocks/mock_agglayer_bridge_l2_reader.go @@ -0,0 +1,97 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + context "context" + + types "github.com/agglayer/aggkit/bridgesync/types" + mock "github.com/stretchr/testify/mock" +) + +// AgglayerBridgeL2Reader is an autogenerated mock type for the AgglayerBridgeL2Reader type +type AgglayerBridgeL2Reader struct { + mock.Mock +} + +type AgglayerBridgeL2Reader_Expecter struct { + mock *mock.Mock +} + +func (_m *AgglayerBridgeL2Reader) EXPECT() *AgglayerBridgeL2Reader_Expecter { + return &AgglayerBridgeL2Reader_Expecter{mock: &_m.Mock} +} + +// GetUnsetClaimsForBlockRange provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *AgglayerBridgeL2Reader) GetUnsetClaimsForBlockRange(ctx context.Context, fromBlock uint64, toBlock uint64) ([]types.Unclaim, error) { + ret := _m.Called(ctx, fromBlock, toBlock) + + if len(ret) == 0 { + panic("no return value specified for GetUnsetClaimsForBlockRange") + } + + var r0 []types.Unclaim + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) ([]types.Unclaim, error)); ok { + return rf(ctx, fromBlock, toBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []types.Unclaim); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Unclaim) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { + r1 = rf(ctx, fromBlock, toBlock) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUnsetClaimsForBlockRange' +type AgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_Call struct { + *mock.Call +} + +// GetUnsetClaimsForBlockRange is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - toBlock uint64 +func (_e *AgglayerBridgeL2Reader_Expecter) GetUnsetClaimsForBlockRange(ctx interface{}, fromBlock interface{}, toBlock interface{}) *AgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_Call { + return &AgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_Call{Call: _e.mock.On("GetUnsetClaimsForBlockRange", ctx, fromBlock, toBlock)} +} + +func (_c *AgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *AgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) + }) + return _c +} + +func (_c *AgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_Call) Return(_a0 []types.Unclaim, _a1 error) *AgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_Call) RunAndReturn(run func(context.Context, uint64, uint64) ([]types.Unclaim, error)) *AgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_Call { + _c.Call.Return(run) + return _c +} + +// NewAgglayerBridgeL2Reader creates a new instance of AgglayerBridgeL2Reader. 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 NewAgglayerBridgeL2Reader(t interface { + mock.TestingT + Cleanup(func()) +}) *AgglayerBridgeL2Reader { + mock := &AgglayerBridgeL2Reader{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/aggsender/mocks/mock_bridge_querier.go b/aggsender/mocks/mock_bridge_querier.go index d272100bf..8916837fb 100644 --- a/aggsender/mocks/mock_bridge_querier.go +++ b/aggsender/mocks/mock_bridge_querier.go @@ -4,6 +4,8 @@ package mocks import ( bridgesync "github.com/agglayer/aggkit/bridgesync" + bridgesynctypes "github.com/agglayer/aggkit/bridgesync/types" + common "github.com/ethereum/go-ethereum/common" context "context" @@ -208,6 +210,66 @@ func (_c *BridgeQuerier_GetLastProcessedBlock_Call) RunAndReturn(run func(contex return _c } +// GetUnsetClaimsForBlockRange provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *BridgeQuerier) GetUnsetClaimsForBlockRange(ctx context.Context, fromBlock uint64, toBlock uint64) ([]bridgesynctypes.Unclaim, error) { + ret := _m.Called(ctx, fromBlock, toBlock) + + if len(ret) == 0 { + panic("no return value specified for GetUnsetClaimsForBlockRange") + } + + var r0 []bridgesynctypes.Unclaim + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) ([]bridgesynctypes.Unclaim, error)); ok { + return rf(ctx, fromBlock, toBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []bridgesynctypes.Unclaim); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]bridgesynctypes.Unclaim) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { + r1 = rf(ctx, fromBlock, toBlock) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BridgeQuerier_GetUnsetClaimsForBlockRange_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUnsetClaimsForBlockRange' +type BridgeQuerier_GetUnsetClaimsForBlockRange_Call struct { + *mock.Call +} + +// GetUnsetClaimsForBlockRange is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - toBlock uint64 +func (_e *BridgeQuerier_Expecter) GetUnsetClaimsForBlockRange(ctx interface{}, fromBlock interface{}, toBlock interface{}) *BridgeQuerier_GetUnsetClaimsForBlockRange_Call { + return &BridgeQuerier_GetUnsetClaimsForBlockRange_Call{Call: _e.mock.On("GetUnsetClaimsForBlockRange", ctx, fromBlock, toBlock)} +} + +func (_c *BridgeQuerier_GetUnsetClaimsForBlockRange_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *BridgeQuerier_GetUnsetClaimsForBlockRange_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) + }) + return _c +} + +func (_c *BridgeQuerier_GetUnsetClaimsForBlockRange_Call) Return(_a0 []bridgesynctypes.Unclaim, _a1 error) *BridgeQuerier_GetUnsetClaimsForBlockRange_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *BridgeQuerier_GetUnsetClaimsForBlockRange_Call) RunAndReturn(run func(context.Context, uint64, uint64) ([]bridgesynctypes.Unclaim, error)) *BridgeQuerier_GetUnsetClaimsForBlockRange_Call { + _c.Call.Return(run) + return _c +} + // OriginNetwork provides a mock function with no fields func (_m *BridgeQuerier) OriginNetwork() uint32 { ret := _m.Called() diff --git a/aggsender/mocks/mock_chain_ger_reader.go b/aggsender/mocks/mock_chain_ger_reader.go index b453aea67..f1fe567ea 100644 --- a/aggsender/mocks/mock_chain_ger_reader.go +++ b/aggsender/mocks/mock_chain_ger_reader.go @@ -3,10 +3,11 @@ package mocks import ( - context "context" - + agglayertypes "github.com/agglayer/aggkit/agglayer/types" common "github.com/ethereum/go-ethereum/common" + context "context" + l2gersync "github.com/agglayer/aggkit/l2gersync" mock "github.com/stretchr/testify/mock" @@ -85,6 +86,66 @@ func (_c *ChainGERReader_GetInjectedGERsForRange_Call) RunAndReturn(run func(con return _c } +// GetRemovedGERsForRange provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *ChainGERReader) GetRemovedGERsForRange(ctx context.Context, fromBlock uint64, toBlock uint64) ([]*agglayertypes.RemovedGER, error) { + ret := _m.Called(ctx, fromBlock, toBlock) + + if len(ret) == 0 { + panic("no return value specified for GetRemovedGERsForRange") + } + + var r0 []*agglayertypes.RemovedGER + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) ([]*agglayertypes.RemovedGER, error)); ok { + return rf(ctx, fromBlock, toBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []*agglayertypes.RemovedGER); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*agglayertypes.RemovedGER) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { + r1 = rf(ctx, fromBlock, toBlock) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ChainGERReader_GetRemovedGERsForRange_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRemovedGERsForRange' +type ChainGERReader_GetRemovedGERsForRange_Call struct { + *mock.Call +} + +// GetRemovedGERsForRange is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - toBlock uint64 +func (_e *ChainGERReader_Expecter) GetRemovedGERsForRange(ctx interface{}, fromBlock interface{}, toBlock interface{}) *ChainGERReader_GetRemovedGERsForRange_Call { + return &ChainGERReader_GetRemovedGERsForRange_Call{Call: _e.mock.On("GetRemovedGERsForRange", ctx, fromBlock, toBlock)} +} + +func (_c *ChainGERReader_GetRemovedGERsForRange_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *ChainGERReader_GetRemovedGERsForRange_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) + }) + return _c +} + +func (_c *ChainGERReader_GetRemovedGERsForRange_Call) Return(_a0 []*agglayertypes.RemovedGER, _a1 error) *ChainGERReader_GetRemovedGERsForRange_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ChainGERReader_GetRemovedGERsForRange_Call) RunAndReturn(run func(context.Context, uint64, uint64) ([]*agglayertypes.RemovedGER, error)) *ChainGERReader_GetRemovedGERsForRange_Call { + _c.Call.Return(run) + return _c +} + // NewChainGERReader creates a new instance of ChainGERReader. 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 NewChainGERReader(t interface { diff --git a/aggsender/mocks/mock_ger_querier.go b/aggsender/mocks/mock_ger_querier.go index e3563b59a..fc3c36505 100644 --- a/aggsender/mocks/mock_ger_querier.go +++ b/aggsender/mocks/mock_ger_querier.go @@ -88,6 +88,66 @@ func (_c *GERQuerier_GetInjectedGERsProofs_Call) RunAndReturn(run func(context.C return _c } +// GetRemovedGERsForRange provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *GERQuerier) GetRemovedGERsForRange(ctx context.Context, fromBlock uint64, toBlock uint64) ([]*agglayertypes.RemovedGER, error) { + ret := _m.Called(ctx, fromBlock, toBlock) + + if len(ret) == 0 { + panic("no return value specified for GetRemovedGERsForRange") + } + + var r0 []*agglayertypes.RemovedGER + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) ([]*agglayertypes.RemovedGER, error)); ok { + return rf(ctx, fromBlock, toBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []*agglayertypes.RemovedGER); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*agglayertypes.RemovedGER) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { + r1 = rf(ctx, fromBlock, toBlock) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GERQuerier_GetRemovedGERsForRange_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRemovedGERsForRange' +type GERQuerier_GetRemovedGERsForRange_Call struct { + *mock.Call +} + +// GetRemovedGERsForRange is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - toBlock uint64 +func (_e *GERQuerier_Expecter) GetRemovedGERsForRange(ctx interface{}, fromBlock interface{}, toBlock interface{}) *GERQuerier_GetRemovedGERsForRange_Call { + return &GERQuerier_GetRemovedGERsForRange_Call{Call: _e.mock.On("GetRemovedGERsForRange", ctx, fromBlock, toBlock)} +} + +func (_c *GERQuerier_GetRemovedGERsForRange_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *GERQuerier_GetRemovedGERsForRange_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) + }) + return _c +} + +func (_c *GERQuerier_GetRemovedGERsForRange_Call) Return(_a0 []*agglayertypes.RemovedGER, _a1 error) *GERQuerier_GetRemovedGERsForRange_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *GERQuerier_GetRemovedGERsForRange_Call) RunAndReturn(run func(context.Context, uint64, uint64) ([]*agglayertypes.RemovedGER, error)) *GERQuerier_GetRemovedGERsForRange_Call { + _c.Call.Return(run) + return _c +} + // NewGERQuerier creates a new instance of GERQuerier. 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 NewGERQuerier(t interface { diff --git a/aggsender/prover/proof_generation_tool.go b/aggsender/prover/proof_generation_tool.go index d46c439fb..24fd1fe53 100644 --- a/aggsender/prover/proof_generation_tool.go +++ b/aggsender/prover/proof_generation_tool.go @@ -10,6 +10,7 @@ import ( "github.com/agglayer/aggkit/aggsender/flows" "github.com/agglayer/aggkit/aggsender/query" "github.com/agglayer/aggkit/aggsender/types" + "github.com/agglayer/aggkit/bridgesync" aggkitgrpc "github.com/agglayer/aggkit/grpc" "github.com/agglayer/aggkit/l2gersync" "github.com/agglayer/aggkit/log" @@ -43,6 +44,9 @@ type Config struct { // SovereignRollupAddr is the address of the sovereign rollup contract on L1 SovereignRollupAddr common.Address `mapstructure:"SovereignRollupAddr"` + + // AgglayerBridgeL2Addr is the address of the bridge L2 sovereign contract on L2 sovereign chain + AgglayerBridgeL2Addr common.Address `mapstructure:"AgglayerBridgeL2Addr"` } // AggchainProofGenerationTool is a tool to generate Aggchain proofs @@ -86,8 +90,13 @@ func NewAggchainProofGenerationTool( return nil, fmt.Errorf("error creating L2 GER reader: %w", err) } + agglayerBridgeL2Reader, err := bridgesync.NewAgglayerBridgeL2Reader(cfg.AgglayerBridgeL2Addr, l2Client) + if err != nil { + return nil, fmt.Errorf("failed to create bridge L2 sovereign reader: %w", err) + } + l1InfoTreeQuerier := query.NewL1InfoTreeDataQuerier(l1Client, l1InfoTreeSyncer) - l2BridgeQuerier := query.NewBridgeDataQuerier(logger, l2Syncer, time.Second) + l2BridgeQuerier := query.NewBridgeDataQuerier(logger, l2Syncer, time.Second, agglayerBridgeL2Reader) baseFlow := flows.NewBaseFlow( logger, @@ -104,6 +113,7 @@ func NewAggchainProofGenerationTool( nil, // optimistic signer is not used in the tool, so we pass nil baseFlow, query.NewGERDataQuerier(l1InfoTreeQuerier, l2GERReader), + query.NewBridgeDataQuerier(logger, l2Syncer, time.Second, agglayerBridgeL2Reader), ) return &AggchainProofGenerationTool{ diff --git a/aggsender/query/aggchain_proof_query.go b/aggsender/query/aggchain_proof_query.go index 30b9a8134..209602771 100644 --- a/aggsender/query/aggchain_proof_query.go +++ b/aggsender/query/aggchain_proof_query.go @@ -10,6 +10,7 @@ import ( "github.com/agglayer/aggkit/aggsender/metrics" "github.com/agglayer/aggkit/aggsender/types" "github.com/agglayer/aggkit/bridgesync" + bridgesynctypes "github.com/agglayer/aggkit/bridgesync/types" "github.com/agglayer/aggkit/grpc" treetypes "github.com/agglayer/aggkit/tree/types" "google.golang.org/grpc/codes" @@ -33,6 +34,7 @@ type aggchainProofQuery struct { lerQuerier types.LocalExitRootQuery optimisticSigner types.OptimisticSigner gerQuerier types.GERQuerier + bridgeQuerier types.BridgeQuerier } // NewAggchainProofQuery creates a new instance of aggchainProofQuery with the provided dependencies. @@ -43,6 +45,7 @@ func NewAggchainProofQuery( optimisticSigner types.OptimisticSigner, lerQuerier types.LocalExitRootQuery, gerQuerier types.GERQuerier, + bridgeQuerier types.BridgeQuerier, ) *aggchainProofQuery { return &aggchainProofQuery{ aggchainProofClient: aggchainproofclient, @@ -51,6 +54,7 @@ func NewAggchainProofQuery( gerQuerier: gerQuerier, lerQuerier: lerQuerier, log: log, + bridgeQuerier: bridgeQuerier, } } @@ -96,6 +100,17 @@ func (a *aggchainProofQuery) GenerateAggchainProof( if err != nil { return nil, nil, fmt.Errorf("aggchainProverFlow - error getting imported bridge exits for prover: %w", err) } + + removedGERs, err := a.gerQuerier.GetRemovedGERsForRange(ctx, fromBlock, toBlock) + if err != nil { + return nil, nil, fmt.Errorf("error getting removed GERs block numbers: %w", err) + } + + unclaims, err := a.convertUnclaimsToAgglayerUnclaims(certBuildParams.Unclaims) + if err != nil { + return nil, nil, fmt.Errorf("error converting unclaims to unclaims: %w", err) + } + var aggchainProof *types.AggchainProof request := &types.AggchainProofRequest{ LastProvenBlock: lastProvenBlock, @@ -108,6 +123,8 @@ func (a *aggchainProofQuery) GenerateAggchainProof( }, GERLeavesWithBlockNumber: injectedGERsProofs, ImportedBridgeExitsWithBlockNumber: importedBridgeExits, + RemovedGERs: removedGERs, + Unclaims: unclaims, } // It decide if must generate optimistic proof using CertType optimisticMode := certBuildParams.CertificateType == types.CertificateTypeOptimistic @@ -183,11 +200,38 @@ func (a *aggchainProofQuery) getImportedBridgeExitsForProver( if err != nil { return nil, fmt.Errorf("aggchainProverFlow - error converting claim to imported bridge exit: %w", err) } + importedBridgeExits = append(importedBridgeExits, &agglayertypes.ImportedBridgeExitWithBlockNumber{ ImportedBridgeExit: ibe, BlockNumber: claim.BlockNum, + LogIndex: claim.BlockPos, }) } return importedBridgeExits, nil } + +func (a *aggchainProofQuery) convertUnclaimsToAgglayerUnclaims( + unclaims []bridgesynctypes.Unclaim) ([]*agglayertypes.Unclaim, error) { + unclaimsConverted := make([]*agglayertypes.Unclaim, 0, len(unclaims)) + + for _, unclaim := range unclaims { + // Decode the *big.Int GlobalIndex to GlobalIndex struct + mainnetFlag, rollupIndex, leafIndex, err := bridgesync.DecodeGlobalIndex(unclaim.GlobalIndex) + if err != nil { + return nil, fmt.Errorf("error decoding global index: %w", err) + } + + unclaimsConverted = append(unclaimsConverted, &agglayertypes.Unclaim{ + GlobalIndex: &agglayertypes.GlobalIndex{ + MainnetFlag: mainnetFlag, + RollupIndex: rollupIndex, + LeafIndex: leafIndex, + }, + BlockNumber: unclaim.BlockNumber, + LogIndex: unclaim.LogIndex, + }) + } + + return unclaimsConverted, nil +} diff --git a/aggsender/query/aggchain_proof_query_test.go b/aggsender/query/aggchain_proof_query_test.go index 5f38d2b00..dd074e4d4 100644 --- a/aggsender/query/aggchain_proof_query_test.go +++ b/aggsender/query/aggchain_proof_query_test.go @@ -10,6 +10,7 @@ import ( "github.com/agglayer/aggkit/aggsender/mocks" "github.com/agglayer/aggkit/aggsender/types" "github.com/agglayer/aggkit/bridgesync" + bridgesynctypes "github.com/agglayer/aggkit/bridgesync/types" "github.com/agglayer/aggkit/l1infotreesync" "github.com/agglayer/aggkit/log" treetypes "github.com/agglayer/aggkit/tree/types" @@ -225,6 +226,7 @@ func TestGenerateOptimisticAggchainProof(t *testing.T) { optimisticSigner, lerQuery, nil, // gerQuerier + nil, // bridgeQuerier ) proof, err := query.generateOptimisticAggchainProof(ctx, tc.buildParams, tc.request) @@ -249,7 +251,8 @@ func TestGenerateAggchainProof(t *testing.T) { mockFn func( *mocks.AggchainProofClientInterface, *mocks.L1InfoTreeDataQuerier, - *mocks.GERQuerier) + *mocks.GERQuerier, + *mocks.BridgeQuerier) lastProvenBlock uint64 buildParams *types.CertificateBuildParams expectedProof *types.AggchainProof @@ -262,7 +265,8 @@ func TestGenerateAggchainProof(t *testing.T) { buildParams: &types.CertificateBuildParams{}, mockFn: func(aggchainProofClient *mocks.AggchainProofClientInterface, l1InfoTreeDataQuerier *mocks.L1InfoTreeDataQuerier, - gerQuerier *mocks.GERQuerier) { + gerQuerier *mocks.GERQuerier, + bridgeQuerier *mocks.BridgeQuerier) { l1InfoTreeDataQuerier.EXPECT().GetFinalizedL1InfoTreeData(ctx).Return(treetypes.Proof{}, nil, nil, errors.New("some error")) }, expectedError: "aggchainProverFlow - error getting finalized L1 Info tree data: some error", @@ -273,7 +277,8 @@ func TestGenerateAggchainProof(t *testing.T) { buildParams: &types.CertificateBuildParams{Claims: []bridgesync.Claim{{}}}, mockFn: func(aggchainProofClient *mocks.AggchainProofClientInterface, l1InfoTreeDataQuerier *mocks.L1InfoTreeDataQuerier, - gerQuerier *mocks.GERQuerier) { + gerQuerier *mocks.GERQuerier, + bridgeQuerier *mocks.BridgeQuerier) { l1InfoTreeDataQuerier.EXPECT().GetFinalizedL1InfoTreeData(ctx).Return( treetypes.Proof{}, &l1infotreesync.L1InfoTreeLeaf{}, @@ -292,7 +297,8 @@ func TestGenerateAggchainProof(t *testing.T) { buildParams: &types.CertificateBuildParams{ToBlock: 200, Claims: []bridgesync.Claim{{}}}, mockFn: func(aggchainProofClient *mocks.AggchainProofClientInterface, l1InfoTreeDataQuerier *mocks.L1InfoTreeDataQuerier, - gerQuerier *mocks.GERQuerier) { + gerQuerier *mocks.GERQuerier, + bridgeQuerier *mocks.BridgeQuerier) { l1InfoTreeDataQuerier.EXPECT().GetFinalizedL1InfoTreeData(ctx).Return( treetypes.Proof{}, &l1infotreesync.L1InfoTreeLeaf{}, @@ -312,7 +318,8 @@ func TestGenerateAggchainProof(t *testing.T) { buildParams: &types.CertificateBuildParams{ToBlock: 200, Claims: []bridgesync.Claim{{GlobalIndex: big.NewInt(1)}}}, mockFn: func(aggchainProofClient *mocks.AggchainProofClientInterface, l1InfoTreeDataQuerier *mocks.L1InfoTreeDataQuerier, - gerQuerier *mocks.GERQuerier) { + gerQuerier *mocks.GERQuerier, + bridgeQuerier *mocks.BridgeQuerier) { l1InfoTreeDataQuerier.EXPECT().GetFinalizedL1InfoTreeData(ctx).Return( treetypes.Proof{}, &l1infotreesync.L1InfoTreeLeaf{}, @@ -323,6 +330,7 @@ func TestGenerateAggchainProof(t *testing.T) { []bridgesync.Claim{{GlobalIndex: big.NewInt(1)}}, ).Return(nil) gerQuerier.EXPECT().GetInjectedGERsProofs(ctx, &treetypes.Root{Hash: common.HexToHash("0x123"), Index: 1}, uint64(101), uint64(200)).Return(nil, nil) + gerQuerier.EXPECT().GetRemovedGERsForRange(ctx, uint64(101), uint64(200)).Return(nil, nil) aggchainProofClient.EXPECT().GenerateAggchainProof(ctx, mock.Anything). Return(nil, errors.New("aggchain proof error")) }, @@ -334,7 +342,8 @@ func TestGenerateAggchainProof(t *testing.T) { buildParams: &types.CertificateBuildParams{ToBlock: 200, Claims: []bridgesync.Claim{{GlobalIndex: big.NewInt(1)}}}, mockFn: func(aggchainProofClient *mocks.AggchainProofClientInterface, l1InfoTreeDataQuerier *mocks.L1InfoTreeDataQuerier, - gerQuerier *mocks.GERQuerier) { + gerQuerier *mocks.GERQuerier, + bridgeQuerier *mocks.BridgeQuerier) { l1InfoTreeDataQuerier.EXPECT().GetFinalizedL1InfoTreeData(ctx).Return( treetypes.Proof{}, &l1infotreesync.L1InfoTreeLeaf{}, @@ -345,6 +354,7 @@ func TestGenerateAggchainProof(t *testing.T) { []bridgesync.Claim{{GlobalIndex: big.NewInt(1)}}, ).Return(nil) gerQuerier.EXPECT().GetInjectedGERsProofs(ctx, &treetypes.Root{Hash: common.HexToHash("0x123"), Index: 1}, uint64(101), uint64(200)).Return(nil, nil) + gerQuerier.EXPECT().GetRemovedGERsForRange(ctx, uint64(101), uint64(200)).Return(nil, nil) aggchainProofClient.EXPECT().GenerateAggchainProof(ctx, mock.Anything). Return(&types.AggchainProof{ LastProvenBlock: 100, @@ -373,8 +383,9 @@ func TestGenerateAggchainProof(t *testing.T) { aggchainProofClient := mocks.NewAggchainProofClientInterface(t) l1InfoTreeDataQuerier := mocks.NewL1InfoTreeDataQuerier(t) gerQuerier := mocks.NewGERQuerier(t) + bridgeQuerier := mocks.NewBridgeQuerier(t) if tc.mockFn != nil { - tc.mockFn(aggchainProofClient, l1InfoTreeDataQuerier, gerQuerier) + tc.mockFn(aggchainProofClient, l1InfoTreeDataQuerier, gerQuerier, bridgeQuerier) } log := log.WithFields("aggchain_proof_query", "TestGenerateAggchainProof") @@ -385,6 +396,7 @@ func TestGenerateAggchainProof(t *testing.T) { nil, // optimisticSigner nil, // lerQuerier gerQuerier, + bridgeQuerier, ) proof, root, err := query.GenerateAggchainProof(ctx, tc.lastProvenBlock, tc.buildParams.ToBlock, tc.buildParams) @@ -398,3 +410,201 @@ func TestGenerateAggchainProof(t *testing.T) { }) } } + +func TestConvertUnclaimsToAgglayerUnclaims(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + unclaims []bridgesynctypes.Unclaim + expectedUnclaims []*agglayertypes.Unclaim + expectedError string + }{ + { + name: "empty map", + unclaims: []bridgesynctypes.Unclaim{}, + expectedUnclaims: []*agglayertypes.Unclaim{}, + }, + { + name: "single unclaim with mainnet flag true", + unclaims: []bridgesynctypes.Unclaim{ + { + GlobalIndex: bridgesync.GenerateGlobalIndex(true, 0, 5), + BlockNumber: 100, + LogIndex: 2, + }, + }, + expectedUnclaims: []*agglayertypes.Unclaim{ + { + GlobalIndex: &agglayertypes.GlobalIndex{ + MainnetFlag: true, + RollupIndex: 0, + LeafIndex: 5, + }, + BlockNumber: 100, + LogIndex: 2, + }, + }, + }, + { + name: "single unclaim with mainnet flag false and rollup index", + unclaims: []bridgesynctypes.Unclaim{ + { + GlobalIndex: bridgesync.GenerateGlobalIndex(false, 3, 7), + BlockNumber: 200, + LogIndex: 1, + }, + }, + expectedUnclaims: []*agglayertypes.Unclaim{ + { + GlobalIndex: &agglayertypes.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 3, + LeafIndex: 7, + }, + BlockNumber: 200, + LogIndex: 1, + }, + }, + }, + { + name: "multiple unclaims with different configurations", + unclaims: []bridgesynctypes.Unclaim{ + { + GlobalIndex: bridgesync.GenerateGlobalIndex(true, 0, 1), + BlockNumber: 100, + LogIndex: 0, + }, + { + GlobalIndex: bridgesync.GenerateGlobalIndex(false, 5, 10), + BlockNumber: 150, + LogIndex: 3, + }, + { + GlobalIndex: bridgesync.GenerateGlobalIndex(false, 0, 0), + BlockNumber: 200, + LogIndex: 1, + }, + }, + expectedUnclaims: []*agglayertypes.Unclaim{ + { + GlobalIndex: &agglayertypes.GlobalIndex{ + MainnetFlag: true, + RollupIndex: 0, + LeafIndex: 1, + }, + BlockNumber: 100, + LogIndex: 0, + }, + { + GlobalIndex: &agglayertypes.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 5, + LeafIndex: 10, + }, + BlockNumber: 150, + LogIndex: 3, + }, + { + GlobalIndex: &agglayertypes.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 0, + LeafIndex: 0, + }, + BlockNumber: 200, + LogIndex: 1, + }, + }, + }, + { + name: "unclaim with zero global index", + unclaims: []bridgesynctypes.Unclaim{ + { + GlobalIndex: big.NewInt(0), + BlockNumber: 100, + LogIndex: 0, + }, + }, + expectedUnclaims: []*agglayertypes.Unclaim{ + { + GlobalIndex: &agglayertypes.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 0, + LeafIndex: 0, + }, + BlockNumber: 100, + LogIndex: 0, + }, + }, + }, + { + name: "unclaim with large values", + unclaims: []bridgesynctypes.Unclaim{ + { + GlobalIndex: bridgesync.GenerateGlobalIndex(false, 4294967295, 4294967295), // max uint32 values + BlockNumber: 999999, + LogIndex: 65535, + }, + }, + expectedUnclaims: []*agglayertypes.Unclaim{ + { + GlobalIndex: &agglayertypes.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 4294967295, + LeafIndex: 4294967295, + }, + BlockNumber: 999999, + LogIndex: 65535, + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + log := log.WithFields("aggchain_proof_query", "TestConvertUnclaimsToAgglayerUnclaims") + query := &aggchainProofQuery{ + log: log, + } + + unclaims, err := query.convertUnclaimsToAgglayerUnclaims(tc.unclaims) + if tc.expectedError != "" { + require.ErrorContains(t, err, tc.expectedError) + require.Nil(t, unclaims) + } else { + require.NoError(t, err) + require.Len(t, unclaims, len(tc.expectedUnclaims)) + + // Sort both slices by BlockNumber for consistent comparison + // since map iteration order is not guaranteed + sortUnclaimsByBlockNumber(t, unclaims) + sortUnclaimsByBlockNumber(t, tc.expectedUnclaims) + + for i, expected := range tc.expectedUnclaims { + require.Equal(t, expected.GlobalIndex.MainnetFlag, unclaims[i].GlobalIndex.MainnetFlag) + require.Equal(t, expected.GlobalIndex.RollupIndex, unclaims[i].GlobalIndex.RollupIndex) + require.Equal(t, expected.GlobalIndex.LeafIndex, unclaims[i].GlobalIndex.LeafIndex) + require.Equal(t, expected.BlockNumber, unclaims[i].BlockNumber) + require.Equal(t, expected.LogIndex, unclaims[i].LogIndex) + } + } + }) + } +} + +// Helper function to sort unclaims by BlockNumber for consistent comparison +func sortUnclaimsByBlockNumber(t *testing.T, unclaims []*agglayertypes.Unclaim) { + t.Helper() + + for i := 0; i < len(unclaims); i++ { + for j := i + 1; j < len(unclaims); j++ { + if unclaims[i].BlockNumber > unclaims[j].BlockNumber { + unclaims[i], unclaims[j] = unclaims[j], unclaims[i] + } + } + } +} diff --git a/aggsender/query/bridge_query.go b/aggsender/query/bridge_query.go index dba9f9f3b..d1b63d8f7 100644 --- a/aggsender/query/bridge_query.go +++ b/aggsender/query/bridge_query.go @@ -7,6 +7,7 @@ import ( "github.com/agglayer/aggkit/aggsender/types" "github.com/agglayer/aggkit/bridgesync" + bridgesynctypes "github.com/agglayer/aggkit/bridgesync/types" "github.com/ethereum/go-ethereum/common" ) @@ -14,9 +15,10 @@ var _ types.BridgeQuerier = (*bridgeDataQuerier)(nil) // bridgeDataQuerier is a struct that holds the logic to query the bridge data type bridgeDataQuerier struct { - log types.Logger - bridgeSyncer types.L2BridgeSyncer - delayBetweenRetries time.Duration + log types.Logger + bridgeSyncer types.L2BridgeSyncer + delayBetweenRetries time.Duration + agglayerBridgeL2Reader types.AgglayerBridgeL2Reader originNetwork uint32 } @@ -26,12 +28,14 @@ func NewBridgeDataQuerier( log types.Logger, bridgeSyncer types.L2BridgeSyncer, delayBetweenRetries time.Duration, + agglayerBridgeL2Reader types.AgglayerBridgeL2Reader, ) *bridgeDataQuerier { return &bridgeDataQuerier{ - log: log, - bridgeSyncer: bridgeSyncer, - delayBetweenRetries: delayBetweenRetries, - originNetwork: bridgeSyncer.OriginNetwork(), + log: log, + bridgeSyncer: bridgeSyncer, + delayBetweenRetries: delayBetweenRetries, + originNetwork: bridgeSyncer.OriginNetwork(), + agglayerBridgeL2Reader: agglayerBridgeL2Reader, } } @@ -133,3 +137,10 @@ func (b *bridgeDataQuerier) WaitForSyncerToCatchUp(ctx context.Context, block ui } } } + +// GetUnsetClaimsForBlockRange gets unset claims from agglayer bridge L2 and converts to unclaim map +func (b *bridgeDataQuerier) GetUnsetClaimsForBlockRange(ctx context.Context, + fromBlock, toBlock uint64) ([]bridgesynctypes.Unclaim, error) { + b.log.Debugf("getting unset claims for block range %d to %d", fromBlock, toBlock) + return b.agglayerBridgeL2Reader.GetUnsetClaimsForBlockRange(ctx, fromBlock, toBlock) +} diff --git a/aggsender/query/bridge_query_test.go b/aggsender/query/bridge_query_test.go index 427c9f453..30ba00cca 100644 --- a/aggsender/query/bridge_query_test.go +++ b/aggsender/query/bridge_query_test.go @@ -90,9 +90,10 @@ func TestGetBridgesAndClaims(t *testing.T) { mockSyncer := new(mocks.L2BridgeSyncer) mockSyncer.EXPECT().OriginNetwork().Return(1).Once() + AgglayerBridgeL2Reader := new(mocks.AgglayerBridgeL2Reader) tc.mockFn(mockSyncer) - bridgeQuerier := NewBridgeDataQuerier(nil, mockSyncer, 0) + bridgeQuerier := NewBridgeDataQuerier(nil, mockSyncer, 0, AgglayerBridgeL2Reader) bridges, claims, err := bridgeQuerier.GetBridgesAndClaims(ctx, tc.fromBlock, tc.toBlock) if tc.expectedError != "" { @@ -148,9 +149,10 @@ func TestGetExitRootByIndex(t *testing.T) { mockSyncer := new(mocks.L2BridgeSyncer) mockSyncer.EXPECT().OriginNetwork().Return(1).Once() + AgglayerBridgeL2Reader := new(mocks.AgglayerBridgeL2Reader) tc.mockFn(mockSyncer) - bridgeQuerier := NewBridgeDataQuerier(nil, mockSyncer, 0) + bridgeQuerier := NewBridgeDataQuerier(nil, mockSyncer, 0, AgglayerBridgeL2Reader) hash, err := bridgeQuerier.GetExitRootByIndex(ctx, tc.index) if tc.expectedError != "" { @@ -199,9 +201,10 @@ func TestGetLastProcessedBlock(t *testing.T) { mockSyncer := new(mocks.L2BridgeSyncer) mockSyncer.EXPECT().OriginNetwork().Return(1).Once() + AgglayerBridgeL2Reader := new(mocks.AgglayerBridgeL2Reader) tc.mockFn(mockSyncer) - bridgeQuerier := NewBridgeDataQuerier(nil, mockSyncer, 0) + bridgeQuerier := NewBridgeDataQuerier(nil, mockSyncer, 0, AgglayerBridgeL2Reader) block, err := bridgeQuerier.GetLastProcessedBlock(ctx) if tc.expectedError != "" { @@ -222,7 +225,9 @@ func TestOriginNetwork(t *testing.T) { mockSyncer := new(mocks.L2BridgeSyncer) mockSyncer.EXPECT().OriginNetwork().Return(uint32(1)).Once() - bridgeQuerier := NewBridgeDataQuerier(nil, mockSyncer, 0) + AgglayerBridgeL2Reader := new(mocks.AgglayerBridgeL2Reader) + + bridgeQuerier := NewBridgeDataQuerier(nil, mockSyncer, 0, AgglayerBridgeL2Reader) originNetwork := bridgeQuerier.OriginNetwork() require.Equal(t, uint32(1), originNetwork) @@ -275,14 +280,16 @@ func TestWaitForSyncerToCatchUp(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() mockSyncer := mocks.NewL2BridgeSyncer(t) + mockReader := mocks.NewAgglayerBridgeL2Reader(t) if tc.mockFn != nil { tc.mockFn(mockSyncer) } bridgeQuerier := &bridgeDataQuerier{ - log: log.WithFields("test", "TestWaitForSyncerToCatchUp"), - bridgeSyncer: mockSyncer, - delayBetweenRetries: tc.delayBetweenRetries, + log: log.WithFields("test", "TestWaitForSyncerToCatchUp"), + bridgeSyncer: mockSyncer, + delayBetweenRetries: tc.delayBetweenRetries, + agglayerBridgeL2Reader: mockReader, } err := bridgeQuerier.WaitForSyncerToCatchUp(ctx, tc.block) @@ -296,3 +303,62 @@ func TestWaitForSyncerToCatchUp(t *testing.T) { }) } } + +func TestGetUnsetClaimsForBlockRange(t *testing.T) { + t.Parallel() + + ctx := context.Background() + testCases := []struct { + name string + fromBlock uint64 + toBlock uint64 + mockFn func(*mocks.AgglayerBridgeL2Reader) + expectedError string + }{ + { + name: "error - failed to get unset claims from reader", + fromBlock: 100, + toBlock: 200, + mockFn: func(mockReader *mocks.AgglayerBridgeL2Reader) { + // No mocks needed for syncer as the error occurs before calling it + mockReader.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(100), uint64(200)).Return(nil, errors.New("failed to get unclaim block range: error")).Once() + }, + expectedError: "failed to get unclaim block range: error", + }, + { + name: "success - empty unclaims list", + fromBlock: 100, + toBlock: 200, + mockFn: func(mockReader *mocks.AgglayerBridgeL2Reader) { + // No mocks needed for syncer as there are no unclaims to process + mockReader.EXPECT().GetUnsetClaimsForBlockRange(ctx, uint64(100), uint64(200)).Return(nil, nil).Once() + }, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + mockSyncer := new(mocks.L2BridgeSyncer) + mockSyncer.EXPECT().OriginNetwork().Return(uint32(1)).Once() + AgglayerBridgeL2Reader := new(mocks.AgglayerBridgeL2Reader) + tc.mockFn(AgglayerBridgeL2Reader) + + bridgeQuerier := NewBridgeDataQuerier(log.WithFields("test", "TestGetUnsetClaimsForBlockRange"), mockSyncer, 0, AgglayerBridgeL2Reader) + + unclaims, err := bridgeQuerier.GetUnsetClaimsForBlockRange(ctx, tc.fromBlock, tc.toBlock) + if tc.expectedError != "" { + require.ErrorContains(t, err, tc.expectedError) + } else { + require.NoError(t, err) + require.Len(t, unclaims, 0) // Empty list case + } + + mockSyncer.AssertExpectations(t) + AgglayerBridgeL2Reader.AssertExpectations(t) + }) + } +} diff --git a/aggsender/query/ger_query.go b/aggsender/query/ger_query.go index 9c6512639..ce07ae3f9 100644 --- a/aggsender/query/ger_query.go +++ b/aggsender/query/ger_query.go @@ -51,7 +51,7 @@ func (g *gerDataQuerier) GetInjectedGERsProofs( fromBlock, toBlock uint64) (map[common.Hash]*agglayertypes.ProvenInsertedGERWithBlockNumber, error) { injectedGERs, err := g.chainGERReader.GetInjectedGERsForRange(ctx, fromBlock, toBlock) if err != nil { - return nil, fmt.Errorf("aggchainProverFlow - error getting injected GERs for range %d : %d: %w", + return nil, fmt.Errorf("error getting injected GERs for range %d : %d: %w", fromBlock, toBlock, err) } @@ -60,17 +60,16 @@ func (g *gerDataQuerier) GetInjectedGERsProofs( for ger, injectedGER := range injectedGERs { info, proof, err := g.l1InfoTreeQuerier.GetProofForGER(ctx, ger, finalizedL1InfoTreeRoot.Hash) if err != nil { - return nil, fmt.Errorf("aggchainProverFlow - error getting proof for GER: %s: %w", ger.String(), err) + return nil, fmt.Errorf("error getting proof for GER: %s: %w", ger.String(), err) } if injectedGER.BlockPosition == nil { - return nil, fmt.Errorf("aggchainProverFlow - block position for GER %s is undefined", ger.String()) + return nil, fmt.Errorf("block position for GER %s is undefined", ger.String()) } - blockPos := uint(*injectedGER.BlockPosition) proofs[ger] = &agglayertypes.ProvenInsertedGERWithBlockNumber{ BlockNumber: injectedGER.BlockNum, - BlockIndex: blockPos, + LogIndex: *injectedGER.BlockPosition, ProvenInsertedGERLeaf: agglayertypes.ProvenInsertedGER{ ProofGERToL1Root: &agglayertypes.MerkleProof{Root: finalizedL1InfoTreeRoot.Hash, Proof: proof}, L1Leaf: &agglayertypes.L1InfoTreeLeaf{ @@ -89,3 +88,14 @@ func (g *gerDataQuerier) GetInjectedGERsProofs( return proofs, nil } + +// GetRemovedGERsForRange returns the removed GlobalExitRoots for the given block range +func (g *gerDataQuerier) GetRemovedGERsForRange(ctx context.Context, + fromBlock, toBlock uint64) ([]*agglayertypes.RemovedGER, error) { + removedGERs, err := g.chainGERReader.GetRemovedGERsForRange(ctx, fromBlock, toBlock) + if err != nil { + return nil, fmt.Errorf("error getting removed GERs for range %d : %d: %w", + fromBlock, toBlock, err) + } + return removedGERs, nil +} diff --git a/aggsender/query/ger_query_test.go b/aggsender/query/ger_query_test.go index e2c5a0ea7..66266f016 100644 --- a/aggsender/query/ger_query_test.go +++ b/aggsender/query/ger_query_test.go @@ -111,3 +111,112 @@ func Test_GetInjectedGERsProofs(t *testing.T) { }) } } + +func Test_GetRemovedGERsForRange(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + testCases := []struct { + name string + mockFn func(*mocks.ChainGERReader) + expectedGERs []*agglayertypes.RemovedGER + expectedError string + }{ + { + name: "error getting removed GERs for range", + mockFn: func(mockChainGERReader *mocks.ChainGERReader) { + mockChainGERReader.EXPECT().GetRemovedGERsForRange(ctx, uint64(1), uint64(10)).Return(nil, errors.New("some error")) + }, + expectedError: "error getting removed GERs for range 1 : 10: some error", + }, + { + name: "success with empty result", + mockFn: func(mockChainGERReader *mocks.ChainGERReader) { + mockChainGERReader.EXPECT().GetRemovedGERsForRange(ctx, uint64(1), uint64(10)).Return([]*agglayertypes.RemovedGER{}, nil) + }, + expectedGERs: []*agglayertypes.RemovedGER{}, + }, + { + name: "success with single removed GER", + mockFn: func(mockChainGERReader *mocks.ChainGERReader) { + mockChainGERReader.EXPECT().GetRemovedGERsForRange(ctx, uint64(1), uint64(10)).Return([]*agglayertypes.RemovedGER{ + { + GlobalExitRoot: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12"), + BlockNumber: 5, + LogIndex: 2, + }, + }, nil) + }, + expectedGERs: []*agglayertypes.RemovedGER{ + { + GlobalExitRoot: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12"), + BlockNumber: 5, + LogIndex: 2, + }, + }, + }, + { + name: "success with multiple removed GERs", + mockFn: func(mockChainGERReader *mocks.ChainGERReader) { + mockChainGERReader.EXPECT().GetRemovedGERsForRange(ctx, uint64(1), uint64(10)).Return([]*agglayertypes.RemovedGER{ + { + GlobalExitRoot: common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111"), + BlockNumber: 3, + LogIndex: 0, + }, + { + GlobalExitRoot: common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222"), + BlockNumber: 7, + LogIndex: 1, + }, + { + GlobalExitRoot: common.HexToHash("0x3333333333333333333333333333333333333333333333333333333333333333"), + BlockNumber: 9, + LogIndex: 3, + }, + }, nil) + }, + expectedGERs: []*agglayertypes.RemovedGER{ + { + GlobalExitRoot: common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111"), + BlockNumber: 3, + LogIndex: 0, + }, + { + GlobalExitRoot: common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222"), + BlockNumber: 7, + LogIndex: 1, + }, + { + GlobalExitRoot: common.HexToHash("0x3333333333333333333333333333333333333333333333333333333333333333"), + BlockNumber: 9, + LogIndex: 3, + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + mockGERReader := mocks.NewChainGERReader(t) + mockL1InfoTreeQuery := mocks.NewL1InfoTreeDataQuerier(t) + gerQuerier := NewGERDataQuerier(mockL1InfoTreeQuery, mockGERReader) + + tc.mockFn(mockGERReader) + + removedGERs, err := gerQuerier.GetRemovedGERsForRange(ctx, 1, 10) + if tc.expectedError != "" { + require.ErrorContains(t, err, tc.expectedError) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectedGERs, removedGERs) + } + + mockGERReader.AssertExpectations(t) + }) + } +} diff --git a/aggsender/types/aggchain_proof_client_interface.go b/aggsender/types/aggchain_proof_client_interface.go index d630c9baa..612a0291f 100644 --- a/aggsender/types/aggchain_proof_client_interface.go +++ b/aggsender/types/aggchain_proof_client_interface.go @@ -23,6 +23,8 @@ type AggchainProofRequest struct { L1InfoTreeMerkleProof agglayer.MerkleProof GERLeavesWithBlockNumber map[common.Hash]*agglayer.ProvenInsertedGERWithBlockNumber ImportedBridgeExitsWithBlockNumber []*agglayer.ImportedBridgeExitWithBlockNumber + RemovedGERs []*agglayer.RemovedGER + Unclaims []*agglayer.Unclaim } func NewAggchainProofRequest( @@ -32,6 +34,8 @@ func NewAggchainProofRequest( l1InfoTreeMerkleProof agglayer.MerkleProof, gerLeavesWithBlockNumber map[common.Hash]*agglayer.ProvenInsertedGERWithBlockNumber, importedBridgeExitsWithBlockNumber []*agglayer.ImportedBridgeExitWithBlockNumber, + removedGers []*agglayer.RemovedGER, + unclaims []*agglayer.Unclaim, ) *AggchainProofRequest { return &AggchainProofRequest{ LastProvenBlock: lastProvenBlock, @@ -41,6 +45,8 @@ func NewAggchainProofRequest( L1InfoTreeMerkleProof: l1InfoTreeMerkleProof, GERLeavesWithBlockNumber: gerLeavesWithBlockNumber, ImportedBridgeExitsWithBlockNumber: importedBridgeExitsWithBlockNumber, + RemovedGERs: removedGers, + Unclaims: unclaims, } } @@ -57,10 +63,12 @@ func (r *AggchainProofRequest) String() string { *leaf: %+v, agglayertypes.MerkleProof{ Root: %s, - Proof: %+v, + Proof: %+v, }, injectedGERsProofs: %+v, - importedBridgeExits: %+v + importedBridgeExits: %+v, + removedGers: %+v, + unclaims: %+v, }`, r.LastProvenBlock, r.RequestedEndBlock, @@ -70,5 +78,7 @@ func (r *AggchainProofRequest) String() string { r.L1InfoTreeMerkleProof.Proof, r.GERLeavesWithBlockNumber, r.ImportedBridgeExitsWithBlockNumber, + r.RemovedGERs, + r.Unclaims, ) } diff --git a/aggsender/types/aggchain_proof_client_interface_test.go b/aggsender/types/aggchain_proof_client_interface_test.go new file mode 100644 index 000000000..7f8bf54c6 --- /dev/null +++ b/aggsender/types/aggchain_proof_client_interface_test.go @@ -0,0 +1,444 @@ +package types + +import ( + "testing" + + agglayer "github.com/agglayer/aggkit/agglayer/types" + "github.com/agglayer/aggkit/l1infotreesync" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +// createTestImportedBridgeExitWithBlockNumber creates a test ImportedBridgeExitWithBlockNumber for testing +func createTestImportedBridgeExitWithBlockNumber(t *testing.T) []*agglayer.ImportedBridgeExitWithBlockNumber { + t.Helper() + + return []*agglayer.ImportedBridgeExitWithBlockNumber{ + { + BlockNumber: 170, + ImportedBridgeExit: &agglayer.ImportedBridgeExit{ + BridgeExit: &agglayer.BridgeExit{ + LeafType: agglayer.LeafTypeAsset, + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: 1, + OriginTokenAddress: common.HexToAddress("0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"), + }, + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"), + Amount: nil, + Metadata: []byte{0x01, 0x02, 0x03}, + }, + ClaimData: &agglayer.ClaimFromMainnet{ + ProofLeafMER: &agglayer.MerkleProof{ + Root: common.HexToHash("0x1010101010101010"), + Proof: [32]common.Hash{common.HexToHash("0x2020202020202020")}, + }, + ProofGERToL1Root: &agglayer.MerkleProof{ + Root: common.HexToHash("0x3030303030303030"), + Proof: [32]common.Hash{common.HexToHash("0x4040404040404040")}, + }, + L1Leaf: &agglayer.L1InfoTreeLeaf{ + L1InfoTreeIndex: 7, + RollupExitRoot: common.HexToHash("0x5050505050505050"), + MainnetExitRoot: common.HexToHash("0x6060606060606060"), + Inner: &agglayer.L1InfoTreeLeafInner{ + GlobalExitRoot: common.HexToHash("0x7070707070707070"), + BlockHash: common.HexToHash("0x8080808080808080"), + Timestamp: 1234567892, + }, + }, + }, + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: true, + RollupIndex: 1, + LeafIndex: 10, + }, + }, + }, + } +} + +func TestNewAggchainProofRequest(t *testing.T) { + tests := []struct { + name string + lastProvenBlock uint64 + requestedEndBlock uint64 + l1InfoTreeRootHash common.Hash + l1InfoTreeLeaf l1infotreesync.L1InfoTreeLeaf + l1InfoTreeMerkleProof agglayer.MerkleProof + gerLeavesWithBlockNumber map[common.Hash]*agglayer.ProvenInsertedGERWithBlockNumber + importedBridgeExitsWithBlockNumber []*agglayer.ImportedBridgeExitWithBlockNumber + removedGers []*agglayer.RemovedGER + unclaims []*agglayer.Unclaim + expectedLastProvenBlock uint64 + expectedRequestedEndBlock uint64 + expectedL1InfoTreeRootHash common.Hash + expectedL1InfoTreeLeaf l1infotreesync.L1InfoTreeLeaf + expectedL1InfoTreeMerkleProof agglayer.MerkleProof + expectedGERLeavesWithBlockNumber map[common.Hash]*agglayer.ProvenInsertedGERWithBlockNumber + expectedImportedBridgeExitsWithBlockNumber []*agglayer.ImportedBridgeExitWithBlockNumber + expectedRemovedGers []*agglayer.RemovedGER + expectedUnclaims []*agglayer.Unclaim + }{ + { + name: "EmptyRequest", + lastProvenBlock: 0, + requestedEndBlock: 0, + l1InfoTreeRootHash: common.Hash{}, + l1InfoTreeLeaf: l1infotreesync.L1InfoTreeLeaf{}, + l1InfoTreeMerkleProof: agglayer.MerkleProof{}, + gerLeavesWithBlockNumber: nil, + importedBridgeExitsWithBlockNumber: nil, + removedGers: nil, + unclaims: nil, + expectedLastProvenBlock: 0, + expectedRequestedEndBlock: 0, + expectedL1InfoTreeRootHash: common.Hash{}, + expectedL1InfoTreeLeaf: l1infotreesync.L1InfoTreeLeaf{}, + expectedL1InfoTreeMerkleProof: agglayer.MerkleProof{}, + expectedGERLeavesWithBlockNumber: nil, + expectedImportedBridgeExitsWithBlockNumber: nil, + expectedRemovedGers: nil, + expectedUnclaims: nil, + }, + { + name: "BasicRequest", + lastProvenBlock: 100, + requestedEndBlock: 200, + l1InfoTreeRootHash: common.HexToHash("0x1234567890abcdef"), + l1InfoTreeLeaf: l1infotreesync.L1InfoTreeLeaf{ + BlockNumber: 150, + BlockPosition: 1, + L1InfoTreeIndex: 5, + PreviousBlockHash: common.HexToHash("0xabcdef1234567890"), + Timestamp: 1234567890, + MainnetExitRoot: common.HexToHash("0x1111111111111111"), + RollupExitRoot: common.HexToHash("0x2222222222222222"), + GlobalExitRoot: common.HexToHash("0x3333333333333333"), + Hash: common.HexToHash("0x4444444444444444"), + }, + l1InfoTreeMerkleProof: agglayer.MerkleProof{ + Root: common.HexToHash("0x5555555555555555"), + Proof: [32]common.Hash{common.HexToHash("0x6666666666666666")}, + }, + gerLeavesWithBlockNumber: map[common.Hash]*agglayer.ProvenInsertedGERWithBlockNumber{ + common.HexToHash("0x7777777777777777"): { + BlockNumber: 160, + ProvenInsertedGERLeaf: agglayer.ProvenInsertedGER{ + ProofGERToL1Root: &agglayer.MerkleProof{ + Root: common.HexToHash("0x8888888888888888"), + Proof: [32]common.Hash{common.HexToHash("0x9999999999999999")}, + }, + L1Leaf: &agglayer.L1InfoTreeLeaf{ + L1InfoTreeIndex: 6, + RollupExitRoot: common.HexToHash("0xaaaaaaaaaaaaaaaa"), + MainnetExitRoot: common.HexToHash("0xbbbbbbbbbbbbbbbb"), + Inner: &agglayer.L1InfoTreeLeafInner{ + GlobalExitRoot: common.HexToHash("0xcccccccccccccccc"), + BlockHash: common.HexToHash("0xdddddddddddddddd"), + Timestamp: 1234567891, + }, + }, + }, + LogIndex: 2, + }, + }, + importedBridgeExitsWithBlockNumber: createTestImportedBridgeExitWithBlockNumber(t), + removedGers: []*agglayer.RemovedGER{ + { + GlobalExitRoot: common.HexToHash("0x9090909090909090"), + BlockNumber: 180, + LogIndex: 3, + }, + }, + unclaims: []*agglayer.Unclaim{ + { + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 2, + LeafIndex: 20, + }, + BlockNumber: 190, + LogIndex: 4, + }, + }, + expectedLastProvenBlock: 100, + expectedRequestedEndBlock: 200, + expectedL1InfoTreeRootHash: common.HexToHash("0x1234567890abcdef"), + expectedL1InfoTreeLeaf: l1infotreesync.L1InfoTreeLeaf{ + BlockNumber: 150, + BlockPosition: 1, + L1InfoTreeIndex: 5, + PreviousBlockHash: common.HexToHash("0xabcdef1234567890"), + Timestamp: 1234567890, + MainnetExitRoot: common.HexToHash("0x1111111111111111"), + RollupExitRoot: common.HexToHash("0x2222222222222222"), + GlobalExitRoot: common.HexToHash("0x3333333333333333"), + Hash: common.HexToHash("0x4444444444444444"), + }, + expectedL1InfoTreeMerkleProof: agglayer.MerkleProof{ + Root: common.HexToHash("0x5555555555555555"), + Proof: [32]common.Hash{common.HexToHash("0x6666666666666666")}, + }, + expectedGERLeavesWithBlockNumber: map[common.Hash]*agglayer.ProvenInsertedGERWithBlockNumber{ + common.HexToHash("0x7777777777777777"): { + BlockNumber: 160, + ProvenInsertedGERLeaf: agglayer.ProvenInsertedGER{ + ProofGERToL1Root: &agglayer.MerkleProof{ + Root: common.HexToHash("0x8888888888888888"), + Proof: [32]common.Hash{common.HexToHash("0x9999999999999999")}, + }, + L1Leaf: &agglayer.L1InfoTreeLeaf{ + L1InfoTreeIndex: 6, + RollupExitRoot: common.HexToHash("0xaaaaaaaaaaaaaaaa"), + MainnetExitRoot: common.HexToHash("0xbbbbbbbbbbbbbbbb"), + Inner: &agglayer.L1InfoTreeLeafInner{ + GlobalExitRoot: common.HexToHash("0xcccccccccccccccc"), + BlockHash: common.HexToHash("0xdddddddddddddddd"), + Timestamp: 1234567891, + }, + }, + }, + LogIndex: 2, + }, + }, + expectedImportedBridgeExitsWithBlockNumber: createTestImportedBridgeExitWithBlockNumber(t), + expectedRemovedGers: []*agglayer.RemovedGER{ + { + GlobalExitRoot: common.HexToHash("0x9090909090909090"), + BlockNumber: 180, + LogIndex: 3, + }, + }, + expectedUnclaims: []*agglayer.Unclaim{ + { + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 2, + LeafIndex: 20, + }, + BlockNumber: 190, + LogIndex: 4, + }, + }, + }, + { + name: "EmptySlicesAndMaps", + lastProvenBlock: 50, + requestedEndBlock: 100, + l1InfoTreeRootHash: common.HexToHash("0x0000000000000001"), + l1InfoTreeLeaf: l1infotreesync.L1InfoTreeLeaf{ + BlockNumber: 75, + }, + l1InfoTreeMerkleProof: agglayer.MerkleProof{ + Root: common.HexToHash("0x0000000000000002"), + }, + gerLeavesWithBlockNumber: make(map[common.Hash]*agglayer.ProvenInsertedGERWithBlockNumber), + importedBridgeExitsWithBlockNumber: make([]*agglayer.ImportedBridgeExitWithBlockNumber, 0), + removedGers: make([]*agglayer.RemovedGER, 0), + unclaims: make([]*agglayer.Unclaim, 0), + expectedLastProvenBlock: 50, + expectedRequestedEndBlock: 100, + expectedL1InfoTreeRootHash: common.HexToHash("0x0000000000000001"), + expectedL1InfoTreeLeaf: l1infotreesync.L1InfoTreeLeaf{ + BlockNumber: 75, + }, + expectedL1InfoTreeMerkleProof: agglayer.MerkleProof{ + Root: common.HexToHash("0x0000000000000002"), + }, + expectedGERLeavesWithBlockNumber: make(map[common.Hash]*agglayer.ProvenInsertedGERWithBlockNumber), + expectedImportedBridgeExitsWithBlockNumber: make([]*agglayer.ImportedBridgeExitWithBlockNumber, 0), + expectedRemovedGers: make([]*agglayer.RemovedGER, 0), + expectedUnclaims: make([]*agglayer.Unclaim, 0), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := NewAggchainProofRequest( + tt.lastProvenBlock, + tt.requestedEndBlock, + tt.l1InfoTreeRootHash, + tt.l1InfoTreeLeaf, + tt.l1InfoTreeMerkleProof, + tt.gerLeavesWithBlockNumber, + tt.importedBridgeExitsWithBlockNumber, + tt.removedGers, + tt.unclaims, + ) + + require.NotNil(t, req) + require.Equal(t, tt.expectedLastProvenBlock, req.LastProvenBlock) + require.Equal(t, tt.expectedRequestedEndBlock, req.RequestedEndBlock) + require.Equal(t, tt.expectedL1InfoTreeRootHash, req.L1InfoTreeRootHash) + require.Equal(t, tt.expectedL1InfoTreeLeaf, req.L1InfoTreeLeaf) + require.Equal(t, tt.expectedL1InfoTreeMerkleProof, req.L1InfoTreeMerkleProof) + require.Equal(t, tt.expectedGERLeavesWithBlockNumber, req.GERLeavesWithBlockNumber) + require.Equal(t, tt.expectedImportedBridgeExitsWithBlockNumber, req.ImportedBridgeExitsWithBlockNumber) + require.Equal(t, tt.expectedRemovedGers, req.RemovedGERs) + require.Equal(t, tt.expectedUnclaims, req.Unclaims) + }) + } +} + +func TestAggchainProofRequest_String(t *testing.T) { + tests := []struct { + name string + request *AggchainProofRequest + expected string + }{ + { + name: "NilRequest", + request: nil, + expected: "", + }, + { + name: "RequestWithData", + request: &AggchainProofRequest{ + LastProvenBlock: 100, + RequestedEndBlock: 200, + L1InfoTreeRootHash: common.HexToHash("0x1234567890abcdef"), + L1InfoTreeLeaf: l1infotreesync.L1InfoTreeLeaf{ + BlockNumber: 150, + BlockPosition: 1, + L1InfoTreeIndex: 5, + PreviousBlockHash: common.HexToHash("0xabcdef1234567890"), + Timestamp: 1234567890, + MainnetExitRoot: common.HexToHash("0x1111111111111111"), + RollupExitRoot: common.HexToHash("0x2222222222222222"), + GlobalExitRoot: common.HexToHash("0x3333333333333333"), + Hash: common.HexToHash("0x4444444444444444"), + }, + L1InfoTreeMerkleProof: agglayer.MerkleProof{ + Root: common.HexToHash("0x5555555555555555"), + Proof: [32]common.Hash{common.HexToHash("0x6666666666666666")}, + }, + GERLeavesWithBlockNumber: map[common.Hash]*agglayer.ProvenInsertedGERWithBlockNumber{ + common.HexToHash("0x7777777777777777"): { + BlockNumber: 160, + ProvenInsertedGERLeaf: agglayer.ProvenInsertedGER{ + ProofGERToL1Root: &agglayer.MerkleProof{ + Root: common.HexToHash("0x8888888888888888"), + Proof: [32]common.Hash{common.HexToHash("0x9999999999999999")}, + }, + L1Leaf: &agglayer.L1InfoTreeLeaf{ + L1InfoTreeIndex: 6, + RollupExitRoot: common.HexToHash("0xaaaaaaaaaaaaaaaa"), + MainnetExitRoot: common.HexToHash("0xbbbbbbbbbbbbbbbb"), + Inner: &agglayer.L1InfoTreeLeafInner{ + GlobalExitRoot: common.HexToHash("0xcccccccccccccccc"), + BlockHash: common.HexToHash("0xdddddddddddddddd"), + Timestamp: 1234567891, + }, + }, + }, + LogIndex: 2, + }, + }, + ImportedBridgeExitsWithBlockNumber: createTestImportedBridgeExitWithBlockNumber(t), + RemovedGERs: []*agglayer.RemovedGER{ + { + GlobalExitRoot: common.HexToHash("0x9090909090909090"), + BlockNumber: 180, + LogIndex: 3, + }, + }, + Unclaims: []*agglayer.Unclaim{ + { + GlobalIndex: &agglayer.GlobalIndex{ + MainnetFlag: false, + RollupIndex: 2, + LeafIndex: 20, + }, + BlockNumber: 190, + LogIndex: 4, + }, + }, + }, + expected: `AggchainProofRequest{ + lastProvenBlock: 100, + toBlock: 200, + root.Hash: 0x0000000000000000000000000000000000000000000000001234567890abcdef, + *leaf: {BlockNumber:150 BlockPosition:1 L1InfoTreeIndex:5 PreviousBlockHash:0x000000000000000000000000000000000000000000000000abcdef1234567890 Timestamp:1234567890 MainnetExitRoot:0x0000000000000000000000000000000000000000000000001111111111111111 RollupExitRoot:0x0000000000000000000000000000000000000000000000002222222222222222 GlobalExitRoot:0x0000000000000000000000000000000000000000000000003333333333333333 Hash:0x0000000000000000000000000000000000000000000000004444444444444444}, + agglayertypes.MerkleProof{ + Root: 0x0000000000000000000000000000000000000000000000005555555555555555, + Proof: [0x0000000000000000000000000000000000000000000000006666666666666666 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000], + }, + injectedGERsProofs: map[0x0000000000000000000000000000000000000000000000007777777777777777:0x140001b3380], + importedBridgeExits: [BlockNumber: 170, ImportedBridgeExit: BridgeExit: LeafType: Transfer, DestinationNetwork: 2, DestinationAddress: 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF, Amount: , Metadata: 010203, TokenInfo: OriginNetwork: 1, OriginTokenAddress: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, GlobalIndex: MainnetFlag: true, RollupIndex: 1, LeafIndex: 10ClaimData: ProofLeafMER: Root: 0x0000000000000000000000000000000000000000000000001010101010101010, Proof: [0x0000000000000000000000000000000000000000000000002020202020202020 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000], ProofGERToL1Root: Root: 0x0000000000000000000000000000000000000000000000003030303030303030, Proof: [0x0000000000000000000000000000000000000000000000004040404040404040 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000], L1Leaf: L1InfoTreeIndex: 7, RollupExitRoot: 0x0000000000000000000000000000000000000000000000005050505050505050, MainnetExitRoot: 0x0000000000000000000000000000000000000000000000006060606060606060, Inner: GlobalExitRoot: 0x0000000000000000000000000000000000000000000000007070707070707070, BlockHash: 0x0000000000000000000000000000000000000000000000008080808080808080, Timestamp: 1234567892], + removedGers: [0x140001ef530], + unclaims: [0x1400000f308], + }`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result string + if tt.request != nil { + result = tt.request.String() + } + + // For the complex test case, we need to check that the string contains the expected parts + // rather than exact match due to pointer addresses in the output + if tt.name == "RequestWithData" { + require.Contains(t, result, "lastProvenBlock: 100") + require.Contains(t, result, "toBlock: 200") + require.Contains(t, result, "root.Hash: 0x0000000000000000000000000000000000000000000000001234567890abcdef") + require.Contains(t, result, "BlockNumber:150") + require.Contains(t, result, "L1InfoTreeIndex:5") + require.Contains(t, result, "Root: 0x0000000000000000000000000000000000000000000000005555555555555555,") + require.Contains(t, result, "injectedGERsProofs: map[") + require.Contains(t, result, "importedBridgeExits: [") + require.Contains(t, result, "removedGers: [") + require.Contains(t, result, "unclaims: [") + } else { + require.Equal(t, tt.expected, result) + } + }) + } +} + +func TestAggchainProofRequest_String_EdgeCases(t *testing.T) { + t.Run("EmptyImportedBridgeExits", func(t *testing.T) { + req := &AggchainProofRequest{ + LastProvenBlock: 100, + RequestedEndBlock: 200, + L1InfoTreeRootHash: common.HexToHash("0x1234567890abcdef"), + L1InfoTreeLeaf: l1infotreesync.L1InfoTreeLeaf{}, + L1InfoTreeMerkleProof: agglayer.MerkleProof{}, + GERLeavesWithBlockNumber: make(map[common.Hash]*agglayer.ProvenInsertedGERWithBlockNumber), + ImportedBridgeExitsWithBlockNumber: make([]*agglayer.ImportedBridgeExitWithBlockNumber, 0), + RemovedGERs: make([]*agglayer.RemovedGER, 0), + Unclaims: make([]*agglayer.Unclaim, 0), + } + + result := req.String() + require.Contains(t, result, "lastProvenBlock: 100") + require.Contains(t, result, "toBlock: 200") + require.Contains(t, result, "importedBridgeExits: []") + require.Contains(t, result, "removedGers: []") + require.Contains(t, result, "unclaims: []") + }) + + t.Run("NilImportedBridgeExits", func(t *testing.T) { + req := &AggchainProofRequest{ + LastProvenBlock: 100, + RequestedEndBlock: 200, + L1InfoTreeRootHash: common.HexToHash("0x1234567890abcdef"), + L1InfoTreeLeaf: l1infotreesync.L1InfoTreeLeaf{}, + L1InfoTreeMerkleProof: agglayer.MerkleProof{}, + GERLeavesWithBlockNumber: nil, + ImportedBridgeExitsWithBlockNumber: nil, + RemovedGERs: nil, + Unclaims: nil, + } + + result := req.String() + require.Contains(t, result, "lastProvenBlock: 100") + require.Contains(t, result, "toBlock: 200") + require.Contains(t, result, "importedBridgeExits: []") + require.Contains(t, result, "removedGers: []") + require.Contains(t, result, "unclaims: []") + }) +} diff --git a/aggsender/types/certificate_build_params.go b/aggsender/types/certificate_build_params.go index ff3822b41..56e014c5e 100644 --- a/aggsender/types/certificate_build_params.go +++ b/aggsender/types/certificate_build_params.go @@ -6,6 +6,7 @@ import ( agglayertypes "github.com/agglayer/aggkit/agglayer/types" "github.com/agglayer/aggkit/bridgesync" + bridgesynctypes "github.com/agglayer/aggkit/bridgesync/types" aggkitcommon "github.com/agglayer/aggkit/common" "github.com/ethereum/go-ethereum/common" ) @@ -52,6 +53,7 @@ type CertificateBuildParams struct { ToBlock uint64 Bridges []bridgesync.Bridge Claims []bridgesync.Claim + Unclaims []bridgesynctypes.Unclaim CreatedAt uint32 RetryCount int LastSentCertificate *CertificateHeader @@ -63,8 +65,10 @@ type CertificateBuildParams struct { } func (c *CertificateBuildParams) String() string { - return fmt.Sprintf("Type: %s FromBlock: %d, ToBlock: %d, numBridges: %d, numClaims: %d, createdAt: %d", - c.CertificateType, c.FromBlock, c.ToBlock, c.NumberOfBridges(), c.NumberOfClaims(), c.CreatedAt) + return fmt.Sprintf( + "Type: %s FromBlock: %d, ToBlock: %d, numBridges: %d, "+ + "numClaims: %d, numUnclaims: %d, createdAt: %d", + c.CertificateType, c.FromBlock, c.ToBlock, c.NumberOfBridges(), c.NumberOfClaims(), c.NumberOfUnclaims(), c.CreatedAt) } // Range create a new CertificateBuildParams with the given range @@ -92,6 +96,8 @@ func (c *CertificateBuildParams) Range(fromBlock, toBlock uint64) (*CertificateB aggkitcommon.EstimateSliceCapacity(len(c.Bridges), span, fullSpan)), Claims: make([]bridgesync.Claim, 0, aggkitcommon.EstimateSliceCapacity(len(c.Claims), span, fullSpan)), + Unclaims: make([]bridgesynctypes.Unclaim, 0, + aggkitcommon.EstimateSliceCapacity(len(c.Unclaims), span, fullSpan)), CreatedAt: c.CreatedAt, RetryCount: c.RetryCount, LastSentCertificate: c.LastSentCertificate, @@ -112,6 +118,12 @@ func (c *CertificateBuildParams) Range(fromBlock, toBlock uint64) (*CertificateB newCert.Claims = append(newCert.Claims, claim) } } + + for _, unclaim := range c.Unclaims { + if unclaim.BlockNumber >= fromBlock && unclaim.BlockNumber <= toBlock { + newCert.Unclaims = append(newCert.Unclaims, unclaim) + } + } return newCert, nil } @@ -131,6 +143,14 @@ func (c *CertificateBuildParams) NumberOfClaims() int { return len(c.Claims) } +// NumberOfUnclaims returns the number of unclaims in the certificate +func (c *CertificateBuildParams) NumberOfUnclaims() int { + if c == nil { + return 0 + } + return len(c.Unclaims) +} + // NumberOfBlocks returns the number of blocks in the certificate func (c *CertificateBuildParams) NumberOfBlocks() int { if c == nil { diff --git a/aggsender/types/certificate_build_params_test.go b/aggsender/types/certificate_build_params_test.go index fab91dfd9..c9fb817ca 100644 --- a/aggsender/types/certificate_build_params_test.go +++ b/aggsender/types/certificate_build_params_test.go @@ -4,6 +4,8 @@ import ( "math" "testing" + "github.com/agglayer/aggkit/bridgesync" + bridgesynctypes "github.com/agglayer/aggkit/bridgesync/types" "github.com/stretchr/testify/require" ) @@ -61,3 +63,102 @@ func TestNumberOfBlocks(t *testing.T) { }) } } + +func TestCertificateBuildParamsString(t *testing.T) { + tests := []struct { + name string + params *CertificateBuildParams + expected string + }{ + { + name: "Empty certificate", + params: &CertificateBuildParams{ + CertificateType: CertificateTypePP, + FromBlock: 100, + ToBlock: 200, + Bridges: []bridgesync.Bridge{}, + Claims: []bridgesync.Claim{}, + Unclaims: []bridgesynctypes.Unclaim{}, + CreatedAt: 1234567890, + }, + expected: "Type: pp FromBlock: 100, ToBlock: 200, numBridges: 0, numClaims: 0, numUnclaims: 0, createdAt: 1234567890", + }, + { + name: "Certificate with bridges, claims, and unclaims", + params: &CertificateBuildParams{ + CertificateType: CertificateTypeFEP, + FromBlock: 1000, + ToBlock: 2000, + Bridges: []bridgesync.Bridge{ + {BlockNum: 1001}, + {BlockNum: 1002}, + }, + Claims: []bridgesync.Claim{ + {BlockNum: 1500}, + }, + Unclaims: []bridgesynctypes.Unclaim{ + {BlockNumber: 1600}, + {BlockNumber: 1700}, + {BlockNumber: 1800}, + }, + CreatedAt: 987654321, + }, + expected: "Type: fep FromBlock: 1000, ToBlock: 2000, numBridges: 2, numClaims: 1, numUnclaims: 3, createdAt: 987654321", + }, + { + name: "Optimistic certificate type", + params: &CertificateBuildParams{ + CertificateType: CertificateTypeOptimistic, + FromBlock: 500, + ToBlock: 600, + Bridges: []bridgesync.Bridge{ + {BlockNum: 550}, + }, + Claims: []bridgesync.Claim{}, + Unclaims: []bridgesynctypes.Unclaim{}, + CreatedAt: 1111111111, + }, + expected: "Type: optimistic FromBlock: 500, ToBlock: 600, numBridges: 1, numClaims: 0, numUnclaims: 0, createdAt: 1111111111", + }, + { + name: "Unknown certificate type", + params: &CertificateBuildParams{ + CertificateType: CertificateTypeUnknown, + FromBlock: 1, + ToBlock: 10, + Bridges: []bridgesync.Bridge{}, + Claims: []bridgesync.Claim{}, + Unclaims: []bridgesynctypes.Unclaim{}, + CreatedAt: 0, + }, + expected: "Type: FromBlock: 1, ToBlock: 10, numBridges: 0, numClaims: 0, numUnclaims: 0, createdAt: 0", + }, + { + name: "Large numbers", + params: &CertificateBuildParams{ + CertificateType: CertificateTypePP, + FromBlock: 999999999, + ToBlock: 9999999999, + Bridges: []bridgesync.Bridge{ + {BlockNum: 1000000000}, + {BlockNum: 2000000000}, + {BlockNum: 3000000000}, + }, + Claims: []bridgesync.Claim{ + {BlockNum: 4000000000}, + {BlockNum: 5000000000}, + }, + Unclaims: []bridgesynctypes.Unclaim{}, + CreatedAt: 4294967295, + }, + expected: "Type: pp FromBlock: 999999999, ToBlock: 9999999999, numBridges: 3, numClaims: 2, numUnclaims: 0, createdAt: 4294967295", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.params.String() + require.Equal(t, tt.expected, result) + }) + } +} diff --git a/aggsender/types/interfaces.go b/aggsender/types/interfaces.go index 41273610a..9cceda373 100644 --- a/aggsender/types/interfaces.go +++ b/aggsender/types/interfaces.go @@ -9,6 +9,7 @@ import ( "github.com/0xPolygon/cdk-contracts-tooling/contracts/aggchain-multisig/agglayermanager" agglayertypes "github.com/agglayer/aggkit/agglayer/types" "github.com/agglayer/aggkit/bridgesync" + bridgesynctypes "github.com/agglayer/aggkit/bridgesync/types" "github.com/agglayer/aggkit/l1infotreesync" "github.com/agglayer/aggkit/l2gersync" treetypes "github.com/agglayer/aggkit/tree/types" @@ -112,12 +113,22 @@ type BridgeQuerier interface { GetLastProcessedBlock(ctx context.Context) (uint64, error) OriginNetwork() uint32 WaitForSyncerToCatchUp(ctx context.Context, block uint64) error + GetUnsetClaimsForBlockRange(ctx context.Context, + fromBlock, toBlock uint64) ([]bridgesynctypes.Unclaim, error) } // ChainGERReader is an interface defining functions that an ChainGERReader should implement type ChainGERReader interface { GetInjectedGERsForRange(ctx context.Context, fromBlock, toBlock uint64) (map[common.Hash]l2gersync.GlobalExitRootInfo, error) + GetRemovedGERsForRange(ctx context.Context, + fromBlock, toBlock uint64) ([]*agglayertypes.RemovedGER, error) +} + +// AgglayerBridgeL2Reader is an interface defining functions that an AgglayerBridgeL2Reader should implement +type AgglayerBridgeL2Reader interface { + GetUnsetClaimsForBlockRange(ctx context.Context, + fromBlock, toBlock uint64) ([]bridgesynctypes.Unclaim, error) } // L1InfoTreeDataQuerier is an interface defining functions that an L1InfoTreeDataQuerier should implement @@ -156,6 +167,8 @@ type GERQuerier interface { ctx context.Context, finalizedL1InfoTreeRoot *treetypes.Root, fromBlock, toBlock uint64) (map[common.Hash]*agglayertypes.ProvenInsertedGERWithBlockNumber, error) + GetRemovedGERsForRange(ctx context.Context, + fromBlock, toBlock uint64) ([]*agglayertypes.RemovedGER, error) } // Logger is an interface that defines the methods to log messages diff --git a/aggsender/validator/config.go b/aggsender/validator/config.go index 412b00bc9..70ef6b3f1 100644 --- a/aggsender/validator/config.go +++ b/aggsender/validator/config.go @@ -44,6 +44,8 @@ type Config struct { Mode aggsendertypes.AggsenderMode `jsonschema:"enum=PessimisticProof, enum=AggchainProof, enum=Auto" mapstructure:"Mode"` //nolint:lll // RequireCommitteeMembershipCheck indicates whether to check if the validator is part of the committee RequireCommitteeMembershipCheck bool `mapstructure:"RequireCommitteeMembershipCheck"` + // AgglayerBridgeL2Addr is the address of the bridge L2 sovereign contract on L2 sovereign chain + AgglayerBridgeL2Addr ethCommon.Address `mapstructure:"AgglayerBridgeL2Addr"` } type PPConfig struct { diff --git a/aggsender/validator/proto/v1/validator.pb.go b/aggsender/validator/proto/v1/validator.pb.go index 88bb2c687..36ac058a9 100644 --- a/aggsender/validator/proto/v1/validator.pb.go +++ b/aggsender/validator/proto/v1/validator.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.36.8 // protoc (unknown) // source: aggsender/validator/proto/v1/validator.proto @@ -14,6 +14,7 @@ import ( emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -25,22 +26,19 @@ const ( // Response Status() type HealthCheckResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` // Version of the validator + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` // Status of the validator + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` // Reason of the validator status unknownFields protoimpl.UnknownFields - - Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` // Version of the validator - Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` // Status of the validator - Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` // Reason of the validator status + sizeCache protoimpl.SizeCache } func (x *HealthCheckResponse) Reset() { *x = HealthCheckResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_aggsender_validator_proto_v1_validator_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_aggsender_validator_proto_v1_validator_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *HealthCheckResponse) String() string { @@ -51,7 +49,7 @@ func (*HealthCheckResponse) ProtoMessage() {} func (x *HealthCheckResponse) ProtoReflect() protoreflect.Message { mi := &file_aggsender_validator_proto_v1_validator_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -89,25 +87,22 @@ func (x *HealthCheckResponse) GetReason() string { // Request to validate a certificate type ValidateCertificateRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Previous certificate identifier PreviousCertificateId *v1.CertificateId `protobuf:"bytes,1,opt,name=previous_certificate_id,json=previousCertificateId,proto3" json:"previous_certificate_id,omitempty"` // Certificate to be validated Certificate *v1.Certificate `protobuf:"bytes,2,opt,name=certificate,proto3" json:"certificate,omitempty"` // Last L2 block number included in the certificate LastL2BlockInCert uint64 `protobuf:"varint,3,opt,name=last_l2_block_in_cert,json=lastL2BlockInCert,proto3" json:"last_l2_block_in_cert,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ValidateCertificateRequest) Reset() { *x = ValidateCertificateRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_aggsender_validator_proto_v1_validator_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_aggsender_validator_proto_v1_validator_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ValidateCertificateRequest) String() string { @@ -118,7 +113,7 @@ func (*ValidateCertificateRequest) ProtoMessage() {} func (x *ValidateCertificateRequest) ProtoReflect() protoreflect.Message { mi := &file_aggsender_validator_proto_v1_validator_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -156,21 +151,18 @@ func (x *ValidateCertificateRequest) GetLastL2BlockInCert() uint64 { // Type used as response to a certificate validation request. type ValidateCertificateResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // The signature provided by the aggsender validator. - Signature *v11.FixedBytes65 `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` + Signature *v11.FixedBytes65 `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ValidateCertificateResponse) Reset() { *x = ValidateCertificateResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_aggsender_validator_proto_v1_validator_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_aggsender_validator_proto_v1_validator_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ValidateCertificateResponse) String() string { @@ -181,7 +173,7 @@ func (*ValidateCertificateResponse) ProtoMessage() {} func (x *ValidateCertificateResponse) ProtoReflect() protoreflect.Message { mi := &file_aggsender_validator_proto_v1_validator_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -205,86 +197,37 @@ func (x *ValidateCertificateResponse) GetSignature() *v11.FixedBytes65 { var File_aggsender_validator_proto_v1_validator_proto protoreflect.FileDescriptor -var file_aggsender_validator_proto_v1_validator_proto_rawDesc = []byte{ - 0x0a, 0x2c, 0x61, 0x67, 0x67, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x2f, 0x76, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1d, - 0x61, 0x67, 0x67, 0x6b, 0x69, 0x74, 0x2e, 0x61, 0x67, 0x67, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, - 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x1a, 0x28, 0x61, - 0x67, 0x67, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2b, 0x61, 0x67, 0x67, 0x6c, 0x61, 0x79, 0x65, - 0x72, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, - 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x25, 0x61, 0x67, 0x67, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2f, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6f, 0x70, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, - 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, - 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5f, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xf4, 0x01, 0x0a, 0x1a, 0x56, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x17, 0x70, 0x72, 0x65, 0x76, - 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x61, 0x67, 0x67, 0x6c, - 0x61, 0x79, 0x65, 0x72, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x64, - 0x52, 0x15, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x45, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x61, - 0x67, 0x67, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x30, - 0x0a, 0x15, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6c, 0x32, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, - 0x69, 0x6e, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x6c, - 0x61, 0x73, 0x74, 0x4c, 0x32, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x43, 0x65, 0x72, 0x74, - 0x22, 0x64, 0x0a, 0x1b, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x45, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x61, 0x67, 0x67, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2e, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6f, 0x70, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x46, - 0x69, 0x78, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x36, 0x35, 0x52, 0x09, 0x73, 0x69, 0x67, - 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x32, 0xfe, 0x01, 0x0a, 0x12, 0x41, 0x67, 0x67, 0x73, 0x65, - 0x6e, 0x64, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x59, 0x0a, - 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x32, 0x2e, 0x61, 0x67, 0x67, 0x6b, 0x69, 0x74, 0x2e, 0x61, 0x67, - 0x67, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x13, 0x56, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x12, 0x39, 0x2e, 0x61, 0x67, 0x67, 0x6b, 0x69, 0x74, 0x2e, 0x61, 0x67, 0x67, 0x73, 0x65, 0x6e, - 0x64, 0x65, 0x72, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x61, 0x67, - 0x67, 0x6b, 0x69, 0x74, 0x2e, 0x61, 0x67, 0x67, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x2e, 0x76, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, 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, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_aggsender_validator_proto_v1_validator_proto_rawDesc = "" + + "\n" + + ",aggsender/validator/proto/v1/validator.proto\x12\x1daggkit.aggsender.validator.v1\x1a(agglayer/node/types/v1/certificate.proto\x1a+agglayer/node/types/v1/certificate_id.proto\x1a%agglayer/interop/types/v1/bytes.proto\x1a\x1bgoogle/protobuf/empty.proto\"_\n" + + "\x13HealthCheckResponse\x12\x18\n" + + "\aversion\x18\x01 \x01(\tR\aversion\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12\x16\n" + + "\x06reason\x18\x03 \x01(\tR\x06reason\"\xf4\x01\n" + + "\x1aValidateCertificateRequest\x12]\n" + + "\x17previous_certificate_id\x18\x01 \x01(\v2%.agglayer.node.types.v1.CertificateIdR\x15previousCertificateId\x12E\n" + + "\vcertificate\x18\x02 \x01(\v2#.agglayer.node.types.v1.CertificateR\vcertificate\x120\n" + + "\x15last_l2_block_in_cert\x18\x03 \x01(\x04R\x11lastL2BlockInCert\"d\n" + + "\x1bValidateCertificateResponse\x12E\n" + + "\tsignature\x18\x01 \x01(\v2'.agglayer.interop.types.v1.FixedBytes65R\tsignature2\xfe\x01\n" + + "\x12AggsenderValidator\x12Y\n" + + "\vHealthCheck\x12\x16.google.protobuf.Empty\x1a2.aggkit.aggsender.validator.v1.HealthCheckResponse\x12\x8c\x01\n" + + "\x13ValidateCertificate\x129.aggkit.aggsender.validator.v1.ValidateCertificateRequest\x1a:.aggkit.aggsender.validator.v1.ValidateCertificateResponseB9Z7github.com/agglayer/aggkit/aggsender/validator/proto/v1b\x06proto3" var ( file_aggsender_validator_proto_v1_validator_proto_rawDescOnce sync.Once - file_aggsender_validator_proto_v1_validator_proto_rawDescData = file_aggsender_validator_proto_v1_validator_proto_rawDesc + file_aggsender_validator_proto_v1_validator_proto_rawDescData []byte ) func file_aggsender_validator_proto_v1_validator_proto_rawDescGZIP() []byte { file_aggsender_validator_proto_v1_validator_proto_rawDescOnce.Do(func() { - file_aggsender_validator_proto_v1_validator_proto_rawDescData = protoimpl.X.CompressGZIP(file_aggsender_validator_proto_v1_validator_proto_rawDescData) + file_aggsender_validator_proto_v1_validator_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_aggsender_validator_proto_v1_validator_proto_rawDesc), len(file_aggsender_validator_proto_v1_validator_proto_rawDesc))) }) return file_aggsender_validator_proto_v1_validator_proto_rawDescData } var file_aggsender_validator_proto_v1_validator_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_aggsender_validator_proto_v1_validator_proto_goTypes = []interface{}{ +var file_aggsender_validator_proto_v1_validator_proto_goTypes = []any{ (*HealthCheckResponse)(nil), // 0: aggkit.aggsender.validator.v1.HealthCheckResponse (*ValidateCertificateRequest)(nil), // 1: aggkit.aggsender.validator.v1.ValidateCertificateRequest (*ValidateCertificateResponse)(nil), // 2: aggkit.aggsender.validator.v1.ValidateCertificateResponse @@ -313,49 +256,11 @@ func file_aggsender_validator_proto_v1_validator_proto_init() { if File_aggsender_validator_proto_v1_validator_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_aggsender_validator_proto_v1_validator_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HealthCheckResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_aggsender_validator_proto_v1_validator_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ValidateCertificateRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_aggsender_validator_proto_v1_validator_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ValidateCertificateResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_aggsender_validator_proto_v1_validator_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_aggsender_validator_proto_v1_validator_proto_rawDesc), len(file_aggsender_validator_proto_v1_validator_proto_rawDesc)), NumEnums: 0, NumMessages: 3, NumExtensions: 0, @@ -366,7 +271,6 @@ func file_aggsender_validator_proto_v1_validator_proto_init() { MessageInfos: file_aggsender_validator_proto_v1_validator_proto_msgTypes, }.Build() File_aggsender_validator_proto_v1_validator_proto = out.File - file_aggsender_validator_proto_v1_validator_proto_rawDesc = nil file_aggsender_validator_proto_v1_validator_proto_goTypes = nil file_aggsender_validator_proto_v1_validator_proto_depIdxs = nil } diff --git a/aggsender/validator/proto/v1/validator_grpc.pb.go b/aggsender/validator/proto/v1/validator_grpc.pb.go index d8d3bf35e..08c857d96 100644 --- a/aggsender/validator/proto/v1/validator_grpc.pb.go +++ b/aggsender/validator/proto/v1/validator_grpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.3.0 +// - protoc-gen-go-grpc v1.5.1 // - protoc (unknown) // source: aggsender/validator/proto/v1/validator.proto @@ -16,8 +16,8 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 const ( AggsenderValidator_HealthCheck_FullMethodName = "/aggkit.aggsender.validator.v1.AggsenderValidator/HealthCheck" @@ -27,6 +27,8 @@ const ( // AggsenderValidatorClient is the client API for AggsenderValidator service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// Service for validating new certificates type AggsenderValidatorClient interface { // Method to get the status of the validator HealthCheck(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*HealthCheckResponse, error) @@ -43,8 +45,9 @@ func NewAggsenderValidatorClient(cc grpc.ClientConnInterface) AggsenderValidator } func (c *aggsenderValidatorClient) HealthCheck(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*HealthCheckResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(HealthCheckResponse) - err := c.cc.Invoke(ctx, AggsenderValidator_HealthCheck_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, AggsenderValidator_HealthCheck_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -52,8 +55,9 @@ func (c *aggsenderValidatorClient) HealthCheck(ctx context.Context, in *emptypb. } func (c *aggsenderValidatorClient) ValidateCertificate(ctx context.Context, in *ValidateCertificateRequest, opts ...grpc.CallOption) (*ValidateCertificateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ValidateCertificateResponse) - err := c.cc.Invoke(ctx, AggsenderValidator_ValidateCertificate_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, AggsenderValidator_ValidateCertificate_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -62,7 +66,9 @@ func (c *aggsenderValidatorClient) ValidateCertificate(ctx context.Context, in * // AggsenderValidatorServer is the server API for AggsenderValidator service. // All implementations must embed UnimplementedAggsenderValidatorServer -// for forward compatibility +// for forward compatibility. +// +// Service for validating new certificates type AggsenderValidatorServer interface { // Method to get the status of the validator HealthCheck(context.Context, *emptypb.Empty) (*HealthCheckResponse, error) @@ -71,9 +77,12 @@ type AggsenderValidatorServer interface { mustEmbedUnimplementedAggsenderValidatorServer() } -// UnimplementedAggsenderValidatorServer must be embedded to have forward compatible implementations. -type UnimplementedAggsenderValidatorServer struct { -} +// UnimplementedAggsenderValidatorServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAggsenderValidatorServer struct{} func (UnimplementedAggsenderValidatorServer) HealthCheck(context.Context, *emptypb.Empty) (*HealthCheckResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method HealthCheck not implemented") @@ -82,6 +91,7 @@ func (UnimplementedAggsenderValidatorServer) ValidateCertificate(context.Context return nil, status.Errorf(codes.Unimplemented, "method ValidateCertificate not implemented") } func (UnimplementedAggsenderValidatorServer) mustEmbedUnimplementedAggsenderValidatorServer() {} +func (UnimplementedAggsenderValidatorServer) testEmbeddedByValue() {} // UnsafeAggsenderValidatorServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to AggsenderValidatorServer will @@ -91,6 +101,13 @@ type UnsafeAggsenderValidatorServer interface { } func RegisterAggsenderValidatorServer(s grpc.ServiceRegistrar, srv AggsenderValidatorServer) { + // If the following call pancis, it indicates UnimplementedAggsenderValidatorServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&AggsenderValidator_ServiceDesc, srv) } diff --git a/bridgesync/agglayer_bridge_l2_reader.go b/bridgesync/agglayer_bridge_l2_reader.go new file mode 100644 index 000000000..595401a7e --- /dev/null +++ b/bridgesync/agglayer_bridge_l2_reader.go @@ -0,0 +1,89 @@ +package bridgesync + +import ( + "context" + "fmt" + "math/big" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/aggchain-multisig/agglayerbridgel2" + "github.com/agglayer/aggkit/bridgesync/types" + "github.com/agglayer/aggkit/log" + aggkittypes "github.com/agglayer/aggkit/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" +) + +// AgglayerBridgeL2Reader provides functionality to read and interact with the AggLayer Bridge L2 contract. +// It encapsulates the contract instance and provides methods to query bridge-related data from the L2 chain. +type AgglayerBridgeL2Reader struct { + agglayerBridgeL2 *agglayerbridgel2.Agglayerbridgel2 +} + +// NewAgglayerBridgeL2Reader creates a new instance of AgglayerBridgeL2Reader. +// It initializes the contract instance using the provided bridge address and L2 client. +// +// Parameters: +// - bridgeAddr: The Ethereum address of the AggLayer Bridge L2 contract +// - l2Client: The Ethereum client for interacting with the L2 chain +// +// Returns: +// - *AgglayerBridgeL2Reader: A new reader instance +// - error: Any error that occurred during contract initialization +func NewAgglayerBridgeL2Reader( + bridgeAddr common.Address, + l2Client aggkittypes.BaseEthereumClienter, +) (*AgglayerBridgeL2Reader, error) { + agglayerBridgeL2Contract, err := agglayerbridgel2.NewAgglayerbridgel2(bridgeAddr, l2Client) + if err != nil { + return nil, err + } + return &AgglayerBridgeL2Reader{agglayerBridgeL2: agglayerBridgeL2Contract}, nil +} + +// GetUnsetClaimsForBlockRange retrieves all unset claims (unclaims) within a specified block range. +// It filters the UpdatedUnsetGlobalIndexHashChain events from the bridge contract and converts them +// into Unclaim objects for further processing. +// +// Parameters: +// - ctx: Context for cancellation and timeout control +// - fromBlock: The starting block number for the search range (inclusive) +// - toBlock: The ending block number for the search range (inclusive) +// +// Returns: +// - []types.Unclaim: A slice of Unclaim objects containing global index, block number, and block index +// - error: Any error that occurred during the event filtering or iteration +func (r *AgglayerBridgeL2Reader) GetUnsetClaimsForBlockRange(ctx context.Context, + fromBlock, toBlock uint64) ([]types.Unclaim, error) { + if fromBlock > toBlock { + return nil, fmt.Errorf("invalid block range: fromBlock(%d) > toBlock(%d)", fromBlock, toBlock) + } + unclaimIterator, err := r.agglayerBridgeL2.FilterUpdatedUnsetGlobalIndexHashChain( + &bind.FilterOpts{Context: ctx, Start: fromBlock, End: &toBlock}) + if err != nil { + return nil, err + } + + defer func() { + if err := unclaimIterator.Close(); err != nil { + log.Errorf("failed to close UpdatedUnsetGlobalIndexHashChain iterator: %v", err) + } + }() + + unclaims := make([]types.Unclaim, 0) + for unclaimIterator.Next() { + globalIndex := unclaimIterator.Event.UnsetGlobalIndex + log.Infof("unset claim: %s at block %d, index %d", new(big.Int).SetBytes(globalIndex[:]), + unclaimIterator.Event.Raw.BlockNumber, unclaimIterator.Event.Raw.Index) + unclaims = append(unclaims, types.Unclaim{ + GlobalIndex: new(big.Int).SetBytes(globalIndex[:]), + BlockNumber: unclaimIterator.Event.Raw.BlockNumber, + LogIndex: uint64(unclaimIterator.Event.Raw.Index), + }) + } + + if unclaimIterator.Error() != nil { + return nil, unclaimIterator.Error() + } + + return unclaims, nil +} diff --git a/bridgesync/agglayer_bridge_l2_reader_test.go b/bridgesync/agglayer_bridge_l2_reader_test.go new file mode 100644 index 000000000..53f36bf87 --- /dev/null +++ b/bridgesync/agglayer_bridge_l2_reader_test.go @@ -0,0 +1,375 @@ +package bridgesync + +import ( + "context" + "errors" + "testing" + + aggkittypes "github.com/agglayer/aggkit/types" + mocksethclient "github.com/agglayer/aggkit/types/mocks" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestNewAgglayerBridgeL2Reader(t *testing.T) { + tests := []struct { + name string + bridgeAddr common.Address + l2Client aggkittypes.BaseEthereumClienter + expectError bool + errorMsg string + }{ + { + name: "successful creation", + bridgeAddr: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + l2Client: mocksethclient.NewBaseEthereumClienter(t), + expectError: false, + }, + { + name: "zero address", + bridgeAddr: common.Address{}, + l2Client: mocksethclient.NewBaseEthereumClienter(t), + expectError: false, // Zero address is valid, contract creation might still work + }, + { + name: "contract creation with valid mock client", + bridgeAddr: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + l2Client: mocksethclient.NewBaseEthereumClienter(t), + expectError: false, // The contract creation should succeed with a valid mock client + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader, err := NewAgglayerBridgeL2Reader(tt.bridgeAddr, tt.l2Client) + + if tt.expectError { + require.Error(t, err) + require.Nil(t, reader) + if tt.errorMsg != "" { + require.Contains(t, err.Error(), tt.errorMsg) + } + } else { + require.NoError(t, err) + require.NotNil(t, reader) + require.NotNil(t, reader.agglayerBridgeL2) + } + }) + } +} + +func TestAgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_WithMockedClient(t *testing.T) { + ctx := context.Background() + bridgeAddr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + mockClient := mocksethclient.NewBaseEthereumClienter(t) + + // Mock the FilterLogs method that will be called by the contract + mockClient.On("FilterLogs", mock.Anything, mock.Anything).Return([]ethtypes.Log{}, nil) + + reader, err := NewAgglayerBridgeL2Reader(bridgeAddr, mockClient) + require.NoError(t, err) + + t.Run("successful call with mocked client", func(t *testing.T) { + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 100, 200) + require.NoError(t, err) + require.NotNil(t, unclaims) + require.Empty(t, unclaims) // Should be empty since we mocked empty results + }) + + t.Run("zero block range", func(t *testing.T) { + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 0, 0) + require.NoError(t, err) + require.NotNil(t, unclaims) + require.Empty(t, unclaims) + }) + + t.Run("same from and to block", func(t *testing.T) { + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 100, 100) + require.NoError(t, err) + require.NotNil(t, unclaims) + require.Empty(t, unclaims) + }) + + t.Run("large block range", func(t *testing.T) { + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 0, ^uint64(0)) + require.NoError(t, err) + require.NotNil(t, unclaims) + require.Empty(t, unclaims) + }) + + mockClient.AssertExpectations(t) +} + +func TestAgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_ErrorHandling(t *testing.T) { + bridgeAddr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + mockClient := mocksethclient.NewBaseEthereumClienter(t) + + reader, err := NewAgglayerBridgeL2Reader(bridgeAddr, mockClient) + require.NoError(t, err) + + t.Run("context cancellation", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() // Cancel immediately + + // Mock the FilterLogs method + mockClient.On("FilterLogs", mock.Anything, mock.Anything).Return([]ethtypes.Log{}, nil) + + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 100, 200) + require.NoError(t, err) // Context cancellation doesn't cause error in this implementation + require.NotNil(t, unclaims) + require.Empty(t, unclaims) + }) + + t.Run("nil context handling", func(t *testing.T) { + // Test that nil context is handled gracefully + unclaims, err := reader.GetUnsetClaimsForBlockRange(context.TODO(), 100, 200) + require.NoError(t, err) // The function handles nil context gracefully + require.NotNil(t, unclaims) + require.Empty(t, unclaims) + }) + + mockClient.AssertExpectations(t) +} + +func TestAgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_InputValidation(t *testing.T) { + ctx := context.Background() + bridgeAddr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + mockClient := mocksethclient.NewBaseEthereumClienter(t) + + // Mock the FilterLogs method + mockClient.On("FilterLogs", mock.Anything, mock.Anything).Return([]ethtypes.Log{}, nil) + + reader, err := NewAgglayerBridgeL2Reader(bridgeAddr, mockClient) + require.NoError(t, err) + + t.Run("fromBlock greater than toBlock", func(t *testing.T) { + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 200, 100) + // This should return an error as it's an invalid block range + require.Error(t, err) + require.Nil(t, unclaims) + require.Contains(t, err.Error(), "invalid block range") + require.Contains(t, err.Error(), "fromBlock(200) > toBlock(100)") + }) + + t.Run("maximum uint64 values", func(t *testing.T) { + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, ^uint64(0), ^uint64(0)) + require.NoError(t, err) + require.NotNil(t, unclaims) + require.Empty(t, unclaims) + }) + + t.Run("minimum values", func(t *testing.T) { + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 0, 0) + require.NoError(t, err) + require.NotNil(t, unclaims) + require.Empty(t, unclaims) + }) + + mockClient.AssertExpectations(t) +} + +// Test error handling in GetUnsetClaimsForBlockRange +func TestAgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_FilterErrorHandling(t *testing.T) { + ctx := context.Background() + bridgeAddr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + mockClient := mocksethclient.NewBaseEthereumClienter(t) + + reader, err := NewAgglayerBridgeL2Reader(bridgeAddr, mockClient) + require.NoError(t, err) + + t.Run("filter error", func(t *testing.T) { + // Mock FilterLogs to return an error + mockClient.On("FilterLogs", mock.Anything, mock.Anything).Return([]ethtypes.Log{}, errors.New("filter error")) + + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 100, 200) + require.Error(t, err) + require.Nil(t, unclaims) + require.Contains(t, err.Error(), "filter error") + }) + + mockClient.AssertExpectations(t) +} + +// Test iterator close error handling +func TestAgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_IteratorCloseError(t *testing.T) { + ctx := context.Background() + bridgeAddr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + mockClient := mocksethclient.NewBaseEthereumClienter(t) + + // Mock FilterLogs to return empty results + mockClient.On("FilterLogs", mock.Anything, mock.Anything).Return([]ethtypes.Log{}, nil) + + reader, err := NewAgglayerBridgeL2Reader(bridgeAddr, mockClient) + require.NoError(t, err) + + // Test normal operation - iterator close error is logged but doesn't affect return + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 100, 200) + require.NoError(t, err) + require.NotNil(t, unclaims) + require.Empty(t, unclaims) + + mockClient.AssertExpectations(t) +} + +// Test with simulated backend to get real contract behavior +func TestAgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_SimulatedBackend(t *testing.T) { + ctx := context.Background() + bridgeAddr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + + // Use a simulated backend to get real contract behavior + simulatedBackend := simulated.NewBackend(nil, simulated.WithBlockGasLimit(10000000)) + defer simulatedBackend.Close() + + // Use the client from the simulated backend + client := simulatedBackend.Client() + + reader, err := NewAgglayerBridgeL2Reader(bridgeAddr, client) + require.NoError(t, err) + + // Test with the simulated backend - need to mine some blocks first + simulatedBackend.Commit() // Mine the genesis block + + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 0, 1) + require.NoError(t, err) + require.NotNil(t, unclaims) + // Should be empty since no events were emitted + require.Empty(t, unclaims) +} + +// Test with real contract events to test iterator behavior +func TestAgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_WithRealEvents(t *testing.T) { + ctx := context.Background() + bridgeAddr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + + // Use a simulated backend to get real contract behavior + simulatedBackend := simulated.NewBackend(nil, simulated.WithBlockGasLimit(10000000)) + defer simulatedBackend.Close() + + // Use the client from the simulated backend + client := simulatedBackend.Client() + + reader, err := NewAgglayerBridgeL2Reader(bridgeAddr, client) + require.NoError(t, err) + + // Mine some blocks to create a valid range + simulatedBackend.Commit() // Block 1 + simulatedBackend.Commit() // Block 2 + simulatedBackend.Commit() // Block 3 + + // Test with a valid block range + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 1, 3) + require.NoError(t, err) + require.NotNil(t, unclaims) + // Should be empty since no events were emitted, but this tests the iterator path + require.Empty(t, unclaims) +} + +// Test the actual iterator behavior by creating a test that exercises the iterator loop +func TestAgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_IteratorBehavior(t *testing.T) { + ctx := context.Background() + bridgeAddr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + + // Use a simulated backend to get real contract behavior + simulatedBackend := simulated.NewBackend(nil, simulated.WithBlockGasLimit(10000000)) + defer simulatedBackend.Close() + + // Use the client from the simulated backend + client := simulatedBackend.Client() + + reader, err := NewAgglayerBridgeL2Reader(bridgeAddr, client) + require.NoError(t, err) + + // Mine some blocks to create a valid range + simulatedBackend.Commit() // Block 1 + simulatedBackend.Commit() // Block 2 + simulatedBackend.Commit() // Block 3 + + // Test with a valid block range - this will test the iterator behavior + // The iterator will be created and the Next() method will be called + // Even though there are no events, this tests the iterator loop structure + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 1, 3) + require.NoError(t, err) + require.NotNil(t, unclaims) + // Should be empty since no events were emitted, but this tests the iterator path + require.Empty(t, unclaims) + + // Test with a single block range + unclaims, err = reader.GetUnsetClaimsForBlockRange(ctx, 1, 1) + require.NoError(t, err) + require.NotNil(t, unclaims) + require.Empty(t, unclaims) +} + +// Test with different block ranges +func TestAgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_BlockRanges(t *testing.T) { + ctx := context.Background() + bridgeAddr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + mockClient := mocksethclient.NewBaseEthereumClienter(t) + + // Mock FilterLogs to return empty results for all calls + mockClient.On("FilterLogs", mock.Anything, mock.Anything).Return([]ethtypes.Log{}, nil) + + reader, err := NewAgglayerBridgeL2Reader(bridgeAddr, mockClient) + require.NoError(t, err) + + testCases := []struct { + name string + fromBlock uint64 + toBlock uint64 + }{ + {"zero to zero", 0, 0}, + {"zero to max", 0, ^uint64(0)}, + {"max to max", ^uint64(0), ^uint64(0)}, + {"normal range", 100, 200}, + {"single block", 100, 100}, + {"large range", 0, 1000000}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, tc.fromBlock, tc.toBlock) + require.NoError(t, err) + require.NotNil(t, unclaims) + }) + } + + mockClient.AssertExpectations(t) +} + +// Test context handling +func TestAgglayerBridgeL2Reader_GetUnsetClaimsForBlockRange_ContextHandling(t *testing.T) { + bridgeAddr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + mockClient := mocksethclient.NewBaseEthereumClienter(t) + + // Mock FilterLogs to return empty results + mockClient.On("FilterLogs", mock.Anything, mock.Anything).Return([]ethtypes.Log{}, nil) + + reader, err := NewAgglayerBridgeL2Reader(bridgeAddr, mockClient) + require.NoError(t, err) + + t.Run("cancelled context", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() // Cancel immediately + + unclaims, err := reader.GetUnsetClaimsForBlockRange(ctx, 100, 200) + require.NoError(t, err) // Context cancellation doesn't cause error in this implementation + require.NotNil(t, unclaims) + }) + + t.Run("background context", func(t *testing.T) { + unclaims, err := reader.GetUnsetClaimsForBlockRange(context.Background(), 100, 200) + require.NoError(t, err) + require.NotNil(t, unclaims) + }) + + t.Run("TODO context", func(t *testing.T) { + unclaims, err := reader.GetUnsetClaimsForBlockRange(context.TODO(), 100, 200) + require.NoError(t, err) + require.NotNil(t, unclaims) + }) + + mockClient.AssertExpectations(t) +} diff --git a/bridgesync/bridgesync_test.go b/bridgesync/bridgesync_test.go index af2ab8b37..a5612ea9d 100644 --- a/bridgesync/bridgesync_test.go +++ b/bridgesync/bridgesync_test.go @@ -107,7 +107,6 @@ func TestNewLx(t *testing.T) { require.NotNil(t, l1BridgeSync) require.Equal(t, originNetwork, l2BridgdeSync.OriginNetwork()) - // Fails the sanity check of the contract address mockEthClient = mocksethclient.NewEthClienter(t) mockEthClient.EXPECT().CallContract(mock.Anything, mock.Anything, mock.Anything).Return(nil, nil).Once() mockEthClient.EXPECT().CodeAt(mock.Anything, mock.Anything, mock.Anything).Return(nil, nil).Once() @@ -668,12 +667,10 @@ func TestBridgeSync_GetLastRoot(t *testing.T) { root, err := s.GetLastRoot(ctx) require.Error(t, err) require.Nil(t, root) - // The error should be db.ErrNotFound from the tree package require.Contains(t, err.Error(), "not found") }) t.Run("get last root after processing bridge events", func(t *testing.T) { - // Create some bridge events to process bridgeEvents := []interface{}{ Event{Bridge: &Bridge{ BlockNum: 1, @@ -732,10 +729,8 @@ func TestBridgeSync_GetLastRoot(t *testing.T) { }) t.Run("get last root after multiple blocks", func(t *testing.T) { - // Reset halted state s.processor.halted = false - // Process another block with more bridge events bridgeEvents := []interface{}{ Event{Bridge: &Bridge{ BlockNum: 2, diff --git a/bridgesync/downloader.go b/bridgesync/downloader.go index 84d7bc373..2e7e46ee8 100644 --- a/bridgesync/downloader.go +++ b/bridgesync/downloader.go @@ -69,10 +69,9 @@ func buildAppender( return nil, fmt.Errorf("failed to create PolygonZkEVMBridge SC binding (bridge addr: %s): %w", bridgeAddr, err) } - bridgeSovereignChain, err := agglayerbridgel2.NewAgglayerbridgel2(bridgeAddr, client) + agglayerBridgeL2, err := agglayerbridgel2.NewAgglayerbridgel2(bridgeAddr, client) if err != nil { - return nil, fmt.Errorf("failed to create BridgeL2SovereignChain SC binding (bridge addr: %s): %w", - bridgeAddr, err) + return nil, fmt.Errorf("failed to create Agglayerbridgel2 SC binding (bridge addr: %s): %w", bridgeAddr, err) } appender := make(sync.LogAppenderMap) @@ -88,11 +87,11 @@ func buildAppender( appender[tokenMappingEventSignature] = buildTokenMappingHandler( agglayerBridge) appender[setSovereignTokenEventSignature] = buildSetSovereignTokenHandler( - bridgeSovereignChain) + agglayerBridgeL2) appender[migrateLegacyTokenEventSignature] = buildMigrateLegacyTokenHandler( - bridgeSovereignChain) + agglayerBridgeL2) appender[removeLegacySovereignTokenEventSignature] = buildRemoveLegacyTokenHandler( - bridgeSovereignChain) + agglayerBridgeL2) return appender, nil } diff --git a/bridgesync/types/types.go b/bridgesync/types/types.go new file mode 100644 index 000000000..fc48bc220 --- /dev/null +++ b/bridgesync/types/types.go @@ -0,0 +1,9 @@ +package types + +import "math/big" + +type Unclaim struct { + GlobalIndex *big.Int `json:"global_index"` + BlockNumber uint64 `json:"block_number"` + LogIndex uint64 `json:"log_index"` +} diff --git a/buf.lock b/buf.lock index c0de4f79f..bf0065ad1 100644 --- a/buf.lock +++ b/buf.lock @@ -2,8 +2,8 @@ version: v2 deps: - name: buf.build/agglayer/agglayer - commit: 57743a879f16408a884b0d3484e7b0c2 - digest: b5:8518aac1006bad7b202c17020760371223f435a23708c39f172a7eb5ce02a2811e14c621fa7b39ef2910cb32f22bb33654489606ee066dc62e9ff7411ad43024 + commit: b2806847d7f34b16b2d8ef488316eead + digest: b5:ec5415acd7424f150e2bebfc572530345dca08bc6fe7746ea8e4a26dee5001a9a8b3498990d4c3f9c7f4f2d2d064a0c04a6537b6463121231e8c4a078c4e517b - name: buf.build/agglayer/interop - commit: 85e8a3d9f59c4f9790789b45afb87c8e - digest: b5:27520cc3a605a2a49e8b577e89f2cdd1796e91823ad23088be0cf43dbf7760eaa0949d460bfb69156633b7a3d5e921f8f3b08143062db4f82e41cef1834592f7 + commit: 31a4887bc637444c819f92e05c0ebc93 + digest: b5:16cba44cc3991d20b7575ea30d6d9377d389e6616eb2a84b41900fa92b0a3c67545a32d3676011dc1f4dfda0c3bf1befd2d95428b83527913a981cf64a2dceae diff --git a/cmd/run.go b/cmd/run.go index 46fd3ba16..7998422f7 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -192,6 +192,7 @@ func start(cliCtx *cli.Context) error { l1InfoTreeSync, l2BridgeSync, l1Client, + l2Client, rollupDataQuerier, committeeQuerier, ) @@ -256,6 +257,7 @@ func createAggSenderValidator(ctx context.Context, l1InfoTreeSync *l1infotreesync.L1InfoTreeSync, l2Syncer *bridgesync.BridgeSync, l1Client aggkittypes.BaseEthereumClienter, + l2Client aggkittypes.BaseEthereumClienter, rollupDataQuerier *etherman.RollupDataQuerier, committeeQuerier aggsendertypes.MultisigQuerier, ) (*aggsender.AggsenderValidator, error) { @@ -300,6 +302,7 @@ func createAggSenderValidator(ctx context.Context, cfg, logger, l1Client, + l2Client, l1InfoTreeSync, l2Syncer, rollupDataQuerier, @@ -366,7 +369,8 @@ func createAggSender( go epochNotifier.Start(ctx) aggsender, err := aggsender.New(ctx, logger, cfg, agglayerClient, - l1InfoTreeSync, l2Syncer, epochNotifier, l1EthClient, l2Client, rollupDataQuerier, committeeQuerier) + l1InfoTreeSync, l2Syncer, epochNotifier, l1EthClient, l2Client, + rollupDataQuerier, committeeQuerier) if err != nil { return nil, fmt.Errorf("failed to create AggSender: %w", err) } diff --git a/config.toml.example b/config.toml.example index 1783af96a..0a261ab6a 100644 --- a/config.toml.example +++ b/config.toml.example @@ -27,8 +27,6 @@ NetworkID = 1 SequencerPrivateKeyPath = "/path/to/keystore" SequencerPrivateKeyPassword = "test" -polygonBridgeAddr = "0x0000000000000000000000000000000000000000" - # This values can be override directly from genesis.json rollupCreationBlockNumber = 0 rollupManagerCreationBlockNumber = 0 @@ -57,6 +55,10 @@ genesisBlockNumber = 0 # Address of the sovereign rollup contract on L1 # ------------------------------------------------------------------------------ polygonZkEVMAddress = "0x0000000000000000000000000000000000000000" + # ------------------------------------------------------------------------------ + # Address of the bridge addr on L1 + # ------------------------------------------------------------------------------ + BridgeAddr = "0x0000000000000000000000000000000000000000" [L2Config] # ------------------------------------------------------------------------------ @@ -67,6 +69,10 @@ genesisBlockNumber = 0 # Address of the aggoracle committee. To be used when aggoracle committee is enabled # ------------------------------------------------------------------------------ AggOracleCommitteeAddr = "0x0000000000000000000000000000000000000000" + # ------------------------------------------------------------------------------ + # Address of the bridge addr on L2 + # ------------------------------------------------------------------------------ + BridgeAddr = "0x0000000000000000000000000000000000000000" # ============================================================================== diff --git a/config/config.go b/config/config.go index 29732a968..8f4b36908 100644 --- a/config/config.go +++ b/config/config.go @@ -45,8 +45,8 @@ const ( DefaultCreationFilePermissions = os.FileMode(0600) - bridgeAddrSetOnWrongSection = "Bridge contract address must be set in the root of " + - "config file as polygonBridgeAddr." + bridgeAddrSetOnWrongSection = "Bridge contract address must be set in the L1 or L2 section of " + + "config file as BridgeAddr." l2URLHint = "Use L2URL instead" bridgeMetadataAsHashHint = "BridgeMetaDataAsHash is deprecated, remove it from configuration " + "(bridge metadata is always stored as hash)" @@ -108,11 +108,7 @@ type DeprecatedField struct { var ( deprecatedFieldsOnConfig = []DeprecatedField{ { - FieldNamePattern: "L1Config.polygonBridgeAddr", - Reason: bridgeAddrSetOnWrongSection, - }, - { - FieldNamePattern: "L2Config.polygonBridgeAddr", + FieldNamePattern: "polygonBridgeAddr", Reason: bridgeAddrSetOnWrongSection, }, { diff --git a/config/config_test.go b/config/config_test.go index 6687130ad..ecd65bc40 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -122,6 +122,8 @@ func TestLoadConfigWithDeprecatedFields(t *testing.T) { require.NoError(t, err) defer os.Remove(tmpFile.Name()) _, err = tmpFile.Write([]byte(` + + polygonBridgeAddr = "0x0000000000000000000000000000000000000000" [Common] IsValidiumMode = true ContractVersions="banana" diff --git a/config/default.go b/config/default.go index e9be7b694..d9082daf9 100644 --- a/config/default.go +++ b/config/default.go @@ -14,8 +14,6 @@ AggchainProofURL = "http://localhost:5576" SequencerPrivateKeyPath = "/etc/aggkit/sequencer.keystore" SequencerPrivateKeyPassword = "test" -polygonBridgeAddr = "0x0000000000000000000000000000000000000000" - # This values can be override directly from genesis.json rollupCreationBlockNumber = 0 rollupManagerCreationBlockNumber = 0 @@ -29,10 +27,12 @@ genesisBlockNumber = 0 polygonZkEVMAddress = "0x0000000000000000000000000000000000000000" BlocksChunkSize = 1000 RollupManagerCreationBlock = {{rollupManagerCreationBlockNumber}} + BridgeAddr = "0x0000000000000000000000000000000000000000" [L2Config] GlobalExitRootAddr = "0x0000000000000000000000000000000000000000" AggOracleCommitteeAddr = "0x0000000000000000000000000000000000000000" + BridgeAddr = "0x0000000000000000000000000000000000000000" ` // This doesnt below to config, but are the vars used @@ -152,7 +152,7 @@ MaxRequestsPerIPAndSecond = 10 DBPath = "{{PathRWData}}/bridgel1sync.sqlite" BlockFinality = "LatestBlock" InitialBlockNum = 0 -BridgeAddr = "{{polygonBridgeAddr}}" +BridgeAddr = "{{L1Config.BridgeAddr}}" SyncBlockChunkSize = 100 RetryAfterErrorPeriod = "1s" MaxRetryAttemptsAfterError = -1 @@ -164,7 +164,7 @@ DBQueryTimeout = "{{defaultDBQueryTimeout}}" DBPath = "{{PathRWData}}/bridgel2sync.sqlite" BlockFinality = "LatestBlock" InitialBlockNum = 0 -BridgeAddr = "{{polygonBridgeAddr}}" +BridgeAddr = "{{L2Config.BridgeAddr}}" SyncBlockChunkSize = 100 RetryAfterErrorPeriod = "1s" MaxRetryAttemptsAfterError = -1 @@ -209,6 +209,7 @@ RollupCreationBlockL1 = {{rollupCreationBlockNumber}} MaxL2BlockNumber = 0 StopOnFinishedSendingAllCertificates = false RequireCommitteeMembershipCheck = false +AgglayerBridgeL2Addr = "{{L2Config.BridgeAddr}}" [AggSender.RetriesToBuildAndSendCertificate] RetryMode = "delays" Delays = [ "1m", "1m", "2m", "5m", "5m", "8m" ] @@ -287,6 +288,7 @@ DelayBetweenRetries = "{{AggSender.DelayBetweenRetries}}" # PessimisticProof, AggchainProof or Auto Mode = "{{AggSender.Mode}}" RequireCommitteeMembershipCheck = {{AggSender.RequireCommitteeMembershipCheck}} +AgglayerBridgeL2Addr = "{{L2Config.BridgeAddr}}" [Validator.ServerConfig] Host = "0.0.0.0" Port = 5578 diff --git a/go.mod b/go.mod index d6b958df3..907eda5ee 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( buf.build/gen/go/agglayer/agglayer/protocolbuffers/go v1.36.10-20251003171624-441244f9c27c.1 buf.build/gen/go/agglayer/interop/protocolbuffers/go v1.36.10-20250923143526-220bf697ba47.1 buf.build/gen/go/agglayer/provers/grpc/go v1.5.1-20250520163122-7efa0a2f81a8.2 - buf.build/gen/go/agglayer/provers/protocolbuffers/go v1.36.9-20250520163122-7efa0a2f81a8.1 + buf.build/gen/go/agglayer/provers/protocolbuffers/go v1.36.10-20251103121356-e8eff0e259e6.1 github.com/0xPolygon/cdk-contracts-tooling v0.0.11 github.com/0xPolygon/cdk-rpc v0.0.0-20250213125803-179882ad6229 github.com/0xPolygon/zkevm-ethtx-manager v0.2.16 diff --git a/go.sum b/go.sum index 023fa2db0..a9fb837c4 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ buf.build/gen/go/agglayer/interop/protocolbuffers/go v1.36.10-20250923143526-220 buf.build/gen/go/agglayer/interop/protocolbuffers/go v1.36.10-20250923143526-220bf697ba47.1/go.mod h1:j8EqojEdmnuL9uVgWyb3w9LBRebDCHk1pZW9pygiiao= buf.build/gen/go/agglayer/provers/grpc/go v1.5.1-20250520163122-7efa0a2f81a8.2 h1:gV+6xXfMHDXA4XI5jCpJmMITl6A51SUaoECeRO4/GAY= buf.build/gen/go/agglayer/provers/grpc/go v1.5.1-20250520163122-7efa0a2f81a8.2/go.mod h1:NJnqmcfr760oCyvQ8MC6H8HsXRTHtzqAkzhEj+ms0uU= -buf.build/gen/go/agglayer/provers/protocolbuffers/go v1.36.9-20250520163122-7efa0a2f81a8.1 h1:60giwJ9yVmC8b4WYqDfGrZUJW9TSs7NYOfCOCUUEioQ= -buf.build/gen/go/agglayer/provers/protocolbuffers/go v1.36.9-20250520163122-7efa0a2f81a8.1/go.mod h1:JcOKDmcGPAamgkf4yywE/brha0A2H5l2KQurNRIDYXY= +buf.build/gen/go/agglayer/provers/protocolbuffers/go v1.36.10-20251103121356-e8eff0e259e6.1 h1:wR6BW+nHB6gITbVJ1EIp5DxlZZOUKHKiVtbc9jd9UTE= +buf.build/gen/go/agglayer/provers/protocolbuffers/go v1.36.10-20251103121356-e8eff0e259e6.1/go.mod h1:Grj01QLlAZvQqEmdviS5eYextanYMliZMLH/bsQ6YBY= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= diff --git a/l2gersync/l2_evm_ger_reader.go b/l2gersync/l2_evm_ger_reader.go index 936d7f087..81c6f6cd8 100644 --- a/l2gersync/l2_evm_ger_reader.go +++ b/l2gersync/l2_evm_ger_reader.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/0xPolygon/cdk-contracts-tooling/contracts/aggchain-multisig/agglayergerl2" + agglayertypes "github.com/agglayer/aggkit/agglayer/types" "github.com/agglayer/aggkit/aggoracle/types" "github.com/agglayer/aggkit/log" aggkittypes "github.com/agglayer/aggkit/types" @@ -95,8 +96,16 @@ func (e *L2EVMGERReader) GetInjectedGERsForRange(ctx context.Context, return nil, insertIterator.Error() } - // then get all removed GERs in the block range - // and remove them from the injectedGERs map + return injectedGERs, nil +} + +// GetRemovedGERsForRange returns the removed GlobalExitRoots for the given block range +func (e *L2EVMGERReader) GetRemovedGERsForRange(ctx context.Context, + fromBlock, toBlock uint64) ([]*agglayertypes.RemovedGER, error) { + if fromBlock > toBlock { + return nil, fmt.Errorf("invalid block range: fromBlock(%d) > toBlock(%d)", fromBlock, toBlock) + } + removalIterator, err := e.l2GERManager.FilterUpdateRemovalHashChainValue( &bind.FilterOpts{ Context: ctx, @@ -114,14 +123,22 @@ func (e *L2EVMGERReader) GetInjectedGERsForRange(ctx context.Context, } }() + removedGERs := make([]*agglayertypes.RemovedGER, 0) + for removalIterator.Next() { ger := removalIterator.Event.RemovedGlobalExitRoot - delete(injectedGERs, ger) + log.Infof("removed GER: %s at block %d, index %d", common.Hash(ger).String(), + removalIterator.Event.Raw.BlockNumber, removalIterator.Event.Raw.Index) + removedGERs = append(removedGERs, &agglayertypes.RemovedGER{ + GlobalExitRoot: common.Hash(ger), + BlockNumber: removalIterator.Event.Raw.BlockNumber, + LogIndex: uint64(removalIterator.Event.Raw.Index), + }) } if removalIterator.Error() != nil { return nil, removalIterator.Error() } - return injectedGERs, nil + return removedGERs, nil } diff --git a/l2gersync/l2_evm_ger_reader_test.go b/l2gersync/l2_evm_ger_reader_test.go index 926d34f6f..0107498d1 100644 --- a/l2gersync/l2_evm_ger_reader_test.go +++ b/l2gersync/l2_evm_ger_reader_test.go @@ -114,26 +114,182 @@ func TestL2EVMGERReader_GetInjectedGERsForRange(t *testing.T) { ger, exists := injectedGERs[expectedGER] require.True(t, exists) require.Equal(t, expectedGER, ger.GlobalExitRoot) + }) +} + +func TestL2EVMGERReader_GetRemovedGERsForRange(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + t.Run("failed to create iterator", func(t *testing.T) { + t.Parallel() + + toBlock := uint64(10) + mockL2GERManager := aggoraclemocks.NewL2GERManagerContract(t) + mockL2GERManager.EXPECT(). + FilterUpdateRemovalHashChainValue(mock.Anything, mock.Anything, mock.Anything). + Return(nil, errors.New("failed to create removal iterator")) - // commit one block so the current block is block 12 + gerReader := &L2EVMGERReader{l2GERManager: mockL2GERManager} + + _, err := gerReader.GetRemovedGERsForRange(ctx, 1, toBlock) + require.ErrorContains(t, err, "failed to create removal iterator") + }) + + t.Run("success with no removed GERs", func(t *testing.T) { + t.Parallel() + + _, l2 := helpers.NewSimulatedEVMEnvironment(t, helpers.DefaultEnvironmentConfig(helpers.SovereignChainL2GERContract)) + l2.EthTxManagerMock.ExpectedCalls = nil + + l1InfoTreeSync := mocks.NewL1InfoTreeQuerier(t) + + gerReader, err := NewL2EVMGERReader(l2.GERAddr, l2.SimBackend.Client(), l1InfoTreeSync) + require.NoError(t, err) + + // commit one block so the current block is block 6 l2.SimBackend.Commit() - tx, err = l2.GERManagerSovereignSC.RemoveGlobalExitRoots(l2.Auth, [][32]byte{expectedGER}) + // Get the current block number + currentBlock, err := l2.SimBackend.Client().BlockNumber(ctx) + require.NoError(t, err) + + removedGERs, err := gerReader.GetRemovedGERsForRange(ctx, 1, currentBlock) require.NoError(t, err) + require.Len(t, removedGERs, 0) + }) + + t.Run("success with removed GERs", func(t *testing.T) { + t.Parallel() - // commit one block so the current block is block 13 + _, l2 := helpers.NewSimulatedEVMEnvironment(t, helpers.DefaultEnvironmentConfig(helpers.SovereignChainL2GERContract)) + l2.EthTxManagerMock.ExpectedCalls = nil + + l1InfoTreeSync := mocks.NewL1InfoTreeQuerier(t) + + gerReader, err := NewL2EVMGERReader(l2.GERAddr, l2.SimBackend.Client(), l1InfoTreeSync) + require.NoError(t, err) + + // First insert a GER + gerToRemove := common.HexToHash("0x1234567890abcdef1234567890abcdef12345678") + _, err = l2.GERManagerSovereignSC.InsertGlobalExitRoot(l2.Auth, gerToRemove) + require.NoError(t, err) + + // commit one block so the current block is block 6 l2.SimBackend.Commit() - receipt, err = l2.SimBackend.Client().TransactionReceipt(ctx, tx.Hash()) + // Now remove the GER + removalTx, err := l2.GERManagerSovereignSC.RemoveGlobalExitRoots(l2.Auth, [][32]byte{gerToRemove}) require.NoError(t, err) - require.Equal(t, receipt.Status, types.ReceiptStatusSuccessful) - // Query again to verify the GER was removed - currentBlock, err = l2.SimBackend.Client().BlockNumber(ctx) + // commit another block + l2.SimBackend.Commit() + + // Get the current block number + currentBlock, err := l2.SimBackend.Client().BlockNumber(ctx) + require.NoError(t, err) + + removalReceipt, err := l2.SimBackend.Client().TransactionReceipt(ctx, removalTx.Hash()) + require.NoError(t, err) + require.Equal(t, removalReceipt.Status, types.ReceiptStatusSuccessful) + + removedGERs, err := gerReader.GetRemovedGERsForRange(ctx, 1, currentBlock) + require.NoError(t, err) + require.Len(t, removedGERs, 1) + + removedGER := removedGERs[0] + require.Equal(t, gerToRemove, removedGER.GlobalExitRoot) + require.Equal(t, removalReceipt.BlockNumber.Uint64(), removedGER.BlockNumber) + require.Equal(t, uint64(0), removedGER.LogIndex) // First event in the block + }) + + t.Run("success with multiple removed GERs", func(t *testing.T) { + t.Parallel() + + _, l2 := helpers.NewSimulatedEVMEnvironment(t, helpers.DefaultEnvironmentConfig(helpers.SovereignChainL2GERContract)) + l2.EthTxManagerMock.ExpectedCalls = nil + + l1InfoTreeSync := mocks.NewL1InfoTreeQuerier(t) + + gerReader, err := NewL2EVMGERReader(l2.GERAddr, l2.SimBackend.Client(), l1InfoTreeSync) + require.NoError(t, err) + + // Insert and remove multiple GERs + ger1 := common.HexToHash("0x1111111111111111111111111111111111111111") + ger2 := common.HexToHash("0x2222222222222222222222222222222222222222") + ger3 := common.HexToHash("0x3333333333333333333333333333333333333333") + + // Insert all GERs first + _, err = l2.GERManagerSovereignSC.InsertGlobalExitRoot(l2.Auth, ger1) + require.NoError(t, err) + _, err = l2.GERManagerSovereignSC.InsertGlobalExitRoot(l2.Auth, ger2) + require.NoError(t, err) + _, err = l2.GERManagerSovereignSC.InsertGlobalExitRoot(l2.Auth, ger3) + require.NoError(t, err) + + // commit one block + l2.SimBackend.Commit() + + // Remove all GERs + removalTx1, err := l2.GERManagerSovereignSC.RemoveGlobalExitRoots(l2.Auth, [][32]byte{ger1}) + require.NoError(t, err) + removalTx2, err := l2.GERManagerSovereignSC.RemoveGlobalExitRoots(l2.Auth, [][32]byte{ger2}) + require.NoError(t, err) + removalTx3, err := l2.GERManagerSovereignSC.RemoveGlobalExitRoots(l2.Auth, [][32]byte{ger3}) + require.NoError(t, err) + + // commit another block + l2.SimBackend.Commit() + + // Get the current block number + currentBlock, err := l2.SimBackend.Client().BlockNumber(ctx) + require.NoError(t, err) + + // Verify all removal transactions were successful + removalReceipt1, err := l2.SimBackend.Client().TransactionReceipt(ctx, removalTx1.Hash()) + require.NoError(t, err) + require.Equal(t, removalReceipt1.Status, types.ReceiptStatusSuccessful) + + removalReceipt2, err := l2.SimBackend.Client().TransactionReceipt(ctx, removalTx2.Hash()) + require.NoError(t, err) + require.Equal(t, removalReceipt2.Status, types.ReceiptStatusSuccessful) + + removalReceipt3, err := l2.SimBackend.Client().TransactionReceipt(ctx, removalTx3.Hash()) + require.NoError(t, err) + require.Equal(t, removalReceipt3.Status, types.ReceiptStatusSuccessful) + + removedGERs, err := gerReader.GetRemovedGERsForRange(ctx, 1, currentBlock) + require.NoError(t, err) + require.Len(t, removedGERs, 3) + + // Verify all three GERs were removed + gerHashes := make(map[common.Hash]bool) + for _, removedGER := range removedGERs { + gerHashes[removedGER.GlobalExitRoot] = true + } + + require.True(t, gerHashes[ger1]) + require.True(t, gerHashes[ger2]) + require.True(t, gerHashes[ger3]) + }) + + t.Run("iterator error during iteration", func(t *testing.T) { + t.Parallel() + + // This test would require a more complex mock setup to simulate iterator errors + // For now, we'll test the basic functionality with the simulated environment + _, l2 := helpers.NewSimulatedEVMEnvironment(t, helpers.DefaultEnvironmentConfig(helpers.SovereignChainL2GERContract)) + l2.EthTxManagerMock.ExpectedCalls = nil + + l1InfoTreeSync := mocks.NewL1InfoTreeQuerier(t) + + gerReader, err := NewL2EVMGERReader(l2.GERAddr, l2.SimBackend.Client(), l1InfoTreeSync) require.NoError(t, err) - injectedGERs, err = gerReader.GetInjectedGERsForRange(ctx, 1, currentBlock) + // Test with a valid range that should not cause iterator errors + removedGERs, err := gerReader.GetRemovedGERsForRange(ctx, 1, 5) require.NoError(t, err) - require.Empty(t, injectedGERs) + require.Len(t, removedGERs, 0) }) } diff --git a/scripts/local_config_fep b/scripts/local_config_fep index 712285be3..1a9363a18 100755 --- a/scripts/local_config_fep +++ b/scripts/local_config_fep @@ -26,4 +26,4 @@ function export_ports_from_kurtosis() { ############################################################################### ORIG_TEMPLATE_FILE=test/config/fep-config.toml.template DEST_TEMPLATE_FILE=$DEST/test.kurtosis.toml -main $* \ No newline at end of file +main $* diff --git a/scripts/local_config_helper b/scripts/local_config_helper index 60bcea84b..f6da1c2df 100644 --- a/scripts/local_config_helper +++ b/scripts/local_config_helper @@ -253,13 +253,14 @@ function common_export_values_of_aggkit_config() { local AGGKIT_CONFIG_FILE=$1 export_key_from_toml_file_or_fatal zkevm_rollup_id $AGGKIT_CONFIG_FILE "." NetworkID export_key_from_toml_file_or_fatal zkevm_rollup_manager_block_number $AGGKIT_CONFIG_FILE "." genesisBlockNumber - export_key_from_toml_file_or_fatal zkevm_bridge_address $AGGKIT_CONFIG_FILE "." polygonBridgeAddr export_key_from_toml_file_or_fatal zkevm_l2_keystore_password $AGGKIT_CONFIG_FILE "." SequencerPrivateKeyPassword export_key_from_toml_file_or_fatal l1_chain_id $AGGKIT_CONFIG_FILE L1Config chainId export_key_from_toml_file_or_fatal pol_token_address $AGGKIT_CONFIG_FILE L1Config polTokenAddress export_key_from_toml_file_or_fatal zkevm_rollup_manager_address $AGGKIT_CONFIG_FILE L1Config polygonRollupManagerAddress export_key_from_toml_file_or_fatal zkevm_global_exit_root_address $AGGKIT_CONFIG_FILE L1Config polygonZkEVMGlobalExitRootAddress export_key_from_toml_file_or_fatal zkevm_rollup_chain_id $AGGKIT_CONFIG_FILE AggOracle.EVMSender.EthTxManager.Etherman L1ChainID + export_key_from_toml_file_or_fatal zkevm_bridge_address $AGGKIT_CONFIG_FILE L1Config BridgeAddr + export_key_from_toml_file_or_fatal zkevm_bridge_l2_address $AGGKIT_CONFIG_FILE L2Config BridgeAddr } ############################################################################### @@ -281,15 +282,14 @@ function common_aggsender_committee_override_urls(){ export aggsender_committee_override_urls="" local _VALIDATOR for _VALIDATOR in $(kurtosis enclave inspect $KURTOSIS_ENCLAVE | grep aggkit-001-aggsender-validator | sed -r "s/\ +/\ /g" | cut -f 2 -d ' '); do - export_portnum_from_kurtosis_or_fail _LOCAL_PORT_VALIDATOR $_VALIDATOR validator-grpc - log_debug "Found validator: $_VALIDATOR port=$_LOCAL_PORT_VALIDATOR" + log_debug "Found validator: $_VALIDATOR port=$_LOCAL_PORT_VALIDATOR" aggsender_committee_override_urls="${aggsender_committee_override_urls} \"http://${_VALIDATOR}:5578\" = \"http://localhost:${_LOCAL_PORT_VALIDATOR}\"," done if [ ! -z "$aggsender_committee_override_urls" ]; then # Remove last ',' and add the {} aggsender_committee_override_urls="{ ${aggsender_committee_override_urls::-1} }" - else + else aggsender_committee_override_urls="{}" fi log_debug "aggsender_committee_override_urls=${aggsender_committee_override_urls}" @@ -347,7 +347,6 @@ function main(){ common_aggsender_committee_override_urls download_kurtosis_artifacts - export_ports_from_kurtosis export_values_of_aggkit_config $DEST/config.toml export_forced_values diff --git a/sync/evmdownloader.go b/sync/evmdownloader.go index e39e62b26..8fb02a421 100644 --- a/sync/evmdownloader.go +++ b/sync/evmdownloader.go @@ -333,7 +333,7 @@ func (d *EVMDownloaderImplementation) WaitForNewBlocks( d.log.Debugf("Getting tracked block for block number %d and latest synced block %d", blockNumber, latestSyncedBlock) trackedBlock, err := d.reorgDetector.GetTrackedBlockByBlockNumber(d.reorgDetectorID, blockNumber) if err != nil { - d.log.Errorf("Failed to get tracked block: %v, block number: %d", err, blockNumber) + d.log.Debugf("Failed to get tracked block: %v, block number: %d", err, blockNumber) return latestSyncedBlock } diff --git a/test/config/fep-config.toml.template b/test/config/fep-config.toml.template index 94410bf81..14ccb41fe 100644 --- a/test/config/fep-config.toml.template +++ b/test/config/fep-config.toml.template @@ -25,8 +25,6 @@ NetworkID = {{.zkevm_rollup_id}} SequencerPrivateKeyPath = "{{.zkevm_l2_sequencer_keystore_file_path}}" SequencerPrivateKeyPassword = "{{.zkevm_l2_keystore_password}}" -polygonBridgeAddr = "{{.zkevm_bridge_address}}" - RPCURL = "{{.op_el_rpc_url}}" # These values can be overriden directly from genesis.json @@ -74,6 +72,11 @@ polTokenAddress = "{{.pol_token_address}}" # ------------------------------------------------------------------------------ polygonZkEVMAddress = "{{.sovereign_rollup_addr}}" +# ------------------------------------------------------------------------------ +# Address of the bridge addr on L1 +# ------------------------------------------------------------------------------ +BridgeAddr = "0x0000000000000000000000000000000000000000" + # ============================================================================== # _ ____ ____ ___ _ _ _____ ___ ____ # | | |___ \ / ___/ _ \| \ | | ___|_ _/ ___| @@ -87,6 +90,10 @@ polygonZkEVMAddress = "{{.sovereign_rollup_addr}}" # Address of the sovereign global exit root proxy contract on L2 # ------------------------------------------------------------------------------ GlobalExitRootAddr = "{{.sovereign_ger_proxy_addr}}" +# ------------------------------------------------------------------------------ +# Address of the bridge addr on L2 +# ------------------------------------------------------------------------------ +BridgeAddr = "0x0000000000000000000000000000000000000000" # ============================================================================== # _ ___ ____ diff --git a/test/config/pp-config.toml.template b/test/config/pp-config.toml.template index 2d8da1fdb..7c3f30004 100644 --- a/test/config/pp-config.toml.template +++ b/test/config/pp-config.toml.template @@ -22,8 +22,6 @@ NetworkID = {{.zkevm_rollup_id}} SequencerPrivateKeyPath = "{{.zkevm_l2_sequencer_keystore_file_path}}" SequencerPrivateKeyPassword = "{{.zkevm_l2_keystore_password}}" -polygonBridgeAddr = "{{.zkevm_bridge_address}}" - # These values can be overriden directly from genesis.json rollupCreationBlockNumber = "{{.zkevm_rollup_manager_block_number}}" rollupManagerCreationBlockNumber = "{{.zkevm_rollup_manager_block_number}}" @@ -69,6 +67,11 @@ polTokenAddress = "{{.pol_token_address}}" # ------------------------------------------------------------------------------ polygonZkEVMAddress = "{{.zkevm_rollup_address}}" +# ------------------------------------------------------------------------------ +# Address of the bridge addr on L1 +# ------------------------------------------------------------------------------ +BridgeAddr = "0x0000000000000000000000000000000000000000" + # ============================================================================== # _ ____ ____ ___ _ _ _____ ___ ____ # | | |___ \ / ___/ _ \| \ | | ___|_ _/ ___| @@ -82,6 +85,10 @@ polygonZkEVMAddress = "{{.zkevm_rollup_address}}" # Address of the sovereign global exit root proxy contract on L2 # ------------------------------------------------------------------------------ GlobalExitRootAddr = "{{.zkevm_global_exit_root_l2_address}}" +# ------------------------------------------------------------------------------ +# Address of the bridge addr on L2 +# ------------------------------------------------------------------------------ +BridgeAddr = "0x0000000000000000000000000000000000000000" # ============================================================================== # _ ___ ____ diff --git a/test/run-local-e2e.sh b/test/run-local-e2e.sh index 35d68670f..9e9150d74 100755 --- a/test/run-local-e2e.sh +++ b/test/run-local-e2e.sh @@ -150,17 +150,14 @@ if [ "$E2E_REPO_PATH" != "-" ]; then exit 1 fi - log_info "Using provided Agglayer E2E repo at: $E2E_REPO_PATH" - - aggsender_find_imported_bridge_bin="./target/aggsender_find_imported_bridge" + aggsender_find_imported_bridge_bin="$PROJECT_ROOT/target/aggsender_find_imported_bridge" if [ ! -f "$aggsender_find_imported_bridge_bin" ]; then log_error "The aggsender imported bridges monitor tool is not built. Expected path: $aggsender_find_imported_bridge_bin" exit 1 fi - cp "$aggsender_find_imported_bridge_bin" "$E2E_REPO_PATH/aggsender_find_imported_bridge" - chmod +x "$E2E_REPO_PATH/aggsender_find_imported_bridge" + log_info "Using provided E2E repo at: $E2E_REPO_PATH" pushd "$E2E_REPO_PATH" >/dev/null log_info "Setting up e2e environment..." @@ -180,25 +177,30 @@ if [ "$E2E_REPO_PATH" != "-" ]; then bats ./tests/op/optimistic-mode.bats || exit 1 bats ./tests/aggkit/e2e-pp.bats || exit 1 bats ./tests/aggkit/bridge-sovereign-chain-e2e.bats || exit 1 + bats ./tests/op/optimistic-mode.bats || exit 1 + bats ./tests/aggkit/bridge-e2e-nightly.bats || exit 1 + bats ./tests/aggkit/internal-claims.bats || exit 1 + bats ./tests/aggkit/claim-reetrancy.bats || exit 1 bats ./tests/aggkit/aggsender-committee-updates.bats || exit 1 ;; single-l2-network-op-succinct-aggoracle-committee) - bats ./tests/aggkit/bridge-e2e-aggoracle-committee.bats + bats ./tests/aggkit/bridge-e2e-aggoracle-committee.bats || exit 1 ;; single-l2-network-op-pessimistic) bats ./tests/aggkit/bridge-e2e.bats || exit 1 bats ./tests/aggkit/e2e-pp.bats || exit 1 bats ./tests/aggkit/bridge-sovereign-chain-e2e.bats || exit 1 + bats ./tests/op/optimistic-mode.bats || exit 1 bats ./tests/aggkit/bridge-e2e-nightly.bats || exit 1 bats ./tests/aggkit/internal-claims.bats || exit 1 bats ./tests/aggkit/claim-reetrancy.bats || exit 1 bats ./tests/aggkit/aggsender-committee-updates.bats || exit 1 ;; multi-l2-networks-2-chains-op-pessimistic) - bats ./tests/aggkit/bridge-e2e-2-chains.bats + bats ./tests/aggkit/bridge-e2e-2-chains.bats || exit 1 ;; multi-l2-networks-3-chains-cdk-erigon-pessimistic) - bats ./tests/aggkit/bridge-e2e-3-chains.bats + bats ./tests/aggkit/bridge-e2e-3-chains.bats || exit 1 ;; esac rm -f aggsender_find_imported_bridge combined.json rollup_params.json