Skip to content

Network Transaction Builder: Account-level Parallelization #1192

@sergerad

Description

@sergerad

Context

We currently store state in a builder-wide, shared State struct. All actively processed accounts are shared:

    /// Tracks all network accounts with inflight state.
    ///
    /// This is network account deltas, network notes and their nullifiers.
    accounts: HashMap<NetworkAccountPrefix, AccountState>,

The AccountState instances hold account updates and inflight notes:

/// Tracks the state of a network account and its notes.
pub struct AccountState {
    /// The committed account state, if any.
    ///
    /// Its possible this is `None` if the account creation transaction is still inflight.
    committed: Option<Account>,

    /// Inflight account updates in chronological order.
    inflight: VecDeque<Account>,

    /// Unconsumed notes of this account.
    available_notes: HashMap<Nullifier, InflightNetworkNote>,

    /// Notes which have been consumed by transactions that are still inflight.
    nullified_notes: HashMap<Nullifier, InflightNetworkNote>,
}

We have the option to split up network account workloads because network accounts are independent of each other. The main benefit of this is to allow for specialized per-account logic. But we expect to also have wins in terms of performance, complexity, and further functionality as we continue to develop the NTX builder.

High Level Design

Core Components

1. Account Actor (AccountActor)

Each network account prefix gets a dedicated long-running async task that:

  • Maintains isolated account state (inflight notes, nullifiers)
  • Processes transactions sequentially (no internal concurrency)
  • Handles any account-specific configuration and logic
  • Captures panics in order to inform coordinator of impending shutdown
struct AccountActor {
    account_prefix: NetworkAccountPrefix,
    state: AccountState,
    coordinator_rx: mpsc::UnboundedReceiver<CoordinatorMessage>,
    coordinator_tx: mpsc::UnboundedSender<ActorMessage>,
    ntx_context: NtxContext,
    rate_limiter: Arc<Semaphore>,
    config: AccountConfig,
}

pub struct ActorHandle {
    account_prefix: NetworkAccountPrefix,
    coordinator_tx: mpsc::UnboundedSender<CoordinatorMessage>,
    join_handle: tokio::task::JoinHandle<()>,
}

2. Central Coordinator (NtxCoordinator or just existing NetworkTransactionBuilder)

A single coordinator task that:

  • Subscribes to mempool events from the block producer
  • Routes events to appropriate account actors via channels
  • Manages actor lifecycle (spawn, monitor, restart on failure)
  • Implements global rate limiting
  • Maintains a registry of active account actors
  • Handles account discovery and cleanup

This could either be embedded directly into the current NetworkTransactionBuilder::serve_new() loop, or made as a separate struct that is integrated a bit more loosely.

// Within NetworkTransactionBuilder::serve_new()
let mut actor_registry = HashMap::<NetworkAccountPrefix, ActorHandle>::new();
let rate_limiter = Arc::new(Semaphore::new(MAX_IN_PROGRESS_TXS));

loop {
    tokio::select! {
        event = mempool_events.try_next() => {
            // Route to appropriate actors or spawn new ones
        },
        actor_msg = actor_message_rx.recv() => {
            // Handle actor completion/failure messages
        },
        _tick = interval.tick() => {
            // Trigger periodic processing across all actors
        },
    }
}

3. Coordinator-Actor Communication

  • Mempool Events: Coordinator fans out events to relevant actors via bounded channels
  • Control Messages: Coordinator sends lifecycle and configuration messages to actors
  • Status Reporting: Actors report health and completion status back to coordinator
  • Global Coordination: Semaphore-based permits for global rate limiting
enum CoordinatorMessage {
    MempoolEvent(MempoolEvent),
    ConfigUpdate(AccountConfig),
    Shutdown,
}

enum ActorMessage {
    TransactionCompleted { 
        account: NetworkAccountPrefix,
        tx_id: TransactionId, 
        result: Result<()>, NtxError> 
    },
    ActorFailed { 
        account: NetworkAccountPrefix,
        error: NtxError 
    },
    ActorReady { 
        account: NetworkAccountPrefix 
    },
}

Key Flows

Actor Lifecycle Management

Actor management is handled by the NtxCoordinator.

New Transaction → New Network Account → Spawn Actor
Actor Crash → Capture and Send Message to Coordinator → Respawn Actor
Account Deleted → Shutdown Actor

Event Flow

Typical transaction processing would occur as follows:

Block Producer → Coordinator → Account Actor → Coordinator

The second actor message would indicate success or failure.

Sub-issues

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions