-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add transaction rate-limit functionality #750
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis pull request extends error handling and transaction configuration by introducing an address-based rate limiting mechanism, especially for the testnet. A new case is added in the error handler to manage rate limit errors. Additionally, new configuration options and command-line flags are provided to control transaction submission frequency and duration. The changes also integrate a rate limiter into the transaction processing flow in the service layer, ensuring that excessive transaction submissions from the same address are curtailed. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant EVM
participant Limiter
Client->>EVM: SendRawTransaction(tx)
alt If testnet is used
EVM->>Limiter: Request token (using sender's address)
alt Token acquired
Limiter-->>EVM: Token granted
EVM->>EVM: Process transaction
EVM-->>Client: Transaction processed
else Token unavailable
Limiter-->>EVM: No token available
EVM-->>Client: Return ErrRateLimit error
end
else Non-testnet
EVM->>EVM: Process transaction normally
EVM-->>Client: Transaction processed
end
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
services/requester/requester.go (1)
172-180
: Consider implementing a cleanup mechanism for the memory store.The memory store could grow unbounded as it accumulates rate limit data for different addresses. Consider implementing a cleanup mechanism or using a store with built-in TTL support.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
api/utils.go
(1 hunks)cmd/run/cmd.go
(3 hunks)config/config.go
(1 hunks)services/requester/requester.go
(5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Test
🔇 Additional comments (4)
config/config.go (1)
92-96
: LGTM! Well-documented configuration fields.The new configuration fields for transaction rate limiting are well-typed and clearly documented.
api/utils.go (1)
134-135
: LGTM! Consistent error handling.The rate limit error handling is consistent with the existing error handling pattern.
cmd/run/cmd.go (1)
290-291
: LGTM! Well-configured command-line flags.The default values (1 request per 3 seconds) align well with the testnet block production time, and the flags are clearly documented.
services/requester/requester.go (1)
219-224
: LGTM! Well-implemented rate limiting.The rate limiting implementation is clean and effective:
- Only applies to testnet
- Uses sender's address as key for per-address limiting
- Returns appropriate error when limit is reached
cmd/run/cmd.go
Outdated
txDuration, err := time.ParseDuration(txDurationLimit) | ||
if err != nil { | ||
return fmt.Errorf("invalid unit %s for TX duration limit: %w", txDuration, err) | ||
} | ||
cfg.TxDurationLimit = txDuration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix error message variable.
The error message uses txDuration
instead of txDurationLimit
which could be confusing.
Apply this diff to fix the error message:
- return fmt.Errorf("invalid unit %s for TX duration limit: %w", txDuration, err)
+ return fmt.Errorf("invalid unit %s for TX duration limit: %w", txDurationLimit, err)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
txDuration, err := time.ParseDuration(txDurationLimit) | |
if err != nil { | |
return fmt.Errorf("invalid unit %s for TX duration limit: %w", txDuration, err) | |
} | |
cfg.TxDurationLimit = txDuration | |
txDuration, err := time.ParseDuration(txDurationLimit) | |
if err != nil { | |
return fmt.Errorf("invalid unit %s for TX duration limit: %w", txDurationLimit, err) | |
} | |
cfg.TxDurationLimit = txDuration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is legit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@peterargue Thanks for confirming! I'll mark this as resolved.
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
services/requester/requester.go
Outdated
_, _, _, ok, _ := e.limiter.Take(ctx, from.Hex()) | ||
if !ok { | ||
return common.Hash{}, errs.ErrRateLimit | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle the error from limiter.Take().
The error from limiter.Take()
should be handled as it could indicate issues with the rate limiter.
Apply this diff to handle the error:
- _, _, _, ok, _ := e.limiter.Take(ctx, from.Hex())
+ _, _, _, ok, err := e.limiter.Take(ctx, from.Hex())
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("failed to check rate limit: %w", err)
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
_, _, _, ok, _ := e.limiter.Take(ctx, from.Hex()) | |
if !ok { | |
return common.Hash{}, errs.ErrRateLimit | |
} | |
} | |
_, _, _, ok, err := e.limiter.Take(ctx, from.Hex()) | |
if err != nil { | |
return common.Hash{}, fmt.Errorf("failed to check rate limit: %w", err) | |
} | |
if !ok { | |
return common.Hash{}, errs.ErrRateLimit | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this sounds legit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@peterargue Thanks for confirming! Looking forward to the improved error handling. 👍
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point indeed. Updated in 7b49b08
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
services/requester/requester.go
Outdated
@@ -202,6 +216,13 @@ func (e *EVM) SendRawTransaction(ctx context.Context, data []byte) (common.Hash, | |||
return common.Hash{}, fmt.Errorf("failed to derive the sender: %w", err) | |||
} | |||
|
|||
if e.config.FlowNetworkID == flowGo.Testnet { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest to check whether tx-request-limit > 0
:
rateLimitEnabled := tx-request-limit > 0
if rateLimitEnabled {
...
}
So that by default, the rate limit is disabled, for testnet, we will add a flag tx-request-limit=1
to enable it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1. let's make this flexible so it could be used anywhere, and let the operator decide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea 👍 Updated in 7b49b08
cmd/run/cmd.go
Outdated
txDuration, err := time.ParseDuration(txDurationLimit) | ||
if err != nil { | ||
return fmt.Errorf("invalid unit %s for TX duration limit: %w", txDuration, err) | ||
} | ||
cfg.TxDurationLimit = txDuration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is legit
cmd/run/cmd.go
Outdated
@@ -280,4 +287,6 @@ func init() { | |||
Cmd.Flags().StringVar(&cfg.ProfilerHost, "profiler-host", "localhost", "Host for the Profiler server") | |||
Cmd.Flags().IntVar(&cfg.ProfilerPort, "profiler-port", 6060, "Port for the Profiler server") | |||
Cmd.Flags().StringVar(&txStateValidation, "tx-state-validation", "tx-seal", "Sets the transaction validation mechanism. It can validate using the local state index, or wait for the outer Flow transaction to seal. Available values ('local-index' / 'tx-seal'), defaults to 'tx-seal'.") | |||
Cmd.Flags().Uint64Var(&cfg.TxRequestLimit, "tx-request-limit", 1, "Number of transaction submissions to allow per the specified interval. Applies only on Testnet.") | |||
Cmd.Flags().StringVar(&txDurationLimit, "tx-duration-limit", "3s", "Time interval upon which to enforce transaction submission rate limiting. Applies only on Testnet.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could use DurationVar
which will automatically parse the value into a time.Duration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, I missed this method 💯
Updated in b33cd9c
cmd/run/cmd.go
Outdated
@@ -280,4 +287,6 @@ func init() { | |||
Cmd.Flags().StringVar(&cfg.ProfilerHost, "profiler-host", "localhost", "Host for the Profiler server") | |||
Cmd.Flags().IntVar(&cfg.ProfilerPort, "profiler-port", 6060, "Port for the Profiler server") | |||
Cmd.Flags().StringVar(&txStateValidation, "tx-state-validation", "tx-seal", "Sets the transaction validation mechanism. It can validate using the local state index, or wait for the outer Flow transaction to seal. Available values ('local-index' / 'tx-seal'), defaults to 'tx-seal'.") | |||
Cmd.Flags().Uint64Var(&cfg.TxRequestLimit, "tx-request-limit", 1, "Number of transaction submissions to allow per the specified interval. Applies only on Testnet.") | |||
Cmd.Flags().StringVar(&txDurationLimit, "tx-duration-limit", "3s", "Time interval upon which to enforce transaction submission rate limiting. Applies only on Testnet.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could use DurationVar
which will automatically parse the value into a time.Duration
the current naming sounds like a limit for the duration, not the other way around. Also, this applies on whichever network the gateway is configured to use
Cmd.Flags().StringVar(&txDurationLimit, "tx-duration-limit", "3s", "Time interval upon which to enforce transaction submission rate limiting. Applies only on Testnet.") | |
Cmd.Flags().StringVar(&txDurationLimit, "tx-request-limit-duration", "3s", "Time interval upon which to enforce transaction submission rate limiting.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point 👍 Updated in b33cd9c
services/requester/requester.go
Outdated
@@ -202,6 +216,13 @@ func (e *EVM) SendRawTransaction(ctx context.Context, data []byte) (common.Hash, | |||
return common.Hash{}, fmt.Errorf("failed to derive the sender: %w", err) | |||
} | |||
|
|||
if e.config.FlowNetworkID == flowGo.Testnet { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1. let's make this flexible so it could be used anywhere, and let the operator decide.
services/requester/requester.go
Outdated
_, _, _, ok, _ := e.limiter.Take(ctx, from.Hex()) | ||
if !ok { | ||
return common.Hash{}, errs.ErrRateLimit | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this sounds legit
5de2d4d
to
7b49b08
Compare
7b49b08
to
4e60c99
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
services/requester/requester.go (1)
219-228
: Consider adding metrics for rate limit rejections.While the rate limit implementation is correct, adding metrics would help monitor the rate limiting behavior in production.
Add metrics before returning the rate limit error:
if !ok { + e.collector.RateLimitRejection(from.Hex()) return common.Hash{}, errs.ErrRateLimit }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
api/utils.go
(1 hunks)cmd/run/cmd.go
(1 hunks)config/config.go
(1 hunks)services/requester/requester.go
(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- api/utils.go
- cmd/run/cmd.go
- config/config.go
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Test
🔇 Additional comments (4)
services/requester/requester.go (4)
24-25
: LGTM! Appropriate imports added.The required imports for rate limiting functionality are correctly added.
115-115
: LGTM! Rate limiter field added.The
limiter
field is appropriately added to theEVM
struct with the correct type.
172-180
: LGTM! Rate limiter initialization looks good.The rate limiter is properly initialized with configuration parameters for tokens and interval.
219-228
:✅ Verification successful
Verify rate limit configuration in the codebase.
Let's ensure the rate limit configuration is properly documented and consistently used.
🏁 Script executed:
#!/bin/bash # Search for rate limit configuration usage rg -A 5 "TxRequestLimit|TxRequestDurationLimit"Length of output: 1598
Rate Limit Configuration Verified
The rate limit configuration is properly documented and consistently used across the codebase. In particular:
Configuration Definitions:
TheTxRequestLimit
andTxRequestDurationLimit
fields are documented inconfig/config.go
with clear comments and are set via CLI flags incmd/run/cmd.go
.Usage Consistency:
The requester service inservices/requester/requester.go
checkse.config.TxRequestLimit > 0
to enable rate limiting and correctly invokes the limiter with these configurations.No further changes are required.
@@ -280,4 +280,6 @@ func init() { | |||
Cmd.Flags().StringVar(&cfg.ProfilerHost, "profiler-host", "localhost", "Host for the Profiler server") | |||
Cmd.Flags().IntVar(&cfg.ProfilerPort, "profiler-port", 6060, "Port for the Profiler server") | |||
Cmd.Flags().StringVar(&txStateValidation, "tx-state-validation", "tx-seal", "Sets the transaction validation mechanism. It can validate using the local state index, or wait for the outer Flow transaction to seal. Available values ('local-index' / 'tx-seal'), defaults to 'tx-seal'.") | |||
Cmd.Flags().Uint64Var(&cfg.TxRequestLimit, "tx-request-limit", 0, "Number of transaction submissions to allow per the specified interval.") | |||
Cmd.Flags().DurationVar(&cfg.TxRequestDurationLimit, "tx-request-duration-limit", time.Second*3, "Time interval upon which to enforce transaction submission rate limiting.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tx-request-duration-limit
sounds like the limit is applied on the duration, not the other way around
Cmd.Flags().DurationVar(&cfg.TxRequestDurationLimit, "tx-request-duration-limit", time.Second*3, "Time interval upon which to enforce transaction submission rate limiting.") | |
Cmd.Flags().DurationVar(&cfg.TxRequestDurationLimit, "tx-request-limit-duration", time.Second*3, "Time interval upon which to enforce transaction submission rate limiting.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added couple small comments, but looks good
rateLimitEnabled := e.config.TxRequestLimit > 0 | ||
if rateLimitEnabled { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit
rateLimitEnabled := e.config.TxRequestLimit > 0 | |
if rateLimitEnabled { | |
if e.config.TxRequestLimit > 0 { |
Closes: #746
Description
The default TX submission rate-limit is set for 1 request / 3s..
The
3s
magic value here is the block production time ontestnet
.Both of these are configurable with the flags below:
tx-request-limit
(default is0
, meaning no rate-limit applies)tx-request-duration-limit
(default is3s
, for 3 seconds)For contributor use:
master
branchFiles changed
in the Github PR explorerSummary by CodeRabbit