| Crates.io | hashgraph-like-consensus |
| lib.rs | hashgraph-like-consensus |
| version | 0.1.0 |
| created_at | 2025-12-18 10:08:49.62054+00 |
| updated_at | 2025-12-18 10:08:49.62054+00 |
| description | A lightweight Rust library for making binary decisions in networks using hashgraph-style consensus |
| homepage | |
| repository | https://github.com/vacp2p/hashgraph-like-consensus |
| max_upload_size | |
| id | 1992104 |
| size | 302,680 |
A lightweight Rust library for making binary decisions in peer-to-peer or gossipsub networks. Perfect for group governance, voting systems, or any scenario where you need distributed agreement.
This library helps groups of peers vote on proposals and reach consensus, even when some peers are offline or trying to cause trouble. It's based on the Hashgraph-like Consensus Protocol RFC, which means:
The library handles all the tricky parts: validating signatures, checking vote chains, managing timeouts, and determining when consensus is reached.
use hashgraph_like_consensus::{
scope::ScopeID,
service::DefaultConsensusService,
types::CreateProposalRequest,
};
use alloy::signers::local::PrivateKeySigner;
use std::time::Duration;
#[tokio::main]
async fn main() {
let service = DefaultConsensusService::default();
let scope = ScopeID::from("example-scope");
let owner = PrivateKeySigner::random();
let proposal = service.create_proposal(
&scope,
CreateProposalRequest::new(
"Upgrade contract".into(),
"Switch to v2".into(),
owner.address().as_slice().to_vec(),
3, // expected voters
Duration::from_secs(60), // expiration in seconds
true, // tie-breaker favors YES on equality
).unwrap(),
).await.unwrap();
let vote = service.cast_vote(&scope, proposal.proposal_id, true, owner).await.unwrap();
println!("Recorded vote {:?}", vote.vote_id);
}
Scope (Group/Channel)
├── ScopeConfig (defaults for all proposals)
└── Proposals
├── Proposal 1 → Session 1 (inherits scope config)
├── Proposal 2 → Session 2 (inherits scope config)
└── Proposal 3 → Session 3 (overrides scope config)
Scopes carry defaults (network type, thresholds, timeouts) so you don't have to pass config on every proposal. Use the builder to initialize or update a scope:
use hashgraph_like_consensus::{
scope::ScopeID,
scope_config::NetworkType,
service::DefaultConsensusService,
};
async fn example() -> Result<(), Box<dyn std::error::Error>> {
let service = DefaultConsensusService::default();
let scope = ScopeID::from("team_votes");
service
.scope(&scope)
.await?
.with_network_type(NetworkType::P2P)
.with_threshold(0.75)
.with_timeout(120)
.with_liveness_criteria(false)
.initialize()
.await?;
Ok(())
}
// Simple: use defaults (in-memory storage, 10 max sessions per scope)
let service = DefaultConsensusService::default();
// Custom: set your own session limit
let service = DefaultConsensusService::new_with_max_sessions(20);
// Advanced: plug in your own storage and event bus
let service = ConsensusService::new_with_components(
my_storage,
my_event_bus,
10
);
Create a proposal - Start a new voting session:
let proposal = service.create_proposal(
&scope,
CreateProposalRequest::new(
"Upgrade contract".into(),
"Switch to v2".into(),
owner.address().as_slice().to_vec(),
3, // expected voters
60, // expiration in seconds
true, // tie-breaker: YES wins on equality
)?
).await?;
Process incoming proposals - When you receive a proposal from the network:
service.process_incoming_proposal(&scope, proposal).await?;
List proposals - See what's active or finalized:
let active = service.get_active_proposals(&scope).await;
let finalized = service.get_reached_proposals(&scope).await;
Cast your vote - Vote yes or no on a proposal:
let vote = service.cast_vote(&scope, proposal_id, true, signer).await?;
Vote and fetch proposal - Useful for the creator who wants to gossip the updated proposal:
let proposal = service.cast_vote_and_get_proposal(&scope, proposal_id, true, signer).await?;
ConsensusConfig::gossipsub() enforces the RFC’s
two-round flow (round 1 = proposal, round 2 = everyone’s votes).ConsensusConfig::p2p() to derive the round cap dynamically (ceil(2n/3) of expected voters)
and advance one round per vote.Pass configs via create_proposal_with_config (or helper methods) when you need explicit control.
Scope defaults are usually easier: set them once with
service.scope(&scope).await?.with_network_type(...)...initialize().await?,
then override per proposal only when needed.
Process incoming votes - When you receive votes from other peers:
service.process_incoming_vote(&scope, vote).await?;
Get consensus result - See if a proposal has reached consensus:
if let Some(result) = service.get_consensus_result(&scope, proposal_id).await {
println!("Consensus reached: {}", result);
}
Check vote count - See if enough votes have been collected:
let enough_votes = service
.has_sufficient_votes_for_proposal(&scope, proposal_id)
.await?;
Subscribe to events - Get notified when consensus is reached or fails:
use hashgraph_like_consensus::types::ConsensusEvent;
let mut receiver = service.subscribe_to_events();
tokio::spawn(async move {
while let Ok((scope, event)) = receiver.recv().await {
match event {
ConsensusEvent::ConsensusReached { proposal_id, result } => {
println!("Proposal {} reached consensus: {}", proposal_id, result);
}
ConsensusEvent::ConsensusFailed { proposal_id, reason } => {
println!("Proposal {} failed: {}", proposal_id, reason);
}
}
}
});
Get scope statistics - See how many proposals are active, finalized, etc:
let stats = service.get_scope_stats(&scope).await;
println!("Active: {}, Finalized: {}", stats.active_sessions, stats.consensus_reached);
Custom storage - Want to persist proposals to a database? Implement the ConsensusStorage trait:
pub trait ConsensusStorage<Scope> {
async fn save_session(&self, scope: &Scope, session: ConsensusSession) -> Result<()>;
async fn get_session(&self, scope: &Scope, proposal_id: u32) -> Result<Option<ConsensusSession>>;
// ... more methods
}
Custom events - Need different event handling? Implement ConsensusEventBus:
pub trait ConsensusEventBus<Scope> {
type Receiver;
fn subscribe(&self) -> Self::Receiver;
fn publish(&self, scope: Scope, event: ConsensusEvent);
}
Utility functions - The utils module provides helpers for validation and ID generation:
validate_proposal() - Check if a proposal and its votes are validvalidate_vote() - Verify a single vote's signature and structurevalidate_vote_chain() - Ensure vote parent/received hash chains are correcthas_sufficient_votes() - Quick threshold check (count-based only)calculate_consensus_result(votes, expected_voters, consensus_threshold, liveness_criteria_yes) -
Determine the result from collected votes (returns Some(result) when consensus is reached,
otherwise None), using the configured threshold and liveness rulesThis library implements the Hashgraph-like Consensus Protocol RFC. For details on how the protocol works, security guarantees, and edge cases, check out the RFC.