Skip to content

feat: add support for dual rate limits#2053

Draft
chris-de-leon-cll wants to merge 5 commits into
mainfrom
feat/dual-rate-limits
Draft

feat: add support for dual rate limits#2053
chris-de-leon-cll wants to merge 5 commits into
mainfrom
feat/dual-rate-limits

Conversation

@chris-de-leon-cll
Copy link
Copy Markdown
Collaborator

TPRL: explicit default + fast-finality buckets (no pool-finality inference)

Summary

This change replaces the previous idea that v2 token pool tooling could infer which TPRL bucket to update by reading the pool’s AllowedFinalityConfig. That model was incorrect: allowed finality can combine modes such that both default-finality and fast-finality paths remain relevant over time, so both buckets may matter operationally.

Instead, the deployment stack now treats bucket selection as explicit configuration: legacy scalar InboundRateLimiterConfig / OutboundRateLimiterConfig reconcile to the default (FastFinality=false) bucket via normalization, and an optional fast-finality bucket is applied only when the normalized config includes paired per-direction slices (OutboundRateLimits / InboundRateLimits, or equivalent TPRL RemoteOutbounds.Outbounds rows with FastFinality=true). The EVM v2 adapter batches one SetRateLimitConfig call with one arg per bucket that actually needs an update, using each bucket’s FastFinality flag directly—without deriving it from on-chain finality.

Motivation

Earlier framing suggested users only ever cared about “the active” bucket and that omitting the inactive bucket was safely ignorable. In practice:

  • Misconfiguration risk: zero-valued Go structs could still flow to chain writes and clobber limits users did not intend to touch.
  • Semantic mismatch: routing scalar updates based on AllowedFinalityConfig conflates what the pool allows with which limits should be written, and understates cases where both buckets should be maintained.

This PR aligns tooling with an explicit multi-bucket contract while preserving backwards-compatible scalar fields as aliases for the default bucket after Normalize().

What changed

Core model (deployment/tokens)

  • Introduces RateLimitConfig (RateLimitConfig + FastFinality) and expands RemoteOutbounds with canonical Outbounds []RateLimitConfig, keeping RateLimit as the default-bucket alias.
  • Adds Normalize(), BucketForFinality(), validation on RateLimiterConfigFloatInput, and stricter TPRL input verification (at most two outbound buckets per remote, at most one per FastFinality value).
  • Extends RemoteChainConfig with InboundRateLimits / OutboundRateLimits plus Normalize(), GetOutbounds(), and GetInbounds() so inbound/outbound directions reconcile independently the same way as RemoteOutbounds.
  • TPRL apply path builds lane remotes via buildTPRLRemotesForSetRateLimitsLane, producing TPRLRemotes.RateLimitBuckets for the adapter; counterpart configs are normalized when stitching lanes.

Configure tokens for transfers

  • convertRemoteChainConfig now receives the full counterpart RemoteChainConfig (not only its outbound scalar), copies normalized scalars and bucket slices for both directions, and returns outCfg.Normalize().

EVM v2 configure sequence

  • ConfigureTokenPoolForRemoteChain normalizes RemoteChainConfig, builds rlInputs: always the default bucket from legacy scalars, optionally appends fast-finality when both outbound and inbound FF buckets exist after normalization, and passes that slice to maybeUpdateRateLimiters, which diffs per bucket and emits batched SetRateLimitConfig args only where needed.

EVM v2 adapter

  • SetTokenPoolRateLimits applies optional AllowedFinalityConfig when requested (read-before-write only in that case), then maps input.RateLimitBuckets directly to RateLimitConfigArgs—no finality.RawWaitForFinality comparison for picking FastFinality.

Reviewer / operator notes

  • AllowedFinalityConfig remains meaningful for what the pool accepts, but it is not used to decide where scalar TPRL floats land; scalars stay on the default bucket unless you also supply explicit paired FF slice configuration.
  • Fast-finality limits require symmetric specification on both sides of the lane where the TPRL builder enforces pairing (slice present locally iff present on remote for FastFinality=true).
  • Comments and sequence-level documentation were updated to remove the old “read finality → choose bucket” story and to describe batching and normalization instead.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the token deployment/configuration tooling to support two explicit TPRL rate-limit buckets (default-finality and fast-finality) without inferring bucket selection from on-chain AllowedFinalityConfig. It introduces canonical per-bucket configuration structures + normalization, propagates bucket data through the transfer-configuration flow, and updates the EVM v2.0.0 configure logic + adapter to diff and batch-set per-bucket rate limits.

Changes:

  • Introduces explicit rate-limit bucket modeling (RateLimitConfig, Outbounds, Normalize(), per-bucket validation) and extends RemoteChainConfig to carry per-direction bucket slices.
  • Updates ConfigureTokensForTransfers to pass/normalize full counterpart config and reconcile inbound/outbound buckets independently.
  • Updates EVM v2.0.0 configure sequence + adapter to build/apply a list of desired buckets (default always, FF only when explicitly paired) and batch SetRateLimitConfig per bucket.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
deployment/tokens/rate_limits.go Adds bucket model + normalization, TPRL verification changes, and lane bucket building logic.
deployment/tokens/product.go Adds float-input validation and extends RemoteChainConfig with inbound/outbound bucket slices + helpers.
deployment/tokens/configure_tokens_for_transfers.go Propagates normalized bucket slices using full counterpart RemoteChainConfig.
deployment/tokens/configure_tokens_for_transfers_test.go Adds unit tests for normalization + bucket access helpers.
chains/evm/deployment/v2_0_0/sequences/tokens/configure_token_pool_for_remote_chain.go Switches from finality-based inference to explicit per-bucket desired updates with batched writes.
chains/evm/deployment/v2_0_0/sequences/tokens/configure_token_pool_for_remote_chain_test.go Updates tests to assert scalar fields only affect the default bucket unless FF slices are explicitly provided.
chains/evm/deployment/v2_0_0/sequences/tokens/configure_token_for_transfers_test.go Updates expectations/documentation around which bucket scalar floats populate.
chains/evm/deployment/v2_0_0/adapters/tokens.go Updates adapter to map RateLimitBuckets directly to SetRateLimitConfig args (no finality inference).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread deployment/tokens/rate_limits.go Outdated
Comment thread deployment/tokens/rate_limits.go Outdated
Comment on lines +124 to 151
if len(input.Outbounds) == 0 {
if err := input.RateLimit.Validate(); err != nil {
return fmt.Errorf("outbound rate limiter config for remote chain %d is invalid: %w", remoteSelector, err)
}
if input.RateLimit.Rate > input.RateLimit.Capacity {
return fmt.Errorf("outbound rate limiter config for remote chain %d has rate greater than capacity", remoteSelector)
}

if len(input.Outbounds) > 2 {
return fmt.Errorf("at most two outbound rate limit buckets allowed for remote chain %d", remoteSelector)
}

defaultCount, fastFinCount := 0, 0
for _, rl := range input.Outbounds {
if err := rl.RateLimitConfig.Validate(); err != nil {
return fmt.Errorf("outbound rate limiter config for remote chain %d is invalid: %w", remoteSelector, err)
}
if rl.FastFinality {
fastFinCount++
} else {
defaultCount++
}
}

if defaultCount > 1 {
return fmt.Errorf("multiple outbound rate limit buckets with fastFinality=false provided for remote chain %d", remoteSelector)
}
if fastFinCount > 1 {
return fmt.Errorf("multiple outbound rate limit buckets with fastFinality=true provided for remote chain %d", remoteSelector)
}
Comment thread deployment/tokens/rate_limits.go Outdated
Comment thread deployment/tokens/product.go Outdated
@github-actions
Copy link
Copy Markdown

Metric feat/dual-rate-limits main
Coverage 70.1% 69.9%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants