Просмотр исходного кода

darkfid2: proper producer tx usage

aggstam 2 лет назад
Родитель
Сommit
5f053ac265

+ 3 - 0
bin/darkfid2/darkfid_config.toml

@@ -12,6 +12,9 @@ rpc_listen = "tcp://127.0.0.1:18340"
 # Participate in the consensus protocol
 consensus = false
 
+# Wallet address to receive consensus rewards
+#recipient = "YOUR_WALLET_ADDRESS_HERE"
+
 # Skip syncing process and start node right away
 skip_sync = false
 

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

@@ -18,6 +18,7 @@
 
 use std::{
     collections::{HashMap, HashSet},
+    str::FromStr,
     sync::Arc,
 };
 
@@ -41,6 +42,7 @@ use darkfi::{
     Error, Result,
 };
 use darkfi_contract_test_harness::vks;
+use darkfi_sdk::crypto::PublicKey;
 
 #[cfg(test)]
 mod tests;
@@ -83,6 +85,10 @@ struct Args {
     /// Participate in the consensus protocol
     consensus: bool,
 
+    #[structopt(long)]
+    /// Wallet address to receive consensus rewards
+    recipient: Option<String>,
+
     #[structopt(long)]
     /// Skip syncing process and start node right away
     skip_sync: bool,
@@ -245,11 +251,20 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
     // Consensus protocol
     let (consensus_task, consensus_sender) = if args.consensus {
         info!(target: "darkfid", "Starting consensus protocol task");
+        // Grab rewards recipient public key(address)
+        if args.recipient.is_none() {
+            return Err(Error::ParseFailed("Recipient address missing"))
+        }
+        let recipient = match PublicKey::from_str(&args.recipient.unwrap()) {
+            Ok(address) => address,
+            Err(_) => return Err(Error::InvalidAddress),
+        };
+
         let (sender, recvr) = smol::channel::bounded(1);
         let task = StoppableTask::new();
         task.clone().start(
             // Weird hack to prevent lifetimes hell
-            async move { miner_task(&darkfid, &recvr).await },
+            async move { miner_task(&darkfid, &recipient, &recvr).await },
             |res| async {
                 match res {
                     Ok(()) | Err(Error::MinerTaskStopped) => { /* Do nothing */ }

+ 139 - 23
bin/darkfid2/src/task/miner.rs

@@ -16,16 +16,41 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use darkfi::{system::sleep, tx::Transaction, validator::consensus::Proposal, Result};
-use darkfi_sdk::crypto::SecretKey;
+use darkfi::{
+    blockchain::BlockInfo,
+    tx::Transaction,
+    validator::{
+        consensus::{Fork, Proposal},
+        pow::PoWModule,
+    },
+    zk::{empty_witnesses, ProvingKey, ZkCircuit},
+    zkas::ZkBinary,
+    Result,
+};
+use darkfi_consensus_contract::model::SECRET_KEY_PREFIX;
+use darkfi_money_contract::{
+    client::pow_reward_v1::PoWRewardCallBuilder, MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+};
+use darkfi_sdk::{
+    crypto::{poseidon_hash, PublicKey, SecretKey, MONEY_CONTRACT_ID},
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::Encodable;
 use log::info;
 use rand::rngs::OsRng;
 use smol::channel::Receiver;
 
 use crate::{proto::BlockInfoMessage, Darkfid};
 
+// TODO: handle all ? so the task don't stop on errors
+
 /// async task used for participating in the PoW consensus protocol
-pub async fn miner_task(node: &Darkfid, stop_signal: &Receiver<()>) -> Result<()> {
+pub async fn miner_task(
+    node: &Darkfid,
+    recipient: &PublicKey,
+    stop_signal: &Receiver<()>,
+) -> Result<()> {
     // TODO: For now we asume we have a single miner that produces block,
     //       until the PoW consensus and proper validations have been added.
     //       The miner workflow would be:
@@ -45,43 +70,55 @@ pub async fn miner_task(node: &Darkfid, stop_signal: &Receiver<()>) -> Result<()
     //          next best fork block.
     info!(target: "darkfid::task::miner_task", "Starting miner task...");
 
-    // TODO: Remove this once proper validations are added
-    // We sleep so our miner can grab their pickaxe
-    sleep(10).await;
-
     // Start miner loop
-    miner_loop(node, stop_signal).await?;
+    miner_loop(node, recipient, stop_signal).await?;
 
     Ok(())
 }
 
 /// Miner loop
-async fn miner_loop(node: &Darkfid, stop_signal: &Receiver<()>) -> Result<()> {
-    // TODO: secret should be a daemon arg(.toml config)
-    let secret_key = SecretKey::random(&mut OsRng);
-    let tx = Transaction::default();
+async fn miner_loop(
+    node: &Darkfid,
+    recipient: &PublicKey,
+    stop_signal: &Receiver<()>,
+) -> Result<()> {
+    // Grab zkas proving keys and bin for PoWReward transaction
+    info!(target: "darkfid::task::miner_task", "Generating zkas bin and proving keys...");
+    let blockchain = node.validator.read().await.blockchain.clone();
+    let (zkbin, _) = blockchain.contracts.get_zkas(
+        &blockchain.sled_db,
+        &MONEY_CONTRACT_ID,
+        MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+    )?;
+    let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
+    let pk = ProvingKey::build(zkbin.k, &circuit);
+
+    // Generate a random master secret key, to derive all signing keys from.
+    // This enables us to deanonimize proposals from reward recipient(miner).
+    // TODO: maybe miner wants to keep this master secret so they can
+    //       verify their signature in the future?
+    info!(target: "darkfid::task::miner_task", "Generating signing key...");
+    let mut secret = SecretKey::random(&mut OsRng);
 
     // Generate a new fork to be able to extend
+    info!(target: "darkfid::task::miner_task", "Generating new empty fork...");
     node.validator.write().await.consensus.generate_pow_slot()?;
 
+    info!(target: "darkfid::task::miner_task", "Miner loop starts!");
     // Miner loop
     loop {
-        // Mine next block proposal
-        let (next_proposal, fork_index) = node
-            .validator
-            .read()
-            .await
-            .consensus
-            .generate_proposal(&secret_key, tx.clone())
-            .await?;
-        let mut next_block = next_proposal.block;
-        let module = node.validator.read().await.consensus.forks[fork_index].module.clone();
+        // Grab next block
+        let (mut next_block, module) =
+            generate_next_block(node, &mut secret, recipient, &zkbin, &pk).await?;
         module.mine_block(&mut next_block, stop_signal)?;
 
+        // Sign the mined block
+        next_block.sign(&secret)?;
+
         // Verify it
         node.validator.read().await.consensus.module.verify_current_block(&next_block)?;
 
-        // Append the mined proposal
+        // Append the mined block as a proposal
         let proposal = Proposal::new(next_block)?;
         let mut lock = node.validator.write().await;
         lock.consensus.append_proposal(&proposal).await?;
@@ -96,3 +133,82 @@ async fn miner_loop(node: &Darkfid, stop_signal: &Receiver<()>) -> Result<()> {
         }
     }
 }
+
+/// Auxiliary function to generate next block in an atomic manner
+async fn generate_next_block(
+    node: &Darkfid,
+    secret: &mut SecretKey,
+    recipient: &PublicKey,
+    zkbin: &ZkBinary,
+    pk: &ProvingKey,
+) -> Result<(BlockInfo, PoWModule)> {
+    let lock = node.validator.read().await;
+
+    // Grab best current fork
+    let fork_index = lock.consensus.best_forks_indexes()?[0];
+    let fork = &lock.consensus.forks[fork_index];
+
+    // Generate new signing key for next block
+    let height = fork.slots.last().unwrap().id;
+    // We are deriving the next secret key for optimization.
+    // Next secret is the poseidon hash of:
+    //  [prefix, current(previous) secret, signing(block) height].
+    let next_secret = poseidon_hash([SECRET_KEY_PREFIX, secret.inner(), height.into()]);
+    *secret = SecretKey::from(next_secret);
+
+    // Generate reward transaction
+    let tx = generate_pow_transaction(fork, secret, recipient, zkbin, pk)?;
+
+    // Mine next block proposal
+    let next_block = lock.consensus.generate_unsigned_block(fork, tx).await?;
+    let module = lock.consensus.forks[fork_index].module.clone();
+    Ok((next_block, module))
+}
+
+/// Auxiliary function to generate a Money::PoWReward transaction
+fn generate_pow_transaction(
+    fork: &Fork,
+    secret: &SecretKey,
+    recipient: &PublicKey,
+    zkbin: &ZkBinary,
+    pk: &ProvingKey,
+) -> Result<Transaction> {
+    // Grab next block height
+    let block_height = fork.slots.last().unwrap().id;
+
+    // Grab extended proposal info
+    let last_proposal = fork.last_proposal()?;
+    let last_nonce = last_proposal.block.header.nonce;
+    let fork_hash = last_proposal.hash;
+    let fork_previous_hash = last_proposal.block.header.previous;
+
+    // We're just going to be using a zero spend-hook and user-data
+    let spend_hook = pallas::Base::zero();
+    let user_data = pallas::Base::zero();
+
+    // Build the transaction debris
+    let debris = PoWRewardCallBuilder {
+        secret: *secret,
+        recipient: *recipient,
+        block_height,
+        last_nonce,
+        fork_hash,
+        fork_previous_hash,
+        spend_hook,
+        user_data,
+        mint_zkbin: zkbin.clone(),
+        mint_pk: pk.clone(),
+    }
+    .build()?;
+
+    // Generate and sign the actual transaction
+    let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
+    debris.params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
+    let proofs = vec![debris.proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &[*secret])?;
+    tx.signatures = vec![sigs];
+
+    Ok(tx)
+}

+ 13 - 1
src/blockchain/block_store.rs

@@ -18,7 +18,10 @@
 
 use darkfi_sdk::{
     blockchain::Slot,
-    crypto::schnorr::Signature,
+    crypto::{
+        schnorr::{SchnorrSecret, Signature},
+        SecretKey,
+    },
     pasta::{group::ff::FromUniformBytes, pallas},
 };
 #[cfg(feature = "async-serial")]
@@ -26,6 +29,7 @@ use darkfi_serial::async_trait;
 
 use darkfi_serial::{deserialize, serialize, Encodable, SerialDecodable, SerialEncodable};
 use num_bigint::BigUint;
+use rand::rngs::OsRng;
 
 use crate::{tx::Transaction, Error, Result};
 
@@ -144,6 +148,14 @@ impl BlockInfo {
 
         Ok(())
     }
+
+    /// Sign block header using provided secret key
+    // TODO: sign more stuff?
+    pub fn sign(&mut self, secret_key: &SecretKey) -> Result<()> {
+        self.signature = secret_key.sign(&mut OsRng, &self.hash()?.as_bytes()[..]);
+
+        Ok(())
+    }
 }
 
 /// [`Block`] sled tree

+ 28 - 20
src/validator/consensus.rs

@@ -18,12 +18,11 @@
 
 use darkfi_sdk::{
     blockchain::{expected_reward, PidOutput, PreviousSlot, Slot, POS_START},
-    crypto::{schnorr::SchnorrSecret, SecretKey},
+    crypto::SecretKey,
     pasta::{group::ff::PrimeField, pallas},
 };
 use darkfi_serial::{async_trait, serialize, SerialDecodable, SerialEncodable};
 use log::{debug, error, info};
-use rand::rngs::OsRng;
 
 use crate::{
     blockchain::{BlockInfo, Blockchain, BlockchainOverlay, BlockchainOverlayPtr, Header},
@@ -133,20 +132,15 @@ impl Consensus {
         Ok((producers, last_hashes, second_to_last_hashes))
     }
 
-    /// Generate a block proposal for the next/current hot/live(last) slot,
-    /// containing all pending transactions. Proposal extends the best fork
-    /// chain the node is holding. This should only be called after
-    /// generating next/current slot. Proposal is signed using provided secret
-    /// key, which must also have signed the provided proposal transaction.
-    /// Best fork index is also returned in case its required.
-    pub async fn generate_proposal(
+    /// Generate an unsigned block for provided fork, containing all
+    /// pending transactions. This should only be called after generating
+    /// next/current slot.
+    pub async fn generate_unsigned_block(
         &self,
-        secret_key: &SecretKey,
-        proposal_tx: Transaction,
-    ) -> Result<(Proposal, usize)> {
-        // Grab best forks, pick the first and its last slot
-        let fork_index = self.best_forks_indexes()?[0];
-        let fork = &self.forks[fork_index];
+        fork: &Fork,
+        producer_tx: Transaction,
+    ) -> Result<BlockInfo> {
+        // Grab fork's last slot
         let slot = fork.slots.last().unwrap();
 
         // Generate a time keeper for next/current slot
@@ -160,7 +154,7 @@ impl Consensus {
 
         // Grab forks' unproposed transactions
         let mut unproposed_txs = fork.unproposed_txs(&self.blockchain, &time_keeper).await?;
-        unproposed_txs.push(proposal_tx);
+        unproposed_txs.push(producer_tx);
 
         // Grab forks' last block proposal(previous)
         let previous = fork.last_proposal()?;
@@ -181,14 +175,28 @@ impl Consensus {
         // Add transactions to the block
         block.append_txs(unproposed_txs)?;
 
-        // TODO: sign more stuff?
-        // Sign block header using provided secret key
-        block.signature = secret_key.sign(&mut OsRng, &block.header.hash()?.as_bytes()[..]);
+        Ok(block)
+    }
+
+    /// Generate a block proposal for provided fork, containing all
+    /// pending transactions. This should only be called after generating
+    /// next/current slot. Proposal is signed using provided secret key,
+    /// which must also have signed the provided proposal transaction.
+    pub async fn generate_signed_proposal(
+        &self,
+        fork: &Fork,
+        producer_tx: Transaction,
+        secret_key: &SecretKey,
+    ) -> Result<Proposal> {
+        let mut block = self.generate_unsigned_block(fork, producer_tx).await?;
+
+        // Sign block
+        block.sign(secret_key)?;
 
         // Generate the block proposal from the block
         let proposal = Proposal::new(block)?;
 
-        Ok((proposal, fork_index))
+        Ok(proposal)
     }
 
     /// Given a proposal, the node verifys it and finds which fork it extends.

+ 9 - 0
src/validator/verification.rs

@@ -90,6 +90,10 @@ pub async fn verify_genesis_block(
         return Err(Error::BlockContainsNoTransactions(block_hash))
     }
 
+    // Insert genesis slot so transactions can be validated against.
+    // Since an overlay is used, original database is not affected.
+    overlay.lock().unwrap().slots.insert(&[genesis_slot.clone()])?;
+
     // Genesis transaction must be the Transaction::default() one(empty)
     let tx = block.txs.last().unwrap();
     if tx != &Transaction::default() {
@@ -149,6 +153,11 @@ pub async fn verify_block(
         return Err(Error::BlockContainsNoTransactions(block_hash))
     }
 
+    // Insert last block slot so transactions can be validated against.
+    // Rest (empty) slots will be inserted along with the block.
+    // Since an overlay is used, original database is not affected.
+    overlay.lock().unwrap().slots.insert(&[block.slots.last().unwrap().clone()])?;
+
     // Verify proposal transaction if not in testing mode
     if !testing_mode {
         let tx = block.txs.last().unwrap();