vrf-pbft 0.1.0

A Rust implementation of VRF-enhanced PBFT consensus protocol
Documentation
use vrf_pbft::{Config, ConsensusEngine, Error, Node};

#[test]
fn four_node_consensus() {
    let config = Config::new(1);
    let mut engine = ConsensusEngine::new(config);
    for i in 1..=4 {
        engine.add_node(Node::new(i, 1000).unwrap());
    }

    let blocks = engine.run(50).unwrap();
    assert!(
        !blocks.is_empty(),
        "should commit at least one block in 50 rounds"
    );

    // All nodes should have the same ledger length
    let first_len = engine.nodes()[0].ledger().len();
    assert!(engine.nodes().iter().all(|n| n.ledger().len() == first_len));
}

#[test]
fn not_enough_nodes_errors() {
    let config = Config::new(1); // needs 4
    let mut engine = ConsensusEngine::new(config);
    engine.add_node(Node::new(1, 100).unwrap());

    match engine.run_round() {
        Err(Error::NotEnoughNodes { needed: 4, have: 1 }) => {}
        other => panic!("expected NotEnoughNodes, got {:?}", other),
    }
}

#[test]
fn byzantine_nodes_cannot_prevent_consensus() {
    let config = Config::new(2); // f=2, needs 7 nodes
    let mut engine = ConsensusEngine::new(config);

    for i in 1..=7 {
        let mut node = Node::new(i, 1000).unwrap();
        if i <= 2 {
            node.set_byzantine(true);
        }
        engine.add_node(node);
    }

    let blocks = engine.run(200).unwrap();
    assert!(
        !blocks.is_empty(),
        "should reach consensus despite 2 byzantine nodes in 200 rounds"
    );
}

#[test]
fn committed_blocks_form_chain() {
    let config = Config::new(1);
    let mut engine = ConsensusEngine::new(config);
    for i in 1..=4 {
        engine.add_node(Node::new(i, 1000).unwrap());
    }

    let blocks = engine.run(100).unwrap();
    if blocks.len() >= 2 {
        // Each block's prev_hash should match the previous block's hash
        // (they share the same genesis, so consecutive committed blocks link)
        for window in blocks.windows(2) {
            assert_eq!(
                window[1].prev_hash,
                window[0].hash(),
                "block chain is broken at round {}",
                window[1].round
            );
        }
    }
}

#[test]
fn run_returns_correct_count() {
    let config = Config::new(1);
    let mut engine = ConsensusEngine::new(config);
    for i in 1..=4 {
        engine.add_node(Node::new(i, 1000).unwrap());
    }

    let blocks = engine.run(30).unwrap();
    assert_eq!(blocks.len(), engine.committed_blocks().len());
}