Skip to content

Enable concurrent fee collection#575

Open
janezpodhostnik wants to merge 6 commits intomasterfrom
janez/enable-concurrent-fee-collection
Open

Enable concurrent fee collection#575
janezpodhostnik wants to merge 6 commits intomasterfrom
janez/enable-concurrent-fee-collection

Conversation

@janezpodhostnik
Copy link
Collaborator

@janezpodhostnik janezpodhostnik commented Jan 15, 2026

This PR enables concurrent fee collection by introducing child fee accounts that can collect transaction fees in parallel, reducing contention on the single FlowFees vault during high-throughput scenarios.

Changes:

  • Child Fee Accounts: Fees are now distributed across multiple child accounts using getTransactionIndex() % numChildAccounts, allowing concurrent deposits without vault contention
  • Updated getFeeBalance(): Now aggregates balances from the main vault plus all child fee accounts
  • Updated withdrawTokensFromFeeVault(): Withdraws from the main vault first, then iterates through child accounts if more funds are needed
  • New collectFeesOnChildAccounts() function: Routes deposited fees to the appropriate child account based on transaction index
  • New transaction add_fee_child_accounts.cdc: Allows creating and registering child fee accounts

Falls back to the original single-vault behavior when no child accounts are configured, ensuring existing deployments continue to work.

Missing:

  • Fix for the cadence code checker
  • check epoch transitions work normally

@janezpodhostnik janezpodhostnik self-assigned this Jan 15, 2026
Copy link
Member

@joshuahannan joshuahannan left a comment

Choose a reason for hiding this comment

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

I assume the purpose here is to have many different fee accounts so that depositing fees to the service account doesn't cause conflicts that would prevent concurrent execution, right? How many accounts do we think we would have and is that configurable based on network load or something?

@janezpodhostnik
Copy link
Collaborator Author

@joshuahannan We can add as little or as many child accounts as we want. The number on child accounts affects how many transactions we can theoretically execute concurrently (if they had no other conflicts). The traffic that is currently on mainnet has small blocks and a significant amount of collisions within blocks. With that in mind I don't think we will not need more than 5 child accounts for a long time.

If the blocks become larger, and the transactions more independent, we can add a few child accounts.

Without code changes in the FVM the theoretical maximum for now would be 256.

let accountIndex = Int(txIndex % UInt32(childFeeAccounts!.length))

if let feeAccount = childFeeAccounts![accountIndex].borrow() {
let receiver = feeAccount.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) ?? panic("Could not borrow a Receiver reference on the fee child account")
Copy link
Member

Choose a reason for hiding this comment

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

Should this also fallback to depositing to the main vault if the receiver reference can't be borrowed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The child account if it exists it should have a default receiver, otherwise there might be something wrong with the setup. I think we should panic here, but I you recommend we don't, I can change it.

Copy link
Member

Choose a reason for hiding this comment

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

This looks like it skips the account at index zero. Is that intentional?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

no. This was a bug, thanks!


if let feeAccount = childFeeAccounts![accountIndex].borrow() {

let childVaultRef = feeAccount.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault) ?? panic("Could not borrow child account flowTokenVault")
Copy link
Member

Choose a reason for hiding this comment

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

Should we just continue to the next child account if this borrow fails instead of panicing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

see other comment. The same logic applies here.


for feeAccountRef in childFeeAccounts! {
if let feeAccount = feeAccountRef.borrow() {
totalFees = totalFees + feeAccount.availableBalance
Copy link
Member

Choose a reason for hiding this comment

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

I don't remember, does availableBalance include the storage reservation for the data the account stores? I just want to make sure that the balance we're getting doesn't include FLOW that is reserved to pay for storage fees

Copy link
Collaborator Author

@janezpodhostnik janezpodhostnik Feb 19, 2026

Choose a reason for hiding this comment

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

availableBalance is the balance after excluding the needed storage reservation. so this should be fine.

@janezpodhostnik janezpodhostnik force-pushed the janez/enable-concurrent-fee-collection branch from f0b80cf to 67ce203 Compare February 19, 2026 15:19
@janezpodhostnik janezpodhostnik force-pushed the janez/enable-concurrent-fee-collection branch from 67ce203 to 503ce90 Compare February 19, 2026 15:26
@janezpodhostnik janezpodhostnik marked this pull request as ready for review February 19, 2026 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments