Skip to main content

entropa_node/
consensus.rs

1//! Consensus — **Proof of Entropy (PoE)**.
2//!
3//! No mining. No staking. No wasted energy. Each round, exactly one Probe in the
4//! validator set is chosen to propose the next block, and the choice is derived
5//! deterministically from the round's **public randomness beacon** (see
6//! `entropa_core::beacon`) — so every honest node computes the same proposer without
7//! coordination, and no validator (however rich or powerful) can bias who gets
8//! selected.
9//!
10//! This is a stub VRF (hash the beacon). Production replaces `select_proposer`'s inner
11//! draw with a verifiable random function whose proof is committed on-chain.
12
13/// Human-readable name of Entropa's consensus mechanism.
14pub const NAME: &str = "Proof of Entropy (PoE)";
15
16/// A validator's public identity in the set.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Validator {
19    /// Probe fingerprint, e.g. `PROBE-1A2B3C4D`.
20    pub id: String,
21    /// Hex ML-DSA verifying key.
22    pub pubkey_hex: String,
23}
24
25impl Validator {
26    pub fn new(id: impl Into<String>, pubkey_hex: impl Into<String>) -> Self {
27        Self {
28            id: id.into(),
29            pubkey_hex: pubkey_hex.into(),
30        }
31    }
32}
33
34/// Deterministically select the proposer index for a round from the cosmic `beacon`.
35/// Returns `None` if the validator set is empty.
36pub fn select_proposer(validators: &[Validator], beacon: &str) -> Option<usize> {
37    if validators.is_empty() {
38        return None;
39    }
40    let digest = blake3::hash(beacon.as_bytes());
41    let mut buf = [0u8; 8];
42    buf.copy_from_slice(&digest.as_bytes()[..8]);
43    let draw = u64::from_be_bytes(buf);
44    Some((draw % validators.len() as u64) as usize)
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn empty_set_selects_nobody() {
53        assert_eq!(select_proposer(&[], "COSMIC-abc"), None);
54    }
55
56    #[test]
57    fn selection_is_deterministic() {
58        let vs = vec![
59            Validator::new("PROBE-A", "aa"),
60            Validator::new("PROBE-B", "bb"),
61            Validator::new("PROBE-C", "cc"),
62        ];
63        let a = select_proposer(&vs, "COSMIC-round-7");
64        let b = select_proposer(&vs, "COSMIC-round-7");
65        assert_eq!(a, b);
66        assert!(a.unwrap() < vs.len());
67    }
68}