Breaking — the library is now fully synchronous. Every method drops async
and returns its value directly; remove .await from all call sites. tokio is
no longer a dependency.
- All
ConsensusService,ConsensusStorage, andConsensusEventBusmethods, plusConsensusSignatureScheme::signandutils::build_vote, are now sync:// before let vote = service.cast_vote(&scope, id, choice).await?; // after let vote = service.cast_vote(&scope, id, choice)?;
ConsensusStorage::stream_scope_sessionsreturnsimpl Iterator<Item = ...>instead of afutures::Stream.BroadcastEventBusnow fans out overstd::sync::mpsc.subscribe()returns astd::sync::mpsc::Receiver; consume it withrecv()/try_recv()/recv_timeout()(wasrecv().await). Slow subscribers miss events rather than blocking the publisher.InMemoryConsensusStorageusesparking_lot::RwLockinternally.- The default
EthereumConsensusSignersigns via alloy'sSignerSync.
- Optional
ethereumfeature (enabled by default) gating the alloy-backedEthereumConsensusSignerand theDefaultConsensusServicealias. Build withdefault-features = falseto drop the alloy dependency and supply your ownConsensusSignatureScheme.
Remove .await from every consensus call, drop #[tokio::main]/#[tokio::test]
(no async runtime needed), and switch event subscribers from recv().await to a
blocking recv() / recv_timeout().
Breaking — ConsensusService now holds its peer's signer instead of
taking one per call. cast_vote and friends drop the signer parameter.
ConsensusService<Scope, Storage, Event, Signer>now stores aSignerinstance (previouslyPhantomData<fn() -> Signer>). The signer represents this peer's identity for the lifetime of the service; storage, event bus, and signer are all peer-scoped held state.ConsensusSignatureSchemenow requiresClone + Send + Sync + 'static, matchingConsensusStorageandConsensusEventBus.cast_voteandcast_vote_and_get_proposaldrop thesignerparameter and use the service's held signer:// before service.cast_vote(&scope, id, choice, signer).await?; // after service.cast_vote(&scope, id, choice).await?;
ConsensusService::new_with_componentsnow takes the signer:new_with_components(storage, event_bus, signer, max_sessions_per_scope).DefaultConsensusService::new(signer)andnew_with_max_sessions(signer, max)require a signer.Default::default()is removed (no sensible identity to fabricate).utils::build_votenow takessigner: &Signer(was by value).
ConsensusService::signer(&self) -> &Signeraccessor, mirroringstorage()andevent_bus().
// before
let service = DefaultConsensusService::default();
service.cast_vote(&scope, id, true, signer).await?;
// after
let signer = EthereumConsensusSigner::new(PrivateKeySigner::random());
let service = DefaultConsensusService::new(signer);
service.cast_vote(&scope, id, true).await?;For multi-peer setups (one process simulating many identities), construct
one ConsensusService per peer with shared storage and (optionally) a
shared event bus — see the README "Service shape" section.
Breaking — the crate is no longer hard-coded to Ethereum signing.
- New
signingmodule with a singleConsensusSignatureSchemetrait. Each peer implements it for signing (identity,sign) and the same type's staticverifymethod is used by the service to validate incoming votes. Embedders can plug in Ed25519, HSM-backed signers, libchat accounts, or any other scheme without forking the crate. signing::ConsensusSchemeError— athiserror-based enum withSignandVerifyvariants, returned by both scheme operations. Implementors construct variants directly (e.g.ConsensusSchemeError::Sign(msg)).signing::EthereumConsensusSignerprovides the default ECDSA-secp256k1 implementation matching the historical behavior of the crate. It wraps analloy::signers::local::PrivateKeySignerand verifies via recoverable signature recovery against 20-byte addresses.- New
tests/custom_scheme_tests.rsexercises the generic path end-to-end with a non-Ethereum stub scheme.
ConsensusServicenow has a fourth generic parameterSigner: ConsensusSignatureSchemecarried asPhantomData. The scheme is selected at construction (typically via theDefaultConsensusServicealias for Ethereum, or by spelling out the type explicitly for custom schemes).cast_vote/cast_vote_and_get_proposalnow takesigner: Signerinstead of a barealloy_signer::Signer— the signer must impl the scheme the service is parameterized over.utils::build_vote,utils::validate_proposal, andutils::validate_voteare generic over the scheme; downstream callers pick a type at the call site (turbofish or inference).ConsensusSession::from_proposal/initialize_with_votesaccept the scheme as a generic parameter instead of receiving an explicit verifier.
- The hard-coded
SIGNATURE_LENGTHconstant and 65-byte signature assumption inutils.rs. Length and format are now scheme-specific. ConsensusError::MismatchedLengthandConsensusError::InvalidSignaturevariants. Scheme-specific length/encoding errors surface asConsensusError::FailedToVerifyVote(ConsensusVerifyError).From<alloy_signer::Error>forConsensusError. Sign failures now go throughConsensusSignError(wrapping any concrete error type).
If you previously did:
use alloy::signers::local::PrivateKeySigner;
use hashgraph_like_consensus::service::DefaultConsensusService;
let service = DefaultConsensusService::default();
let signer = PrivateKeySigner::random();
service.cast_vote(&scope, proposal_id, true, signer).await?;Wrap the signer in EthereumConsensusSigner:
use alloy::signers::local::PrivateKeySigner;
use hashgraph_like_consensus::{
service::DefaultConsensusService,
signing::EthereumConsensusSigner,
};
let service = DefaultConsensusService::default();
let signer = EthereumConsensusSigner::new(PrivateKeySigner::random());
service.cast_vote(&scope, proposal_id, true, signer).await?;If you call validate_proposal directly, add a turbofish:
use hashgraph_like_consensus::{signing::EthereumConsensusSigner, utils::validate_proposal};
validate_proposal::<EthereumConsensusSigner>(&proposal)?;- Removed the
ConsensusServiceAPItrait. - Timeout liveness fix.
- Storage query defaults.
- Visibility cleanup.