Guidance for Claude Code (claude.ai/code) when working in this repository.
Alpenglow is a research reference implementation of the Alpenglow consensus protocol — a high-performance, global Proof-of-Stake blockchain consensus system with erasure coding for data availability. Written in Rust for distributed systems research.
Dev tasks run through a Justfile (cargo install just; just lists recipes,
just setup installs the toolchain once per machine). Only run.sh and
download_data.sh remain of the old *.sh scripts.
cargo build --release # Build in release mode
./run.sh # Run local cluster (alpenglow=debug)
RUST_LOG="alpenglow=debug" cargo run --release --bin=local_cluster
cargo run --release --bin=simulations # Run protocol simulationsOther binaries: node, all2all_test, performance_test, workload_generator.
just test # Fast tests (default), all targets/features
just test-doc # Doctests (nextest doesn't run these)
just test-smoke # Ignored-by-default smoke tests, release mode
just test-sequential # Perf-sensitive tests that must run with --jobs=1
just test-ci # Full CI: test + test-doc + test-smoke + test-sequential
just test-slow # Full slow suite (~10 min)
just test-many # Run fast+sequential 50x to surface flaky tests
cargo nextest run <name> # Run a specific test directlyjust check # Full local CI: editorconfig, fmt, clippy, build, doc, deny, machete, typos, license, fuzz-build, test-ci
just fmt # cargo +nightly fmt --all -- --check
just clippy # cargo clippy --all-targets --all-features -- -D warnings
just doc # cargo doc with -D warningsjust bench # All micro-benchmarks (divan); CI never runs benches
cargo bench --bench crypto # Specific bench (crypto, disseminator, network, shredder)
./download_data.sh # Required before latency simulations (ping dataset)- Alpenglow (
src/consensus.rs) — consensus coordinator; async message loops wiring togetherAll2All,Disseminator,Blockstore,Pool,Votor. - Validator (
src/validator.rs) — pairs anAlpenglowinstance with anExecutionEngineto form a full node. - Blockstore (
src/consensus/blockstore.rs) — stores shreds, reconstructs blocks. - Pool (
src/consensus/pool.rs) — tracks votes/certs, manages finalization. - Votor (
src/consensus/votor.rs) — per-slot voting state machine.
Leader → Shredder → Disseminator → Network
↓
Network → Repair (if needed) → Blockstore → Pool → Votor → All2All → Certificates
Independent channels, each trait-abstracted (All2All, Disseminator,
Network<Send, Recv>) with UDP / TCP / simulated impls:
- All2All (
src/all2all/) — broadcasts votes and certs to all validators. - Disseminator (
src/disseminator/) — spreads block shreds (Rotor or Turbine). - Repair (
src/repair.rs) — point-to-point recovery of missing shreds. Each response carries aDoubleMerkleProof, so untrusted sources can't corrupt data. Requests:LastSliceRoot,SliceRoot,Shred; stake-weighted target sampling. - Transaction network — receives incoming transactions.
Blocks split into slices; each slice Reed-Solomon-coded into shreds:
Block → Slices (fixed-size chunks)
Each Slice → n data + (k−n) coding = k shreds
- Shredder (
src/shredder.rs) — encodes slices. Impls:RegularShredder,CodingOnlyShredder(DA-focused),AontShredder/PetsShredder(all-or-nothing). - Double-Merkle — block tree over slice roots, per-slice tree over shreds. A
Shredis one UDP packet with slice header, Merkle proof, and leader signature.
- Produce (
block_producer.rs) — leader shreds its block and sends via disseminator (DELTA_BLOCK400ms,DELTA_FIRST_SLICE10ms). - Reconstruct — validators collect shreds; n-of-k reconstructs the block →
VotorEvent::BlockReconstructed(viaBlockstore::add_shred_from_disseminator).Votorthen decides whether to vote based on parent availability. - Vote (
votor.rs) — per-slot state machine reacts to block/cert/timeout events, emitsVotes (Notar/Confirm/Finalize) overAll2All. - Certify (
pool.rs) —Poolaggregates votes; at 2/3+ stake forms aCert(BLS aggregate signature) that drives the next phase and tracks finalization. - Repair (
repair.rs) — see Networks above.
- Rotor (
src/disseminator/rotor/) — primary protocol; a Turbine evolution with push-based probabilistic forwarding and configurable sampling (uniform, stake-weighted, Fait Accompli, decaying acceptance). - Turbine (
src/disseminator/turbine/) — Solana's tree-based protocol, deterministic routing by node position.
- Ed25519 (
signature.rs) — block/shred signatures with batch verification. - BLS12-381 (
aggsig.rs, viablst) — aggregate signatures for compact certs. - Double-Merkle (
merkle.rs) —MerkleRoot/MerkleTree/MerkleProof; per-shred verification during repair. SHA-256 for hashing / content addressing.
ExecutionEngine (src/execution.rs) bridges consensus and transaction execution,
running alongside consensus and reporting back over an ExecutionEvent channel.
Four ops: begin_block (first slice), execute_transactions (per slice, pipelined),
end_block (last slice), finalize (commit state, prune unreachable forks). A
placeholder impl lives in the same module.
The simulations binary (src/bin/simulations/) runs discrete-event sims — latency
(real ping data), bandwidth/throughput, and crash/Byzantine robustness — for Rotor,
Alpenglow, Ryse, and Pyjama (one module each). Uses real validator distributions
(Solana/Sui mainnet) and ping datasets (data/pings-*.csv). Configure via constants
at the top of src/bin/simulations/main.rs (RUN_*_TESTS, SAMPLING_STRATEGIES,
SHRED_COMBINATIONS, MAX_BANDWIDTHS).
Domain scalars are newtypes (mostly tuple structs wrapping u64), not bare
aliases — each in its own module under src/types/, re-exported from types:
struct Slot(u64); // src/types/slot.rs
struct Stake(u64); // src/types/stake.rs
struct ValidatorIndex(u64); // src/types/validator_index.rs (NOT `ValidatorId`)
struct SliceIndex(usize); // src/types/slice_index.rs
struct Fraction { numerator, denominator } // src/types/fraction.rsThe only bare aliases are type BlockId = (Slot, BlockHash) (src/lib.rs) and
type BlockHash = DoubleMerkleRoot (src/crypto/merkle.rs). Prefer the newtype
(with its .inner() / constructor API) over raw u64; new domain quantities should
follow the same pattern rather than aliasing u64.
Serialized via wincode (custom library): ConsensusMessage (votes and certs),
Shred, RepairRequest/RepairResponse, Transaction.
DELTA = 250ms // Network synchrony bound
DELTA_BLOCK = 400ms // Leader block production time
DELTA_FIRST_SLICE = 10ms // First slice send deadline
DELTA_TIMEOUT = 750ms // Base voting timeout (3 * DELTA)
DELTA_STANDSTILL = 10s // Standstill detection timeout- Unit tests:
#[cfg(test)] mod testsat the bottom of each file. - Integration tests:
tests/(liveness.rs,smoke_tests.rs). - Slow/perf tests:
#[ignore](run viajust test-smoke/just test-slow). - Sequential tests: need
--jobs=1(run viajust test-sequential). - Test cluster:
create_test_nodes(count)(src/lib.rs, helpers insrc/test_utils.rs) returnsVec<TestNode>on localhost UDP. - Mocks:
mockall#[automock]givesMockDisseminator,MockNetwork, etc.
- Copyright + SPDX header 2. Module doc comment (
//!) 3. Submodule decls - Imports (std, external, internal) 5. Public items 6. Private items
#[cfg(test)] mod tests
- Equalizing operand types: When two sides of a comparison (
==,assert_eq!, etc.) differ only by a reference level, prefer lifting both to a reference with&over lowering both to a value with*— e.g.assert_eq!(&hash, block_hash)orassert_eq!(hash, &block_hash), notassert_eq!(*hash, block_hash). The two forms compile identically (the comparison macros re-borrow), but&doesn't imply a move/copy that isn't happening and reads the same whether or not the type isCopy. Keep this consistent within a file.
- rustfmt runs on nightly (
cargo +nightly fmt) because the config uses unstable options:edition = "2024",group_imports = "StdExternalCrate"(three import groups — std, external crates, thencrate/super/self),imports_granularity = "Module"(oneuseper module path, not merged trees or one-per-item), anduse_field_init_shorthand. Match these when writing imports by hand rather than relying on the formatter to fix them. - Indentation is spaces, width 4 for
*.rs/*.py/*.shand 2 for*.json/*.toml/*.yml/*.yaml(Markdown is unconstrained). Files are UTF-8, LF line endings, with a trailing newline and no trailing whitespace (except Markdown). Enforced byjust editorconfig.
- Mood & structure: Write item docs in the third-person present indicative, as
rustdoc convention dictates — "Creates a new
Votorinstance.", "Returns the slot this vote is for.", "Votorimplements the decision process…". Not imperative ("Create…"), not "This function…". The first line is a single-sentence summary; if more is needed, add a blank///line, then the details. - Intra-doc links: Reference other items with rustdoc link syntax
[`Name`]— e.g.[`ValidatedVote`],[`All2All`],[`super::Pool::finalized_slot`]— not plain backticked text. This is enforced in spirit byjust doc(rustdoc runs with-D warnings), so broken links fail CI. - Fallible fns get an
# Errorssection describing which error variant is returned when; seeValidatedVote::try_new. Public getters that would be misused if ignored are marked#[must_use]. - Every source file starts with the two-line copyright + SPDX header (checked by
just license) and, for modules, a//!module doc comment.
- Tag callout comments with an uppercase prefix + colon, matching
existing usage:
// NOTE:(non-obvious invariant or subtlety),// PERF:(a deliberate performance choice),// SAFETY:(justifies anunsafeblock or a panic-avoidance guard),// TODO:(deferred work),// HACK:(known-ugly workaround). Plain explanatory comments need no tag. - Comments explain why, not what the code already says.
thiserrorfor library errors,anyhowfor binaries/glue. Public/library fallible APIs return a typedpub enum XxxErrorderivingthiserror::Error. Binaries (src/bin/*) and top-level orchestration (consensus.rs) useanyhowwhere a typed error buys nothing.- Error message style:
#[error("…")]messages are lowercase and have no trailing period ("signer is not a validator in the current epoch"). Name the enum<Thing>Erroror<Thing>ValidationError; give each variant a///doc line in addition to its#[error]message. Validated*newtype pattern: To make "this value passed verification" a type-level invariant, wrap the raw type in aValidated*newtype whose only constructor is a fallibletry_new(...) -> Result<Self, XxxValidationError>that runs the checks. Downstream code takes theValidated*type and can assume it is well-formed. Examples:ValidatedVote,ValidatedCert,ValidatedShred.- Panic policy: Never panic on untrusted input (peer messages, network bytes,
byzantine data) — reject it with a
Resultinstead.try_new-style validators guard before any indexing/slicing that could panic on adversarial input (see theUnknownSignerbounds check inValidatedVote::try_new). Reserveexpect()/unwrap()(preferexpect(), with a message documenting the invariant) for genuine local invariants that cannot fail.
- 64-bit assumption: code assumes
usize == 8 bytes; 32-bit unsupported. --release: always use it for realistic performance testing.- Logging: control levels via the
RUST_LOGenv var (logforthcrate). - Standstill recovery: after
DELTA_STANDSTILLwith no progress, thestandstill_loopinAlpenglowcallsPool::recover_from_standstill()(repair requests for missing blocks). - Leader schedule:
EpochInfo::leader(slot)— deterministic, stake-weighted random over slot + validator set (src/consensus/epoch_info.rs).
Create a module in src/disseminator/, implement Disseminator (send, forward,
receive) generic over Network<Shred, Shred> and SamplingStrategy, export from
src/disseminator.rs, wire into create_test_nodes (lib.rs) and add a sim variant
in src/bin/simulations/ as needed.
- Alpenglow Whitepaper
- Alpenglow Presentation
- Related protocols: Kudzu, DispersedSimplex, Simplex, Banyan, Solana TowerBFT