Skip to content

Conversation

@m-Peter
Copy link
Collaborator

@m-Peter m-Peter commented Jul 8, 2025

Closes: #841

Description

Introduce a background process on keystore to improve release frequency for signing keys, by checking tx result status on each new block.


For contributor use:

  • Targeted PR against master branch
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Code follows the standards mentioned here.
  • Updated relevant documentation
  • Re-reviewed Files changed in the Github PR explorer
  • Added appropriate labels

Summary by CodeRabbit

  • New Features

    • Improved key management by processing locked keys asynchronously using block headers, enhancing unlocking accuracy and reliability.
    • Added configuration option to enable releasing signing keys by transaction result lookup, increasing key capacity.
    • Added a new test verifying signing key release and reuse during EVM transaction submission with key rotation.
    • Introduced a command-line flag to toggle transaction result-based key release.
  • Bug Fixes

    • Updated block notifications to include full block headers instead of just block heights, ensuring correct key release.
  • Tests

    • Refactored tests for context-aware initialization and block header-based notifications.
    • Added mock client support for transaction result retrieval by block ID in tests.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 8, 2025

"""

Walkthrough

The changes refactor the keystore's key release mechanism from a block height-based synchronous process to an asynchronous, block ID-driven approach. The keystore now queries transaction results per block to determine which signing keys to release. This update propagates through the keystore implementation, its usage, and all related tests and mocks.

Changes

Files / Areas Change Summary
bootstrap/bootstrap.go Updated keystore initialization to use new constructor signature with context, client, and logger.
services/ingestion/event_subscriber.go Changed NotifyBlock call to use block ID instead of block height.
services/ingestion/event_subscriber_test.go Refactored tests to use context, logger, and updated keystore constructor and NotifyBlock usage.
services/requester/keystore/key_store.go Major refactor: NotifyBlock now takes block ID, async block processing, new methods for key release, new constructor signature.
services/requester/keystore/key_store_test.go Updated tests for new keystore API, added mock client for transaction results, adjusted for async behavior.
services/testutils/mock_client.go Added support for mocking GetTransactionResultsByBlockID in MockClient.
tests/key_store_release_test.go Added new integration test verifying key release and reuse under EVM transaction submission with key rotation.
cmd/run/cmd.go Added new CLI flag coa-tx-lookup-enabled to enable transaction result lookup for key release.
config/config.go Added new config field COATxLookupEnabled to control transaction result lookup feature.

Sequence Diagram(s)

sequenceDiagram
    participant API_Server
    participant Bootstrap
    participant KeyStore
    participant FlowClient

    API_Server->>Bootstrap: StartAPIServer(ctx)
    Bootstrap->>KeyStore: New(ctx, keys, client, config, logger)
    Note over KeyStore: Starts processLockedKeys goroutine

    API_Server->>KeyStore: NotifyBlock(blockHeader)
    KeyStore->>KeyStore: enqueue blockHeader on blockChan
    KeyStore->>FlowClient: GetTransactionResultsByBlockID(ctx, blockID)
    FlowClient-->>KeyStore: []*TransactionResult
    KeyStore->>KeyStore: releasekeys(blockHeight, txResults)
    KeyStore->>API_Server: keys released for block
Loading

Assessment against linked issues

Objective Addressed Explanation
Release signing keys based on transaction result status instead of fixed block expiration (#841)
Refactor NotifyBlock and related APIs to use block ID instead of block height (#841)
Update tests and mocks to support new keystore behavior and dependencies (#841)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes identified related to the objectives from the linked issues.

Possibly related PRs

Suggested labels

Feature

Suggested reviewers

  • peterargue
  • janezpodhostnik

Poem

In the warren of keys, a change hops through,
From block heights to IDs, a logic anew!
Async bunnies now check each block’s fate,
Releasing the keys—no longer must they wait.
With every result, the locks gently fall,
Flowing like carrots, shared by all! 🥕
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed8c510 and 28a04f2.

📒 Files selected for processing (2)
  • config/config.go (1 hunks)
  • services/requester/keystore/key_store.go (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • config/config.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
services/requester/keystore/key_store.go (10)
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#615
File: bootstrap/bootstrap.go:167-197
Timestamp: 2024-10-17T18:04:04.165Z
Learning: In the `flow-evm-gateway` Go project, the validation ensuring that `startHeight` is less than or equal to `endHeight` is performed before the `StartTraceDownloader` method in `bootstrap/bootstrap.go`, so additional checks in this method are unnecessary.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/batch_tx_pool.go:0-0
Timestamp: 2025-06-19T11:36:25.478Z
Learning: In Go, when copying maps that contain slices (like `map[gethCommon.Address][]pooledEvmTx`), perform deep copies by iterating over the map and copying each slice individually using `make()` and `copy()` to avoid shared references that could lead to race conditions and data corruption.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#615
File: services/traces/engine.go:175-178
Timestamp: 2024-10-17T18:05:27.084Z
Learning: In the trace backfill process, returning upon encountering an error when retrieving a block or its Cadence ID is intentional because future blocks are unlikely to exist if these blocks don't.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:50-62
Timestamp: 2025-03-07T01:35:09.751Z
Learning: In the flow-evm-gateway codebase, panics are acceptable in scenarios where immediate detection of critical bugs is desired during development and testing, particularly for invariant violations that should never occur in a correctly functioning system (e.g., when a key is available but locked in the keystore implementation).
Learnt from: peterargue
PR: onflow/flow-evm-gateway#682
File: services/ingestion/event_subscriber.go:85-90
Timestamp: 2025-01-24T20:15:10.908Z
Learning: When calculating blocks to catch up in Flow, initialize the counter to 0 and only set it when the latest chain height is greater than the current height (latestOnChainHeight > currentHeight). There's no need to handle the else case as 0 blocks to catch up is the correct default when we're at the same height or ahead.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#617
File: api/stream.go:62-67
Timestamp: 2024-10-18T19:26:37.579Z
Learning: In the `flow-evm-gateway` project, within the Go file `api/stream.go`, the `prepareBlockResponse` method includes the Bloom filter as the field `LogsBloom` in the returned `Block` struct.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#615
File: services/traces/engine.go:190-190
Timestamp: 2024-10-17T18:04:41.062Z
Learning: In `services/traces/engine.go`, the `e.indexBlockTraces` function limits the number of goroutines to a maximum of 5 and blocks until all are complete, so during backfill, concurrency is already managed to prevent resource exhaustion.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (9)
services/requester/keystore/key_store.go (9)

4-15: LGTM! Dependencies properly added for new functionality.

The new imports support the enhanced keystore functionality: context for goroutine lifecycle management, access for Flow client interactions, config for configuration flags, zerolog for structured logging, and gRPC status codes for error handling.


22-37: Good documentation for the notification methods.

The added comments clearly distinguish between the happy path (valid EVM transactions via NotifyTransaction) and unhappy path (invalid transactions via NotifyBlock). The method signature change from uint64 to flowsdk.BlockHeader aligns with the new block-driven approach.


40-52: Well-structured additions to KeyStore struct.

The new fields support the asynchronous key release mechanism:

  • client and config enable transaction result queries
  • blockChan with appropriate buffer size (200) handles block notifications
  • logger provides structured logging
  • done channel enables graceful shutdown signaling

56-92: Constructor properly initializes new functionality.

The enhanced constructor signature and initialization logic are well-implemented:

  • Proper dependency injection with required parameters
  • Conditional goroutine launch based on IndexOnly mode
  • Appropriate channel buffering and initialization

99-103: Simple and efficient key usage check.

The HasKeysInUse method provides a clean way to check if any keys are currently locked, which is used as an optimization to avoid unnecessary API calls.


119-133: Proper early return for index-only mode.

The addition of the index-only check prevents unnecessary locking operations when the gateway is running in read-only mode, which is a good optimization.


195-225: Robust background processing with proper error handling.

The processLockedKeys goroutine is well-implemented with:

  • Proper context cancellation handling
  • Optimization to skip API calls when no keys are in use
  • Conditional transaction lookup based on configuration
  • Error logging with appropriate filtering (ignoring canceled context errors)
  • Graceful shutdown signaling

232-245: Efficient key release logic with dual criteria.

The releasekeys method properly handles both immediate release (for completed transactions) and expiration-based release (for stuck transactions). The logic correctly processes transaction results first, then handles expired keys, all under proper mutex protection.


137-165: Validate blockChan overflow handling in NotifyBlock

The non‐blocking send in the default case will drop block notifications once the 200-entry buffer is full, causing only expiration-based key releases and skipping transaction-specific cleanup. This can lead to keys being held longer than intended and may impact throughput if it happens frequently.

• Verify that, under expected load, processLockedKeys can keep up so the channel rarely fills.
• Add a warning log in the default branch to surface when the buffer is full:

default:
    k.logger.Warn().Msg("blockChan buffer full; releasing only expired keys")
    k.releasekeys(blockHeader.Height, nil)
}

• Introduce tests or metrics to detect and monitor full-channel fallbacks, ensuring you’re not silently skipping transaction result processing.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

close(k.done)
return
case blockID := <-k.blockChan:
// TODO: If calling `k.client.GetTransactionResultsByBlockID` for each
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is something we need to consider. If calling GetTransactionResultsByBlockID for each new block, is a bit aggressive for ANs, we might consider the approach with the available keys ratio.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it should be OK for each gw to send a single request per block since it's generally low impact. Some of these requests are served by ENs, but it shouldn't be hard to add caching on ANs if it becomes a problem.

this does highlight that we need a tx streaming endpoint.

Copy link
Contributor

Choose a reason for hiding this comment

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

how about making these new calls opt-in. that way most GWs would not start polling each block, but the few infra providers that need it could turn it on.

Looking over the implementation, we could probably just skip the lookup if it's not enabled and pass an empty slice

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Makes sense 👍 Updated in 97550d2 .

@m-Peter m-Peter marked this pull request as ready for review July 8, 2025 17:12
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
services/requester/keystore/key_store.go (1)

143-154: Consider implementing the suggested optimization.

The TODO comment raises a valid concern about AN load. The suggested optimization could reduce unnecessary API calls.

Would you like me to implement the available keys ratio optimization to reduce load on Access Nodes?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f540b99 and 6f911ad.

📒 Files selected for processing (6)
  • bootstrap/bootstrap.go (1 hunks)
  • services/ingestion/event_subscriber.go (1 hunks)
  • services/ingestion/event_subscriber_test.go (5 hunks)
  • services/requester/keystore/key_store.go (5 hunks)
  • services/requester/keystore/key_store_test.go (6 hunks)
  • services/testutils/mock_client.go (2 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
services/ingestion/event_subscriber.go (3)
Learnt from: peterargue
PR: onflow/flow-evm-gateway#617
File: api/stream.go:62-67
Timestamp: 2024-10-18T19:26:37.579Z
Learning: In the `flow-evm-gateway` project, within the Go file `api/stream.go`, the `prepareBlockResponse` method includes the Bloom filter as the field `LogsBloom` in the returned `Block` struct.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
services/testutils/mock_client.go (2)
Learnt from: peterargue
PR: onflow/flow-evm-gateway#617
File: api/stream.go:62-67
Timestamp: 2024-10-18T19:26:37.579Z
Learning: In the `flow-evm-gateway` project, within the Go file `api/stream.go`, the `prepareBlockResponse` method includes the Bloom filter as the field `LogsBloom` in the returned `Block` struct.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#615
File: bootstrap/bootstrap.go:167-197
Timestamp: 2024-10-17T18:04:04.165Z
Learning: In the `flow-evm-gateway` Go project, the validation ensuring that `startHeight` is less than or equal to `endHeight` is performed before the `StartTraceDownloader` method in `bootstrap/bootstrap.go`, so additional checks in this method are unnecessary.
bootstrap/bootstrap.go (2)
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
services/requester/keystore/key_store.go (4)
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:50-62
Timestamp: 2025-03-07T01:35:09.751Z
Learning: In the flow-evm-gateway codebase, panics are acceptable in scenarios where immediate detection of critical bugs is desired during development and testing, particularly for invariant violations that should never occur in a correctly functioning system (e.g., when a key is available but locked in the keystore implementation).
services/ingestion/event_subscriber_test.go (2)
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
services/requester/keystore/key_store_test.go (4)
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:50-62
Timestamp: 2025-03-07T01:35:09.751Z
Learning: In the flow-evm-gateway codebase, panics are acceptable in scenarios where immediate detection of critical bugs is desired during development and testing, particularly for invariant violations that should never occur in a correctly functioning system (e.g., when a key is available but locked in the keystore implementation).
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
🔇 Additional comments (18)
services/ingestion/event_subscriber.go (1)

179-179: LGTM! Correctly updated to use block ID.

The change from blockEvents.Height to blockEvents.BlockID properly aligns with the keystore refactoring to use block IDs instead of block heights for asynchronous key release processing.

bootstrap/bootstrap.go (1)

246-246: LGTM! Correctly updated keystore initialization.

The updated constructor signature properly provides the keystore with the required dependencies (context, client, logger) to support asynchronous key release processing based on transaction results.

services/testutils/mock_client.go (2)

21-21: LGTM! New mock function field added.

The GetTransactionResultsByBlockIDFunc field correctly follows the established pattern for mock function fields in this client.


54-62: LGTM! Mock method implementation follows established pattern.

The GetTransactionResultsByBlockID method correctly implements the standard mock pattern: delegate to the function field if set, otherwise fall back to the embedded client's method.

services/ingestion/event_subscriber_test.go (5)

47-51: LGTM! Test updated for new keystore constructor.

The changes correctly introduce shared context and logger variables and update the keystore initialization to use the new constructor signature requiring context, client, and logger.


91-95: LGTM! Consistent test updates.

The test correctly uses shared context and logger variables and updates the keystore initialization to match the new constructor signature.


197-201: LGTM! Test properly updated.

The keystore initialization correctly uses the new constructor signature with context, client, and logger dependencies.


264-268: LGTM! Test updates are consistent.

The changes maintain consistency with other test functions by using shared variables and the updated keystore constructor.


330-334: LGTM! Final test function properly updated.

The test correctly follows the established pattern with shared context and logger variables and updated keystore initialization.

services/requester/keystore/key_store_test.go (6)

32-32: LGTM! Keystore constructor updated correctly.

The keystore initialization properly uses the new constructor signature with context, client, and logger dependencies.


74-97: LGTM! Mock client setup for testing expiration behavior.

The mock client correctly implements the GetTransactionResultsByBlockIDFunc to test key expiration behavior, returning empty results for the specific block ID and finalized transaction results for others.


113-113: LGTM! Updated to use block ID for notification.

The change from block height to block ID (blockID10) correctly aligns with the new keystore interface.


119-123: LGTM! Properly handles asynchronous processing.

The change to use block ID for notification and the added sleep appropriately accounts for the asynchronous processLockedKeys background goroutine processing time.


141-148: LGTM! Constructor updated with all required dependencies.

The keystore initialization correctly provides context, account keys, client, and logger to support the new asynchronous processing capabilities.


194-194: LGTM! Final keystore constructor update.

The concurrent use test properly uses the updated keystore constructor with all required dependencies.

services/requester/keystore/key_store.go (3)

24-34: LGTM! Well-structured additions for async processing.

The new fields properly support the asynchronous block-based key release mechanism with appropriate shutdown signaling.


39-68: LGTM! Constructor properly handles the new async design.

Good optimization to skip the background goroutine when no keys are present (index mode).


100-108: Add handling for full channel buffer.

The current implementation silently drops block notifications when blockChan is full. This could delay key releases during high load.

Consider adding a default case that logs when blocks are dropped:

 select {
 case <-k.done:
 	k.logger.Warn().Msg(
 		"received `NotifyBlock` while the server is shutting down",
 	)
-default:
+case k.blockChan <- blockID:
+	// Successfully queued block for processing
+default:
+	k.logger.Warn().
+		Str("blockID", blockID.String()).
+		Msg("block notification dropped - channel full")
 	k.blockChan <- blockID
 }

Alternatively, consider making this a blocking send to ensure all blocks are processed, or increase the buffer size if dropping is acceptable.

⛔ Skipped due to learnings
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#617
File: api/stream.go:62-67
Timestamp: 2024-10-18T19:26:37.579Z
Learning: In the `flow-evm-gateway` project, within the Go file `api/stream.go`, the `prepareBlockResponse` method includes the Bloom filter as the field `LogsBloom` in the returned `Block` struct.

@m-Peter m-Peter force-pushed the mpeter/improve-signing-keys-release-frequency branch 3 times, most recently from 4ed4dd4 to f1f3f8e Compare July 9, 2025 08:23
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
tests/key_store_release_test.go (2)

90-90: Document or dynamically generate the test address.

The hardcoded address lacks context. Consider adding a comment explaining its purpose or generate it dynamically.

-	testAddr := common.HexToAddress("55253ed90B70b96C73092D8680915aaF50081194")
+	// Use a deterministic test address for balance verification
+	testAddr := common.HexToAddress("55253ed90B70b96C73092D8680915aaF50081194")

Alternatively, generate it dynamically:

testKey, _ := crypto.GenerateKey()
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)

114-121: Improve assertion message and consider additional verification.

The assertion message is grammatically awkward. Also, consider adding more explicit verification of the key release mechanism.

 	expectedBalance := int64(keyCount) * transferAmount
 	assert.Eventually(t, func() bool {
 		balance, err := rpcTester.getBalance(testAddr)
 		require.NoError(t, err)
 
 		return balance.Cmp(big.NewInt(expectedBalance)) == 0
-	}, time.Second*15, time.Second*1, "all transactions were not executed")
+	}, time.Second*15, time.Second*1, "expected %d valid transactions to execute, resulting in balance of %d", keyCount, expectedBalance)

Consider also logging transaction receipts to verify which transactions succeeded:

// After all transactions are submitted, verify receipts for valid nonces
validNonces := []uint64{0, 1, 2, 3, 4}
for _, nonce := range validNonces {
    // Verify transaction with this nonce was mined
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ed4dd4 and f1f3f8e.

📒 Files selected for processing (7)
  • bootstrap/bootstrap.go (1 hunks)
  • services/ingestion/event_subscriber.go (1 hunks)
  • services/ingestion/event_subscriber_test.go (5 hunks)
  • services/requester/keystore/key_store.go (5 hunks)
  • services/requester/keystore/key_store_test.go (6 hunks)
  • services/testutils/mock_client.go (2 hunks)
  • tests/key_store_release_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • services/ingestion/event_subscriber.go
  • services/testutils/mock_client.go
  • bootstrap/bootstrap.go
  • services/ingestion/event_subscriber_test.go
  • services/requester/keystore/key_store_test.go
  • services/requester/keystore/key_store.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
tests/key_store_release_test.go (4)
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:50-62
Timestamp: 2025-03-07T01:35:09.751Z
Learning: In the flow-evm-gateway codebase, panics are acceptable in scenarios where immediate detection of critical bugs is desired during development and testing, particularly for invariant violations that should never occur in a correctly functioning system (e.g., when a key is available but locked in the keystore implementation).
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test

@m-Peter m-Peter force-pushed the mpeter/improve-signing-keys-release-frequency branch from f1f3f8e to eed7b42 Compare July 9, 2025 08:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f1f3f8e and eed7b42.

📒 Files selected for processing (7)
  • bootstrap/bootstrap.go (1 hunks)
  • services/ingestion/event_subscriber.go (1 hunks)
  • services/ingestion/event_subscriber_test.go (5 hunks)
  • services/requester/keystore/key_store.go (5 hunks)
  • services/requester/keystore/key_store_test.go (6 hunks)
  • services/testutils/mock_client.go (2 hunks)
  • tests/key_store_release_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • services/ingestion/event_subscriber.go
  • services/testutils/mock_client.go
  • bootstrap/bootstrap.go
  • services/ingestion/event_subscriber_test.go
  • services/requester/keystore/key_store_test.go
  • services/requester/keystore/key_store.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
tests/key_store_release_test.go (5)
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:50-62
Timestamp: 2025-03-07T01:35:09.751Z
Learning: In the flow-evm-gateway codebase, panics are acceptable in scenarios where immediate detection of critical bugs is desired during development and testing, particularly for invariant violations that should never occur in a correctly functioning system (e.g., when a key is available but locked in the keystore implementation).
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/batch_tx_pool.go:0-0
Timestamp: 2025-06-19T11:36:25.478Z
Learning: In Go, when copying maps that contain slices (like `map[gethCommon.Address][]pooledEvmTx`), perform deep copies by iterating over the map and copying each slice individually using `make()` and `copy()` to avoid shared references that could lead to race conditions and data corruption.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (5)
tests/key_store_release_test.go (5)

1-21: LGTM! Clean imports and package structure.

The imports are well-organized and include all necessary dependencies for the key store release test.


23-44: LGTM! Excellent improvement over hardcoded sleep.

The readiness check using require.Eventually properly addresses the past review feedback about replacing hardcoded sleep with emulator readiness polling.


46-73: LGTM! Proper multi-key account setup for key rotation testing.

The configuration and multi-key account creation are well-structured and appropriate for testing the key release mechanism with key rotation.


75-87: LGTM! Proper gateway startup without unnecessary delays.

The ready channel is used correctly without additional sleep, properly addressing the past review feedback about removing unnecessary delays.


124-131: LGTM! Sound final assertion logic.

The balance calculation and eventual assertion are correctly implemented, providing appropriate timeout and retry logic for transaction processing.

@m-Peter m-Peter force-pushed the mpeter/improve-signing-keys-release-frequency branch from eed7b42 to 4677a8e Compare July 9, 2025 08:44
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 (3)
tests/key_store_release_test.go (3)

103-103: Fix typo in comment.

There's a typo in the comment.

-	// Finalliy, we assert that `testAddr` has the expected balance
+	// Finally, we assert that `testAddr` has the expected balance

94-105: Improve test documentation clarity.

The comment explaining the nonce sequence logic could be clearer about which specific transactions are expected to succeed and fail.

Consider expanding the comment to explicitly state which nonces will succeed (0, 1, 2, 3, 4) and which will fail, making the test logic more transparent:

-	// The first 6 nonces: [5, 4, 3, 2, 1, 1], are all invalid EVM
-	// transactions that will fail with `nonce too high`.
-	// Given that we created our COA above with a `keyCount := 5`,
-	// we assert that transaction submission still functions properly
-	// and that signing keys get released frequently, with each new
-	// Flow block.
-	// The rest 8 nonces: [0, 1, 5, 2, 5, 3, 5, 4], have 5 valid
-	// EVM transactions and in between there are 3 invalid EVM
-	// transactions.
+	// The first 6 nonces: [5, 4, 3, 2, 1, 1], are all invalid EVM
+	// transactions that will fail with `nonce too high` (fresh account starts at nonce 0).
+	// Given that we created our COA above with a `keyCount := 5`,
+	// we assert that transaction submission still functions properly
+	// and that signing keys get released frequently, with each new
+	// Flow block.
+	// The rest 8 nonces: [0, 1, 5, 2, 5, 3, 5, 4], contain 5 valid
+	// EVM transactions (nonces 0, 1, 2, 3, 4) and 3 invalid ones
+	// (the duplicate nonce 5 submissions).

23-130: Consider adding test timeout and resource cleanup.

The test relies on eventually consistent behavior but lacks an overall timeout. Consider adding a test timeout context to prevent hanging tests.

func Test_KeyStoreSigningKeysRelease(t *testing.T) {
+	// Set overall test timeout
+	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
+	defer cancel()
+
	srv, err := startEmulator(true)
	require.NoError(t, err)
-
-	ctx, cancel := context.WithCancel(context.Background())
	defer func() {
-		cancel()
		srv.Stop()
	}()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eed7b42 and 4677a8e.

📒 Files selected for processing (7)
  • bootstrap/bootstrap.go (1 hunks)
  • services/ingestion/event_subscriber.go (1 hunks)
  • services/ingestion/event_subscriber_test.go (5 hunks)
  • services/requester/keystore/key_store.go (5 hunks)
  • services/requester/keystore/key_store_test.go (6 hunks)
  • services/testutils/mock_client.go (2 hunks)
  • tests/key_store_release_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • bootstrap/bootstrap.go
  • services/ingestion/event_subscriber.go
  • services/requester/keystore/key_store_test.go
  • services/testutils/mock_client.go
  • services/ingestion/event_subscriber_test.go
  • services/requester/keystore/key_store.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
tests/key_store_release_test.go (8)
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:50-62
Timestamp: 2025-03-07T01:35:09.751Z
Learning: In the flow-evm-gateway codebase, panics are acceptable in scenarios where immediate detection of critical bugs is desired during development and testing, particularly for invariant violations that should never occur in a correctly functioning system (e.g., when a key is available but locked in the keystore implementation).
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/batch_tx_pool.go:0-0
Timestamp: 2025-06-19T11:36:25.478Z
Learning: In Go, when copying maps that contain slices (like `map[gethCommon.Address][]pooledEvmTx`), perform deep copies by iterating over the map and copying each slice individually using `make()` and `copy()` to avoid shared references that could lead to race conditions and data corruption.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#615
File: bootstrap/bootstrap.go:167-197
Timestamp: 2024-10-17T18:04:04.165Z
Learning: In the `flow-evm-gateway` Go project, the validation ensuring that `startHeight` is less than or equal to `endHeight` is performed before the `StartTraceDownloader` method in `bootstrap/bootstrap.go`, so additional checks in this method are unnecessary.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#682
File: services/ingestion/event_subscriber.go:85-90
Timestamp: 2025-01-24T20:15:10.908Z
Learning: When calculating blocks to catch up in Flow, initialize the counter to 0 and only set it when the latest chain height is greater than the current height (latestOnChainHeight > currentHeight). There's no need to handle the else case as 0 blocks to catch up is the correct default when we're at the same height or ahead.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#617
File: api/stream.go:62-67
Timestamp: 2024-10-18T19:26:37.579Z
Learning: In the `flow-evm-gateway` project, within the Go file `api/stream.go`, the `prepareBlockResponse` method includes the Bloom filter as the field `LogsBloom` in the returned `Block` struct.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (2)
tests/key_store_release_test.go (2)

123-129: Verify balance calculation logic.

The test expects exactly 5 successful transactions based on the nonce sequence. Ensure this calculation aligns with the actual transaction success criteria.

The balance calculation correctly expects 5 valid transactions (keyCount * transferAmount) based on the nonce sequence where nonces 0, 1, 2, 3, 4 should succeed sequentially.


52-53: Fix undefined variable references.

The variables sc.FungibleToken.Address and sc.FlowToken.Address are referenced but sc is not defined in the imports or anywhere in the file. This will cause compilation errors.

Please ensure these variables are properly imported or defined. They likely should reference Flow service contracts.

⛔ Skipped due to learnings
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: tests/tx_batching_test.go:43-50
Timestamp: 2025-06-17T10:21:14.312Z
Learning: In Go, package-level variables declared in one file are accessible to all other files within the same package. When reviewing Go code, check other files in the same package before flagging variables as undefined. The `sc` variable in the `tests` package is declared in `tests/helpers.go` and used across test files.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:50-62
Timestamp: 2025-03-07T01:35:09.751Z
Learning: In the flow-evm-gateway codebase, panics are acceptable in scenarios where immediate detection of critical bugs is desired during development and testing, particularly for invariant violations that should never occur in a correctly functioning system (e.g., when a key is available but locked in the keystore implementation).

@m-Peter m-Peter force-pushed the mpeter/improve-signing-keys-release-frequency branch 3 times, most recently from 5eb750d to 5d72c1f Compare July 10, 2025 08:13
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (2)
tests/key_store_release_test.go (2)

65-65: Define missing test account variable.

The variable eoaTestAccount is referenced but not defined in this file. This will cause a compilation error.

Please define eoaTestAccount in this test file or import it from the appropriate package where it's declared.


91-91: Define missing private key variable.

The variable eoaTestPrivateKey is referenced but not defined in this file. This will cause a compilation error.

Please define eoaTestPrivateKey in this test file or import it from the appropriate package where it's declared.

🧹 Nitpick comments (1)
tests/key_store_release_test.go (1)

89-89: Replace hardcoded sleep with proper readiness check.

The 3-second sleep after bootstrap completion could make tests flaky. Consider implementing a proper readiness check for the EVM gateway.

You could poll for the gateway's readiness by checking if it responds to RPC calls:

-	time.Sleep(3 * time.Second) // some time for EVM GW to startup
+	// Poll for EVM gateway readiness
+	require.Eventually(t, func() bool {
+		_, err := rpcTester.getBalance(common.HexToAddress("0x0000000000000000000000000000000000000000"))
+		return err == nil
+	}, 10*time.Second, 500*time.Millisecond, "EVM gateway failed to start")
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77998c7 and 5d72c1f.

📒 Files selected for processing (7)
  • bootstrap/bootstrap.go (1 hunks)
  • services/ingestion/event_subscriber.go (1 hunks)
  • services/ingestion/event_subscriber_test.go (5 hunks)
  • services/requester/keystore/key_store.go (5 hunks)
  • services/requester/keystore/key_store_test.go (6 hunks)
  • services/testutils/mock_client.go (2 hunks)
  • tests/key_store_release_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • services/ingestion/event_subscriber.go
  • services/testutils/mock_client.go
  • services/requester/keystore/key_store_test.go
  • bootstrap/bootstrap.go
  • services/ingestion/event_subscriber_test.go
  • services/requester/keystore/key_store.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
tests/key_store_release_test.go (9)
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:50-62
Timestamp: 2025-03-07T01:35:09.751Z
Learning: In the flow-evm-gateway codebase, panics are acceptable in scenarios where immediate detection of critical bugs is desired during development and testing, particularly for invariant violations that should never occur in a correctly functioning system (e.g., when a key is available but locked in the keystore implementation).
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/batch_tx_pool.go:0-0
Timestamp: 2025-06-19T11:36:25.478Z
Learning: In Go, when copying maps that contain slices (like `map[gethCommon.Address][]pooledEvmTx`), perform deep copies by iterating over the map and copying each slice individually using `make()` and `copy()` to avoid shared references that could lead to race conditions and data corruption.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#615
File: bootstrap/bootstrap.go:167-197
Timestamp: 2024-10-17T18:04:04.165Z
Learning: In the `flow-evm-gateway` Go project, the validation ensuring that `startHeight` is less than or equal to `endHeight` is performed before the `StartTraceDownloader` method in `bootstrap/bootstrap.go`, so additional checks in this method are unnecessary.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#682
File: services/ingestion/event_subscriber.go:85-90
Timestamp: 2025-01-24T20:15:10.908Z
Learning: When calculating blocks to catch up in Flow, initialize the counter to 0 and only set it when the latest chain height is greater than the current height (latestOnChainHeight > currentHeight). There's no need to handle the else case as 0 blocks to catch up is the correct default when we're at the same height or ahead.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#617
File: api/stream.go:62-67
Timestamp: 2024-10-18T19:26:37.579Z
Learning: In the `flow-evm-gateway` project, within the Go file `api/stream.go`, the `prepareBlockResponse` method includes the Bloom filter as the field `LogsBloom` in the returned `Block` struct.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: tests/tx_batching_test.go:43-50
Timestamp: 2025-06-17T10:21:14.312Z
Learning: In Go, package-level variables declared in one file are accessible to all other files within the same package. When reviewing Go code, check other files in the same package before flagging variables as undefined. The `sc` variable in the `tests` package is declared in `tests/helpers.go` and used across test files.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (1)
tests/key_store_release_test.go (1)

94-133: Excellent test design for key release validation.

The test logic is well-structured and comprehensive:

  • Uses a clever mix of valid/invalid nonces to test the asynchronous key release mechanism
  • Properly handles expected errors while still testing key release
  • The final balance assertion correctly validates only successful transactions
  • Good use of assert.Eventually for async validation

This effectively tests the new block ID-driven key release mechanism mentioned in the PR objectives.

@m-Peter m-Peter force-pushed the mpeter/improve-signing-keys-release-frequency branch from 5d72c1f to fb95c78 Compare July 10, 2025 08:22
k.keyMu.Lock()
defer k.keyMu.Unlock()

for _, txResult := range txResults {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do we care about the value of txResult.Status here?
Given that we received the transaction results for a given block ID, with: GetTransactionResultsByBlockID, isn't it safe to release the account keys associated with the blocks' transactions, regardless of transaction result status?

Copy link
Contributor

Choose a reason for hiding this comment

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

there are some exceptions that I can think of where simply unlocking the key would be problematic because the transaction failed in a way where sequence number did not increase:

  1. transaction had an incorrect proposer signature
  2. transaction had an incorrect sequence number
  3. incorrect reference block

It might be easiest to simply re-fetch the account key if there was an issue, but not sure if that is feasible performance wise

The alternative is to differentiate the special error cases where the sequence number did not increase...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good call-out @janezpodhostnik . Let me go case by case:

  1. I can't think of a scenario that would make this case manifest. Unless the node operator did something very wrong with the COA setup. But anyway, that would mean that no transactions would go through at all, it won't be a transient behavior. The EVM GW node will be non-functional upon bootstrap.
  2. This case I have seen happen every now and then, so good call-out. However, for every Flow transaction, we do fetch the COA account each time, and we use that sequence number, we don't keep the sequence number of each key in-memory. See 1 & 2.
  3. I think this is also something highly unlikely to happen. For every Flow transaction, we do fetch the latest sealed block, and we use it as the reference block. See 1 & 2 .

That being said, what do you think if we change the logic to unlock the keys for which their transaction result status is TransactionStatusSealed ? This way, for the cases that you mentioned, the respective locked account keys, will only come into availability after ~600 blocks have passed, just like we do now.

The Flow transactions that fail with nonce too low / nonce too high, such as this one: https://www.flowscan.io/tx/7ed8540dabf96edc462c5d7e96925f42abc5ef16b4a32c49bdf7d1fdcc1b9b0f?tab=script, are the majority of the transactions that cause account keys to be locked for ~600 blocks. But they get sealed, so we could take advantage of that.

Copy link
Contributor

Choose a reason for hiding this comment

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

... we do fetch the COA account each time, and we use that sequence number ...

Since we do that I think its OK to just unlock the key here no matter the result. I thought we keep track of the sequence number, so we don't have to query it all the time.

@m-Peter m-Peter force-pushed the mpeter/improve-signing-keys-release-frequency branch 2 times, most recently from fdb49f8 to 06cea2d Compare July 11, 2025 08:47
@m-Peter m-Peter force-pushed the mpeter/improve-signing-keys-release-frequency branch from 06cea2d to 0ba50b2 Compare July 14, 2025 10:28
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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)
tests/key_store_release_test.go (1)

86-88: Remove unnecessary sleep after ready signal.

The ready channel already signals when bootstrap is complete. The additional 3-second sleep is unnecessary and slows down tests.

 <-ready
-
-time.Sleep(3 * time.Second) // some time for EVM GW to startup
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06cea2d and 0ba50b2.

📒 Files selected for processing (7)
  • bootstrap/bootstrap.go (1 hunks)
  • services/ingestion/event_subscriber.go (1 hunks)
  • services/ingestion/event_subscriber_test.go (5 hunks)
  • services/requester/keystore/key_store.go (4 hunks)
  • services/requester/keystore/key_store_test.go (6 hunks)
  • services/testutils/mock_client.go (2 hunks)
  • tests/key_store_release_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • services/ingestion/event_subscriber.go
  • bootstrap/bootstrap.go
  • services/ingestion/event_subscriber_test.go
  • services/testutils/mock_client.go
  • services/requester/keystore/key_store_test.go
  • services/requester/keystore/key_store.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
tests/key_store_release_test.go (9)
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:50-62
Timestamp: 2025-03-07T01:35:09.751Z
Learning: In the flow-evm-gateway codebase, panics are acceptable in scenarios where immediate detection of critical bugs is desired during development and testing, particularly for invariant violations that should never occur in a correctly functioning system (e.g., when a key is available but locked in the keystore implementation).
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/batch_tx_pool.go:0-0
Timestamp: 2025-06-19T11:36:25.478Z
Learning: In Go, when copying maps that contain slices (like `map[gethCommon.Address][]pooledEvmTx`), perform deep copies by iterating over the map and copying each slice individually using `make()` and `copy()` to avoid shared references that could lead to race conditions and data corruption.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#615
File: bootstrap/bootstrap.go:167-197
Timestamp: 2024-10-17T18:04:04.165Z
Learning: In the `flow-evm-gateway` Go project, the validation ensuring that `startHeight` is less than or equal to `endHeight` is performed before the `StartTraceDownloader` method in `bootstrap/bootstrap.go`, so additional checks in this method are unnecessary.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#682
File: services/ingestion/event_subscriber.go:85-90
Timestamp: 2025-01-24T20:15:10.908Z
Learning: When calculating blocks to catch up in Flow, initialize the counter to 0 and only set it when the latest chain height is greater than the current height (latestOnChainHeight > currentHeight). There's no need to handle the else case as 0 blocks to catch up is the correct default when we're at the same height or ahead.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#617
File: api/stream.go:62-67
Timestamp: 2024-10-18T19:26:37.579Z
Learning: In the `flow-evm-gateway` project, within the Go file `api/stream.go`, the `prepareBlockResponse` method includes the Bloom filter as the field `LogsBloom` in the returned `Block` struct.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: tests/tx_batching_test.go:43-50
Timestamp: 2025-06-17T10:21:14.312Z
Learning: In Go, package-level variables declared in one file are accessible to all other files within the same package. When reviewing Go code, check other files in the same package before flagging variables as undefined. The `sc` variable in the `tests` package is declared in `tests/helpers.go` and used across test files.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (5)
tests/key_store_release_test.go (5)

22-44: Well-structured test setup with proper readiness checks.

The test correctly uses context cancellation, proper cleanup patterns, and polls for emulator readiness instead of using hardcoded sleeps. This ensures reliable test execution.


45-72: Effective multi-key account setup for testing key rotation.

The test properly creates a multi-key account with 5 keys to test the key rotation mechanism. The configuration setup correctly uses the created account as the COA (Coinbase of Authority).


90-94: ECDSA key setup is correct.

The conversion from hex to ECDSA private key is handled properly. The test address setup is appropriate for the test scenario.


95-123: Comprehensive test logic for key release mechanism.

The test effectively exercises the new key release mechanism by:

  • Testing both valid and invalid nonces to ensure keys are released regardless of transaction success
  • Validating that the system can handle key rotation with the configured 5-key setup
  • Properly handling expected "nonce too high" errors while logging for debugging

This aligns well with the PR objective of improving signing key release frequency.


125-132: Well-designed final assertion with proper async handling.

The test correctly uses Eventually to wait for all valid transactions to be processed, with appropriate timeout and polling intervals. The expected balance calculation properly accounts for the 5 valid transactions out of the 14 submitted.

Copy link
Contributor

@janezpodhostnik janezpodhostnik left a comment

Choose a reason for hiding this comment

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

Looks good, but we maybe want to look into transaction errors where the sequence number is not increased.

k.keyMu.Lock()
defer k.keyMu.Unlock()

for _, txResult := range txResults {
Copy link
Contributor

Choose a reason for hiding this comment

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

there are some exceptions that I can think of where simply unlocking the key would be problematic because the transaction failed in a way where sequence number did not increase:

  1. transaction had an incorrect proposer signature
  2. transaction had an incorrect sequence number
  3. incorrect reference block

It might be easiest to simply re-fetch the account key if there was an issue, but not sure if that is feasible performance wise

The alternative is to differentiate the special error cases where the sequence number did not increase...

// Optimization to avoid AN calls when no signing keys have
// been used. For example, when back-filling the EVM GW state,
// we don't care about releasing signing keys.
if k.AvailableKeys() == k.size {
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe introduce a function to make it clearer

Suggested change
if k.AvailableKeys() == k.size {
if !k.HasKeysInUse() {

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea 👍 Added in 318b1bb .

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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/keystore/key_store.go (1)

156-167: Consider implementing the TODO optimization for production environments.

The commented-out optimization using available keys ratio could be valuable for reducing Access Node load in production. Based on past review comments, there's concern about the aggressiveness of calling GetTransactionResultsByBlockID for each block.

Consider implementing the available keys ratio optimization as a configurable feature to balance between key release frequency and Access Node load.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ba50b2 and 318b1bb.

📒 Files selected for processing (1)
  • services/requester/keystore/key_store.go (5 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
services/requester/keystore/key_store.go (8)
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/pool.go:202-214
Timestamp: 2025-06-17T10:29:35.941Z
Learning: In Flow EVM Gateway's transaction pool (services/requester/pool.go), signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, not released immediately after sending. The key release happens asynchronously when sealed transactions are ingested via event_subscriber.go NotifyTransaction() -> keystore unsafeUnlockKey() -> key.Done(). This pattern ensures transaction integrity by preventing key reuse before transaction finalization.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:94-97
Timestamp: 2025-03-07T01:34:25.462Z
Learning: The `release` method in KeyStore must use blocking channel sends to ensure no keys are ever dropped from the pool, even if this means potentially blocking until the channel has space.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#615
File: bootstrap/bootstrap.go:167-197
Timestamp: 2024-10-17T18:04:04.165Z
Learning: In the `flow-evm-gateway` Go project, the validation ensuring that `startHeight` is less than or equal to `endHeight` is performed before the `StartTraceDownloader` method in `bootstrap/bootstrap.go`, so additional checks in this method are unnecessary.
Learnt from: m-Peter
PR: onflow/flow-evm-gateway#831
File: services/requester/batch_tx_pool.go:0-0
Timestamp: 2025-06-19T11:36:25.478Z
Learning: In Go, when copying maps that contain slices (like `map[gethCommon.Address][]pooledEvmTx`), perform deep copies by iterating over the map and copying each slice individually using `make()` and `copy()` to avoid shared references that could lead to race conditions and data corruption.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#615
File: services/traces/engine.go:175-178
Timestamp: 2024-10-17T18:05:27.084Z
Learning: In the trace backfill process, returning upon encountering an error when retrieving a block or its Cadence ID is intentional because future blocks are unlikely to exist if these blocks don't.
Learnt from: peterargue
PR: onflow/flow-evm-gateway#772
File: services/requester/keystore/key_store.go:50-62
Timestamp: 2025-03-07T01:35:09.751Z
Learning: In the flow-evm-gateway codebase, panics are acceptable in scenarios where immediate detection of critical bugs is desired during development and testing, particularly for invariant violations that should never occur in a correctly functioning system (e.g., when a key is available but locked in the keystore implementation).
Learnt from: peterargue
PR: onflow/flow-evm-gateway#682
File: services/ingestion/event_subscriber.go:85-90
Timestamp: 2025-01-24T20:15:10.908Z
Learning: When calculating blocks to catch up in Flow, initialize the counter to 0 and only set it when the latest chain height is greater than the current height (latestOnChainHeight > currentHeight). There's no need to handle the else case as 0 blocks to catch up is the correct default when we're at the same height or ahead.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (11)
services/requester/keystore/key_store.go (11)

4-4: Import additions look good.

The new imports (context, access, zerolog) are necessary for the enhanced functionality and properly support the architectural changes.

Also applies to: 9-9, 11-11


24-24: Well-designed struct fields for asynchronous processing.

The addition of client, blockChan, logger, and done fields properly supports the new asynchronous architecture. The buffered channel size of 100 for blockChan is reasonable for handling block notifications without blocking.

Also applies to: 29-34


39-44: Constructor signature change aligns with new requirements.

The updated constructor properly accepts the necessary dependencies (context, client, logger) for the enhanced functionality.


65-70: Conditional goroutine launch is well-reasoned.

The check for ks.size > 0 before launching the processLockedKeys goroutine is appropriate for index-mode deployments where transaction submission is not allowed. The comment clearly explains the rationale.


80-83: HasKeysInUse method provides useful optimization.

This method enables the optimization in processLockedKeys to skip unnecessary Access Node calls when no keys are in use, which is particularly beneficial during state back-filling operations.


109-118: Robust shutdown handling in NotifyBlock.

The implementation properly handles shutdown scenarios by checking the done channel and logging warnings if called during shutdown. This prevents potential blocking on the blockChan during node shutdown.


172-174: Excellent optimization to reduce unnecessary Access Node calls.

The HasKeysInUse() check effectively prevents unnecessary calls to the Access Node when no keys are locked, which is particularly beneficial during back-filling operations.


176-183: Proper error handling and context usage.

The implementation correctly uses the provided context instead of context.Background() and includes appropriate error logging instead of silently ignoring errors. This addresses previous review feedback.


194-197: Safety check prevents potential panics.

The empty slice check at the beginning of releasekeys prevents potential panics when accessing txResults[0] later in the method. This addresses previous review feedback.


202-211: Transaction status consideration.

The current implementation unlocks keys for all transaction results regardless of their status. Based on past review discussion, this approach may unlock keys for transactions that failed in ways where the sequence number didn't increase (e.g., incorrect sequence number, invalid proposer signature).

Please verify that unlocking keys regardless of transaction status aligns with the intended behavior, particularly for failed transactions where the sequence number may not have increased.


207-211: Expiration-based key release maintains backward compatibility.

The logic to unlock keys that have been locked beyond the expiration threshold preserves the existing behavior while adding the new transaction result-based unlocking. This ensures keys aren't permanently locked due to processing failures.

)
return
default:
k.blockChan <- blockID
Copy link
Contributor

Choose a reason for hiding this comment

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

this also needs a select otherwise it could block forever

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch 👍 Added in 720f8c9 .

close(k.done)
return
case blockID := <-k.blockChan:
// TODO: If calling `k.client.GetTransactionResultsByBlockID` for each
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it should be OK for each gw to send a single request per block since it's generally low impact. Some of these requests are served by ENs, but it shouldn't be hard to add caching on ANs if it becomes a problem.

this does highlight that we need a tx streaming endpoint.

close(k.done)
return
case blockID := <-k.blockChan:
// TODO: If calling `k.client.GetTransactionResultsByBlockID` for each
Copy link
Contributor

Choose a reason for hiding this comment

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

how about making these new calls opt-in. that way most GWs would not start polling each block, but the few infra providers that need it could turn it on.

Looking over the implementation, we could probably just skip the lookup if it's not enabled and pass an empty slice

continue
}

txResults, err := k.client.GetTransactionResultsByBlockID(ctx, blockID)
Copy link
Member

Choose a reason for hiding this comment

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

Compare to the previous height based approach, this blockID based approach would query AN for each block, wouldn't it be slower than height based approach?

My understanding is that if GW is indexing each block, then most keys would be released by NotifyTransaction method, only those tx that didn't get included in a block will need to be released by NotifyHeight/NotifyBlock. Since GW is indexing each block, why would it need to query AN for the transaction results. Note, AN could also fall behind, which will introduce more edge cases here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Compare to the previous height based approach, this blockID based approach would query AN for each block, wouldn't it be slower than height based approach?

Not really, querying ANs for each new Flow block increases the capacity of the available signing keys. And this is done outside the ingestion process, in a dedicated goroutine.

My understanding is that if GW is indexing each block, then most keys would be released by NotifyTransaction method, only those tx that didn't get included in a block will need to be released by NotifyHeight/NotifyBlock. Since GW is indexing each block, why would it need to query AN for the transaction results. Note, AN could also fall behind, which will introduce more edge cases here.

We make use of the Event Streaming API, and we subscribe only to the EVM-related events:

  • EVM.TransactionExecuted
  • EVM.BlockExecuted

so NotifyTransaction will release the account keys for all valid EVM transactions, because they emit EVM.TransactionExecuted events.

But invalid EVM transactions do not emit EVM.TransactionExecuted events. The same goes for Flow transactions that expired, or had some sequence number issues. These get released after 600 Flow blocks, because they didn't even get included in a Flow block.

type KeyLock interface {
NotifyTransaction(txID flowsdk.Identifier)
NotifyBlock(blockHeight uint64)
NotifyBlock(blockID flowsdk.Identifier)
Copy link
Member

Choose a reason for hiding this comment

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

Can we add comments to these two methods for scenarios they will be used?

And since this PR is to address the swam of invalid transactions (nonce too lower), I understand it takes time for NotifyBlockByHeight to release them, but why can't GW release them through NotifyTransaction? I think those invalid transactions are all included in the blocks, therefore will be seen and indexed by GW locally, no?

Copy link
Collaborator Author

@m-Peter m-Peter Jul 16, 2025

Choose a reason for hiding this comment

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

Good idea 👍 I added some descriptive comments in 7f0145f .

I think those invalid transactions are all included in the blocks, therefore will be seen and indexed by GW locally, no?

You are right, they are generally included in Flow blocks. But we use the Event Streaming API in our event ingestion, and we only subscribe to these events:

  • EVM.TransactionExecuted
  • EVM.BlockExecuted

Invalid EVM transactions do not emit EVM.TransactionExecuted events. That's why we resort to the gRPC API, GetTransactionResultsByBlockID.

Copy link
Member

@zhangchiqing zhangchiqing Jul 16, 2025

Choose a reason for hiding this comment

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

I understand invalid transaction will not trigger EVM.TransactionExecuted events, because they don't change EVM state, it can be ignored.

But isn't it nicer to be able to recognize invalid txs in GW? So that we can take corresponding actions if they are sent from us. And also reduce the load to ANs, and therefore further improve the release frequency.

When we send the tx, we know both the EVM tx ID and flow tx ID. When GW follows the chain, does it know the flow tx ID of each block? If so, we can release key with that. When we query GetTransactionResultsByBlockID, we kinda fetching tx id for each block, since we only take the tx id, and throw away the actual transaction results, which means we technically only need GetTransactionIDsByBlockID, no?

Or is it possible for An to add a new event such as EVM.TransactionRejected events for invalid transactions? Then we don't even need to fetch tx IDs for each block.

Copy link

@bluesign bluesign Jul 16, 2025

Choose a reason for hiding this comment

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

Or is it possible for An to add a new event such as EVM.TransactionRejected events for invalid transactions? Then we don't even need to fetch tx IDs for each block.

Cannot emit event there as flow transaction panics. But maybe transaction can be modified accordingly.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, the flow tx panics when nonce is low, so the flow tx is a failed tx, which doesn't emit any event. The question is if possible we can change this flow tx to be succeeded, and emit a EVM.TransactionRejected instead. Technically, the flow tx succeeded, just that its internal EVM tx got rejected (not failed, since we didn't even run this tx).

IMO, changing the flow tx from failed to succeed, and letting EVM to emit EVM.TransactionRejected event would be a better approach. Thoughts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We had discussed the possibility of using an EVM event dedicated for this (such as EVM.TransactionRejected), but it has certain drawbacks:

  • Events are stored on-chain
  • Trading bots are spamming 5-10 such transactions per Flow block, so about 5-10 such EVM events will be emitted per block. And that only reflects the activity as of today.
  • Events in general are supposed to give insights about on-chain state changes. In this case, we're introducing an event for a technicality, which doesn't provide any actual insight to devs/users.

In general, I think we're better off using an off-chain approach for the issue at hand.

Choose a reason for hiding this comment

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

I am not sure even if we need to query transactionIDs actually. We query account for each send, can't we just use sequenceID from there.

For example: I locked Index:0, which has seq 42. When I query account if Index:0 has seq 43, it should be unlocked etc.

Copy link
Contributor

Choose a reason for hiding this comment

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

In general, I think we're better off using an off-chain approach for the issue at hand.

I agree with this. I'm also worried about including failure events because that would allow EVM users to cause events to be emitted while paying no gas.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure even if we need to query transactionIDs actually. We query account for each send, can't we just use sequenceID from there.

For example: I locked Index:0, which has seq 42. When I query account if Index:0 has seq 43, it should be unlocked etc.

The problem is that we can't differentiate between a tx that had an invalid evm transaction and failed from one that was delayed and may eventually get executed. currently, we wait for tx expiration. this new system would allow us to detect failures much quicker.

Choose a reason for hiding this comment

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

@peterargue But shouldn't delayed keep the sequence number but others increase?

Only issue maybe can be some AN falling back somehow I guess. But as seq never decrease shouldn't be that much of a deal breaker.

@m-Peter m-Peter force-pushed the mpeter/improve-signing-keys-release-frequency branch from 318b1bb to 87ecd69 Compare July 16, 2025 09:34
k.logger.Warn().Msg(
"received `NotifyBlock` while the server is shutting down",
)
case k.blockChan <- blockHeader:
Copy link
Contributor

Choose a reason for hiding this comment

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

this could block ingestion if processLockedKeys is slow. would it be better to just drop block notifications in that case and rely on the normal height based expirations?

Copy link
Collaborator Author

@m-Peter m-Peter Jul 18, 2025

Choose a reason for hiding this comment

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

That was a very neat catch 💯 It's not that processLockedKeys is actually very slow, but in certain cases it can't keep up with the block notifications coming through the event ingestion.

For example, if a EVM GW node is say 10,000 blocks behind, then it will receive quite a lot of EVM events through the Event Streaming API. This will cause quite a lot of block notifications too for the KeyStore. But since k.blockChan only has a capacity of 200 (it was 100 and I doubled it), then naturally it can't keep up with the incoming number of block notifications. This has the unexpected side-effect of slowing down/blocking the event ingestion.

On the other side, if a EVM GW node has caught up, and is only a few blocks behind, I believe the event ingestion will not experience any delay. We'll be receiving events every ~0.8 seconds (block production), and the capacity of k.blockChan should be enough to handle this block notification rate.

I have applied your suggestion in 28a04f2. I added a default case for the existing select, which basically releases keys only through the normal height-based expiration logic.

@m-Peter m-Peter merged commit ed19e93 into main Jul 18, 2025
2 checks passed
@m-Peter m-Peter deleted the mpeter/improve-signing-keys-release-frequency branch July 18, 2025 13:43
}

for txID, key := range k.usedKeys {
if blockHeight-key.lastLockedBlock.Load() >= accountKeyBlockExpiration {
Copy link
Member

Choose a reason for hiding this comment

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

To avoid overflow

Suggested change
if blockHeight-key.lastLockedBlock.Load() >= accountKeyBlockExpiration {
if blockHeight >= accountKeyBlockExpiration + key.lastLockedBlock.Load() {

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's a good point 👍 I'll fix it in a follow-up PR.


txResults := []*flowsdk.TransactionResult{}
var err error
if k.config.COATxLookupEnabled {
Copy link
Member

Choose a reason for hiding this comment

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

requesting AN for each block is a heavy, maybe we could it when 50% of the key has not been released? I mean if only 1 key is used, we don't have to query AN, and most likely NotifyTransaction would work, or even if it was used by an invalid tx, we still have 99% keys left. Maybe we only need to enable lookup when 50% keys are used.

So instead of using a bool here, we could use a percentage as usedKeyPercentageThresholdForEnableCOATxLookup, which can cover both case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's a good idea. I used to have a TODO comment for exactly what you just described, just with a different ration. See c738a82#diff-4d0c2f3e8fbdcb80962681f8bf741810b15391ed5d0f2d20163e1f8edbd9a78bL175-L187 .

// TODO: If calling k.client.GetTransactionResultsByBlockID for each
// new block, seems to be problematic for ANs, we can take an approach
// like the one below:
// If the available keys ratio is >= 60% of the total signing keys,
// then we can skip checking the transaction result statuses.
// The signing keys from invalid EVM transactions, will eventually
// come again into availability, after accountKeyBlockExpiration
// blocks have passed.

availablekeysRatio := float64(k.AvailableKeys()) / float64(k.size)
if availablekeysRatio >= 0.6 {
	continue
}

defer k.keyMu.Unlock()

for _, txResult := range txResults {
k.unsafeUnlockKey(txResult.TransactionID)
Copy link
Member

Choose a reason for hiding this comment

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

We only care about the TransactionID field of the transaction result.

It would be nice that we can just query GetTransactionIDsByBlockID, as it's much more lightweight. however, there is no such API from AN, but AN does have such storage API .

It's called flow.LightTransactionResult, which only contains transaction ID.

@peterargue is it worth to add this AN API? seems like a good fit for devin AI, no?

But one thing to note is GetTransactionIDsByBlockID might not be able to return a correct order, because it has no collection info. each light transaction result is stored with its Transaction Index, but multiple txs from different collections might have the same tx Index. So we might want to make it explicit as something like GetUnsortedTransactionIDsByBlockID. For our usecase here, unsorted is fine.

Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Just checked LookupTransactionResultsByBlockIDUsingIndex also does not sort transaction results.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's a good point. It would be much lighter if we could just fetch the transaction ID. From the current endpoint, most fields should be rather small, except for Events, which in certain cases could be a bit bulky. But since there's no other option, we can just stick with GetTransactionResultsByBlockID for now.

But one thing to note is GetTransactionIDsByBlockID might not be able to return a correct order, because it has no collection info. each light transaction result is stored with its Transaction Index, but multiple txs from different collections might have the same tx Index. So we might want to make it explicit as something like GetUnsortedTransactionIDsByBlockID. For our usecase here, unsorted is fine.

We do not really care for sorting, we simply need the transaction ID for releasing the corresponding account key.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve release frequency for signing keys

6 participants