Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/reshare/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ commonware-codec.workspace = true
commonware-consensus.workspace = true
commonware-cryptography.workspace = true
commonware-formatting.workspace = true
commonware-glue.workspace = true
commonware-macros.workspace = true
commonware-math.workspace = true
commonware-p2p.workspace = true
Expand Down
85 changes: 56 additions & 29 deletions examples/reshare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,75 @@

Reshare a threshold secret over an epoched log.

## Setup
## CLI

_To run this example, you must first install [Rust](https://www.rust-lang.org/tools/install) and [`mprocs`](https://github.com/pvolok/mprocs)._

## Usage (Trusted Setup)

First, set up the network participants:
The example has one binary with three subcommands:

```sh
# Default configuration (4 active participants, 2 inactive participants)
cargo run --bin commonware-reshare setup

# With configuration:
cargo run --bin commonware-reshare setup [--num-peers <n>] [--num-bootstrappers <n>] [--datadir <path>] [--base-port <port>]
cargo run --bin commonware-reshare -- setup --node-dir ./data --peers 6 --committee-size 4 --base-port 3000
cargo run --bin commonware-reshare -- setup --bootstrap dkg --node-dir ./data --peers 6 --committee-size 4 --base-port 3000
cargo run --bin commonware-reshare -- dkg --node-dir ./data/validator-0
cargo run --bin commonware-reshare -- validator --node-dir ./data/validator-0
```

Then, run the `mprocs` command emitted by the setup procedure to start all participants simultaneously.
`setup` creates every `validator-*` directory and writes node and network
config. By default, it also performs a trusted threshold setup, writes the
genesis `EpochInfo`, and seeds each active epoch-0 player with its private
share.

## Usage (DKG Setup)
Use `setup --bootstrap dkg` for deployments that want the initial secret
generated by protocol. In this mode, `setup` writes only node and network config
and prints DKG commands only for the epoch-0 committee.

First, set up the network participants:
`dkg` runs the one-shot glue DKG bootstrap for deployments that want the initial
secret generated by protocol instead of trusted setup. Only the epoch-0 players
run it because only they receive shares. After DKG completes, the generated
`genesis.json` is written into every generated validator directory so all
validators can start from the same genesis.

```sh
# Default configuration (4 active participants, 2 inactive participants)
cargo run --bin commonware-reshare setup --with-dkg
`validator` starts the application chain, stateful QMDB, continuous reshare, DKG
orchestrator, DKG anchor, and stateful probe.

## Node Layout

# With configuration:
cargo run --bin commonware-reshare setup --with-dkg [--num-peers <n>] [--num-bootstrappers <n>] [--datadir <path>] [--base-port <port>]
Each validator owns one directory under the configured data directory:

```text
data/
validator-0/
node.json
network.json
genesis.json
secrets.json
runtime/
```

Then, run the first `mprocs` command emitted by the setup procedure to start all participants simultaneously, kicking off the initial DKG.
`node.json` contains the node's Ed25519 signing key and listen/dial addresses.
`network.json` contains the ordered participant list, fixed committee size, and
peer dial addresses. `genesis.json` is written by `setup` or `dkg` and then
consumed by `validator`. `secrets.json` is the file-backed DKG secret store.
`runtime/` is the Commonware storage root for all blob partitions used by that
node.

Once the DKG is complete amongst all participants, shut down the participants and run the second `mprocs` command emitted by the setup procedure
to start all participants again, this time with the distributed threshold secret established.
Committees rotate deterministically: for epoch `E`, the committee starts at
offset `E % participants.len()` and takes `committee_size` consecutive
participants with wraparound.

### Troubleshooting
The application state is intentionally tiny: one `any::unordered::fixed` QMDB,
one fixed key, and each non-genesis block writes its height to that key. Genesis
carries the epoch-0 `EpochInfo`.

If you see an error like `unable to append to journal: Runtime(BlobOpenFailed("engine-consensus", "00000000000000ee", Os { code: 24, kind: Uncategorized, message: "Too many open files" }))`,
you may need to increase the maximum number of open files. You can do this by running:
## State Sync

```bash
ulimit -n 65536
```
`validator --state-sync` is only for a new late joiner whose key is already in a
future committee. It asks peers for a finalized anchor, uses `stateful::probe` to
select a floor, and state-syncs QMDB from that floor before normal processing.

Do not use `--state-sync` as a normal restart flag. It cannot recover a player
that missed private dealing traffic for an epoch in which it was already a
player; those dealings are not replayable as private messages.

## Limits

_MacOS defaults to 256 open files, which is too low for the default settings (where 1 journal file is maintained per recent view)._
Generated data from older versions of this example is not migrated. Recreate the
`data/validator-*` directories when switching to this glue-based example.
112 changes: 112 additions & 0 deletions examples/reshare/src/application.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use crate::types::{Block, Database, Scheme};
use commonware_consensus::{
marshal::ancestry::Ancestry, simplex::types::Context, types::Height, Heightable as _,
};
use commonware_cryptography::{
bls12381::primitives::variant::MinSig, ed25519, sha256, Digestible as _,
};
use commonware_glue::{
dkg::reshare::Input as ReshareInput,
stateful::{
db::{DatabaseSet, Merkleized as _, Unmerkleized as _},
Application, Input, Proposed,
},
};
use commonware_runtime::{BufferPooler, Clock, Metrics, Spawner, Storage};
use commonware_storage::{mmr::Location, qmdb::sync::Target};
use commonware_utils::{non_empty_range, sequence::U64};
use futures::StreamExt;
use rand::Rng;

const HEIGHT_KEY: U64 = U64::new(0);

#[derive(Clone)]
pub struct App {
genesis: Block,
}

impl App {
pub const fn new(genesis: Block) -> Self {
Self { genesis }
}

async fn execute<E: Spawner + Metrics + Clock + Storage + BufferPooler>(
height: Height,
batches: <Database<E> as DatabaseSet<E>>::Unmerkleized,
) -> <Database<E> as DatabaseSet<E>>::Merkleized {
batches
.write(HEIGHT_KEY, Some(U64::new(height.get())))
.merkleize()
.await
.expect("height write must merkleize")
}
}

impl<E> Application<E> for App
where
E: Rng + Spawner + Metrics + Clock + Storage + BufferPooler,
{
type SigningScheme = Scheme;
type Context = Context<sha256::Digest, ed25519::PublicKey>;
type Block = Block;
type Databases = Database<E>;
type Provider = ();
type Input = ReshareInput<(), MinSig, ed25519::PrivateKey>;

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

async fn propose(
&mut self,
context: (E, Self::Context),
mut ancestry: impl Ancestry<Self::Block>,
batches: <Self::Databases as DatabaseSet<E>>::Unmerkleized,
input: Input<Self::Input, Self::Provider>,
) -> Option<Proposed<Self, E>> {
let parent = ancestry.next().await?;
let height = parent.height().next();
// The `reshare::Application` wrapper selected and fetched the payload.
let payload = input.parent.payload;
let merkleized = Self::execute(height, batches).await;
let bounds = merkleized.bounds();
let block = Block {
context: context.1,
parent: parent.digest(),
height,
state_root: merkleized.root(),
range: non_empty_range!(bounds.inactivity_floor, Location::new(bounds.total_size)),
payload,
};
Some(Proposed { block, merkleized })
}

async fn verify(
&mut self,
_context: (E, Self::Context),
mut ancestry: impl Ancestry<Self::Block>,
batches: <Self::Databases as DatabaseSet<E>>::Unmerkleized,
) -> Option<<Self::Databases as DatabaseSet<E>>::Merkleized> {
// Validation from higher layers:
// - Epoch validation is handled by `Deferred`
// - QMDB root / range validation is handled by `stateful::Application`
// - Reshare `Payload` validation is handled by `reshare::Application`

let block = ancestry.next().await?;
let merkleized = Self::execute(block.height(), batches).await;
Some(merkleized)
}

async fn apply(
&mut self,
_context: (E, Self::Context),
block: &Self::Block,
batches: <Self::Databases as DatabaseSet<E>>::Unmerkleized,
) -> <Self::Databases as DatabaseSet<E>>::Merkleized {
Self::execute(block.height(), batches).await
}

fn sync_targets(block: &Self::Block) -> <Self::Databases as DatabaseSet<E>>::SyncTargets {
Target::new(block.state_root, block.range.clone())
}
}
108 changes: 0 additions & 108 deletions examples/reshare/src/application/core.rs

This file was deleted.

10 changes: 0 additions & 10 deletions examples/reshare/src/application/mod.rs

This file was deleted.

Loading
Loading