2) Concurrency and Parallelism on Blockchains
Treat the node as a pipeline with clear stage ownership:
- Ingress: receive tx/block from gossip
- Validation: signatures and stateless checks
- Mempool: dedupe and prioritization
- Block production: assemble candidate block
- Commit: apply transitions and persist
- Egress: broadcast newly accepted block
Runtime model
Use one JoinSet to own long-running tasks:
- network loop
- mempool manager
- block producer
- sync worker
- API server
Use bounded mpsc channels between stages so backpressure is explicit and measurable.
#![allow(unused)]
fn main() {
use tokio::{sync::mpsc, task::JoinSet};
pub struct NodeRuntime {
pub joinset: JoinSet<anyhow::Result<()>>,
}
pub fn spawn_node_tasks() -> NodeRuntime {
let (ingress_tx, mut ingress_rx) = mpsc::channel::<Message>(1024);
let (valid_tx, mut valid_rx) = mpsc::channel::<ValidatedTx>(2048);
let mut joinset = JoinSet::new();
joinset.spawn(async move { network_ingress(ingress_tx).await });
joinset.spawn(async move {
while let Some(msg) = ingress_rx.recv().await {
if let Some(vtx) = validate_message(msg).await? {
valid_tx.send(vtx).await?;
}
}
Ok(())
});
joinset.spawn(async move {
while let Some(vtx) = valid_rx.recv().await {
mempool_insert(vtx).await?;
}
Ok(())
});
NodeRuntime { joinset }
}
}
CPU-heavy work
Signature verification and hashing are CPU-heavy. Run them in spawn_blocking (or a CPU pool such as Rayon). Do not block async reactor threads.
Failure modes to call out
- Invalid tx spam: reject early and rate-limit per peer.
- Slow peers: bound outbound queues and use request deadlines.
- Fork storms: isolate fork-choice from ingress and avoid unbounded reorg work.
Deliverable
- Pipeline diagram in docs.
node.rsthat wires tasks and bounded channels with graceful shutdown.