Explorar el Código

darkfid2: calculate genesis txs total and use that for genesis block validation

aggstam hace 3 años
padre
commit
dce1fb929d

+ 3 - 0
Cargo.lock

@@ -1780,8 +1780,11 @@ version = "0.4.1"
 dependencies = [
  "async-std",
  "darkfi",
+ "darkfi-consensus-contract",
  "darkfi-contract-test-harness",
+ "darkfi-money-contract",
  "darkfi-sdk",
+ "darkfi-serial",
  "easy-parallel",
  "log",
  "serde",

+ 3 - 0
bin/darkfid2/Cargo.toml

@@ -10,8 +10,11 @@ edition = "2021"
 
 [dependencies]
 darkfi = {path = "../../", features = ["async-runtime", "util"]}
+darkfi-consensus-contract = {path = "../../src/contract/consensus"}
+darkfi-money-contract = {path = "../../src/contract/money"}
 darkfi-contract-test-harness = {path = "../../src/contract/test-harness"}
 darkfi-sdk = {path = "../../src/sdk"}
+darkfi-serial = {path = "../../src/serial"}
 log = "0.4.19"
 sled = "0.34.7"
 

+ 12 - 1
bin/darkfid2/src/main.rs

@@ -33,6 +33,10 @@ use darkfi_contract_test_harness::vks;
 #[cfg(test)]
 mod tests;
 
+/// Utility functions
+mod utils;
+use utils::genesis_txs_total;
+
 const CONFIG_FILE: &str = "darkfid_config.toml";
 const CONFIG_FILE_CONTENTS: &str = include_str!("../darkfid_config.toml");
 
@@ -78,8 +82,15 @@ async fn realmain(args: Args, _ex: Arc<smol::Executor<'_>>) -> Result<()> {
 
     // Initialize validator configuration
     let genesis_block = BlockInfo::default();
+    let genesis_txs_total = genesis_txs_total(&genesis_block.txs)?;
     let time_keeper = TimeKeeper::new(genesis_block.header.timestamp, 10, 90, 0);
-    let config = ValidatorConfig::new(time_keeper, genesis_block, vec![], args.testing_mode);
+    let config = ValidatorConfig::new(
+        time_keeper,
+        genesis_block,
+        genesis_txs_total,
+        vec![],
+        args.testing_mode,
+    );
 
     if args.testing_mode {
         info!("Node is configured to run in testing mode!");

+ 16 - 5
bin/darkfid2/src/tests/harness.rs

@@ -31,9 +31,10 @@ use darkfi_sdk::{
     pasta::{group::ff::Field, pallas},
 };
 
-use crate::Darkfid;
+use crate::{utils::genesis_txs_total, Darkfid};
 
 pub struct Harness {
+    pub genesis_txs_total: u64,
     pub alice: Darkfid,
     pub bob: Darkfid,
 }
@@ -43,11 +44,21 @@ impl Harness {
         // Generate default genesis block
         let genesis_block = BlockInfo::default();
 
+        // Generate each node wallet here and add their corresponding
+        // genesis txs
+        let genesis_txs_total = genesis_txs_total(&genesis_block.txs)?;
+
         // Generate validators configuration
         // NOTE: we are not using consensus constants here so we
         // don't get circular dependencies.
         let time_keeper = TimeKeeper::new(genesis_block.header.timestamp, 10, 90, 0);
-        let config = ValidatorConfig::new(time_keeper, genesis_block, vec![], testing_node);
+        let config = ValidatorConfig::new(
+            time_keeper,
+            genesis_block,
+            genesis_txs_total,
+            vec![],
+            testing_node,
+        );
 
         // Generate validators using pregenerated vks
         let sled_db = sled::Config::new().temporary(true).open()?;
@@ -59,15 +70,15 @@ impl Harness {
         let validator = Validator::new(&sled_db, config.clone()).await?;
         let bob = Darkfid::new(validator).await;
 
-        Ok(Self { alice, bob })
+        Ok(Self { genesis_txs_total, alice, bob })
     }
 
     pub async fn validate_chains(&self) -> Result<()> {
         let alice = &self.alice._validator.read().await;
         let bob = &self.bob._validator.read().await;
 
-        alice.validate_blockchain().await?;
-        bob.validate_blockchain().await?;
+        alice.validate_blockchain(self.genesis_txs_total).await?;
+        bob.validate_blockchain(self.genesis_txs_total).await?;
 
         assert_eq!(alice.blockchain.len(), bob.blockchain.len());
 

+ 59 - 0
bin/darkfid2/src/utils.rs

@@ -0,0 +1,59 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi::{error::TxVerifyFailed, tx::Transaction, Result};
+use darkfi_consensus_contract::{
+    model::ConsensusGenesisStakeParamsV1, ConsensusFunction::GenesisStakeV1,
+};
+use darkfi_money_contract::{model::MoneyTokenMintParamsV1, MoneyFunction::GenesisMintV1};
+use darkfi_sdk::crypto::{CONSENSUS_CONTRACT_ID, MONEY_CONTRACT_ID};
+use darkfi_serial::deserialize;
+
+/// Auxiliary function to calculate the total amount of minted tokens in provided
+/// genesis transactions set. This includes both staked and normal tokens.
+/// If a non-genesis transaction is found, execution fails.
+pub fn genesis_txs_total(txs: &[Transaction]) -> Result<u64> {
+    let mut total = 0;
+
+    for tx in txs {
+        // Transaction must contain a single Consensus::GenesisStake or Money::GenesisMint call
+        if tx.calls.len() != 1 {
+            return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
+        }
+        let call = &tx.calls[0];
+        let function = call.data[0];
+        if !(call.contract_id == *CONSENSUS_CONTRACT_ID || call.contract_id == *MONEY_CONTRACT_ID) ||
+            (call.contract_id == *CONSENSUS_CONTRACT_ID && function != GenesisStakeV1 as u8) ||
+            (call.contract_id == *MONEY_CONTRACT_ID && function != GenesisMintV1 as u8)
+        {
+            return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
+        }
+
+        let value = if function == GenesisStakeV1 as u8 {
+            let params: ConsensusGenesisStakeParamsV1 = deserialize(&call.data[1..])?;
+            params.input.value
+        } else {
+            let params: MoneyTokenMintParamsV1 = deserialize(&call.data[1..])?;
+            params.input.value
+        };
+
+        total += value;
+    }
+
+    Ok(total)
+}

+ 1 - 0
src/contract/test-harness/src/lib.rs

@@ -145,6 +145,7 @@ impl Wallet {
         let config = ValidatorConfig::new(
             time_keeper,
             genesis_block.clone(),
+            0,
             faucet_pubkeys.to_vec(),
             false,
         );

+ 0 - 3
src/error.rs

@@ -314,9 +314,6 @@ pub enum Error {
     #[error("Block {0} already in database")]
     BlockAlreadyExists(String),
 
-    #[error("Didn't provide blocks' previous")]
-    BlockPreviousMissing(),
-
     #[error("Block {0} not found in database")]
     BlockNotFound(String),
 

+ 18 - 20
src/validator/mod.rs

@@ -34,7 +34,7 @@ use consensus::{next_block_reward, Consensus};
 
 /// Verification functions
 pub mod verification;
-use verification::{verify_block, verify_transactions};
+use verification::{verify_block, verify_genesis_block, verify_transactions};
 
 /// Helper utilities
 pub mod utils;
@@ -47,6 +47,8 @@ pub struct ValidatorConfig {
     pub time_keeper: TimeKeeper,
     /// Genesis block
     pub genesis_block: BlockInfo,
+    /// Total amount of minted tokens in genesis block
+    pub genesis_txs_total: u64,
     /// Whitelisted faucet pubkeys (testnet stuff)
     pub faucet_pubkeys: Vec<PublicKey>,
     /// Flag to enable testing mode
@@ -57,10 +59,11 @@ impl ValidatorConfig {
     pub fn new(
         time_keeper: TimeKeeper,
         genesis_block: BlockInfo,
+        genesis_txs_total: u64,
         faucet_pubkeys: Vec<PublicKey>,
         testing_mode: bool,
     ) -> Self {
-        Self { time_keeper, genesis_block, faucet_pubkeys, testing_mode }
+        Self { time_keeper, genesis_block, genesis_txs_total, faucet_pubkeys, testing_mode }
     }
 }
 
@@ -91,13 +94,11 @@ impl Validator {
         // Add genesis block if blockchain is empty
         if blockchain.genesis().is_err() {
             info!(target: "validator", "Appending genesis block");
-            verify_block(
+            verify_genesis_block(
                 &overlay,
                 &config.time_keeper,
                 &config.genesis_block,
-                None,
-                0,
-                testing_mode,
+                config.genesis_txs_total,
             )
             .await?;
         };
@@ -135,14 +136,8 @@ impl Validator {
         debug!(target: "validator", "Instantiating BlockchainOverlay");
         let overlay = BlockchainOverlay::new(&self.blockchain)?;
 
-        // Retrieve last block. If blockchain is empty it will error out here
-        let last_block = match overlay.lock().unwrap().last_block() {
-            Ok(l) => l,
-            Err(_) => BlockInfo::default(),
-        };
-        // We only need the reference, thats why we do it like this
-        let mut previous =
-            if !overlay.lock().unwrap().is_empty()? { Some(&last_block) } else { None };
+        // Retrieve last block
+        let mut previous = &overlay.lock().unwrap().last_block()?;
 
         // Create a time keeper to validate each block
         let mut time_keeper = self.consensus.time_keeper.clone();
@@ -160,7 +155,7 @@ impl Validator {
                 &overlay,
                 &time_keeper,
                 block,
-                previous,
+                &previous,
                 expected_reward,
                 self.testing_mode,
             )
@@ -173,7 +168,7 @@ impl Validator {
             };
 
             // Use last inserted block as next iteration previous
-            previous = Some(block);
+            previous = block;
         }
 
         debug!(target: "validator", "Applying overlay changes");
@@ -236,7 +231,7 @@ impl Validator {
     /// Retrieve all existing blocks and try to apply them
     /// to an in memory overlay to verify their correctness.
     /// Be careful as this will try to load everything in memory.
-    pub async fn validate_blockchain(&self) -> Result<()> {
+    pub async fn validate_blockchain(&self, genesis_txs_total: u64) -> Result<()> {
         let blocks = self.blockchain.get_all()?;
 
         // An empty blockchain is considered valid
@@ -250,13 +245,16 @@ impl Validator {
         let overlay = BlockchainOverlay::new(&blockchain)?;
 
         // Set previous
-        let mut previous = None;
+        let mut previous = &blocks[0];
 
         // Create a time keeper to validate each block
         let mut time_keeper = self.consensus.time_keeper.clone();
 
+        // Validate genesis block
+        verify_genesis_block(&overlay, &time_keeper, previous, genesis_txs_total).await?;
+
         // Validate and insert each block
-        for block in &blocks {
+        for block in &blocks[1..] {
             // Use block slot in time keeper
             time_keeper.verifying_slot = block.header.slot;
 
@@ -281,7 +279,7 @@ impl Validator {
             };
 
             // Use last inserted block as next iteration previous
-            previous = Some(block);
+            previous = block;
         }
 
         Ok(())

+ 37 - 26
src/validator/verification.rs

@@ -37,7 +37,25 @@ use crate::{
 };
 
 /// Validate given genesis [`BlockInfo`], and apply it to the provided overlay
-async fn verify_genesis_block(block: &BlockInfo, block_hash: String) -> Result<()> {
+pub async fn verify_genesis_block(
+    overlay: &BlockchainOverlayPtr,
+    time_keeper: &TimeKeeper,
+    block: &BlockInfo,
+    genesis_txs_total: u64,
+) -> Result<()> {
+    let block_hash = block.blockhash().to_string();
+    debug!(target: "validator", "Validating genesis block {}", block_hash);
+
+    // Check if block already exists
+    if overlay.lock().unwrap().has_block(block)? {
+        return Err(Error::BlockAlreadyExists(block_hash))
+    }
+
+    // Block slot must be the same as the time keeper verifying slot
+    if block.header.slot != time_keeper.verifying_slot {
+        return Err(Error::VerifyingSlotMissmatch())
+    }
+
     // Check genesis slot exist
     if block.slots.len() != 1 {
         return Err(Error::BlockIsInvalid(block_hash))
@@ -51,16 +69,11 @@ async fn verify_genesis_block(block: &BlockInfo, block_hash: String) -> Result<(
         return Err(Error::SlotIsInvalid(genesis_slot.id))
     }
 
-    // TODO:
     // Genesis block slot total token must correspond to the total
     // of all genesis transactions public inputs (genesis distribution).
-    // Retrieve genesis transactions total
-    //let txs_total = genesis_transactions_total(&block.txs);
-
-    // Verify amounts match
-    //if genesis_slot.total_tokens != txs_total {
-    //    return Err(Error::SlotIsInvalid(genesis_slot.id))
-    //}
+    if genesis_slot.total_tokens != genesis_txs_total {
+        return Err(Error::SlotIsInvalid(genesis_slot.id))
+    }
 
     // Verify there is not reward
     if genesis_slot.reward != 0 {
@@ -73,6 +86,13 @@ async fn verify_genesis_block(block: &BlockInfo, block_hash: String) -> Result<(
         return Err(TxVerifyFailed::ErroneousTxs(vec![block.producer.proposal.clone()]).into())
     }
 
+    // Verify transactions
+    verify_transactions(overlay, time_keeper, &block.txs).await?;
+
+    // Insert block
+    overlay.lock().unwrap().add_block(block)?;
+
+    debug!(target: "validator", "Genesis block {} verified successfully", block_hash);
     Ok(())
 }
 
@@ -81,16 +101,16 @@ pub async fn verify_block(
     overlay: &BlockchainOverlayPtr,
     time_keeper: &TimeKeeper,
     block: &BlockInfo,
-    previous: Option<&BlockInfo>,
+    previous: &BlockInfo,
     expected_reward: u64,
     testing_mode: bool,
 ) -> Result<()> {
-    let block_hash = block.blockhash();
+    let block_hash = block.blockhash().to_string();
     debug!(target: "validator", "Validating block {}", block_hash);
 
     // Check if block already exists
     if overlay.lock().unwrap().has_block(block)? {
-        return Err(Error::BlockAlreadyExists(block_hash.to_string()))
+        return Err(Error::BlockAlreadyExists(block_hash))
     }
 
     // Block slot must be the same as the time keeper verifying slot
@@ -98,21 +118,12 @@ pub async fn verify_block(
         return Err(Error::VerifyingSlotMissmatch())
     }
 
-    // Validate block
-    if block.header.slot == 0 {
-        // Validate genesis block
-        verify_genesis_block(block, block_hash.to_string()).await?;
-    } else {
-        // Validate normal block, using its previous
-        if previous.is_none() {
-            return Err(Error::BlockPreviousMissing())
-        }
-        block.validate(previous.unwrap(), expected_reward)?;
+    // Validate block, using its previous
+    block.validate(previous, expected_reward)?;
 
-        // Validate proposal transaction if not in testing mode
-        if !testing_mode {
-            verify_proposal_transaction(overlay, time_keeper, &block.producer.proposal).await?;
-        }
+    // Validate proposal transaction if not in testing mode
+    if !testing_mode {
+        verify_proposal_transaction(overlay, time_keeper, &block.producer.proposal).await?;
     }
 
     // Verify transactions