mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 14:48:08 -05:00
consensus: major changes implemented
finalization syncing period implemented, finalization logic improvements, fork logic improvements, use blockhash instead of header hash as identifiers, remove redundant DELTA, minor fixes
This commit is contained in:
@@ -301,7 +301,7 @@ async fn generate(name: &str, folder: &str) -> Result<()> {
|
||||
|
||||
// Data export
|
||||
let state =
|
||||
ValidatorState::new(&sled_db, genesis_ts, genesis_data, wallet, vec![], vec![]).await?;
|
||||
ValidatorState::new(&sled_db, genesis_ts, genesis_data, wallet, vec![]).await?;
|
||||
println!("Exporting data for {:?}", name);
|
||||
let info = StateInfo::new(&*state.read().await);
|
||||
let info_string = format!("{:#?}", info);
|
||||
|
||||
@@ -29,7 +29,7 @@ const SLED_BLOCK_TREE: &[u8] = b"_blocks";
|
||||
const SLED_BLOCK_ORDER_TREE: &[u8] = b"_block_order";
|
||||
|
||||
/// The `HeaderStore` is a `sled` tree storing all the blockchain's blocks' headers
|
||||
/// where the key is the headers's hash, and value is the serialized header.
|
||||
/// where the key is the headers' hash, and value is the serialized header.
|
||||
#[derive(Clone)]
|
||||
pub struct HeaderStore(sled::Tree);
|
||||
|
||||
@@ -115,7 +115,7 @@ impl HeaderStore {
|
||||
}
|
||||
|
||||
/// The `BlockStore` is a `sled` tree storing all the blockchain's blocks
|
||||
/// where the key is the block's headers' hash, and value is the serialized block.
|
||||
/// where the key is the blocks' hash, and value is the serialized block.
|
||||
#[derive(Clone)]
|
||||
pub struct BlockStore(sled::Tree);
|
||||
|
||||
@@ -135,32 +135,42 @@ impl BlockStore {
|
||||
|
||||
/// Insert a slice of [`Block`] into the store. With sled, the
|
||||
/// operation is done as a batch.
|
||||
/// The block's header is used as the key, while value is the serialized [`Block`] itself.
|
||||
pub fn insert(&self, blocks: &[Block]) -> Result<()> {
|
||||
/// The block are hashed with BLAKE3 and this blockhash is used as
|
||||
/// the key, while value is the serialized [`Block`] itself.
|
||||
/// On success, the function returns the block hashes in the same order.
|
||||
pub fn insert(&self, blocks: &[Block]) -> Result<Vec<blake3::Hash>> {
|
||||
let mut ret = Vec::with_capacity(blocks.len());
|
||||
let mut batch = sled::Batch::default();
|
||||
|
||||
for block in blocks {
|
||||
batch.insert(block.header.as_bytes(), serialize(block));
|
||||
let serialized = serialize(block);
|
||||
let blockhash = blake3::hash(&serialized);
|
||||
batch.insert(blockhash.as_bytes(), serialized);
|
||||
ret.push(blockhash);
|
||||
}
|
||||
|
||||
self.0.apply_batch(batch)?;
|
||||
Ok(())
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Check if the blockstore contains a given headerhash.
|
||||
pub fn contains(&self, headerhash: &blake3::Hash) -> Result<bool> {
|
||||
Ok(self.0.contains_key(headerhash.as_bytes())?)
|
||||
/// Check if the blockstore contains a given blockhash.
|
||||
pub fn contains(&self, blockhash: &blake3::Hash) -> Result<bool> {
|
||||
Ok(self.0.contains_key(blockhash.as_bytes())?)
|
||||
}
|
||||
|
||||
/// Fetch given headerhashes from the blockstore.
|
||||
/// Fetch given blockhashhashes from the blockstore.
|
||||
/// The resulting vector contains `Option`, which is `Some` if the block
|
||||
/// was found in the blockstore, and otherwise it is `None`, if it has not.
|
||||
/// The second parameter is a boolean which tells the function to fail in
|
||||
/// case at least one block was not found.
|
||||
pub fn get(&self, headerhashes: &[blake3::Hash], strict: bool) -> Result<Vec<Option<Block>>> {
|
||||
let mut ret = Vec::with_capacity(headerhashes.len());
|
||||
pub fn get(
|
||||
&self,
|
||||
blockhashhashes: &[blake3::Hash],
|
||||
strict: bool,
|
||||
) -> Result<Vec<Option<Block>>> {
|
||||
let mut ret = Vec::with_capacity(blockhashhashes.len());
|
||||
|
||||
for hash in headerhashes {
|
||||
for hash in blockhashhashes {
|
||||
if let Some(found) = self.0.get(hash.as_bytes())? {
|
||||
let block = deserialize(&found)?;
|
||||
ret.push(Some(block));
|
||||
@@ -177,7 +187,7 @@ impl BlockStore {
|
||||
}
|
||||
|
||||
/// Retrieve all blocks from the blockstore in the form of a tuple
|
||||
/// (`headerhash`, `block`).
|
||||
/// (`blockhash`, `block`).
|
||||
/// Be careful as this will try to load everything in memory.
|
||||
pub fn get_all(&self) -> Result<Vec<(blake3::Hash, Block)>> {
|
||||
let mut blocks = vec![];
|
||||
@@ -195,7 +205,7 @@ impl BlockStore {
|
||||
|
||||
/// The `BlockOrderStore` is a `sled` tree storing the order of the
|
||||
/// blockchain's slots, where the key is the slot uid, and the value is
|
||||
/// the block's headers' hash. [`BlockStore`] can be queried with this hash.
|
||||
/// the blocks' hash. [`BlockStore`] can be queried with this hash.
|
||||
#[derive(Clone)]
|
||||
pub struct BlockOrderStore(sled::Tree);
|
||||
|
||||
@@ -208,15 +218,15 @@ impl BlockOrderStore {
|
||||
// In case the store is empty, initialize it with the genesis block.
|
||||
if store.0.is_empty() {
|
||||
let genesis_block = Block::genesis_block(genesis_ts, genesis_data);
|
||||
store.insert(&[0], &[genesis_block.header])?;
|
||||
store.insert(&[0], &[genesis_block.blockhash()])?;
|
||||
}
|
||||
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
/// Insert a slice of slots and headerhashes into the store. With sled, the
|
||||
/// Insert a slice of slots and blockhashes into the store. With sled, the
|
||||
/// operation is done as a batch.
|
||||
/// The block slot is used as the key, and the headerhash is used as value.
|
||||
/// The block slot is used as the key, and the blockhash is used as value.
|
||||
pub fn insert(&self, slots: &[u64], hashes: &[blake3::Hash]) -> Result<()> {
|
||||
assert_eq!(slots.len(), hashes.len());
|
||||
let mut batch = sled::Batch::default();
|
||||
@@ -259,7 +269,7 @@ impl BlockOrderStore {
|
||||
}
|
||||
|
||||
/// Retrieve all slots from the blockorderstore in the form of a tuple
|
||||
/// (`slot`, `headerhash`).
|
||||
/// (`slot`, `blockhash`).
|
||||
/// Be careful as this will try to load everything in memory.
|
||||
pub fn get_all(&self) -> Result<Vec<(u64, blake3::Hash)>> {
|
||||
let mut slots = vec![];
|
||||
@@ -288,8 +298,8 @@ impl BlockOrderStore {
|
||||
if let Some(found) = self.0.get_gt(key.to_be_bytes())? {
|
||||
let key_bytes: [u8; 8] = found.0.as_ref().try_into().unwrap();
|
||||
key = u64::from_be_bytes(key_bytes);
|
||||
let header_hash = deserialize(&found.1)?;
|
||||
ret.push(header_hash);
|
||||
let blockhash = deserialize(&found.1)?;
|
||||
ret.push(blockhash);
|
||||
counter += 1;
|
||||
continue
|
||||
}
|
||||
@@ -299,7 +309,7 @@ impl BlockOrderStore {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Fetch the last block headerhash in the tree, based on the `Ord`
|
||||
/// Fetch the last blockhash in the tree, based on the `Ord`
|
||||
/// implementation for `Vec<u8>`. This should not be able to
|
||||
/// fail because we initialize the store with the genesis block.
|
||||
pub fn get_last(&self) -> Result<(u64, blake3::Hash)> {
|
||||
|
||||
@@ -99,18 +99,18 @@ impl Blockchain {
|
||||
// TODO: Make db writes here completely atomic
|
||||
for block in blocks {
|
||||
// Store transactions
|
||||
let _tx_hashes = self.transactions.insert(&block.txs)?;
|
||||
self.transactions.insert(&block.txs)?;
|
||||
|
||||
// Store header
|
||||
let headerhash = self.headers.insert(&[block.header.clone()])?;
|
||||
ret.push(headerhash[0]);
|
||||
self.headers.insert(&[block.header.clone()])?;
|
||||
|
||||
// Store block
|
||||
let blk: Block = Block::from(block.clone());
|
||||
self.blocks.insert(&[blk])?;
|
||||
let blockhash = self.blocks.insert(&[blk])?;
|
||||
ret.push(blockhash[0]);
|
||||
|
||||
// Store block order
|
||||
self.order.insert(&[block.header.slot], &[headerhash[0]])?;
|
||||
self.order.insert(&[block.header.slot], &[blockhash[0]])?;
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
@@ -126,19 +126,21 @@ impl Blockchain {
|
||||
// TODO: Check if we have all transactions
|
||||
|
||||
// Check provided info produces the same hash
|
||||
Ok(blockhash == block.header.headerhash())
|
||||
Ok(blockhash == block.blockhash())
|
||||
}
|
||||
|
||||
/// Retrieve [`BlockInfo`]s by given hashes. Fails if any of them are not found.
|
||||
pub fn get_blocks_by_hash(&self, hashes: &[blake3::Hash]) -> Result<Vec<BlockInfo>> {
|
||||
let mut ret = Vec::with_capacity(hashes.len());
|
||||
|
||||
let headers = self.headers.get(hashes, true)?;
|
||||
let blocks = self.blocks.get(hashes, true)?;
|
||||
|
||||
for (i, header) in headers.iter().enumerate() {
|
||||
let header = header.clone().unwrap();
|
||||
let block = blocks[i].clone().unwrap();
|
||||
for (i, block) in blocks.iter().enumerate() {
|
||||
let block = block.clone().unwrap();
|
||||
|
||||
let headers = self.headers.get(&[block.header], true)?;
|
||||
// Since we used strict get, its safe to unwrap here
|
||||
let header = headers[0].clone().unwrap();
|
||||
|
||||
let txs = self.transactions.get(&block.txs, true)?;
|
||||
let txs = txs.iter().map(|x| x.clone().unwrap()).collect();
|
||||
@@ -177,8 +179,10 @@ impl Blockchain {
|
||||
|
||||
/// Retrieve last finalized block leader proof hash.
|
||||
pub fn get_last_proof_hash(&self) -> Result<blake3::Hash> {
|
||||
let (slot, _) = self.last().unwrap();
|
||||
let block = &self.get_blocks_by_slot(&[slot]).unwrap()[0];
|
||||
let (_, hash) = self.last().unwrap();
|
||||
let blocks = self.blocks.get(&[hash], true)?;
|
||||
// Since we used strict get, its safe to unwrap here
|
||||
let block = blocks[0].clone().unwrap();
|
||||
let hash = blake3::hash(&serialize(&block.metadata.proof));
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
@@ -219,6 +219,8 @@ impl net::Message for BlockResponse {
|
||||
/// This struct represents a block proposal, used for consensus.
|
||||
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct BlockProposal {
|
||||
/// Block hash
|
||||
pub hash: blake3::Hash,
|
||||
/// Block header hash
|
||||
pub header: blake3::Hash,
|
||||
/// Block data
|
||||
@@ -229,14 +231,16 @@ impl BlockProposal {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(header: Header, txs: Vec<Transaction>, metadata: Metadata) -> Self {
|
||||
let block = BlockInfo::new(header, txs, metadata);
|
||||
let hash = block.blockhash();
|
||||
let header = block.header.headerhash();
|
||||
Self { header, block }
|
||||
Self { hash, header, block }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for BlockProposal {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.header == other.header &&
|
||||
self.hash == other.hash &&
|
||||
self.header == other.header &&
|
||||
self.block.header == other.block.header &&
|
||||
self.block.txs == other.block.txs
|
||||
}
|
||||
@@ -245,8 +249,9 @@ impl PartialEq for BlockProposal {
|
||||
impl fmt::Display for BlockProposal {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_fmt(format_args!(
|
||||
"BlockProposal {{ leader addr: {}, hash: {}, epoch: {}, slot: {}, txs: {} }}",
|
||||
"BlockProposal {{ leader public key: {}, hash: {}, header: {}, epoch: {}, slot: {}, txs: {} }}",
|
||||
self.block.metadata.public_key,
|
||||
self.hash,
|
||||
self.header,
|
||||
self.block.header.epoch,
|
||||
self.block.header.slot,
|
||||
@@ -289,7 +294,7 @@ impl ProposalChain {
|
||||
return false
|
||||
}
|
||||
|
||||
if proposal.block.header.previous != previous.header ||
|
||||
if proposal.block.header.previous != previous.hash ||
|
||||
proposal.block.header.slot <= previous.block.header.slot
|
||||
{
|
||||
debug!("check_proposal(): Provided proposal is invalid.");
|
||||
|
||||
@@ -46,12 +46,12 @@ pub const BLOCK_INFO_MAGIC_BYTES: [u8; 4] = [0x90, 0x44, 0xf1, 0xf6];
|
||||
/// Number of slots in one epoch
|
||||
pub const EPOCH_LENGTH: usize = 10;
|
||||
|
||||
/// Slot time in seconds
|
||||
pub const SLOT_TIME: u64 = 20;
|
||||
|
||||
/// Block leader reward
|
||||
pub const REWARD: u64 = 420;
|
||||
|
||||
/// `2 * DELTA` represents slot time
|
||||
pub const DELTA: u64 = 20;
|
||||
|
||||
/// Leader proofs k for zk proof rows (rows=2^k)
|
||||
pub const LEADER_PROOF_K: u32 = 11;
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ use darkfi_sdk::{
|
||||
};
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
use log::error;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use super::leadcoin::LeadCoin;
|
||||
use crate::{
|
||||
@@ -47,10 +46,10 @@ pub struct Metadata {
|
||||
pub proof: LeadProof,
|
||||
}
|
||||
|
||||
// FIXME: Why do we even need default() ?
|
||||
impl Default for Metadata {
|
||||
/// Default Metadata used in genesis block generation
|
||||
fn default() -> Self {
|
||||
let keypair = Keypair::random(&mut OsRng);
|
||||
let keypair = Keypair::default();
|
||||
let signature = Signature::dummy();
|
||||
let public_inputs = vec![];
|
||||
let winning_index = 0;
|
||||
|
||||
@@ -81,7 +81,7 @@ impl ProtocolProposal {
|
||||
let proposal_copy = (*proposal).clone();
|
||||
|
||||
// Verify we have the proposal already
|
||||
if self.state.read().await.proposal_exists(&proposal_copy.header) {
|
||||
if self.state.read().await.proposal_exists(&proposal_copy.hash) {
|
||||
debug!("ProtocolProposal::handle_receive_proposal(): Proposal already received.");
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -123,10 +123,7 @@ impl ProtocolSync {
|
||||
}
|
||||
};
|
||||
|
||||
info!(
|
||||
"ProtocolSync::handle_receive_block(): Received block: {}",
|
||||
info.header.headerhash()
|
||||
);
|
||||
info!("ProtocolSync::handle_receive_block(): Received block: {}", info.blockhash());
|
||||
|
||||
debug!("ProtocolSync::handle_receive_block(): Processing received block");
|
||||
let info_copy = (*info).clone();
|
||||
|
||||
@@ -39,7 +39,9 @@ use pasta_curves::{
|
||||
use rand::{rngs::OsRng, thread_rng, Rng};
|
||||
|
||||
use super::{
|
||||
constants::{DELTA, EPOCH_LENGTH, LEADER_PROOF_K, LOTTERY_HEAD_START, P, RADIX_BITS, REWARD},
|
||||
constants::{
|
||||
EPOCH_LENGTH, LEADER_PROOF_K, LOTTERY_HEAD_START, P, RADIX_BITS, REWARD, SLOT_TIME,
|
||||
},
|
||||
leadcoin::{LeadCoin, LeadCoinSecrets},
|
||||
utils::fbig2base,
|
||||
Block, BlockInfo, BlockProposal, Float10, Header, LeadProof, Metadata, ProposalChain,
|
||||
@@ -76,8 +78,7 @@ pub struct ConsensusState {
|
||||
|
||||
impl ConsensusState {
|
||||
pub fn new(genesis_ts: Timestamp, genesis_data: blake3::Hash) -> Result<Self> {
|
||||
let genesis_block =
|
||||
blake3::hash(&serialize(&Block::genesis_block(genesis_ts, genesis_data)));
|
||||
let genesis_block = Block::genesis_block(genesis_ts, genesis_data).blockhash();
|
||||
|
||||
Ok(Self {
|
||||
genesis_ts,
|
||||
@@ -243,9 +244,9 @@ impl ValidatorState {
|
||||
}
|
||||
|
||||
/// Calculates current slot, based on elapsed time from the genesis block.
|
||||
/// Slot duration is configured using the `DELTA` value.
|
||||
/// Slot duration is configured using the `SLOT_TIME` constant.
|
||||
pub fn current_slot(&self) -> u64 {
|
||||
self.consensus.genesis_ts.elapsed() / (2 * DELTA)
|
||||
self.consensus.genesis_ts.elapsed() / SLOT_TIME
|
||||
}
|
||||
|
||||
/// Calculates the relative number of the provided slot.
|
||||
@@ -275,12 +276,12 @@ impl ValidatorState {
|
||||
}
|
||||
|
||||
/// Calculates seconds until next Nth slot starting time.
|
||||
/// Slots duration is configured using the delta value.
|
||||
/// Slots duration is configured using the SLOT_TIME constant.
|
||||
pub fn next_n_slot_start(&self, n: u64) -> Duration {
|
||||
assert!(n > 0);
|
||||
let start_time = NaiveDateTime::from_timestamp(self.consensus.genesis_ts.0, 0);
|
||||
let current_slot = self.current_slot() + n;
|
||||
let next_slot_start = (current_slot * (2 * DELTA)) + (start_time.timestamp() as u64);
|
||||
let next_slot_start = (current_slot * SLOT_TIME) + (start_time.timestamp() as u64);
|
||||
let next_slot_start = NaiveDateTime::from_timestamp(next_slot_start as i64, 0);
|
||||
let current_time = NaiveDateTime::from_timestamp(Utc::now().timestamp(), 0);
|
||||
let diff = next_slot_start - current_time;
|
||||
@@ -563,33 +564,27 @@ impl ValidatorState {
|
||||
}
|
||||
|
||||
let hash = match longest {
|
||||
Some(chain) => chain.proposals.last().unwrap().header,
|
||||
Some(chain) => chain.proposals.last().unwrap().hash,
|
||||
None => self.blockchain.last()?.1,
|
||||
};
|
||||
|
||||
Ok((hash, index))
|
||||
}
|
||||
|
||||
/// Given a proposal, the node verify its sender (slot leader), finds which blockchain
|
||||
/// it extends and check if it can be finalized. If the proposal extends
|
||||
/// the canonical blockchain, a new fork chain is created.
|
||||
pub async fn receive_proposal(
|
||||
&mut self,
|
||||
proposal: &BlockProposal,
|
||||
) -> Result<Option<Vec<BlockInfo>>> {
|
||||
/// Given a proposal, the node verify its sender (slot leader) and finds which blockchain
|
||||
/// it extends. If the proposal extends the canonical blockchain, a new fork chain is created.
|
||||
pub async fn receive_proposal(&mut self, proposal: &BlockProposal) -> Result<()> {
|
||||
let current = self.current_slot();
|
||||
// Node hasn't started participating
|
||||
match self.participating {
|
||||
Some(start) => {
|
||||
if current < start {
|
||||
return Ok(None)
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
None => return Ok(None),
|
||||
None => return Ok(()),
|
||||
}
|
||||
|
||||
// TODO: proposal should contain encrypted block info
|
||||
// we decrypt and check blockhash/headerhash is not tampered with
|
||||
let md = &proposal.block.metadata;
|
||||
let hdr = &proposal.block.header;
|
||||
|
||||
@@ -600,6 +595,16 @@ impl ValidatorState {
|
||||
return Err(Error::InvalidSignature)
|
||||
}
|
||||
|
||||
// Check if proposal hash matches actual one
|
||||
let proposal_hash = proposal.block.blockhash();
|
||||
if proposal.hash != proposal_hash {
|
||||
warn!(
|
||||
"receive_proposal(): Received proposal contains mismatched hashes: {} - {}",
|
||||
proposal.hash, proposal_hash
|
||||
);
|
||||
return Err(Error::ProposalHashesMissmatchError)
|
||||
}
|
||||
|
||||
// Check if proposal header matches actual one
|
||||
let proposal_header = hdr.headerhash();
|
||||
if proposal.header != proposal_header {
|
||||
@@ -635,8 +640,7 @@ impl ValidatorState {
|
||||
|
||||
// TODO: [PLACEHOLDER] Add rewards validation
|
||||
|
||||
// Check if proposal fork has can be finalized, to broadcast those blocks
|
||||
let mut to_broadcast = vec![];
|
||||
// Extend corresponding chain
|
||||
match index {
|
||||
-1 => {
|
||||
let pc = ProposalChain::new(self.consensus.genesis_block, proposal.clone());
|
||||
@@ -644,65 +648,65 @@ impl ValidatorState {
|
||||
}
|
||||
_ => {
|
||||
self.consensus.proposals[index as usize].add(proposal);
|
||||
match self.chain_finalization(index as usize).await {
|
||||
Ok(v) => {
|
||||
to_broadcast = v;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("receive_proposal(): Block finalization failed: {}", e);
|
||||
return Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(to_broadcast))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Given a proposal, find the index of the chain it extends.
|
||||
/// Given a proposal, find the index of the fork chain it extends.
|
||||
pub fn find_extended_chain_index(&mut self, proposal: &BlockProposal) -> Result<i64> {
|
||||
let mut fork = None;
|
||||
for (index, chain) in self.consensus.proposals.iter().enumerate() {
|
||||
let last = chain.proposals.last().unwrap();
|
||||
let hash = last.header;
|
||||
if proposal.block.header.previous == hash &&
|
||||
proposal.block.header.slot > last.block.header.slot
|
||||
{
|
||||
return Ok(index as i64)
|
||||
// We iterate through all forks to find which fork to extend
|
||||
let mut chain_index = -1;
|
||||
let mut prop_index = 0;
|
||||
for (c_index, chain) in self.consensus.proposals.iter().enumerate() {
|
||||
// Traverse proposals in reverse
|
||||
for (p_index, prop) in chain.proposals.iter().enumerate().rev() {
|
||||
if proposal.block.header.previous == prop.hash {
|
||||
chain_index = c_index as i64;
|
||||
prop_index = p_index;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if proposal.block.header.previous == last.block.header.previous &&
|
||||
proposal.block.header.slot > last.block.header.slot
|
||||
{
|
||||
fork = Some(chain.clone());
|
||||
if chain_index != -1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut chain) = fork {
|
||||
debug!("find_extended_chain_index(): Proposal to fork a forkchain was received.");
|
||||
chain.proposals.pop(); // removing last block to create the fork
|
||||
if !chain.proposals.is_empty() {
|
||||
// if len is 0 we will verify against blockchain last block
|
||||
self.consensus.proposals.push(chain);
|
||||
return Ok(self.consensus.proposals.len() as i64 - 1)
|
||||
// If no fork was found, we check with canonical
|
||||
if chain_index == -1 {
|
||||
let (last_slot, last_block) = self.blockchain.last()?;
|
||||
if proposal.block.header.previous != last_block ||
|
||||
proposal.block.header.slot <= last_slot
|
||||
{
|
||||
debug!("find_extended_chain_index(): Proposal doesn't extend any known chain");
|
||||
return Ok(-2)
|
||||
}
|
||||
|
||||
// Proposal extends canonical chain
|
||||
return Ok(-1)
|
||||
}
|
||||
|
||||
let (last_slot, last_block) = self.blockchain.last()?;
|
||||
if proposal.block.header.previous != last_block || proposal.block.header.slot <= last_slot {
|
||||
debug!("find_extended_chain_index(): Proposal doesn't extend any known chain");
|
||||
return Ok(-2)
|
||||
// Found fork chain
|
||||
let chain = &self.consensus.proposals[chain_index as usize];
|
||||
// Proposal extends fork at last proposal
|
||||
if prop_index == (chain.proposals.len() - 1) {
|
||||
return Ok(chain_index)
|
||||
}
|
||||
|
||||
Ok(-1)
|
||||
debug!("find_extended_chain_index(): Proposal to fork a forkchain was received.");
|
||||
let mut chain = self.consensus.proposals[chain_index as usize].clone();
|
||||
// We keep all proposals until the one it extends
|
||||
chain.proposals.drain((prop_index + 1)..);
|
||||
self.consensus.proposals.push(chain);
|
||||
Ok(self.consensus.proposals.len() as i64 - 1)
|
||||
}
|
||||
|
||||
/// Search the chains we're holding for the given proposal.
|
||||
pub fn proposal_exists(&self, input_proposal: &blake3::Hash) -> bool {
|
||||
for chain in self.consensus.proposals.iter() {
|
||||
for proposal in chain.proposals.iter() {
|
||||
if input_proposal == &proposal.header {
|
||||
if input_proposal == &proposal.hash {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -722,42 +726,65 @@ impl ValidatorState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Provided an index, the node checks if the chain can be finalized.
|
||||
/// Node checks if any of the fork chains can be finalized.
|
||||
/// Consensus finalization logic:
|
||||
/// - If the node has observed the creation of 3 proposals in a fork chain and no other
|
||||
/// forks exists at same or greater height, it finalizes (appends to canonical blockchain)
|
||||
/// all proposals up to the last one.
|
||||
/// When fork chain proposals are finalized, the rest of fork chains not
|
||||
/// starting by those proposals are removed.
|
||||
pub async fn chain_finalization(&mut self, chain_index: usize) -> Result<Vec<BlockInfo>> {
|
||||
let length = self.consensus.proposals[chain_index].proposals.len();
|
||||
if length < 3 {
|
||||
debug!(
|
||||
"chain_finalization(): Less than 3 proposals in chain {}, nothing to finalize",
|
||||
chain_index
|
||||
);
|
||||
return Ok(vec![])
|
||||
}
|
||||
|
||||
for (i, c) in self.consensus.proposals.iter().enumerate() {
|
||||
if i == chain_index {
|
||||
/// When fork chain proposals are finalized, the rest of fork chains are removed.
|
||||
pub async fn chain_finalization(&mut self) -> Result<Vec<BlockInfo>> {
|
||||
// First we find longest chain without any other forks at same height
|
||||
let mut chain_index = -1;
|
||||
let mut max_length = 0;
|
||||
for (index, chain) in self.consensus.proposals.iter().enumerate() {
|
||||
let length = chain.proposals.len();
|
||||
// Ignore forks with less that 3 blocks
|
||||
if length < 3 {
|
||||
continue
|
||||
}
|
||||
if c.proposals.len() >= length {
|
||||
debug!("chain_finalization(): Same or greater length fork chain exists, nothing to finalize");
|
||||
return Ok(vec![])
|
||||
// Check if less than max
|
||||
if length < max_length {
|
||||
continue
|
||||
}
|
||||
// Check if same length as max
|
||||
if length == max_length {
|
||||
// Setting chain_index so we know we have multiple
|
||||
// forks at same length.
|
||||
chain_index = -2;
|
||||
continue
|
||||
}
|
||||
// Set chain as max
|
||||
chain_index = index as i64;
|
||||
max_length = length;
|
||||
}
|
||||
|
||||
let chain = &mut self.consensus.proposals[chain_index];
|
||||
let bound = length - 1;
|
||||
let mut finalized = vec![];
|
||||
for proposal in &mut chain.proposals[..bound] {
|
||||
// Check if we found any fork to finalize
|
||||
match chain_index {
|
||||
-2 => {
|
||||
debug!("chain_finalization(): Eligible forks with same heigh exist, nothing to finalize");
|
||||
return Ok(vec![])
|
||||
}
|
||||
-1 => {
|
||||
debug!("chain_finalization(): All chains have less than 3 proposals, nothing to finalize");
|
||||
return Ok(vec![])
|
||||
}
|
||||
_ => debug!("chain_finalization(): Chain {} can be finalized!", chain_index),
|
||||
}
|
||||
|
||||
// Starting finalization
|
||||
let mut chain = self.consensus.proposals[chain_index as usize].clone();
|
||||
|
||||
// Retrieving proposals to finalize
|
||||
let bound = max_length - 1;
|
||||
let mut finalized: Vec<BlockInfo> = vec![];
|
||||
for proposal in &chain.proposals[..bound] {
|
||||
finalized.push(proposal.clone().into());
|
||||
}
|
||||
|
||||
chain.proposals.drain(0..bound);
|
||||
// Removing finalized proposals from chain
|
||||
chain.proposals.drain(..bound);
|
||||
|
||||
// Adding finalized proposals to canonical
|
||||
info!("consensus: Adding {} finalized block to canonical chain.", finalized.len());
|
||||
let blockhashes = match self.blockchain.add(&finalized) {
|
||||
Ok(v) => v,
|
||||
@@ -767,6 +794,7 @@ impl ValidatorState {
|
||||
}
|
||||
};
|
||||
|
||||
// Validating state transitions
|
||||
for proposal in &finalized {
|
||||
// TODO: Is this the right place? We're already doing this in protocol_sync.
|
||||
// TODO: These state transitions have already been checked. (I wrote this, but where?)
|
||||
@@ -779,20 +807,9 @@ impl ValidatorState {
|
||||
}
|
||||
}
|
||||
|
||||
let last_block = *blockhashes.last().unwrap();
|
||||
let last_slot = finalized.last().unwrap().header.slot;
|
||||
|
||||
let mut dropped = vec![];
|
||||
for chain in self.consensus.proposals.iter() {
|
||||
let first = chain.proposals.first().unwrap();
|
||||
if first.block.header.previous != last_block || first.block.header.slot <= last_slot {
|
||||
dropped.push(chain.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for chain in dropped {
|
||||
self.consensus.proposals.retain(|c| *c != chain);
|
||||
}
|
||||
// Removing rest forks
|
||||
self.consensus.proposals = vec![];
|
||||
self.consensus.proposals.push(chain);
|
||||
|
||||
Ok(finalized)
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ use crate::{consensus::ValidatorStatePtr, net::P2pPtr, util::async_util::sleep};
|
||||
|
||||
/// async task used for participating in the consensus protocol
|
||||
pub async fn proposal_task(consensus_p2p: P2pPtr, sync_p2p: P2pPtr, state: ValidatorStatePtr) {
|
||||
// Node waits just before the current or next epoch end, so it can
|
||||
// Node waits just before the current or next epoch last finalization syncing period, so it can
|
||||
// start syncing latest state.
|
||||
let mut seconds_until_next_epoch = state.read().await.next_n_epoch_start(1);
|
||||
let one_sec = Duration::new(1, 0);
|
||||
let three_secs = Duration::new(3, 0);
|
||||
|
||||
loop {
|
||||
if seconds_until_next_epoch > one_sec {
|
||||
seconds_until_next_epoch -= one_sec;
|
||||
if seconds_until_next_epoch > three_secs {
|
||||
seconds_until_next_epoch -= three_secs;
|
||||
break
|
||||
}
|
||||
|
||||
@@ -59,11 +59,39 @@ pub async fn proposal_task(consensus_p2p: P2pPtr, sync_p2p: P2pPtr, state: Valid
|
||||
}
|
||||
|
||||
loop {
|
||||
// Node sleeps until finalization sync period start (2 seconds before next slot)
|
||||
let seconds_sync_period =
|
||||
(state.read().await.next_n_slot_start(1) - Duration::new(2, 0)).as_secs();
|
||||
info!("consensus: Waiting for finalization sync period ({} sec)", seconds_sync_period);
|
||||
sleep(seconds_sync_period).await;
|
||||
|
||||
// Check if any forks can be finalized
|
||||
match state.write().await.chain_finalization().await {
|
||||
Ok(to_broadcast) => {
|
||||
// Broadcast finalized blocks info, if any:
|
||||
if to_broadcast.len() > 0 {
|
||||
info!("consensus: Broadcasting finalized blocks");
|
||||
for info in to_broadcast {
|
||||
match sync_p2p.broadcast(info).await {
|
||||
Ok(()) => info!("consensus: Broadcasted block"),
|
||||
Err(e) => error!("consensus: Failed broadcasting block: {}", e),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("consensus: No finalized blocks to broadcast");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("consensus: Finalization check failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Node sleeps until next slot
|
||||
let seconds_next_slot = state.read().await.next_n_slot_start(1).as_secs();
|
||||
info!("consensus: Waiting for next slot ({} sec)", seconds_next_slot);
|
||||
sleep(seconds_next_slot).await;
|
||||
|
||||
// Node checks if epoch has changed, to broadcast a new participation message
|
||||
// Node checks if epoch has changed, to generate new epoch coins
|
||||
let epoch_changed = state.write().await.epoch_changed().await;
|
||||
match epoch_changed {
|
||||
Ok(changed) => {
|
||||
@@ -103,30 +131,17 @@ pub async fn proposal_task(consensus_p2p: P2pPtr, sync_p2p: P2pPtr, state: Valid
|
||||
}
|
||||
};
|
||||
|
||||
// Node stores the proposal and broadcast to rest nodes
|
||||
info!("consensus: Node is the slot leader: Proposed block: {}", proposal);
|
||||
debug!("consensus: Full proposal: {:?}", proposal);
|
||||
match state.write().await.receive_proposal(&proposal).await {
|
||||
Ok(to_broadcast) => {
|
||||
info!("consensus: Block proposal saved successfully");
|
||||
// Broadcast block to other consensus nodes
|
||||
Ok(()) => {
|
||||
info!("consensus: Block proposal saved successfully");
|
||||
// Broadcast proposal to other consensus nodes
|
||||
match consensus_p2p.broadcast(proposal).await {
|
||||
Ok(()) => info!("consensus: Proposal broadcasted successfully"),
|
||||
Err(e) => error!("consensus: Failed broadcasting proposal: {}", e),
|
||||
}
|
||||
// Broadcast finalized blocks info, if any:
|
||||
if let Some(blocks) = to_broadcast {
|
||||
if blocks.len() > 0 {
|
||||
info!("consensus: Broadcasting finalized blocks");
|
||||
for info in blocks {
|
||||
match sync_p2p.broadcast(info).await {
|
||||
Ok(()) => info!("consensus: Broadcasted block"),
|
||||
Err(e) => error!("consensus: Failed broadcasting block: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("consensus: No finalized blocks to broadcast");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("consensus: Block proposal save failed: {}", e);
|
||||
|
||||
@@ -232,6 +232,9 @@ pub enum Error {
|
||||
#[error("Check if proposal extends any existing fork chains failed")]
|
||||
ExtendedChainIndexNotFound,
|
||||
|
||||
#[error("Proposal contains missmatched hashes")]
|
||||
ProposalHashesMissmatchError,
|
||||
|
||||
#[error("Proposal contains missmatched headers")]
|
||||
ProposalHeadersMissmatchError,
|
||||
|
||||
|
||||
@@ -52,6 +52,15 @@ impl Keypair {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Keypair {
|
||||
/// Default Keypair used in genesis block generation
|
||||
fn default() -> Self {
|
||||
let secret = SecretKey::from(pallas::Base::from(42));
|
||||
let public = PublicKey::from_secret(secret);
|
||||
Self { secret, public }
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure holding a secret key, wrapping a `pallas::Base` element.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, SerialEncodable, SerialDecodable)]
|
||||
pub struct SecretKey(pallas::Base);
|
||||
|
||||
@@ -344,12 +344,12 @@ impl Circuit<pallas::Base> for LeadContract {
|
||||
.map(|typed_path| gen_const_array(|i| typed_path[i].inner()));
|
||||
|
||||
/*
|
||||
let coin1_commit_root = assign_free_advice(
|
||||
layouter.namespace(|| "witness coin_commitment_root"),
|
||||
config.advices[8],
|
||||
self.coin1_commit_root,
|
||||
)?;
|
||||
*/
|
||||
let coin1_commit_root = assign_free_advice(
|
||||
layouter.namespace(|| "witness coin_commitment_root"),
|
||||
config.advices[8],
|
||||
self.coin1_commit_root,
|
||||
)?;
|
||||
*/
|
||||
|
||||
let coin1_sk = assign_free_advice(
|
||||
layouter.namespace(|| "witness coin1_sk"),
|
||||
@@ -385,12 +385,12 @@ impl Circuit<pallas::Base> for LeadContract {
|
||||
)?;
|
||||
|
||||
/*
|
||||
let coin1_serial = assign_free_advice(
|
||||
layouter.namespace(|| "witness coin1_serial"),
|
||||
config.advices[8],
|
||||
self.coin1_serial,
|
||||
)?;
|
||||
*/
|
||||
let coin1_serial = assign_free_advice(
|
||||
layouter.namespace(|| "witness coin1_serial"),
|
||||
config.advices[8],
|
||||
self.coin1_serial,
|
||||
)?;
|
||||
*/
|
||||
|
||||
let coin1_value = assign_free_advice(
|
||||
layouter.namespace(|| "witness coin1_value"),
|
||||
@@ -434,12 +434,12 @@ impl Circuit<pallas::Base> for LeadContract {
|
||||
)?;
|
||||
|
||||
/*
|
||||
let rho = NonIdentityPoint::new(
|
||||
ecc_chip.clone(),
|
||||
layouter.namespace(|| "witness rho"),
|
||||
self.rho.as_ref().map(|cm| cm.to_affine()),
|
||||
)?;
|
||||
*/
|
||||
let rho = NonIdentityPoint::new(
|
||||
ecc_chip.clone(),
|
||||
layouter.namespace(|| "witness rho"),
|
||||
self.rho.as_ref().map(|cm| cm.to_affine()),
|
||||
)?;
|
||||
*/
|
||||
|
||||
let zero = assign_free_advice(
|
||||
layouter.namespace(|| "witness constant zero"),
|
||||
@@ -683,12 +683,12 @@ impl Circuit<pallas::Base> for LeadContract {
|
||||
// Constrain derived `sn_commit` to be equal to witnessed `coin1_serial`.
|
||||
info!("coin1 cm root LHS: {:?}", coin1_cm_root.value());
|
||||
/*
|
||||
info!("coin1 cm root RHS: {:?}", coin1_commit_root.value());
|
||||
layouter.assign_region(
|
||||
|| "coin1_cm_root equality",
|
||||
|mut region| region.constrain_equal(coin1_cm_root.cell(), coin1_commit_root.cell()),
|
||||
)?;
|
||||
*/
|
||||
info!("coin1 cm root RHS: {:?}", coin1_commit_root.value());
|
||||
layouter.assign_region(
|
||||
|| "coin1_cm_root equality",
|
||||
|mut region| region.constrain_equal(coin1_cm_root.cell(), coin1_commit_root.cell()),
|
||||
)?;
|
||||
*/
|
||||
layouter.constrain_instance(
|
||||
coin1_cm_root.cell(),
|
||||
config.primary,
|
||||
@@ -696,12 +696,12 @@ impl Circuit<pallas::Base> for LeadContract {
|
||||
)?;
|
||||
info!("coin1 serial commit LHS: {:?}", sn_commit.value());
|
||||
/*
|
||||
info!("coin1 serial commit RHS: {:?}", coin1_serial.value());
|
||||
layouter.assign_region(
|
||||
|| "sn_commit equality",
|
||||
|mut region| region.constrain_equal(sn_commit.cell(), coin1_serial.cell()),
|
||||
)?;
|
||||
*/
|
||||
info!("coin1 serial commit RHS: {:?}", coin1_serial.value());
|
||||
layouter.assign_region(
|
||||
|| "sn_commit equality",
|
||||
|mut region| region.constrain_equal(sn_commit.cell(), coin1_serial.cell()),
|
||||
)?;
|
||||
*/
|
||||
layouter.constrain_instance(
|
||||
sn_commit.cell(),
|
||||
config.primary,
|
||||
|
||||
Reference in New Issue
Block a user