Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
d0f320a
examples/revm_chain: scaffold alloy-evm sim harness
diegomrsantos Dec 13, 2025
c07f087
examples/revm_chain: add core types and deterministic state_root
diegomrsantos Dec 13, 2025
9834366
examples/revm_chain: execute txs with alloy-evm
diegomrsantos Dec 13, 2025
e374042
examples/revm_chain: add simplex chain skeleton
diegomrsantos Dec 13, 2025
802a7f8
examples/revm_chain: add block gossip + verification hook
diegomrsantos Dec 13, 2025
d41a554
examples/revm_chain: deterministic simplex+EVM smoke sim
diegomrsantos Dec 13, 2025
811ac5b
examples/revm_chain: split sim harness into modules
diegomrsantos Dec 13, 2025
2c55010
examples/revm_chain: split consensus wiring into modules
diegomrsantos Dec 13, 2025
d159173
examples/revm_chain: split simplex ingress and control plane
diegomrsantos Dec 13, 2025
61fec6a
examples/revm_chain: track threshold-simplex seed
diegomrsantos Dec 13, 2025
6814ddc
examples/revm_chain: add seed precompile
diegomrsantos Dec 13, 2025
fa5d643
examples/revm_chain: document seed + print outcome
diegomrsantos Dec 13, 2025
8a4a826
examples/revm_chain: rustfmt
diegomrsantos Dec 13, 2025
9a3e6fe
examples/revm_chain: clarify execution tests
diegomrsantos Dec 13, 2025
dd4b393
examples/revm_chain: refactor application actor
diegomrsantos Dec 13, 2025
e9955d2
examples/revm_chain: refactor sim node startup
diegomrsantos Dec 13, 2025
017c068
examples/revm_chain: rename application input enum
diegomrsantos Dec 13, 2025
c914463
examples/revm_chain: rename mailbox/control messages
diegomrsantos Dec 13, 2025
f7e66e0
examples/revm_chain: drop unused deps
diegomrsantos Dec 13, 2025
9bfaf8f
examples/revm_chain: expand README
diegomrsantos Dec 13, 2025
9f41ca4
examples/revm_chain: harden sim finalization tracking
diegomrsantos Dec 13, 2025
2cfd113
examples/revm_chain: drop verified blocks from cache
diegomrsantos Dec 13, 2025
1d09c05
examples/revm_chain: clarify tx modeling
diegomrsantos Dec 13, 2025
e43ad62
examples/revm_chain: document block and seed lifecycle
diegomrsantos Dec 13, 2025
2a7da94
cryptography: fix quorum import scoping
diegomrsantos Dec 14, 2025
c2d59a0
examples/revm_chain: fix DKG setup for minimal features
diegomrsantos Dec 14, 2025
6a9f9c7
examples/revm_chain: fix genesis tx lifecycle and clippy
diegomrsantos Dec 14, 2025
654d731
chore: update Cargo.lock
diegomrsantos Dec 14, 2025
9a35035
examples/revm_chain: extract block sync module
diegomrsantos Dec 15, 2025
e184194
examples/revm_chain: rename pending verify types
diegomrsantos Dec 15, 2025
6e12677
docs(examples/revm_chain): clarify module responsibilities
diegomrsantos Dec 15, 2025
d19c150
examples/revm_chain: clarify demo tx and caching
diegomrsantos Dec 15, 2025
f1b3331
examples/revm_chain: add tx ingress and mempool
diegomrsantos Dec 15, 2025
551dcf3
chore(examples/revm_chain): format Cargo.toml
diegomrsantos Dec 17, 2025
3825898
examples/revm_chain: verify stored blocks against context parent
diegomrsantos Dec 17, 2025
d895e1b
docs(examples/revm_chain): clarify contextual verify
diegomrsantos Dec 17, 2025
685802b
examples/revm_chain: implement consensus Block traits
diegomrsantos Dec 18, 2025
d01b9dc
revm_chain: add shared application state
diegomrsantos Dec 18, 2025
5793124
revm_chain: add marshaled RevmApplication
diegomrsantos Dec 18, 2025
f43df9d
revm_chain: add seed and finalization reporters
diegomrsantos Dec 18, 2025
bb4bf69
revm_chain: add marshal wiring dependencies
diegomrsantos Dec 18, 2025
6d45532
revm_chain: wire marshal into simulation
diegomrsantos Dec 18, 2025
f8f64f0
revm_chain: drop legacy mailbox-based plumbing
diegomrsantos Dec 18, 2025
97bc479
revm_chain: document marshal-based flow
diegomrsantos Dec 18, 2025
d8b0ca4
fmt(revm_chain): run rustfmt
diegomrsantos Dec 18, 2025
2e0b2b3
revm_chain: satisfy clippy lints
diegomrsantos Dec 18, 2025
abca1d5
fmt(revm_chain): sort Cargo.toml
diegomrsantos Dec 18, 2025
e6b4dd6
revm_chain: rename seed hash helper
diegomrsantos Dec 18, 2025
53136b2
revm_chain: rename handle to NodeHandle
diegomrsantos Dec 18, 2025
791ad0b
revm_chain: store execution snapshots only
diegomrsantos Dec 18, 2025
65c7dd7
revm_chain: execute finalized blocks before ack
diegomrsantos Dec 18, 2025
c7d0d0d
fmt(revm_chain): run rustfmt
diegomrsantos Dec 18, 2025
4655930
revm_chain: flatten consensus module
diegomrsantos Dec 18, 2025
54bdc8b
revm_chain: remove consensus module
diegomrsantos Dec 18, 2025
5ff1270
revm: rename example directory
diegomrsantos Dec 23, 2025
2be504e
revm: rename crate to commonware-revm
diegomrsantos Dec 23, 2025
e5bea61
revm: rename storage partitions and namespaces
diegomrsantos Dec 23, 2025
6cf0f15
docs(revm): update paths and components
diegomrsantos Dec 23, 2025
a6f0859
Add QMDB adapter module for REVM
diegomrsantos Jan 6, 2026
1f9716b
Track QMDB changes during execution
diegomrsantos Jan 6, 2026
2c02d5e
Persist finalized QMDB changes
diegomrsantos Jan 6, 2026
ede9dbe
Run REVM simulation on tokio runtime
diegomrsantos Jan 6, 2026
78d6b65
Docs: update REVM example notes for QMDB
diegomrsantos Jan 6, 2026
43db784
Fix QMDB state merge across transactions
diegomrsantos Jan 6, 2026
70b6410
Fix QMDB adapter build for asyncdb
diegomrsantos Jan 7, 2026
c574850
Allow tokio sim network to bind on localhost
diegomrsantos Jan 7, 2026
3386f56
Merge upstream/main
diegomrsantos Jan 7, 2026
3c741c4
Update revm example for updated consensus APIs
diegomrsantos Jan 7, 2026
81f45e2
Adapt revm qmdb adapter to new store api
diegomrsantos Jan 7, 2026
0e37243
Resolve clippy warnings in revm qmdb adapter
diegomrsantos Jan 7, 2026
f2e3c4a
Refactor qmdb adapter into submodules
diegomrsantos Jan 7, 2026
89c9b4f
Document QMDB adapter module
diegomrsantos Jan 7, 2026
83f86fd
Split QMDB store plumbing
diegomrsantos Jan 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,490 changes: 2,352 additions & 138 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ members = [
"examples/log",
"examples/sync",
"examples/reshare",
"examples/revm",

# Fuzz builds
"broadcast/fuzz",
Expand Down
39 changes: 39 additions & 0 deletions examples/revm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[package]
name = "commonware-revm"
edition.workspace = true
publish = true
version.workspace = true
license.workspace = true
description = "REVM-based example chain running on threshold-simplex (simplex + threshold BLS signatures)."
readme = "README.md"
homepage.workspace = true
repository = "https://github.com/commonwarexyz/monorepo/tree/main/examples/revm"

[lints]
workspace = true

[dependencies]

# EVM execution
alloy-evm = { version = "=0.25.1", default-features = false, features = ["std"] }
anyhow.workspace = true
bytes.workspace = true
clap.workspace = true
commonware-broadcast.workspace = true
commonware-codec.workspace = true
commonware-consensus.workspace = true
commonware-cryptography.workspace = true
commonware-p2p.workspace = true
commonware-parallel.workspace = true
commonware-runtime.workspace = true
commonware-storage.workspace = true
commonware-utils.workspace = true
futures.workspace = true
governor.workspace = true
rand.workspace = true
revm = { version = "33.1.0", default-features = false, features = ["std", "asyncdb"] }
thiserror.workspace = true

[[bin]]
name = "commonware-revm"
bench = false
87 changes: 87 additions & 0 deletions examples/revm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# commonware-revm

[![Crates.io](https://img.shields.io/crates/v/commonware-revm.svg)](https://crates.io/crates/commonware-revm)

REVM-based example chain driven by threshold-simplex (`commonware_consensus::simplex`) and executed with `alloy-evm`.

## What This Demonstrates

- Threshold-simplex orders opaque 32-byte digests; full blocks are disseminated and backfilled by `commonware_consensus::marshal` over `commonware_p2p::simulated`.
- Blocks carry a batch of EVM transactions plus an advertised 32-byte `state_root` commitment.
- Validators re-execute proposed blocks with `alloy-evm` / `revm` and reject proposals whose `state_root` mismatches.
- State is persisted in QMDB and exposed to REVM via `WrapDatabaseAsync` + `CacheDB` (QMDB is the base store; CacheDB is the speculative overlay).
- State commitment is deterministic and does not require iterating the whole DB:
- `delta = keccak256(Encode(StateChanges))`
- `new_root = keccak256(prev_root || delta)`
- Seed plumbing: threshold-simplex certificate seed signatures are hashed to 32 bytes and injected as `block.prevrandao` (EIP-4399).
- Bonus: a stateful precompile at `0x00000000000000000000000000000000000000ff` returns the current block's `prevrandao` (32 bytes).

## Components

- `src/types.rs`: canonical block/tx types and digest mapping (`ConsensusDigest = sha256(BlockId)`).
- `src/application/`: proposal/verification logic (marshaled), shared state (mempool + DB snapshots), reporters, and query handle.
- `src/execution.rs`: EVM execution (`EthEvmBuilder`) and the seed precompile.
- `src/commitment.rs`: canonical `StateChanges` encoding and rolling `StateRoot` commitment.
- `src/sim/`: tokio, single-process simulation harness (N nodes, simulated P2P).

## How It Works

This example is intentionally "digest-first":

- Simplex agrees on a `ConsensusDigest` for each height (32 bytes).
- The application maps `ConsensusDigest <-> Block` and ensures a digest is only accepted if the
corresponding block re-executes to the advertised `state_root`.

### Block Lifecycle (One Height)

1. Genesis: the application creates the genesis block and prefunds accounts in the EVM DB.
2. Propose: when Simplex asks a leader to propose, the application builds a child block, executes
its txs, stores the block + resulting DB snapshot locally, and returns the full block to the
`commonware_consensus::application::marshaled::Marshaled` wrapper (consensus still orders only
the block commitment digest).
3. Disseminate: marshal broadcasts the full block and serves backfill requests for missing ancestors.
4. Verify: validators re-execute the block on the parent snapshot and accept only if the computed
`state_root` matches the advertised `state_root` (the wrapper notifies marshal on success).
5. Finalize: marshal delivers finalized blocks to the node, the simulation records the digest, and stops after the configured
number of finalizations per node.

The main glue points are `src/sim/node.rs` (wiring) and `src/application/` (application logic).

### Seed Lifecycle

- On notarization/finalization, threshold-simplex emits a seed signature.
- This example hashes that seed signature to 32 bytes and stores it alongside the finalized digest.
- The next block uses the parent digest's stored seed hash as `prevrandao` (EIP-4399).
- The seed precompile returns the current block's `prevrandao` so contracts can read it.

## Run (Tokio Simulation)

```sh
cargo run -p commonware-revm --release -- --nodes 4 --blocks 5 --seed 1
```

Flags:

- `--nodes`: number of validators (default: 4)
- `--blocks`: number of finalized blocks to wait for per node (default: 3)
- `--seed`: seeded DKG + demo inputs (default: 1)

Expected output is consistent for a given `--seed` and includes:

- Finalized head digest (agreed by consensus)
- Final `state_root` commitment
- Final balances for the example accounts
- Latest tracked threshold seed (32 bytes) and the current block's `prevrandao`

## Test

```sh
cargo test -p commonware-revm
```

## Notes and Next Steps

- This is intentionally minimal and does not implement an Ethereum trie; `state_root` is a rolling commitment over per-tx state deltas.
- Transactions are built directly as EVM call environments (no signature/fee market modeling); gas price is set to 0.
- The demo block stream is minimal (a single transfer is injected early); extend `src/application/` to add more tx generation.
- The example now uses a QMDB-backed persistence layer with per-finalized-block batch commits.
154 changes: 154 additions & 0 deletions examples/revm/src/application/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//! Consensus-facing application implementation for the REVM chain example.
//!
//! Threshold-simplex orders only block commitments (digests). Full blocks are disseminated and
//! backfilled by `commonware_consensus::marshal`. The `commonware_consensus::application::marshaled::Marshaled`
//! wrapper bridges these layers by fetching required ancestors from marshal and calling into this
//! module with an `AncestorStream` you can iterate to walk back over pending blocks.
//!
//! The node wiring that wraps this application lives in `examples/revm/src/sim/node.rs`.

use super::state::Shared;
use crate::{
execution::{evm_env, execute_txs},
types::{Block, TxId},
ConsensusDigest, PublicKey,
};
use alloy_evm::revm::primitives::B256;
use commonware_consensus::{
marshal::ingress::mailbox::AncestorStream,
simplex::{scheme::Scheme, types::Context},
Application, VerifyingApplication,
};
use commonware_cryptography::Committable as _;
use commonware_runtime::{Clock, Metrics, Spawner};
use futures::StreamExt as _;
use rand::Rng;
use std::{collections::BTreeSet, marker::PhantomData};

#[derive(Clone)]
pub(crate) struct RevmApplication<S> {
max_txs: usize,
state: Shared,
_scheme: PhantomData<S>,
}

impl<S> RevmApplication<S> {
pub(crate) const fn new(max_txs: usize, state: Shared) -> Self {
Self {
max_txs,
state,
_scheme: PhantomData,
}
}
}

impl<E, S> Application<E> for RevmApplication<S>
where
E: Rng + Spawner + Metrics + Clock,
S: Scheme<ConsensusDigest>
+ commonware_cryptography::certificate::Scheme<PublicKey = PublicKey>,
{
type SigningScheme = S;
type Context = Context<ConsensusDigest, PublicKey>;
type Block = Block;

async fn genesis(&mut self) -> Self::Block {
self.state.genesis_block()
}

async fn propose(
&mut self,
_context: (E, Self::Context),
mut ancestry: AncestorStream<Self::SigningScheme, Self::Block>,
) -> Option<Self::Block> {
let parent = ancestry.next().await?;

// Transactions remain in the mempool until the block that includes them finalizes. Walk
// back over pending ancestors so we do not propose a block that re-includes in-flight txs.
let mut included = BTreeSet::<TxId>::new();
for tx in parent.txs.iter() {
included.insert(tx.id());
}
while let Some(block) = ancestry.next().await {
for tx in block.txs.iter() {
included.insert(tx.id());
}
}

let parent_digest = parent.commitment();
let parent_snapshot = self.state.parent_snapshot(&parent_digest).await?;
let seed_hash = self.state.seed_for_parent(&parent_digest).await;
let prevrandao = seed_hash.unwrap_or_else(|| B256::from(parent_digest.0));

let txs = self.state.build_txs(self.max_txs, &included).await;

let mut child = Block {
parent: parent.id(),
height: parent.height + 1,
prevrandao,
state_root: parent.state_root,
txs,
};

let (db, outcome) = execute_txs(
parent_snapshot.db,
evm_env(child.height, child.prevrandao),
parent.state_root,
&child.txs,
)
.ok()?;
child.state_root = outcome.state_root;

let digest = child.commitment();
self.state
.insert_snapshot(digest, db, child.state_root, outcome.qmdb_changes)
.await;
Some(child)
}
}

impl<E, S> VerifyingApplication<E> for RevmApplication<S>
where
E: Rng + Spawner + Metrics + Clock,
S: Scheme<ConsensusDigest>
+ commonware_cryptography::certificate::Scheme<PublicKey = PublicKey>,
{
async fn verify(
&mut self,
_context: (E, Self::Context),
mut ancestry: AncestorStream<Self::SigningScheme, Self::Block>,
) -> bool {
let block = match ancestry.next().await {
Some(block) => block,
None => return false,
};
let parent = match ancestry.next().await {
Some(block) => block,
None => return false,
};

let parent_digest = parent.commitment();
let Some(parent_snapshot) = self.state.parent_snapshot(&parent_digest).await else {
return false;
};

let (db, outcome) = match execute_txs(
parent_snapshot.db,
evm_env(block.height, block.prevrandao),
parent.state_root,
&block.txs,
) {
Ok(result) => result,
Err(_) => return false,
};
if outcome.state_root != block.state_root {
return false;
}

let digest = block.commitment();
self.state
.insert_snapshot(digest, db, block.state_root, outcome.qmdb_changes)
.await;
true
}
}
39 changes: 39 additions & 0 deletions examples/revm/src/application/handle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! Handle for interacting with the application state.
//!
//! The simulation harness uses this handle to:
//! - submit transactions into the node-local mempool, and
//! - query state at a finalized digest for assertions.

use super::state::Shared;
use crate::{
types::{StateRoot, Tx},
ConsensusDigest,
};
use alloy_evm::revm::primitives::{Address, B256, U256};

#[derive(Clone)]
pub struct NodeHandle {
state: Shared,
}

impl NodeHandle {
pub(crate) const fn new(state: Shared) -> Self {
Self { state }
}

pub async fn submit_tx(&self, tx: Tx) -> bool {
self.state.submit_tx(tx).await
}

pub async fn query_balance(&self, digest: ConsensusDigest, address: Address) -> Option<U256> {
self.state.query_balance(digest, address).await
}

pub async fn query_state_root(&self, digest: ConsensusDigest) -> Option<StateRoot> {
self.state.query_state_root(digest).await
}

pub async fn query_seed(&self, digest: ConsensusDigest) -> Option<B256> {
self.state.query_seed(digest).await
}
}
11 changes: 11 additions & 0 deletions examples/revm/src/application/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! Chain application logic (block production and verification).

mod app;
mod handle;
mod reporters;
mod state;

pub(crate) use app::RevmApplication;
pub use handle::NodeHandle;
pub(crate) use reporters::{FinalizedReporter, SeedReporter};
pub(crate) use state::Shared;
Loading