Skip to content

[F97] feat: keep speculative ops in pool, but hard skip them - #5233

Merged
Leo-Besancon merged 3 commits into
mainfrom
5028-f97-speculative-execution-marking-causes-permanent-operation-pool-eviction-after-reorg
Sep 14, 2026
Merged

Leo-Besancon merged 3 commits into
mainfrom
5028-f97-speculative-execution-marking-causes-permanent-operation-pool-eviction-after-reorg

Conversation

@Leo-Besancon

@Leo-Besancon Leo-Besancon commented Aug 27, 2026

Copy link
Copy Markdown
Member
  • document all added functions
  • try in sandbox /simulation/labnet
    • if part of node-launch, checked using the resync_check flag
  • unit tests on the added/changed features
    • make tests compile
    • make tests pass
  • add logs allowing easy debugging in case the changes caused problems
  • if the API has changed, update the API specification

Note: In this version I hard skip speculative ops (and kept the 1/1000° that was there in commented code to drop them in priority if the pool is full). See more details in the discussion below

@Leo-Besancon Leo-Besancon changed the title feat: keep speculative ops in pool, but hard skip them [F97] feat: keep speculative ops in pool, but hard skip them Aug 27, 2026
bilboquet
bilboquet previously approved these changes Sep 3, 2026
@Leo-Besancon

Copy link
Copy Markdown
Member Author

@damip can you take a quick look and see if it's consistant with what you wanted for the reexecution followup (see the main PR comment)?

damip
damip previously requested changes Sep 10, 2026

@damip damip left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good direction, two things to change before merge. Rule for both: block production must be fast and must never block on anything. Dropping or duplicating a few tx is always preferable to slowing down block production.

  1. get_block_operations must not call execution. It runs in the factory thread, inside the pool read guard, right at slot time. The PR adds get_execution_statuses() there: one get_ops_exec_status on every pool op id (up to 600k) behind execution_state.read(), while the execution worker holds the write lock for the whole of execute_candidate_slot / execute_final_slot / read-only requests. And active_history.get_ops_exec_status is O(pool x non-final slots). block_opt_channel_timeout only bounds try_read_for on the pool lock, not this. Today the factory path never touches execution (refresh runs on the buffer in the pool worker) and it must stay that way.
    Fix: an executed: bool on OperationInfo, set in refresh() from the statuses already fetched there, and get_block_operations just does continue on the flag. Zero cost on the production path, travels through replace_with for free. Staleness is at most 5 s and the next block of the same thread is 16 s away, so no duplicate in practice. If one slips in, execution skips it. Fine.

  2. The balance filters undo the fix in the common case. get_sender_balances uses the candidate balance, which already contains the speculative execution. Alice has 10, sends an op spending 9: candidate is 1 after speculative execution, next refresh max_spending <= 1 fails in prefilter_ops, op evicted before any rollback. eliminate_balance_overflows does the same when the sender has other pending ops (marked ops score last, so they are cut first). Only ops small relative to what is left survive. The test does not see it because the mock balance is 1e9.
    Fix: skip both balance checks for ops with a live mark (not selectable anyway, their spend is already in the candidate balance). When the mark disappears the balance is back and the next refresh evaluates them normally. Add a test with a realistic balance: op spending most of it, speculative mark, rollback, selectable again.

@Leo-Besancon on your question: hard skip is right, keep it. Scoring down only would still put every speculatively executed op in the next 2-3 blocks of its thread until the including block is final, each copy ignored by execution, zero fee, pure waste for everyone. The 1/1000 factor is only useful as truncation priority when the pool is full, which is what you did.

Not consensus, node-local, no MIP: execution ignores re-included ops (IncludeOperationError("operation was executed previously"), errors do not interrupt the slot), block validity is unchanged. Close #5028 on merge.

Comment thread massa-pool-worker/src/operation_pool.rs Outdated
Comment thread massa-pool-worker/src/operation_pool.rs Outdated
Comment thread massa-pool-worker/src/tests/operation_pool_tests.rs Outdated

@bilboquet bilboquet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looks ok

@Leo-Besancon
Leo-Besancon added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit e7d0f48 Sep 14, 2026
12 checks passed
@Leo-Besancon
Leo-Besancon deleted the 5028-f97-speculative-execution-marking-causes-permanent-operation-pool-eviction-after-reorg branch September 15, 2026 06:57
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.

[F97] Speculative Execution Marking Causes Permanent Operation Pool Eviction After Reorg

3 participants