vrf-pbft 0.1.0

A Rust implementation of VRF-enhanced PBFT consensus protocol
Documentation
use crate::error::{Error, Result};
use crate::pbft::Round;
use crate::types::{Block, NodeId, Role};
use crate::vrf::{sortition, VrfClient, VrfParams};

pub struct Node {
    id: NodeId,
    weight: u64,
    secret_key: Vec<u8>,
    public_key: Vec<u8>,
    seed: u64,
    role: Role,
    round: Option<Round>,
    ledger: Vec<Block>,
    byzantine: bool,
}

impl Node {
    pub fn new(id: NodeId, weight: u64) -> Result<Self> {
        let mut vrf = VrfClient::new()?;
        let (sk, pk) = vrf.generate_keys(id)?;
        Ok(Self {
            id,
            weight,
            secret_key: sk,
            public_key: pk,
            seed: id.wrapping_mul(31).wrapping_add(7),
            role: Role::Normal,
            round: None,
            ledger: vec![Block::genesis()],
            byzantine: false,
        })
    }

    pub fn id(&self) -> NodeId {
        self.id
    }

    pub fn weight(&self) -> u64 {
        self.weight
    }

    pub fn role(&self) -> Role {
        self.role
    }

    pub fn seed(&self) -> u64 {
        self.seed
    }

    pub fn ledger(&self) -> &[Block] {
        &self.ledger
    }

    pub fn public_key(&self) -> &[u8] {
        &self.public_key
    }

    pub fn is_byzantine(&self) -> bool {
        self.byzantine
    }

    pub fn set_byzantine(&mut self, byzantine: bool) {
        self.byzantine = byzantine;
    }

    /// Use VRF sortition to determine this node's role for a round.
    /// Checks roles in order: Proposer, Validator, Packer.
    /// First role selected wins; otherwise Normal.
    pub fn assign_role(
        &mut self,
        round_num: u64,
        total_weight: u64,
        role_expected: &[(Role, u64)],
    ) -> Result<Role> {
        let mut vrf = VrfClient::new()?;

        for &(role, expected) in role_expected {
            let params = VrfParams {
                weight: self.weight,
                round: round_num,
                seed: self.seed,
                role,
            };
            let vrf_proof = vrf.prove(&self.secret_key, &params)?;
            let index = sortition(&vrf_proof.hash, self.weight, expected, total_weight);
            if index > 0 {
                self.role = role;
                return Ok(role);
            }
        }

        self.role = Role::Normal;
        Ok(Role::Normal)
    }

    /// Propose a new block for this round. Only valid for Proposers.
    pub fn propose_block(&self, round_num: u64) -> Result<Block> {
        if self.role != Role::Proposer {
            return Err(Error::WrongRole(Role::Proposer));
        }
        let prev_hash = self
            .ledger
            .last()
            .map(|b| b.hash())
            .unwrap_or([0u8; 32]);
        Ok(Block::new(round_num, prev_hash, self.id, self.seed))
    }

    /// Validate a proposed block. Byzantine nodes always reject.
    pub fn validate_block(&self, block: &Block) -> bool {
        if self.byzantine {
            return false;
        }
        match self.ledger.last() {
            Some(last) => block.prev_hash == last.hash(),
            None => true,
        }
    }

    /// Commit a finalized block to the local ledger.
    pub fn commit_block(&mut self, block: Block) {
        self.seed = block.seed.wrapping_add(block.round);
        self.ledger.push(block);
    }

    pub fn start_round(&mut self, round_num: u64, threshold: u64) {
        self.round = Some(Round::new(round_num, self.role, threshold));
    }
}