diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 236504741..6394bd36d 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -74,6 +74,7 @@ func TestConfigString(t *testing.T) { "RequireNoFEPBlockGap: false\n"+ "RetriesToBuildAndSendCertificate: RetryPolicyConfig{Mode: , Config: RetryDelaysConfig{Delays: [], MaxRetries: NO RETRIES}}\n"+ "StorageRetainCertificatesPolicy: retain all certificates, keep history: false\n"+ + "MaxLogBlockRange: 0\n"+ "BlockFinalityForL1InfoTree: FinalizedBlock\n"+ "TriggerCertMode: Auto\nTriggerEpochBased: EpochNotificationPercentage: 50\n", config.AgglayerClient.String()) diff --git a/aggsender/config/config.go b/aggsender/config/config.go index 9b18bf4a1..8cfccd5c5 100644 --- a/aggsender/config/config.go +++ b/aggsender/config/config.go @@ -139,9 +139,9 @@ type Config struct { CommitteeOverride query.CommitteeOverride `mapstructure:"CommitteeOverride"` // AgglayerBridgeL2Addr is the address of the bridge L2 sovereign contract on L2 sovereign chain AgglayerBridgeL2Addr ethCommon.Address `mapstructure:"AgglayerBridgeL2Addr"` - // UnsetClaimsMaxLogBlockRange is the proactive max block range for eth_getLogs queries when fetching unset claims. + // MaxLogBlockRange is the proactive max block range for eth_getLogs queries issued by AggSender. // 0 means disabled. - UnsetClaimsMaxLogBlockRange uint64 `mapstructure:"UnsetClaimsMaxLogBlockRange"` + MaxLogBlockRange uint64 `mapstructure:"MaxLogBlockRange"` // BlockFinalityForL1InfoTree indicates the block finality to use when querying for L1InfoRoot to use BlockFinalityForL1InfoTree aggkittypes.BlockNumberFinality `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinalityForL1InfoTree"` //nolint:lll // TriggerCertMode is the mode used to trigger certificate sending @@ -172,6 +172,7 @@ func (c Config) String() string { "RequireNoFEPBlockGap: " + fmt.Sprintf("%t", c.RequireNoFEPBlockGap) + "\n" + "RetriesToBuildAndSendCertificate: " + c.RetriesToBuildAndSendCertificate.String() + "\n" + "StorageRetainCertificatesPolicy: " + c.StorageRetainCertificatesPolicy.String() + "\n" + + "MaxLogBlockRange: " + fmt.Sprintf("%d", c.MaxLogBlockRange) + "\n" + "BlockFinalityForL1InfoTree: " + c.BlockFinalityForL1InfoTree.String() + "\n" + "TriggerCertMode: " + c.TriggerCertMode.String() + "\n" + "TriggerEpochBased: " + c.TriggerEpochBased.String() + "\n" diff --git a/aggsender/flows/builder_flow_factory.go b/aggsender/flows/builder_flow_factory.go index 392883fb4..7450a1062 100644 --- a/aggsender/flows/builder_flow_factory.go +++ b/aggsender/flows/builder_flow_factory.go @@ -25,7 +25,7 @@ import ( var ( // l2GERReaderFactory is a factory function to create L2 GER reader - l2GERReaderFactory = l2gersync.NewL2EVMGERReader + l2GERReaderFactory = l2gersync.NewL2EVMGERReaderWithMaxLogBlockRange ) // NewBuilderFlow creates a new AggsenderBuilderFlow based on the provided configuration. @@ -55,7 +55,7 @@ func NewBuilderFlow( true, // fullClaims required (with calldata) cfg.RequireCommitteeMembershipCheck, cfg.AgglayerBridgeL2Addr, - cfg.UnsetClaimsMaxLogBlockRange, + cfg.MaxLogBlockRange, cfg.GlobalExitRootL1Addr, cfg.BlockFinalityForL1InfoTree, certQuerier, @@ -107,7 +107,7 @@ func NewBuilderFlow( true, // full claims required (with calldata) cfg.RequireCommitteeMembershipCheck, cfg.AgglayerBridgeL2Addr, - cfg.UnsetClaimsMaxLogBlockRange, + cfg.MaxLogBlockRange, cfg.GlobalExitRootL1Addr, cfg.BlockFinalityForL1InfoTree, certQuerier, @@ -117,7 +117,12 @@ func NewBuilderFlow( return nil, fmt.Errorf("failed to create common flow components: %w", err) } - l2GERReader, err := l2GERReaderFactory(cfg.GlobalExitRootL2Addr, l2Client, l1InfoTreeSyncer) + l2GERReader, err := l2GERReaderFactory( + cfg.GlobalExitRootL2Addr, + l2Client, + l1InfoTreeSyncer, + cfg.MaxLogBlockRange, + ) if err != nil { return nil, fmt.Errorf("failed to create L2 GER reader: %w", err) } @@ -176,7 +181,7 @@ func CreateCommonFlowComponents( fullClaimsRequired bool, requireCommitteeMembershipCheck bool, agglayerBridgeL2Addr ethCommon.Address, - unsetClaimsMaxLogBlockRange uint64, + maxLogBlockRange uint64, globalExitRootL1Addr ethCommon.Address, blockFinalityForL1InfoTree aggkittypes.BlockNumberFinality, certQuerier types.CertificateQuerier, @@ -196,7 +201,7 @@ func CreateCommonFlowComponents( agglayerBridgeL2Reader, err := claimsync.NewAgglayerBridgeL2ReaderWithMaxLogBlockRange( agglayerBridgeL2Addr, l2Client, - unsetClaimsMaxLogBlockRange, + maxLogBlockRange, ) if err != nil { return nil, fmt.Errorf("failed to create bridge L2 sovereign reader: %w", err) diff --git a/aggsender/flows/verifier_flow_factory.go b/aggsender/flows/verifier_flow_factory.go index 49da28a60..9038d45fc 100644 --- a/aggsender/flows/verifier_flow_factory.go +++ b/aggsender/flows/verifier_flow_factory.go @@ -40,7 +40,7 @@ func NewVerifierFlow( true, // full claims are (eventually) needed in validator mode cfg.RequireCommitteeMembershipCheck, cfg.AgglayerBridgeL2Addr, - cfg.UnsetClaimsMaxLogBlockRange, + cfg.MaxLogBlockRange, cfg.GlobalExitRootL1Addr, cfg.BlockFinalityForL1InfoTree, nil, // certQuerier not used in validator mode @@ -73,7 +73,7 @@ func NewVerifierFlow( true, // full claims are (eventually) needed in validator mode cfg.RequireCommitteeMembershipCheck, cfg.AgglayerBridgeL2Addr, - cfg.UnsetClaimsMaxLogBlockRange, + cfg.MaxLogBlockRange, cfg.GlobalExitRootL1Addr, cfg.BlockFinalityForL1InfoTree, nil, // certQuerier not used in validator mode diff --git a/aggsender/validator/config.go b/aggsender/validator/config.go index 291394aa4..b6683ce0e 100644 --- a/aggsender/validator/config.go +++ b/aggsender/validator/config.go @@ -47,9 +47,9 @@ type Config struct { RequireCommitteeMembershipCheck bool `mapstructure:"RequireCommitteeMembershipCheck"` // AgglayerBridgeL2Addr is the address of the bridge L2 sovereign contract on L2 sovereign chain AgglayerBridgeL2Addr ethCommon.Address `mapstructure:"AgglayerBridgeL2Addr"` - // UnsetClaimsMaxLogBlockRange is the proactive max block range for eth_getLogs queries when fetching unset claims. + // MaxLogBlockRange is the proactive max block range for eth_getLogs queries issued by the validator. // 0 means disabled. - UnsetClaimsMaxLogBlockRange uint64 `mapstructure:"UnsetClaimsMaxLogBlockRange"` + MaxLogBlockRange uint64 `mapstructure:"MaxLogBlockRange"` // GlobalExitRootL1Addr is the address of the GlobalExitRootManager contract on L1 GlobalExitRootL1Addr ethCommon.Address `mapstructure:"GlobalExitRootL1Addr"` // BlockFinalityForL1InfoTree indicates the block finality to use when querying for L1InfoRoot to use diff --git a/claimsync/agglayer_bridge_l2_reader.go b/claimsync/agglayer_bridge_l2_reader.go index 563b4086e..ad497ef75 100644 --- a/claimsync/agglayer_bridge_l2_reader.go +++ b/claimsync/agglayer_bridge_l2_reader.go @@ -17,8 +17,8 @@ import ( // 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 - unsetClaimsMaxLogBlockRange uint64 + agglayerBridgeL2 *agglayerbridgel2.Agglayerbridgel2 + maxLogBlockRange uint64 } // NewAgglayerBridgeL2Reader creates a new instance of AgglayerBridgeL2Reader. @@ -39,11 +39,11 @@ func NewAgglayerBridgeL2Reader( } // NewAgglayerBridgeL2ReaderWithMaxLogBlockRange creates a new instance of AgglayerBridgeL2Reader -// with an optional proactive max block range for unset claims eth_getLogs queries. +// with an optional proactive max block range for eth_getLogs queries. func NewAgglayerBridgeL2ReaderWithMaxLogBlockRange( bridgeAddr common.Address, l2Client aggkittypes.BaseEthereumClienter, - unsetClaimsMaxLogBlockRange uint64, + maxLogBlockRange uint64, ) (*AgglayerBridgeL2Reader, error) { agglayerBridgeL2Contract, err := agglayerbridgel2.NewAgglayerbridgel2(bridgeAddr, l2Client) if err != nil { @@ -51,8 +51,8 @@ func NewAgglayerBridgeL2ReaderWithMaxLogBlockRange( } return &AgglayerBridgeL2Reader{ - agglayerBridgeL2: agglayerBridgeL2Contract, - unsetClaimsMaxLogBlockRange: unsetClaimsMaxLogBlockRange, + agglayerBridgeL2: agglayerBridgeL2Contract, + maxLogBlockRange: maxLogBlockRange, }, nil } @@ -75,8 +75,8 @@ func (r *AgglayerBridgeL2Reader) GetUnsetClaimsForBlockRange(ctx context.Context return nil, fmt.Errorf("invalid block range: fromBlock(%d) > toBlock(%d)", fromBlock, toBlock) } - if r.unsetClaimsMaxLogBlockRange > 0 && toBlock-fromBlock >= r.unsetClaimsMaxLogBlockRange { - return r.getUnsetClaimsInChunks(ctx, fromBlock, toBlock, r.unsetClaimsMaxLogBlockRange) + if r.maxLogBlockRange > 0 && toBlock-fromBlock >= r.maxLogBlockRange { + return r.getUnsetClaimsInChunks(ctx, fromBlock, toBlock, r.maxLogBlockRange) } return r.fetchUnsetClaimsWithFallbackChunking(ctx, fromBlock, toBlock) diff --git a/common/errors.go b/common/errors.go index be9d7a925..19c4c6196 100644 --- a/common/errors.go +++ b/common/errors.go @@ -14,6 +14,8 @@ var ( reExceededBlockRange = regexp.MustCompile(`exceeded maximum block range:\s*(\d+)`) // Matches "eth_getLogs is limited to a 10,000 range" (number may contain comma thousands separators) reEthGetLogsLimited = regexp.MustCompile(`eth_getLogs is limited to a\s+([\d,]+)\s+range`) + // Matches "query exceeds max block range 100000" + reQueryExceedsMaxBlockRange = regexp.MustCompile(`query exceeds max block range\s+([\d,]+)`) ) // ParseMaxRangeFromError extracts the max range value from error message @@ -21,9 +23,15 @@ var ( // - "block range too large, max range: 1000" // - "exceeded maximum block range: 5000" // - "eth_getLogs is limited to a 10,000 range" +// - "query exceeds max block range 100000" func ParseMaxRangeFromError(errMsg string) (uint64, bool) { var matches []string - for _, re := range []*regexp.Regexp{reMaxRange, reExceededBlockRange, reEthGetLogsLimited} { + for _, re := range []*regexp.Regexp{ + reMaxRange, + reExceededBlockRange, + reEthGetLogsLimited, + reQueryExceedsMaxBlockRange, + } { matches = re.FindStringSubmatch(errMsg) if len(matches) >= maxRangeMatchGroups { break diff --git a/common/errors_test.go b/common/errors_test.go index dc8931525..b3779e49b 100644 --- a/common/errors_test.go +++ b/common/errors_test.go @@ -105,6 +105,18 @@ func TestParseMaxRangeFromError(t *testing.T) { expectedMaxBlock: 100000, expectedIsMaxRange: true, }, + { + name: "query exceeds max block range", + errorMsg: "query exceeds max block range 100000", + expectedMaxBlock: 100000, + expectedIsMaxRange: true, + }, + { + name: "query exceeds max block range with comma-formatted number", + errorMsg: "query exceeds max block range 100,000", + expectedMaxBlock: 100000, + expectedIsMaxRange: true, + }, } for _, tt := range tests { @@ -112,6 +124,7 @@ func TestParseMaxRangeFromError(t *testing.T) { result, isMaxRangeErr := ParseMaxRangeFromError(tt.errorMsg) if tt.expectedIsMaxRange { require.True(t, isMaxRangeErr) + require.Equal(t, tt.expectedMaxBlock, result) } else { require.False(t, isMaxRangeErr) require.Equal(t, tt.expectedMaxBlock, result) diff --git a/config/config.go b/config/config.go index 83d8ebaa3..6344dc3bd 100644 --- a/config/config.go +++ b/config/config.go @@ -78,6 +78,7 @@ const ( networkIDDeprecatedHint = "Common.NetworkID is deprecated, remove it from configuration" urlRPCL1DeprecatedHint = "URLRPCL1 field is deprecated, remove it from configuration" aggsenderEpochPercentageHint = "AggSender.EpochNotificationPercentage moved to AggSender.TriggerEpochBased.EpochNotificationPercentage" //nolint:lll + maxLogBlockRangeHint = "UnsetClaimsMaxLogBlockRange moved to MaxLogBlockRange" ) type DeprecatedFieldsError struct { @@ -239,6 +240,14 @@ var ( FieldNamePattern: "AggSender.EpochNotificationPercentage", Reason: aggsenderEpochPercentageHint, }, + { + FieldNamePattern: "AggSender.UnsetClaimsMaxLogBlockRange", + Reason: maxLogBlockRangeHint, + }, + { + FieldNamePattern: "Validator.UnsetClaimsMaxLogBlockRange", + Reason: maxLogBlockRangeHint, + }, } ) diff --git a/config/config_test.go b/config/config_test.go index 111cfb322..4f8f2ed65 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -71,8 +71,8 @@ func TestLoadDefaultConfig(t *testing.T) { require.Equal(t, cfg.Validator.AgglayerClient.GRPC.MinConnectTimeout, cfg.AggSender.AgglayerClient.GRPC.MinConnectTimeout) require.Equal(t, cfg.Validator.AgglayerClient.GRPC.Retry.MaxAttempts, cfg.AggSender.AgglayerClient.GRPC.Retry.MaxAttempts) require.Equal(t, cfg.AggSender.RollupManagerAddr, cfg.Validator.LerQuerier.RollupManagerAddr) - require.Equal(t, uint64(0), cfg.AggSender.UnsetClaimsMaxLogBlockRange) - require.Equal(t, cfg.AggSender.UnsetClaimsMaxLogBlockRange, cfg.Validator.UnsetClaimsMaxLogBlockRange) + require.Equal(t, uint64(0), cfg.AggSender.MaxLogBlockRange) + require.Equal(t, cfg.AggSender.MaxLogBlockRange, cfg.Validator.MaxLogBlockRange) require.Equal(t, aggsendertypes.AutoMode, cfg.AggSender.Mode) require.Equal(t, aggsendertypes.AutoMode, cfg.Validator.Mode) require.Equal(t, cfg.AggSender.StorageRetainCertificatesPolicy.String(), "retain all certificates, keep history: true") diff --git a/config/default.go b/config/default.go index 238a78d8f..f74f313c0 100644 --- a/config/default.go +++ b/config/default.go @@ -253,8 +253,8 @@ MaxL2BlockNumber = 0 StopOnFinishedSendingAllCertificates = false RequireCommitteeMembershipCheck = false AgglayerBridgeL2Addr = "{{L2Config.BridgeAddr}}" -# Max block range per eth_getLogs call for unset-claims queries. 0 disables proactive chunking. -UnsetClaimsMaxLogBlockRange = 0 +# Max block range per eth_getLogs call issued by AggSender. 0 disables proactive chunking. +MaxLogBlockRange = 0 BlockFinalityForL1InfoTree = "FinalizedBlock" TriggerCertMode = "Auto" [AggSender.TriggerEpochBased] @@ -345,8 +345,8 @@ DelayBetweenRetries = "{{AggSender.DelayBetweenRetries}}" Mode = "{{AggSender.Mode}}" RequireCommitteeMembershipCheck = {{AggSender.RequireCommitteeMembershipCheck}} AgglayerBridgeL2Addr = "{{L2Config.BridgeAddr}}" -# Max block range per eth_getLogs call for unset-claims queries. 0 disables proactive chunking. -UnsetClaimsMaxLogBlockRange = {{AggSender.UnsetClaimsMaxLogBlockRange}} +# Max block range per eth_getLogs call issued by the validator. 0 disables proactive chunking. +MaxLogBlockRange = {{AggSender.MaxLogBlockRange}} GlobalExitRootL1Addr = "{{L1Config.polygonZkEVMGlobalExitRootAddress}}" BlockFinalityForL1InfoTree = "{{AggSender.BlockFinalityForL1InfoTree}}" [Validator.ServerConfig] diff --git a/docs/aggsender.md b/docs/aggsender.md index 976227af7..e810ccde2 100644 --- a/docs/aggsender.md +++ b/docs/aggsender.md @@ -195,7 +195,7 @@ The certificate is the data submitted to `Agglayer`. Must be signed to be accept | MaxL2BlockNumber | uint64 | Set the last block to be included in a certificate (0 = disabled) |StopOnFinishedSendingAllCertificates| bool | Stop when there are no more certificates to send due to MaxL2BlockNumber |StorageRetainCertificatesPolicy| [StorageRetainCertificatesPolicy](#storageretaincertificatespolicy) | Configure the certificate retain policy -| UnsetClaimsMaxLogBlockRange | uint64 | Proactive max block range for `eth_getLogs` queries when fetching unset claims. 0 means disabled (fallback to reactive chunking on error) +| MaxLogBlockRange | uint64 | Proactive max block range for `eth_getLogs` calls issued while building certificates, including unset claims and injected/removed GERs. 0 disables proactive chunking; recognized RPC max-range errors still trigger reactive chunking. ## StorageRetainCertificatesPolicy The `StorageRetainCertificatesPolicy` structure configures the certificate retain policy diff --git a/docs/aggsender_validator.md b/docs/aggsender_validator.md index 864d87139..bca787e9b 100644 --- a/docs/aggsender_validator.md +++ b/docs/aggsender_validator.md @@ -170,7 +170,7 @@ The validator is configured using a `.toml` file. Check the default values in th | Name | Type | Description | |-------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------| -| UnsetClaimsMaxLogBlockRange | uint64 | Proactive max block range for `eth_getLogs` queries when fetching unset claims. 0 means disabled (fallback to reactive chunking on error) | +| MaxLogBlockRange | uint64 | Proactive max block range for `eth_getLogs` calls issued while validating certificates. 0 disables proactive chunking; recognized RPC max-range errors still trigger reactive chunking. | ### Running the Validator @@ -388,4 +388,4 @@ if err != nil { log.Errorf("Remote validation failed: %v", err) return err } -``` \ No newline at end of file +``` diff --git a/l2gersync/l2_evm_ger_reader.go b/l2gersync/l2_evm_ger_reader.go index 1f79a3b88..933edf562 100644 --- a/l2gersync/l2_evm_ger_reader.go +++ b/l2gersync/l2_evm_ger_reader.go @@ -17,8 +17,9 @@ import ( // L2EVMGERReader is a component used to read GlobalExitRootManager L2 contract type L2EVMGERReader struct { - l2GERManager types.L2GERManagerContract - l1InfoTreeSync L1InfoTreeQuerier + l2GERManager types.L2GERManagerContract + l1InfoTreeSync L1InfoTreeQuerier + maxLogBlockRange uint64 } // NewL2EVMGERReader creates a new instance of L2 EVM global exit root reader @@ -26,6 +27,17 @@ func NewL2EVMGERReader( l2GERManagerAddr common.Address, l2Client aggkittypes.BaseEthereumClienter, l1InfoTreeSync L1InfoTreeQuerier, +) (*L2EVMGERReader, error) { + return NewL2EVMGERReaderWithMaxLogBlockRange(l2GERManagerAddr, l2Client, l1InfoTreeSync, 0) +} + +// NewL2EVMGERReaderWithMaxLogBlockRange creates a new instance of L2 EVM global exit root reader +// with a proactive max block range for eth_getLogs queries. A zero max range disables proactive chunking. +func NewL2EVMGERReaderWithMaxLogBlockRange( + l2GERManagerAddr common.Address, + l2Client aggkittypes.BaseEthereumClienter, + l1InfoTreeSync L1InfoTreeQuerier, + maxLogBlockRange uint64, ) (*L2EVMGERReader, error) { l2GERManager, err := agglayergerl2.NewAgglayergerl2( l2GERManagerAddr, l2Client) @@ -38,8 +50,9 @@ func NewL2EVMGERReader( } return &L2EVMGERReader{ - l2GERManager: l2GERManager, - l1InfoTreeSync: l1InfoTreeSync, + l2GERManager: l2GERManager, + l1InfoTreeSync: l1InfoTreeSync, + maxLogBlockRange: maxLogBlockRange, }, nil } @@ -63,22 +76,16 @@ func (e *L2EVMGERReader) GetInjectedGERsForRange(ctx context.Context, return nil, fmt.Errorf("invalid block range: fromBlock(%d) > toBlock(%d)", fromBlock, toBlock) } + if e.maxLogBlockRange > 0 && toBlock-fromBlock >= e.maxLogBlockRange { + return e.getInjectedGERsInChunks(ctx, fromBlock, toBlock, e.maxLogBlockRange) + } + injectedGERs, err := e.fetchInjectedGERs(ctx, fromBlock, toBlock) if err != nil { // Check if error is due to block range being too large maxRange, isMaxRangeErr := aggkitcommon.ParseMaxRangeFromError(err.Error()) if isMaxRangeErr { - log.Debugf("block range too large, splitting into chunks of max %d blocks", maxRange) - return aggkitcommon.ChunkedRangeQuery(ctx, fromBlock, toBlock, maxRange, - e.fetchInjectedGERs, - func(all map[common.Hash]GlobalExitRootInfo, - chunk map[common.Hash]GlobalExitRootInfo, - ) map[common.Hash]GlobalExitRootInfo { - maps.Copy(all, chunk) - return all - }, - make(map[common.Hash]GlobalExitRootInfo), - ) + return e.getInjectedGERsInChunks(ctx, fromBlock, toBlock, maxRange) } return nil, err } @@ -86,6 +93,21 @@ func (e *L2EVMGERReader) GetInjectedGERsForRange(ctx context.Context, return injectedGERs, nil } +func (e *L2EVMGERReader) getInjectedGERsInChunks(ctx context.Context, + fromBlock, toBlock, maxRange uint64) (map[common.Hash]GlobalExitRootInfo, error) { + log.Debugf("block range too large, splitting injected GER query into chunks of max %d blocks", maxRange) + return aggkitcommon.ChunkedRangeQuery(ctx, fromBlock, toBlock, maxRange, + e.fetchInjectedGERs, + func(all map[common.Hash]GlobalExitRootInfo, + chunk map[common.Hash]GlobalExitRootInfo, + ) map[common.Hash]GlobalExitRootInfo { + maps.Copy(all, chunk) + return all + }, + make(map[common.Hash]GlobalExitRootInfo), + ) +} + // fetchInjectedGERs performs the actual event filtering for injected GERs func (e *L2EVMGERReader) fetchInjectedGERs(ctx context.Context, fromBlock, toBlock uint64) (map[common.Hash]GlobalExitRootInfo, error) { @@ -162,19 +184,16 @@ func (e *L2EVMGERReader) GetRemovedGERsForRange(ctx context.Context, return nil, fmt.Errorf("invalid block range: fromBlock(%d) > toBlock(%d)", fromBlock, toBlock) } + if e.maxLogBlockRange > 0 && toBlock-fromBlock >= e.maxLogBlockRange { + return e.getRemovedGERsInChunks(ctx, fromBlock, toBlock, e.maxLogBlockRange) + } + removedGERs, err := e.fetchRemovedGERs(ctx, fromBlock, toBlock) if err != nil { // Check if error is due to block range being too large maxRange, isMaxRangeErr := aggkitcommon.ParseMaxRangeFromError(err.Error()) if isMaxRangeErr { - log.Debugf("block range too large, splitting into chunks of max %d blocks", maxRange) - return aggkitcommon.ChunkedRangeQuery(ctx, fromBlock, toBlock, maxRange, - e.fetchRemovedGERs, - func(all, chunk []*agglayertypes.RemovedGER) []*agglayertypes.RemovedGER { - return append(all, chunk...) - }, - []*agglayertypes.RemovedGER{}, - ) + return e.getRemovedGERsInChunks(ctx, fromBlock, toBlock, maxRange) } return nil, err } @@ -182,6 +201,18 @@ func (e *L2EVMGERReader) GetRemovedGERsForRange(ctx context.Context, return removedGERs, nil } +func (e *L2EVMGERReader) getRemovedGERsInChunks(ctx context.Context, + fromBlock, toBlock, maxRange uint64) ([]*agglayertypes.RemovedGER, error) { + log.Debugf("block range too large, splitting removed GER query into chunks of max %d blocks", maxRange) + return aggkitcommon.ChunkedRangeQuery(ctx, fromBlock, toBlock, maxRange, + e.fetchRemovedGERs, + func(all, chunk []*agglayertypes.RemovedGER) []*agglayertypes.RemovedGER { + return append(all, chunk...) + }, + []*agglayertypes.RemovedGER{}, + ) +} + // fetchRemovedGERs performs the actual event filtering for removed GERs func (e *L2EVMGERReader) fetchRemovedGERs(ctx context.Context, fromBlock, toBlock uint64) ([]*agglayertypes.RemovedGER, error) { diff --git a/l2gersync/l2_evm_ger_reader_test.go b/l2gersync/l2_evm_ger_reader_test.go index 45764085d..3f1a90fc5 100644 --- a/l2gersync/l2_evm_ger_reader_test.go +++ b/l2gersync/l2_evm_ger_reader_test.go @@ -146,6 +146,30 @@ func TestL2EVMGERReader_GetInjectedGERsForRange(t *testing.T) { mockL2Client.AssertExpectations(t) }) + t.Run("configured max log block range triggers proactive chunking", func(t *testing.T) { + t.Parallel() + + mockL2Client := mocksethclient.NewBaseEthereumClienter(t) + mockL2GERManager, err := agglayergerl2.NewAgglayergerl2( + common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), mockL2Client) + require.NoError(t, err) + + gerReader := &L2EVMGERReader{ + l2GERManager: mockL2GERManager, + maxLogBlockRange: 1000, + } + + // 3 chunks for inserted GERs, each chunk also queries removed GERs. + mockL2Client.On("FilterLogs", mock.Anything, mock.Anything). + Return([]types.Log{}, nil).Times(6) + + injectedGERs, err := gerReader.GetInjectedGERsForRange(ctx, 0, 2500) + require.NoError(t, err) + require.Empty(t, injectedGERs) + + mockL2Client.AssertExpectations(t) + }) + t.Run("non-parseable error returns original error", func(t *testing.T) { t.Parallel() @@ -210,6 +234,29 @@ func TestL2EVMGERReader_GetRemovedGERsForRange(t *testing.T) { mockL2GERManager.AssertExpectations(t) }) + t.Run("configured max log block range triggers proactive chunking", func(t *testing.T) { + t.Parallel() + + mockL2Client := mocksethclient.NewBaseEthereumClienter(t) + mockL2GERManager, err := agglayergerl2.NewAgglayergerl2( + common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), mockL2Client) + require.NoError(t, err) + + gerReader := &L2EVMGERReader{ + l2GERManager: mockL2GERManager, + maxLogBlockRange: 1000, + } + + mockL2Client.On("FilterLogs", mock.Anything, mock.Anything). + Return([]types.Log{}, nil).Times(3) + + removedGERs, err := gerReader.GetRemovedGERsForRange(ctx, 0, 2500) + require.NoError(t, err) + require.Empty(t, removedGERs) + + mockL2Client.AssertExpectations(t) + }) + t.Run("non-parseable error returns original error", func(t *testing.T) { t.Parallel()