Skip to content

feat: continuous-build mode#458

Open
julio4 wants to merge 3 commits intomainfrom
feat/continuous-flashblock-build
Open

feat: continuous-build mode#458
julio4 wants to merge 3 commits intomainfrom
feat/continuous-flashblock-build

Conversation

@julio4
Copy link
Copy Markdown
Member

@julio4 julio4 commented Apr 9, 2026

📝 Summary

Adds a continuous flashblock building mode (under --flashblocks.continuous-build flag) that pre-seals candidates between scheduler triggers, so the best block is ready for instant publish when the timer fires.

Built on top of #438

How it works

  1. Fallback block built as usual
  2. Per flashblock interval, a blocking task runs a candidate loop:
    • Empty candidate sealed first (builder txs only) to guarantees a publishable result even if the scheduler fires immediately or the first candidate takes too much time to build
    • Pool candidates built continuously: clone base state → execute pool txs → add bottom builder txs → seal (state root) → keep if better than current best (by cumulative_gas_used)
    • Loop sleeps with exponential backoff when pool hasn't changed (driven by pool_change_epoch from a pool event listener)
    • When fb_cancel fires (scheduler trigger), the loop exits and returns the best candidate
  3. Publish: the async orchestrator receives the pre-sealed result and publishes instantly via WS + payload channels
  4. Spawn next: new blocking task starts immediately for the next interval

Details

  • Gas limiter isolation: AddressGasLimiter wraps Arc<DashMap>, new snapshot()/restore() methods deep-copy the token buckets so each candidate starts with the same gas budget
  • Pool change detection: A dedicated task listens to Pending and Replaced pool events, incrementing an AtomicU64 epoch. The candidate loop skips rebuilds when the epoch hasn't changed
  • State reconstruction: Each blocking task reconstructs State<DB> from (CacheState, TransitionState) + state_by_block_hash since State isn't Send. build_block is designed to leave transition_state intact (saves/restores it internally)
  • BestCandidate struct: All best-candidate state (cache, transitions, info, committed txs, sealed payload) grouped in a single struct, all updates are structurally enforced to be in same lockstep
  • publish_and_spawn_next function: returns FlashblockAction::Continue { fb_span, build_rx, build_start } or Exit
  • reserve_builder_tx_budget helper: Shared between sequential and continuous paths, adjusts gas/DA/uncompressed limits for bottom-of-block builder txs
  • next_after_seal: Shared between sequential and continuous paths, encapsulates the DA/gas limit advancement for the next flashblock
  • New metrics: continuous_build_duration, candidate_staleness, continuous_candidates_evaluated, continuous_candidates_improved

@julio4 julio4 force-pushed the reapply-async-payload-builder branch from d83dffd to cbe4b7a Compare April 15, 2026 06:45
@julio4 julio4 force-pushed the feat/continuous-flashblock-build branch from 895a4a2 to d45b137 Compare April 15, 2026 07:53
Base automatically changed from reapply-async-payload-builder to main April 16, 2026 10:21
Comment thread crates/op-rbuilder/src/builder/continuous.rs Outdated
Comment thread crates/op-rbuilder/src/metrics.rs
@julio4 julio4 force-pushed the feat/continuous-flashblock-build branch from d45b137 to 3435df5 Compare April 19, 2026 02:45
Comment thread crates/op-rbuilder/src/builder/payload.rs Outdated
Comment thread crates/op-rbuilder/src/builder/continuous.rs Outdated

let metrics = Arc::new(OpRBuilderMetrics::default());
let task_metrics = Arc::new(FlashblocksTaskMetrics::new());
let pool_change_epoch = Arc::new(AtomicU64::new(0));
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.

What is the purpose of this pool change atomic?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It lets the continuous loop detect pool changes without awaiting pool events (i.e. avoiding async overhead in the hot path). It coalesces all pool events into a single 'pool changed' signal, which also lets the loop skip useless iterations (if the pool hasn't changed, we can't produce a better candidate)

Comment thread crates/op-rbuilder/src/builder/payload.rs Outdated
Comment thread crates/op-rbuilder/src/builder/continuous.rs Outdated
@avalonche
Copy link
Copy Markdown
Collaborator

did you push your changes correctly? don't see code changes for comments

@julio4 julio4 force-pushed the feat/continuous-flashblock-build branch 2 times, most recently from 932b00a to 88c1026 Compare April 27, 2026 09:44
Comment thread crates/op-rbuilder/src/builder/continuous.rs Outdated
last_seen_pool_change_epoch = current_epoch;
idle_backoff = Duration::from_millis(1);

// Yield CPU briefly to avoid starving the tokio runtime
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.

Shouldn't you run most of this (creating a new candidate) in a blocking task and then await it in each loop iteration?

);
}

if payload_cancel.is_resolved() || payload_cancel.is_new_fcu() {
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.

Are you not able to make this function async and race against this token?


state.ctx = state.ctx.with_cancel(new_fb_cancel);
let build_start = Instant::now();
let new_shared_best: SharedBest = Arc::new(std::sync::Mutex::new(None));
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.

You already create a SharedBest up in run_continuous_flashblocks so you should use that one

Comment thread crates/op-rbuilder/src/builder/continuous.rs Outdated
@julio4 julio4 force-pushed the feat/continuous-flashblock-build branch from 3c71766 to dbdcaf3 Compare April 30, 2026 09:56
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.

3 participants