Skip to content

refactor(ntx-builder): replace the account actors with a scheduler - #2585

Open
SantiagoPittella wants to merge 1 commit into
santiagopittella-ntx-extract-selectionfrom
santiagopittella-ntx-scheduler-swap
Open

refactor(ntx-builder): replace the account actors with a scheduler#2585
SantiagoPittella wants to merge 1 commit into
santiagopittella-ntx-extract-selectionfrom
santiagopittella-ntx-scheduler-swap

Conversation

@SantiagoPittella

Copy link
Copy Markdown
Collaborator

Summary

Now the loop spawns a task per account, up to --max-concurrent-txs at a time. The task picks notes, executes, proves, submits, and returns what happened, also this loops does the writting.

The scheduler doesn't hold anything in memory per account except which transactions it has submitted and not yet seen committed.

Changelog

[[entry]]
scope       = "ntx-builder"
impact      = "breaking"
description = "Removed `--idle-timeout` and `--max-account-crashes`."

[[entry]]
scope       = "ntx-builder"
impact      = "added"
description = "Added `--max-concurrent-txs` to bound the number of network transactions computed concurrently."

[[entry]]
scope       = "ntx-builder"
impact      = "changed"
description = "Network transactions are now built by short-lived per-account attempts driven by the committed-block loop."

@SantiagoPittella
SantiagoPittella force-pushed the santiagopittella-ntx-scheduler-swap branch from c33fb3c to ccbc958 Compare September 7, 2026 14:18
@SantiagoPittella
SantiagoPittella force-pushed the santiagopittella-ntx-scheduler-swap branch from ccbc958 to 831b6f9 Compare September 8, 2026 13:33
@SantiagoPittella
SantiagoPittella force-pushed the santiagopittella-ntx-scheduler-swap branch from 831b6f9 to c5b29a1 Compare September 9, 2026 21:00

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm not a fan of attempt as a base name.

Perhaps something like NetworkTransactionContext and the current free standing functions could be methods?

Comment on lines +32 to +34
/// Chain tip at submission. With the transaction expiration delta this bounds how long the
/// account stays blocked when the submission never lands.
submitted_at: BlockNumber,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would it make more sense to store expires_at given that's what actually triggers?

Comment on lines +53 to +57
/// The spawned attempt tasks.
tasks: JoinSet<AttemptOutcome>,

/// Accounts with a running attempt, keyed by task id. These occupy the attempt slots.
running: HashMap<Id, AccountId>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Perhaps we should make the utils Tasks generic and use that here?

Comment on lines +64 to +69
/// Maximum number of attempts computed concurrently.
max_concurrent_txs: usize,

/// Number of blocks after which a submitted transaction expires. An in-flight entry older than
/// this is dropped, which releases the account for a new attempt.
tx_expiration_delta: NonZeroU16,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Consider moving to a dedicated Config struct.

Comment on lines +294 to +298
pub async fn shutdown(&mut self) {
self.tasks.shutdown().await;
self.running.clear();
self.in_flight.clear();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

When is this called? I'm surprised this isn't shutdown(self), then no need to keep this re-useable afterwards.

Comment on lines +181 to +192
loop {
match self.tasks.join_next_with_id().await {
Some(Ok((id, outcome))) => {
self.running.remove(&id);
return Ok(outcome);
},
Some(Err(err)) => {
let account_id = self.running.remove(&err.id());
// Cancelled tasks were aborted on shutdown.
if err.is_cancelled() {
continue;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This loop and cancellation check feel a bit weird?

Comment on lines +208 to +212
/// A slot is refilled immediately after an attempt that made progress, so a completed
/// transaction does not leave capacity idle until the next block. An attempt that found no
/// viable work, or that could not run at all, does not trigger a refill: the state that
/// selected its account has not changed, so an immediate re-dispatch could pick the same
/// account again.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is referencing stuff outside of this functions scope.

Comment on lines +263 to +264
// Applied before the failures so a note that is both corrected and penalized keeps the
// backoff block the penalty computes, which is the later of the two.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't quite understand why notes need to be corrected?

@SantiagoPittella
SantiagoPittella force-pushed the santiagopittella-ntx-scheduler-swap branch from c5b29a1 to 14c5a4e Compare September 10, 2026 16:47
@SantiagoPittella
SantiagoPittella force-pushed the santiagopittella-ntx-scheduler-swap branch from 14c5a4e to d5adee5 Compare September 11, 2026 21:27
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