Skip to content

Conversation

@m-Peter
Copy link
Collaborator

@m-Peter m-Peter commented Oct 24, 2025

Work Towards: #773

Description

Instead of fetching the entire account info, such as balances and all of its account keys, we can simply fetch only the account key we are going to be using for signing the next Cadence transaction.


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

  • Refactor

    • Simplified transaction construction, signing and submission flows for more reliable and consistent processing.
    • Streamlined latest-block and account/balance handling to reduce errors and latency.
  • Chores

    • Metrics collection updated and extended with rate-limit and dropped-transaction counters for improved observability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 24, 2025

Walkthrough

Refactors remove reliance on full flow.Account objects: operator balance becomes a uint64/*big.Int, signing now receives address + AccountKey, latest-block retrieval simplified, nop metrics gained rate-limiting/drop counters, and request pools updated call signatures and logic accordingly.

Changes

Cohort / File(s) Summary
Metrics Interface & NOP
metrics/collector.go, metrics/nop.go
Removed flow-go-sdk import; OperatorBalance now accepts balance uint64 (collector impls updated); nopCollector adds RequestRateLimited(string), TransactionsDropped(int), and TransactionRateLimited() methods.
Keystore: proposer/payer signing
services/requester/keystore/account_key.go, services/requester/keystore/key_store_test.go
SetProposerPayerAndSign signature changed to (tx, address, acckey); validations, index and sequence number sourcing, and tests updated to use acckey + address instead of flow.Account.
Single Transaction Pool
services/requester/single_tx_pool.go
Removed concurrent fetch of latest block + COA account; buildTransaction now takes ctx and no account; signing fetches account key for COA and calls SetProposerPayerAndSign(tx, coaAddress, accountKey); removed errgroup import.
Batch Transaction Pool
services/requester/batch_tx_pool.go
Replaced COA-specific latest-block/account retrieval with GetLatestBlock(ctx, true); removed account parameter from batchSubmitTransactionsForSameAddress and related buildTransaction calls; updated error messages.
Requester / EVM client usage
services/requester/requester.go
Replaced GetAccount with GetAccountBalanceAtLatestBlock (returns *big.Int); code uses returned balance for metric and checks; variable names and error messages updated.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SingleTxPool
    participant FlowClient
    participant KeyStore

    rect rgb(240,250,255)
    Note over SingleTxPool,KeyStore: Old flow (used full flow.Account)
    Client->>SingleTxPool: Add(tx)
    SingleTxPool->>FlowClient: GetLatestBlock(ctx) & GetAccount(COA)
    FlowClient-->>SingleTxPool: block, account
    SingleTxPool->>SingleTxPool: buildTransaction(block, account, ...)
    SingleTxPool->>KeyStore: SetProposerPayerAndSign(tx, account)
    KeyStore-->>SingleTxPool: signed tx
    end

    rect rgb(255,245,240)
    Note over SingleTxPool,KeyStore: New flow (address + AccountKey)
    Client->>SingleTxPool: Add(tx)
    SingleTxPool->>FlowClient: GetLatestBlock(ctx, true)
    FlowClient-->>SingleTxPool: block
    SingleTxPool->>FlowClient: GetAccountKey(coaAddress, block)
    FlowClient-->>SingleTxPool: accountKey
    SingleTxPool->>SingleTxPool: buildTransaction(ctx, block, ...)
    SingleTxPool->>KeyStore: SetProposerPayerAndSign(tx, coaAddress, accountKey)
    KeyStore-->>SingleTxPool: signed tx
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

Compatibility

Suggested reviewers

  • zhangchiqing
  • janezpodhostnik
  • peterargue

Poem

🐇
I hopped through code with careful feet,
Replaced big accounts with numbers neat.
Address and key now hold the tune,
Metrics count and pools commune.
Hop — a small refactor, bright and sweet.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Update tx submission logic to only fetch a single account key from the COA" directly aligns with the core objective described in the PR: modifying transaction submission to fetch only a specific account key needed for signing, rather than the entire account information. The raw summary confirms this is the central refactoring theme across multiple files—signature changes to SetProposerPayerAndSign, buildTransaction, and OperatorBalance all shift from accepting full account objects to accepting only the necessary account key or balance value. The title is clear, concise, and specific without vague terminology, accurately representing the main change from the developer's perspective.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mpeter/tx-submission-fetch-single-account-key

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

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]

This comment was marked as outdated.

@m-Peter m-Peter force-pushed the mpeter/tx-submission-fetch-single-account-key branch from 616f2b7 to 2490549 Compare October 24, 2025 11:03
@m-Peter m-Peter force-pushed the mpeter/tx-submission-fetch-single-account-key branch from 2490549 to ed88325 Compare October 24, 2025 11:29
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 (4)
services/requester/keystore/key_store_test.go (1)

203-203: Signature update LGTM; consider stronger asserts on envelope signature

Call site matches the new API. Optionally assert signer details and a single envelope sig to harden the test.

- assert.NotEmpty(t, tx.EnvelopeSignatures)
+ if assert.Len(t, tx.EnvelopeSignatures, 1) {
+   sig := tx.EnvelopeSignatures[0]
+   assert.Equal(t, account.Address, sig.Address)
+   assert.Equal(t, account.Keys[0].Index, sig.KeyIndex)
+ }
services/requester/batch_tx_pool.go (2)

219-227: Release signing key on send failure to avoid temporary starvation

If SendTransaction fails, the key remains locked until expiry. Notify the keystore immediately and count drops.

   if err := t.client.SendTransaction(ctx, *flowTx); err != nil {
-    return err
+    // Proactively release the key associated with this Flow tx.
+    t.keystore.NotifyTransaction(flowTx.ID())
+    t.collector.TransactionsDropped(len(hexEncodedTxs))
+    return err
   }

245-263: Do the same early-release for single-submit path

Mirror the early-release on error for single transactions.

   if err := t.client.SendTransaction(ctx, *flowTx); err != nil {
-    return err
+    t.keystore.NotifyTransaction(flowTx.ID())
+    t.collector.TransactionsDropped(1)
+    return err
   }
services/requester/single_tx_pool.go (1)

159-199: Ensure key is released on send failure (call site change in Add)

buildTransaction locks the key and records metadata. If SendTransaction fails, release immediately using NotifyTransaction to avoid waiting for expiry.

   if err := t.client.SendTransaction(ctx, *flowTx); err != nil {
-    return err
+    t.keystore.NotifyTransaction(flowTx.ID())
+    return err
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2490549 and ed88325.

📒 Files selected for processing (4)
  • services/requester/batch_tx_pool.go (4 hunks)
  • services/requester/keystore/account_key.go (1 hunks)
  • services/requester/keystore/key_store_test.go (1 hunks)
  • services/requester/single_tx_pool.go (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
services/requester/single_tx_pool.go (1)
services/requester/keystore/account_key.go (1)
  • AccountKey (11-23)
⏰ 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 (3)
services/requester/single_tx_pool.go (3)

95-107: Latest block via context LGTM

Fetching the sealed latest block with ctx aligns with the new flow and reduces unnecessary COA calls.


159-163: Verified: all call sites successfully updated to new signatures

✓ SetProposerPayerAndSign: Both call sites (single_tx_pool.go:190, key_store_test.go:203) use the 3-argument form
✓ buildTransaction: All three call sites (batch_tx_pool.go:220, batch_tx_pool.go:256, single_tx_pool.go:101) use the new signature with ctx and latestBlock
✓ No legacy call patterns remain


201-208: Verify intentional mutex design with maintainers before removal

The review comment suggests removing the outer mutex because keystore.Take() already enforces per-key exclusivity. However, the signing keys acquired with keystore.Take() are intentionally held until the transaction is sealed, with key release happening asynchronously via event_subscriber.go, and this pattern ensures transaction integrity by preventing key reuse before transaction finalization.

The current implementation serializes all transaction building operations (not just key acquisition), while the keystore itself manages per-key locking through its channel-based mechanism. If multiple signing keys exist, removing the outer mutex would enable concurrent transaction building with different keys. However, the retrieved learning suggests this serialization may be intentional for maintaining transaction integrity guarantees beyond simple per-key exclusivity.

Before removing the mutex, verify:

  1. Whether the serialization serves a purpose beyond per-key locking (e.g., ordering, state consistency)
  2. The impact of allowing concurrent transaction builds when multiple keys exist
  3. Whether the async key release mechanism remains sound under concurrent access patterns

// now that the transaction is prepared, store the transaction's metadata
accKey.SetLockMetadata(flowTx.ID(), latestBlock.Height)

t.collector.OperatorBalance(account)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have removed the OperatorBalance metric, because now we use GetAccountKeyAtLatestBlock, and we don't have access to the COA balance.
I am planning to add this back, on this PR: #896. Generally, we don't have to emit this metric on every tx submission, we can do it at a less frequent rate.

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.

2 participants