Explorar o código

consensus: walletdb infra for consensus coins implemented

aggstam %!s(int64=3) %!d(string=hai) anos
pai
achega
c2e12c7681

+ 6 - 0
src/consensus/consensus_coin.sql

@@ -0,0 +1,6 @@
+-- Wallet definitions for consensus lead coins.
+
+-- The consensus lead coin we have and can use
+CREATE TABLE IF NOT EXISTS consensus_coin (
+	coin BLOB
+);

+ 4 - 0
src/consensus/constants.rs

@@ -127,3 +127,7 @@ pub const GENESIS_TOTAL_STAKE: u64 = 1;
 pub const LEADER_HISTORY_LOG: &str = "/tmp/lead_history.log";
 pub const F_HISTORY_LOG: &str = "/tmp/f_history.log";
 pub const LOTTERY_HISTORY_LOG: &str = "/tmp/lottery_history.log";
+
+// Wallet SQL table constant names. These have to represent the SQL schema.
+pub const CONSENSUS_COIN_TABLE: &str = "consensus_coin";
+pub const CONSENSUS_COIN_COL: &str = "coin";

+ 21 - 12
src/consensus/lead_coin.rs

@@ -26,6 +26,7 @@ use darkfi_sdk::{
     incrementalmerkletree::{bridgetree::BridgeTree, Tree},
     pasta::{arithmetic::CurveAffine, group::Curve, pallas},
 };
+use darkfi_serial::{SerialDecodable, SerialEncodable};
 use halo2_proofs::{arithmetic::Field, circuit::Value};
 use log::info;
 use rand::rngs::OsRng;
@@ -59,7 +60,7 @@ pub const PREFIX_SN: u64 = 6;
 
 // TODO: Unify item names with the names in the ZK proof (those are more descriptive)
 /// Structure representing the consensus leader coin
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
 pub struct LeadCoin {
     /// Coin's stake value
     pub value: u64,
@@ -74,7 +75,7 @@ pub struct LeadCoin {
     /// Coin commitment position
     pub coin1_commitment_pos: u32,
     /// Merkle path to the coin1's commitment
-    pub coin1_commitment_merkle_path: [MerkleNode; MERKLE_DEPTH_LEAD_COIN],
+    pub coin1_commitment_merkle_path: Vec<MerkleNode>,
     /// coin1 sk
     pub coin1_sk: pallas::Base,
     /// Merkle root of the `coin1` secret key
@@ -82,7 +83,7 @@ pub struct LeadCoin {
     /// coin1 sk position in merkle tree
     pub coin1_sk_pos: u32,
     /// Merkle path to the secret key of `coin1`
-    pub coin1_sk_merkle_path: [MerkleNode; MERKLE_DEPTH_LEAD_COIN],
+    pub coin1_sk_merkle_path: Vec<MerkleNode>,
     /// coin1 commitment blinding factor
     pub coin1_blind: pallas::Scalar,
 }
@@ -104,7 +105,7 @@ impl LeadCoin {
         // sk pos
         coin1_sk_pos: usize,
         // Merkle path to the secret key of `coin_1` in the Merkle tree of secret keys
-        coin1_sk_merkle_path: [MerkleNode; MERKLE_DEPTH_LEAD_COIN],
+        coin1_sk_merkle_path: Vec<MerkleNode>,
         // coin1 nonce
         seed: pallas::Base,
         // Merkle tree of coin commitments
@@ -147,7 +148,7 @@ impl LeadCoin {
             coin1_commitment,
             coin1_commitment_root,
             coin1_commitment_pos: u32::try_from(usize::from(coin1_commitment_pos)).unwrap(),
-            coin1_commitment_merkle_path: coin1_commitment_merkle_path.try_into().unwrap(),
+            coin1_commitment_merkle_path,
             coin1_sk,
             coin1_sk_root,
             coin1_sk_pos: u32::try_from(coin1_sk_pos).unwrap(),
@@ -368,7 +369,7 @@ impl LeadCoin {
             coin1_sk: self.coin1_sk,
             coin1_sk_root: self.coin1_sk_root,
             coin1_sk_pos: self.coin1_sk_pos,
-            coin1_sk_merkle_path: self.coin1_sk_merkle_path,
+            coin1_sk_merkle_path: self.coin1_sk_merkle_path.clone(),
             coin1_blind: derived_blind,
         }
     }
@@ -387,13 +388,17 @@ impl LeadCoin {
         let bincode = include_bytes!("../../proof/lead.zk.bin");
         let zkbin = ZkBinary::decode(bincode).unwrap();
         let headstart = Self::headstart();
+        let coin1_commitment_merkle_path: [MerkleNode; MERKLE_DEPTH_LEAD_COIN] =
+            self.coin1_commitment_merkle_path.clone().try_into().unwrap();
+        let coin1_sk_merkle_path: [MerkleNode; MERKLE_DEPTH_LEAD_COIN] =
+            self.coin1_sk_merkle_path.clone().try_into().unwrap();
         let witnesses = vec![
-            Witness::MerklePath(Value::known(self.coin1_commitment_merkle_path)),
+            Witness::MerklePath(Value::known(coin1_commitment_merkle_path)),
             Witness::Uint32(Value::known(self.coin1_commitment_pos)),
             Witness::Uint32(Value::known(self.coin1_sk_pos)),
             Witness::Base(Value::known(self.coin1_sk)),
             Witness::Base(Value::known(self.coin1_sk_root.inner())),
-            Witness::MerklePath(Value::known(self.coin1_sk_merkle_path)),
+            Witness::MerklePath(Value::known(coin1_sk_merkle_path)),
             Witness::Base(Value::known(pallas::Base::from(self.slot))),
             Witness::Base(Value::known(self.nonce)),
             Witness::Scalar(Value::known(self.coin1_blind)),
@@ -431,17 +436,21 @@ impl LeadCoin {
         let xferval = pallas::Base::from(transfered_coin.value);
         let pos: u32 = self.coin1_commitment_pos;
         let value = pallas::Base::from(self.value);
+        let coin1_sk_merkle_path: [MerkleNode; MERKLE_DEPTH_LEAD_COIN] =
+            self.coin1_sk_merkle_path.clone().try_into().unwrap();
+        let coin1_commitment_merkle_path: [MerkleNode; MERKLE_DEPTH_LEAD_COIN] =
+            self.coin1_commitment_merkle_path.clone().try_into().unwrap();
         let witnesses = vec![
             // coin (1) burned coin
             Witness::Base(Value::known(self.coin1_commitment_root.inner())),
             Witness::Base(Value::known(self.coin1_sk_root.inner())),
             Witness::Base(Value::known(self.coin1_sk)),
-            Witness::MerklePath(Value::known(self.coin1_sk_merkle_path)),
+            Witness::MerklePath(Value::known(coin1_sk_merkle_path)),
             Witness::Uint32(Value::known(self.coin1_sk_pos)),
             Witness::Base(Value::known(self.nonce)),
             Witness::Scalar(Value::known(self.coin1_blind)),
             Witness::Base(Value::known(value)),
-            Witness::MerklePath(Value::known(self.coin1_commitment_merkle_path)),
+            Witness::MerklePath(Value::known(coin1_commitment_merkle_path)),
             Witness::Uint32(Value::known(pos)),
             Witness::Base(Value::known(self.sn())),
             // coin (3)
@@ -497,7 +506,7 @@ impl LeadCoin {
 pub struct LeadCoinSecrets {
     pub secret_keys: Vec<SecretKey>,
     pub merkle_roots: Vec<MerkleNode>,
-    pub merkle_paths: Vec<[MerkleNode; MERKLE_DEPTH_LEAD_COIN]>,
+    pub merkle_paths: Vec<Vec<MerkleNode>>,
 }
 
 impl LeadCoinSecrets {
@@ -540,7 +549,7 @@ impl LeadCoinSecrets {
             let path = tree.authentication_path(leaf_pos, &root).unwrap();
 
             root_sks.push(root);
-            path_sks.push(path.try_into().unwrap());
+            path_sks.push(path);
         }
 
         Self { secret_keys: sks, merkle_roots: root_sks, merkle_paths: path_sks }

+ 61 - 27
src/consensus/state.rs

@@ -24,9 +24,10 @@ use darkfi_sdk::{
     incrementalmerkletree::bridgetree::BridgeTree,
     pasta::{group::ff::PrimeField, pallas},
 };
-use darkfi_serial::{SerialDecodable, SerialEncodable};
+use darkfi_serial::{deserialize, serialize, SerialDecodable, SerialEncodable};
 use log::info;
 use rand::{thread_rng, Rng};
+use sqlx::Row;
 
 use super::{
     constants,
@@ -34,7 +35,10 @@ use super::{
     utils::fbig2base,
     Block, BlockProposal, Float10,
 };
-use crate::{blockchain::Blockchain, net, tx::Transaction, util::time::Timestamp, Error, Result};
+use crate::{
+    blockchain::Blockchain, net, tx::Transaction, util::time::Timestamp, wallet::WalletPtr, Error,
+    Result,
+};
 
 use std::{
     fs::File,
@@ -43,6 +47,8 @@ use std::{
 
 /// This struct represents the information required by the consensus algorithm
 pub struct ConsensusState {
+    /// Wallet interface
+    pub wallet: WalletPtr,
     /// Canonical (finalized) blockchain
     pub blockchain: Blockchain,
     /// Network bootstrap timestamp
@@ -88,6 +94,7 @@ pub struct ConsensusState {
 
 impl ConsensusState {
     pub fn new(
+        wallet: WalletPtr,
         blockchain: Blockchain,
         bootstrap_ts: Timestamp,
         genesis_ts: Timestamp,
@@ -97,6 +104,7 @@ impl ConsensusState {
     ) -> Result<Self> {
         let genesis_block = Block::genesis_block(genesis_ts, genesis_data).blockhash();
         Ok(Self {
+            wallet,
             blockchain,
             bootstrap_ts,
             genesis_ts,
@@ -294,35 +302,61 @@ impl ConsensusState {
         let slot = self.current_slot();
 
         // TODO: cleanup LeadCoinSecrets, no need to keep a vector
-        let mut rng = thread_rng();
-        let mut seeds: Vec<u64> = Vec::with_capacity(constants::EPOCH_LENGTH);
-        for _ in 0..constants::EPOCH_LENGTH {
-            seeds.push(rng.gen());
-        }
-        let epoch_secrets = LeadCoinSecrets::generate();
+        let (seeds, epoch_secrets) = {
+            let mut rng = thread_rng();
+            let mut seeds: Vec<u64> = Vec::with_capacity(constants::EPOCH_LENGTH);
+            for _ in 0..constants::EPOCH_LENGTH {
+                seeds.push(rng.gen());
+            }
+            (seeds, LeadCoinSecrets::generate())
+        };
 
         // LeadCoin matrix containing node competing coins.
         let mut coins: Vec<LeadCoin> = Vec::with_capacity(constants::EPOCH_LENGTH);
 
-        // TODO: TESTNET: Here we would look into the wallet to find coins we're able to use.
-        //                The wallet has specific tables for consensus coins.
-        // TODO: TESTNET: Token ID still has to be enforced properly in the consensus.
-
-        // Temporarily, we compete with fixed stake.
-        // This stake should be based on how many nodes we want to run, and they all
-        // must sum to initial distribution total coins.
-        let stake = self.initial_distribution;
-        //let stake = 200;
-        let coin = LeadCoin::new(
-            stake,
-            slot,
-            epoch_secrets.secret_keys[0].inner(),
-            epoch_secrets.merkle_roots[0],
-            0,
-            epoch_secrets.merkle_paths[0],
-            pallas::Base::from(seeds[0]),
-            &mut self.coins_tree,
-        );
+        // Retrieve coin from wallet
+        // NOTE: In future this will be retrieved from the money contract.
+        // Get a wallet connection
+        let mut conn = self.wallet.conn.acquire().await?;
+
+        // Execute the query and see if we find any rows
+        let query_str = format!("SELECT * FROM {}", constants::CONSENSUS_COIN_TABLE);
+        let mut query = sqlx::query(&query_str);
+        let coin = match query.fetch_one(&mut conn).await {
+            Ok(row) => {
+                let bytes: Vec<u8> = row.try_get(constants::CONSENSUS_COIN_COL)?;
+                deserialize(&bytes)?
+            }
+            Err(_) => {
+                // If no records are found, we generate a new coin and save it to the database
+                info!(target: "consensus::state", "create_coins(): No LeadCoin was found in DB, generating new one...");
+                // Temporarily, we compete with fixed stake.
+                // This stake should be based on how many nodes we want to run, and they all
+                // must sum to initial distribution total coins.
+                //let stake = self.initial_distribution;
+                let stake = 200;
+                let c = LeadCoin::new(
+                    stake,
+                    slot,
+                    epoch_secrets.secret_keys[0].inner(),
+                    epoch_secrets.merkle_roots[0],
+                    0,
+                    epoch_secrets.merkle_paths[0].clone(),
+                    pallas::Base::from(seeds[0]),
+                    &mut self.coins_tree,
+                );
+                let query_str = format!(
+                    "INSERT INTO {} ({}) VALUES (?1);",
+                    constants::CONSENSUS_COIN_TABLE,
+                    constants::CONSENSUS_COIN_COL
+                );
+                query = sqlx::query(&query_str);
+                query = query.bind(serialize(&c));
+                query.execute(&mut conn).await?;
+                c
+            }
+        };
+        info!(target: "consensus::state", "create_coins(): Will use LeadCoin with value: {}", coin.value);
         coins.push(coin);
 
         Ok(coins)

+ 26 - 10
src/consensus/validator.rs

@@ -105,12 +105,12 @@ impl ValidatorState {
         debug!(target: "consensus::validator", "Initializing ValidatorState");
 
         debug!(target: "consensus::validator", "Initializing wallet tables for consensus");
-        // TODO: TESTNET: The stuff is kept entirely in memory for now, what should we write
-        //                to disk/wallet?
-        //let consensus_tree_init_query = include_str!("../../script/sql/consensus_tree.sql");
-        //let consensus_keys_init_query = include_str!("../../script/sql/consensus_keys.sql");
-        //wallet.exec_sql(consensus_tree_init_query).await?;
-        //wallet.exec_sql(consensus_keys_init_query).await?;
+
+        // Initialize consensus coin table.
+        // NOTE: In future this will be redundant as consensus coins will live in the money contract.
+        if enable_participation {
+            wallet.exec_sql(include_str!("consensus_coin.sql")).await?;
+        }
 
         debug!(target: "consensus::validator", "Generating leader proof keys with k: {}", constants::LEADER_PROOF_K);
         let bincode = include_bytes!("../../proof/lead.zk.bin");
@@ -128,6 +128,7 @@ impl ValidatorState {
 
         let blockchain = Blockchain::new(db, genesis_ts, genesis_data)?;
         let consensus = ConsensusState::new(
+            wallet.clone(),
             blockchain.clone(),
             bootstrap_ts,
             genesis_ts,
@@ -290,10 +291,10 @@ impl ValidatorState {
 
         // Checking if extending a fork or canonical
         let (prev_hash, coin) = if fork_index == -1 {
-            (self.blockchain.last()?.1, self.consensus.coins[coin_index])
+            (self.blockchain.last()?.1, self.consensus.coins[coin_index].clone())
         } else {
             let checkpoint = self.consensus.forks[fork_index as usize].sequence.last().unwrap();
-            (checkpoint.proposal.hash, checkpoint.coins[coin_index])
+            (checkpoint.proposal.hash, checkpoint.coins[coin_index].clone())
         };
 
         // Generate derived coin blind
@@ -567,8 +568,23 @@ impl ValidatorState {
 
         // If proposal came fromself, we derive new coin
         if let Some((idx, c, derived_blind)) = coin {
-            state_checkpoint.coins[idx] =
-                c.derive_coin(&mut state_checkpoint.coins_tree, derived_blind);
+            info!(target: "consensus::validator", "receive_proposal(): Storing derived coin...");
+            // Derive coin
+            let derived = c.derive_coin(&mut state_checkpoint.coins_tree, derived_blind);
+            // Update consensus coin in wallet
+            // NOTE: In future this will be redundant as consensus coins will live in the money contract.
+            // Get a wallet connection
+            let mut conn = self.wallet.conn.acquire().await?;
+            let query_str = format!(
+                "UPDATE {} SET {} = ?1",
+                constants::CONSENSUS_COIN_TABLE,
+                constants::CONSENSUS_COIN_COL
+            );
+            let mut query = sqlx::query(&query_str);
+            query = query.bind(serialize(&derived));
+            query.execute(&mut conn).await?;
+
+            state_checkpoint.coins[idx] = derived;
         }
         // Store proposal coins nullifiers
         state_checkpoint.nullifiers.push(prop_sn);

+ 2 - 2
src/contract/money/src/client.rs

@@ -1131,11 +1131,11 @@ pub fn build_stake_tx(
             coin.secret.inner(), // coin secret key
             sk_root,
             sk_pos.try_into().unwrap(),
-            sk_merkle_path.try_into().unwrap(),
+            sk_merkle_path,
             coin.note.serial,
             cm_tree,
         );
-        leadcoins.push(leadcoin);
+        leadcoins.push(leadcoin.clone());
         let lead_coin_blind = ValueBlind::random(&mut OsRng);
         let public_key = leadcoin.pk();
         let (lead_proof, lead_revealed) = create_stake_mint_proof(