Răsfoiți Sursa

contract/consensus: use second to last proposal/block hash in vrf input

aggstam 3 ani în urmă
părinte
comite
2b8cfb958a

+ 1 - 1
src/blockchain/mod.rs

@@ -74,7 +74,7 @@ impl Blockchain {
         let headers = HeaderStore::new(db, genesis_ts, genesis_data)?;
         let blocks = BlockStore::new(db, genesis_ts, genesis_data)?;
         let order = BlockOrderStore::new(db, genesis_ts, genesis_data)?;
-        let slot_checkpoints = SlotCheckpointStore::new(db)?;
+        let slot_checkpoints = SlotCheckpointStore::new(db, genesis_data)?;
         let transactions = TxStore::new(db)?;
         let pending_txs = PendingTxStore::new(db)?;
         let pending_txs_order = PendingTxOrderStore::new(db)?;

+ 2 - 2
src/blockchain/slot_checkpoint_store.rs

@@ -30,13 +30,13 @@ pub struct SlotCheckpointStore(sled::Tree);
 
 impl SlotCheckpointStore {
     /// Opens a new or existing `SlotCheckpointStore` on the given sled database.
-    pub fn new(db: &sled::Db) -> Result<Self> {
+    pub fn new(db: &sled::Db, genesis_block: blake3::Hash) -> Result<Self> {
         let tree = db.open_tree(SLED_SLOT_CHECKPOINT_TREE)?;
         let store = Self(tree);
 
         // In case the store is empty, initialize it with the genesis checkpoint.
         if store.0.is_empty() {
-            let genesis_checkpoint = SlotCheckpoint::genesis_slot_checkpoint();
+            let genesis_checkpoint = SlotCheckpoint::genesis_slot_checkpoint(genesis_block);
             store.insert(&[genesis_checkpoint])?;
         }
 

+ 34 - 14
src/consensus/state.rs

@@ -17,7 +17,7 @@
  */
 
 use darkfi_sdk::{
-    crypto::{constants::MERKLE_DEPTH, MerkleNode},
+    crypto::{constants::MERKLE_DEPTH, pasta_prelude::*, MerkleNode},
     incrementalmerkletree::bridgetree::BridgeTree,
     pasta::{group::ff::PrimeField, pallas},
 };
@@ -159,12 +159,20 @@ impl ConsensusState {
     fn generate_slot_checkpoint(
         &mut self,
         fork_hashes: Vec<blake3::Hash>,
+        fork_previous_hashes: Vec<blake3::Hash>,
         sigma1: pallas::Base,
         sigma2: pallas::Base,
     ) {
         let slot = self.time_keeper.current_slot();
         let previous_eta = self.get_previous_eta();
-        let checkpoint = SlotCheckpoint { slot, previous_eta, fork_hashes, sigma1, sigma2 };
+        let checkpoint = SlotCheckpoint {
+            slot,
+            previous_eta,
+            fork_hashes,
+            fork_previous_hashes,
+            sigma1,
+            sigma2,
+        };
         info!(target: "consensus::state", "generate_slot_checkpoint: {:?}", checkpoint);
         self.slot_checkpoints.push(checkpoint);
     }
@@ -182,10 +190,11 @@ impl ConsensusState {
     pub async fn epoch_changed(
         &mut self,
         fork_hashes: Vec<blake3::Hash>,
+        fork_previous_hashes: Vec<blake3::Hash>,
         sigma1: pallas::Base,
         sigma2: pallas::Base,
     ) -> Result<bool> {
-        self.generate_slot_checkpoint(fork_hashes, sigma1, sigma2);
+        self.generate_slot_checkpoint(fork_hashes, fork_previous_hashes, sigma1, sigma2);
         let epoch = self.time_keeper.current_epoch();
         if epoch <= self.epoch {
             return Ok(false)
@@ -590,19 +599,23 @@ impl ConsensusState {
         }
     }
 
-    /// Retrieve current forks last proposal hashes.
-    /// If node holds no fork, retrieve last canonical hash.
-    pub fn fork_hashes(&self) -> Vec<blake3::Hash> {
+    /// Retrieve current forks last proposal hashes and their previous
+    /// hashes. If node holds no fork, retrieve last canonical hash.
+    pub fn fork_hashes(&self) -> (Vec<blake3::Hash>, Vec<blake3::Hash>) {
         let mut hashes = vec![];
+        let mut previous_hashes = vec![];
         for fork in &self.forks {
-            hashes.push(fork.sequence.last().unwrap().proposal.hash);
+            let proposal = &fork.sequence.last().unwrap().proposal;
+            hashes.push(proposal.hash);
+            previous_hashes.push(proposal.block.header.previous);
         }
 
         if hashes.is_empty() {
             hashes.push(self.genesis_block);
+            previous_hashes.push(self.genesis_block);
         }
 
-        hashes
+        (hashes, previous_hashes)
     }
 
     /// Auxiliary structure to reset consensus state for a resync
@@ -694,6 +707,9 @@ pub struct SlotCheckpoint {
     /// Previous slot forks last proposal/block hashes,
     /// as observed by the validator
     pub fork_hashes: Vec<blake3::Hash>,
+    /// Previous slot second to last proposal/block hashes,
+    /// as observed by the validator
+    pub fork_previous_hashes: Vec<blake3::Hash>,
     /// Slot sigma1
     pub sigma1: pallas::Base,
     /// Slot sigma2
@@ -705,20 +721,24 @@ impl SlotCheckpoint {
         slot: u64,
         previous_eta: pallas::Base,
         fork_hashes: Vec<blake3::Hash>,
+        fork_previous_hashes: Vec<blake3::Hash>,
         sigma1: pallas::Base,
         sigma2: pallas::Base,
     ) -> Self {
-        Self { slot, previous_eta, fork_hashes, sigma1, sigma2 }
+        Self { slot, previous_eta, fork_hashes, fork_previous_hashes, sigma1, sigma2 }
     }
 
     /// Generate the genesis slot checkpoint.
-    pub fn genesis_slot_checkpoint() -> Self {
-        let previous_eta = pallas::Base::zero();
+    pub fn genesis_slot_checkpoint(genesis_block: blake3::Hash) -> Self {
+        let previous_eta = pallas::Base::ZERO;
         let fork_hashes = vec![];
-        let sigma1 = pallas::Base::zero();
-        let sigma2 = pallas::Base::zero();
+        // Since genesis block has no previous,
+        // we will use its own hash as its previous.
+        let fork_previous_hashes = vec![genesis_block];
+        let sigma1 = pallas::Base::ZERO;
+        let sigma2 = pallas::Base::ZERO;
 
-        Self::new(0, previous_eta, fork_hashes, sigma1, sigma2)
+        Self::new(0, previous_eta, fork_hashes, fork_previous_hashes, sigma1, sigma2)
     }
 }
 

+ 8 - 4
src/consensus/task/proposal.rs

@@ -186,14 +186,18 @@ async fn propose_period(consensus_p2p: P2pPtr, state: ValidatorStatePtr) -> bool
     // Keep a record of slot to verify if next slot got skipped during processing
     let processing_slot = state.read().await.consensus.time_keeper.current_slot();
 
-    // Retrieve current forks last hash
-    let fork_hashes = state.read().await.consensus.fork_hashes();
+    // Retrieve current forks last and second to last hash
+    let (fork_hashes, fork_previous_hashes) = state.read().await.consensus.fork_hashes();
 
     // Retrieve slot sigmas
     let (sigma1, sigma2) = state.write().await.consensus.sigmas();
     // Node checks if epoch has changed and generate slot checkpoint
-    let epoch_changed =
-        state.write().await.consensus.epoch_changed(fork_hashes, sigma1, sigma2).await;
+    let epoch_changed = state
+        .write()
+        .await
+        .consensus
+        .epoch_changed(fork_hashes, fork_previous_hashes, sigma1, sigma2)
+        .await;
     match epoch_changed {
         Ok(changed) => {
             if changed {

+ 11 - 5
src/contract/consensus/src/client/proposal_v1.rs

@@ -116,7 +116,9 @@ pub struct ConsensusProposalCallBuilder {
     /// Rewarded slot checkpoint
     pub slot_checkpoint: SlotCheckpoint,
     /// Extending fork last proposal/block hash
-    pub previous_hash: blake3::Hash,
+    pub fork_hash: blake3::Hash,
+    /// Extending fork second to last proposal/block hash
+    pub fork_previous_hash: blake3::Hash,
     /// Merkle tree of coins used to create inclusion proofs
     pub tree: MerkleTree,
     /// `Proposal_V1` zkas circuit ZkBinary
@@ -164,7 +166,8 @@ impl ConsensusProposalCallBuilder {
             &input,
             &output,
             &self.slot_checkpoint,
-            self.previous_hash,
+            self.fork_hash,
+            self.fork_previous_hash,
         )?;
 
         let input = ConsensusInput {
@@ -208,7 +211,8 @@ impl ConsensusProposalCallBuilder {
             reward_blind,
             new_serial_commit,
             slot,
-            previous_hash: self.previous_hash,
+            fork_hash: self.fork_hash,
+            fork_previous_hash: self.fork_previous_hash,
             vrf_proof,
             y,
             rho,
@@ -229,8 +233,10 @@ pub fn create_proposal_proof(
     input: &ConsensusBurnInputInfo,
     output: &ConsensusMintOutputInfo,
     slot_checkpoint: &SlotCheckpoint,
-    previous_hash: blake3::Hash,
+    _fork_hash: blake3::Hash,
+    fork_previous_hash: blake3::Hash,
 ) -> Result<(Proof, ConsensusProposalRevealed)> {
+    // TODO: fork_hash to be used as part of rank constrain in the proof
     // Proof parameters
     let nullifier = Nullifier::from(poseidon_hash([input.secret.inner(), input.note.serial]));
     let epoch = input.note.epoch;
@@ -285,7 +291,7 @@ pub fn create_proposal_proof(
     let seed = poseidon_hash([SEED_PREFIX, input.note.serial]);
     let mut vrf_input = Vec::with_capacity(32 + blake3::OUT_LEN + 32);
     vrf_input.extend_from_slice(&slot_checkpoint.previous_eta.to_repr());
-    vrf_input.extend_from_slice(previous_hash.as_bytes());
+    vrf_input.extend_from_slice(fork_previous_hash.as_bytes());
     vrf_input.extend_from_slice(&slot_pallas.to_repr());
     let vrf_proof = VrfProof::prove(input.secret, &vrf_input, &mut OsRng);
     let mut eta = [0u8; 64];

+ 14 - 3
src/contract/consensus/src/entrypoint/proposal_v1.rs

@@ -96,8 +96,19 @@ pub(crate) fn consensus_proposal_get_metadata_v1(
     let slot_checkpoint: SlotCheckpoint = deserialize(&slot_checkpoint)?;
 
     // Verify proposal extends a known fork
-    if !slot_checkpoint.fork_hashes.contains(&params.previous_hash) {
-        msg!("[ConsensusProposalV1] Error: Proposal extends unknown fork {}", params.previous_hash);
+    if !slot_checkpoint.fork_hashes.contains(&params.fork_hash) {
+        msg!("[ConsensusProposalV1] Error: Proposal extends unknown fork {}", params.fork_hash);
+        return Err(ConsensusError::ProposalExtendsUnknownFork.into())
+    }
+
+    // TODO: add fork rank check using params.fork_hash
+
+    // And sequence is correct
+    if !slot_checkpoint.fork_previous_hashes.contains(&params.fork_previous_hash) {
+        msg!(
+            "[ConsensusProposalV1] Error: Proposal extends unknown fork {}",
+            params.fork_previous_hash
+        );
         return Err(ConsensusError::ProposalExtendsUnknownFork.into())
     }
 
@@ -105,7 +116,7 @@ pub(crate) fn consensus_proposal_get_metadata_v1(
     let slot_pallas = pallas::Base::from(slot_checkpoint.slot);
     let mut vrf_input = Vec::with_capacity(32 + blake3::OUT_LEN + 32);
     vrf_input.extend_from_slice(&slot_checkpoint.previous_eta.to_repr());
-    vrf_input.extend_from_slice(params.previous_hash.as_bytes());
+    vrf_input.extend_from_slice(params.fork_previous_hash.as_bytes());
     vrf_input.extend_from_slice(&slot_pallas.to_repr());
     let vrf_proof = &params.vrf_proof;
     if !vrf_proof.verify(params.input.signature_public, &vrf_input) {

+ 6 - 1
src/contract/consensus/src/model.rs

@@ -50,7 +50,9 @@ pub struct ConsensusProposalParamsV1 {
     /// Rewarded slot
     pub slot: u64,
     /// Extending fork last proposal/block hash
-    pub previous_hash: blake3::Hash,
+    pub fork_hash: blake3::Hash,
+    /// Extending fork second to last proposal/block hash
+    pub fork_previous_hash: blake3::Hash,
     /// VRF proof for eta calculation
     pub vrf_proof: VrfProof,
     /// Coin y
@@ -119,6 +121,9 @@ pub struct SlotCheckpoint {
     /// Previous slot forks last proposal/block hashes,
     /// as observed by the validator
     pub fork_hashes: Vec<blake3::Hash>,
+    /// Previous slot second to last proposal/block hashes,
+    /// as observed by the validator
+    pub fork_previous_hashes: Vec<blake3::Hash>,
     /// Slot sigma1
     pub sigma1: pallas::Base,
     /// Slot sigma2

+ 7 - 3
src/contract/consensus/tests/harness.rs

@@ -534,13 +534,14 @@ impl ConsensusTestHarness {
         let timer = Instant::now();
 
         // Proposals always extend genesis block
-        let previous_hash = wallet.state.read().await.consensus.genesis_block;
+        let fork_hash = wallet.state.read().await.consensus.genesis_block;
 
         // Building Consensus::Unstake params
         let proposal_call_debris = ConsensusProposalCallBuilder {
             coin: staked_oc,
             slot_checkpoint,
-            previous_hash,
+            fork_hash,
+            fork_previous_hash: fork_hash,
             tree: wallet.consensus_merkle_tree.clone(),
             proposal_zkbin: proposal_zkbin.clone(),
             proposal_pk: proposal_pk.clone(),
@@ -865,12 +866,15 @@ impl ConsensusTestHarness {
         // We grab the genesis slot to generate slot checkpoint
         // using same consensus parameters
         let faucet = self.holders.get(&Holder::Faucet).unwrap();
-        let fork_hashes = vec![faucet.state.read().await.consensus.genesis_block];
+        let genesis_block = faucet.state.read().await.consensus.genesis_block;
+        let fork_hashes = vec![genesis_block];
+        let fork_previous_hashes = vec![genesis_block];
         let genesis_slot = self.get_slot_checkpoint_by_slot(0).await?;
         let slot_checkpoint = SlotCheckpoint {
             slot,
             previous_eta: genesis_slot.previous_eta,
             fork_hashes,
+            fork_previous_hashes,
             sigma1: genesis_slot.sigma1,
             sigma2: genesis_slot.sigma2,
         };