Explorar o código

validator/consensus: proposal generation implemented

aggstam %!s(int64=3) %!d(string=hai) anos
pai
achega
80ffd5afd6
Modificáronse 4 ficheiros con 177 adicións e 11 borrados
  1. 2 1
      src/blockchain/mod.rs
  2. 28 0
      src/blockchain/tx_store.rs
  3. 139 7
      src/validator/consensus/mod.rs
  4. 8 3
      src/validator/mod.rs

+ 2 - 1
src/blockchain/mod.rs

@@ -29,7 +29,8 @@ use crate::{tx::Transaction, validator::consensus::next_block_reward, Error, Res
 /// Block related definitions and storage implementations
 pub mod block_store;
 pub use block_store::{
-    Block, BlockInfo, BlockOrderStore, BlockOrderStoreOverlay, BlockStore, BlockStoreOverlay,
+    Block, BlockInfo, BlockOrderStore, BlockOrderStoreOverlay, BlockProducer, BlockStore,
+    BlockStoreOverlay,
 };
 
 /// Header definition and storage implementation

+ 28 - 0
src/blockchain/tx_store.rs

@@ -235,6 +235,34 @@ impl PendingTxStore {
         Ok(self.0.contains_key(tx_hash.as_bytes())?)
     }
 
+    /// Fetch given tx hashes from the pending tx store.
+    /// The resulting vector contains `Option`, which is `Some` if the tx
+    /// was found in the pending tx store, 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,
+        tx_hashes: &[blake3::Hash],
+        strict: bool,
+    ) -> Result<Vec<Option<Transaction>>> {
+        let mut ret = Vec::with_capacity(tx_hashes.len());
+
+        for tx_hash in tx_hashes {
+            if let Some(found) = self.0.get(tx_hash.as_bytes())? {
+                let tx = deserialize(&found)?;
+                ret.push(Some(tx));
+            } else {
+                if strict {
+                    let s = tx_hash.to_hex().as_str().to_string();
+                    return Err(Error::TransactionNotFound(s))
+                }
+                ret.push(None);
+            }
+        }
+
+        Ok(ret)
+    }
+
     /// Retrieve all transactions from the pending tx store in the form of
     /// a HashMap with key the transaction hash and value the transaction
     /// itself.

+ 139 - 7
src/validator/consensus/mod.rs

@@ -16,14 +16,22 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use darkfi_sdk::blockchain::{PidOutput, PreviousSlot, Slot};
-use darkfi_serial::{SerialDecodable, SerialEncodable};
+use darkfi_sdk::{
+    blockchain::{PidOutput, PreviousSlot, Slot},
+    crypto::{schnorr::SchnorrSecret, MerkleNode, MerkleTree, SecretKey},
+    pasta::{group::ff::PrimeField, pallas},
+};
+use darkfi_serial::{serialize, SerialDecodable, SerialEncodable};
 use log::{error, warn};
+use rand::rngs::OsRng;
 
 use crate::{
-    blockchain::{BlockInfo, Blockchain, BlockchainOverlay, BlockchainOverlayPtr},
-    util::time::TimeKeeper,
-    validator::{consensus::pid::slot_pid_output, verify_block},
+    blockchain::{
+        BlockInfo, BlockProducer, Blockchain, BlockchainOverlay, BlockchainOverlayPtr, Header,
+    },
+    tx::Transaction,
+    util::time::{TimeKeeper, Timestamp},
+    validator::{consensus::pid::slot_pid_output, verify_block, verify_transactions},
     Error, Result,
 };
 
@@ -104,6 +112,70 @@ impl Consensus {
         Ok((producers, last_hashes, second_to_last_hashes))
     }
 
+    /// Generate a block proposal for the current hot/live(last) slot,
+    /// containing all pending transactions. Proposal extends the longest fork
+    /// chain the node is holding. This should only be called after
+    /// generate_slot(). Proposal is signed using provided secret key, which
+    /// must also have signed the provided proposal transaction.
+    pub async fn generate_proposal(
+        &self,
+        secret_key: SecretKey,
+        proposal_tx: Transaction,
+    ) -> Result<Proposal> {
+        // Generate a time keeper for current slot
+        let time_keeper = self.time_keeper.current();
+
+        // Retrieve longest known fork
+        let mut fork_index = 0;
+        let mut max_fork_length = 0;
+        for (index, fork) in self.forks.iter().enumerate() {
+            if fork.proposals.len() > max_fork_length {
+                fork_index = index;
+                max_fork_length = fork.proposals.len();
+            }
+        }
+        let fork = &self.forks[fork_index];
+
+        // Grab forks' unproposed transactions and their root
+        let unproposed_txs = fork.unproposed_txs(&self.blockchain, &time_keeper).await?;
+        let mut tree = MerkleTree::new(100);
+        // The following is pretty weird, so something better should be done.
+        for tx in &unproposed_txs {
+            let mut hash = [0_u8; 32];
+            hash[0..31].copy_from_slice(&blake3::hash(&serialize(tx)).as_bytes()[0..31]);
+            tree.append(MerkleNode::from(pallas::Base::from_repr(hash).unwrap()));
+        }
+        let root = tree.root(0).unwrap();
+
+        // Grab forks' last block proposal(previous)
+        let previous = fork.last_proposal()?;
+
+        // Generate the new header
+        let slot = fork.slots.last().unwrap();
+        // TODO: verify if header timestamp should be blockchain or system timestamp
+        let header = Header::new(
+            previous.block.blockhash(),
+            time_keeper.slot_epoch(slot.id),
+            slot.id,
+            Timestamp::current_time(),
+            root,
+        );
+
+        // TODO: sign more stuff?
+        // Sign block header using provided secret key
+        let signature =
+            SecretKey::from(secret_key).sign(&mut OsRng, &header.headerhash().as_bytes()[..]);
+
+        // Generate block producer info
+        let block_producer = BlockProducer::new(signature, proposal_tx, slot.last_eta);
+
+        // Generate the block and its proposal
+        let block = BlockInfo::new(header, unproposed_txs, block_producer, fork.slots.clone());
+        let proposal = Proposal::new(block);
+
+        Ok(proposal)
+    }
+
     /// Given a proposal, the node verifys it and finds which fork it extends.
     /// If the proposal extends the canonical blockchain, a new fork chain is created.
     /// A proposal is considered valid when the following rules apply:
@@ -339,8 +411,10 @@ pub struct Fork {
 
 impl Fork {
     pub fn new(blockchain: &Blockchain) -> Result<Self> {
+        let mempool =
+            blockchain.get_pending_txs()?.iter().map(|tx| blake3::hash(&serialize(tx))).collect();
         let overlay = BlockchainOverlay::new(blockchain)?;
-        Ok(Self { overlay, proposals: vec![], slots: vec![], mempool: vec![] })
+        Ok(Self { overlay, proposals: vec![], slots: vec![], mempool })
     }
 
     /// Auxiliary function to retrieve last proposal
@@ -355,6 +429,64 @@ impl Fork {
         Ok(Proposal::new(block))
     }
 
+    /// Utility function to extract leader selection lottery randomness(eta),
+    /// defined as the hash of the last block, converted to pallas base.
+    fn get_last_eta(&self) -> Result<pallas::Base> {
+        // Retrieve last block(or proposal) hash
+        let hash = if self.proposals.is_empty() {
+            self.overlay.lock().unwrap().last_block()?.blockhash()
+        } else {
+            self.proposals.last().unwrap().clone()
+        };
+
+        // Read first 240 bits
+        let mut bytes: [u8; 32] = *hash.as_bytes();
+        bytes[30] = 0;
+        bytes[31] = 0;
+
+        Ok(pallas::Base::from_repr(bytes).unwrap())
+    }
+
+    /// Auxiliary function to retrieve unproposed valid transactions.
+    pub async fn unproposed_txs(
+        &self,
+        blockchain: &Blockchain,
+        time_keeper: &TimeKeeper,
+    ) -> Result<Vec<Transaction>> {
+        // Retrieve all mempool transactions
+        let mut unproposed_txs: Vec<Transaction> = blockchain
+            .pending_txs
+            .get(&self.mempool, true)?
+            .iter()
+            .map(|x| x.clone().unwrap())
+            .collect();
+
+        // Iterate over fork proposals to find already proposed transactions
+        // and remove them from the unproposed_txs vector.
+        let proposals = self.overlay.lock().unwrap().get_blocks_by_hash(&self.proposals)?;
+        for proposal in proposals {
+            for tx in &proposal.txs {
+                unproposed_txs.retain(|x| x != tx);
+            }
+        }
+
+        // Check if transactions exceed configured cap
+        if unproposed_txs.len() > TXS_CAP {
+            unproposed_txs = unproposed_txs[0..TXS_CAP].to_vec()
+        }
+
+        // Clone forks' overlay
+        let overlay = self.overlay.lock().unwrap().full_clone()?;
+
+        // Verify transactions
+        let erroneous_txs = verify_transactions(&overlay, &time_keeper, &unproposed_txs).await?;
+        if !erroneous_txs.is_empty() {
+            unproposed_txs.retain(|x| !erroneous_txs.contains(x));
+        }
+
+        Ok(unproposed_txs)
+    }
+
     /// Generate current hot/live slot
     pub fn generate_slot(
         &mut self,
@@ -384,7 +516,7 @@ impl Fork {
 
         // Each slot starts as an empty slot(not reward) when generated, carrying
         // last eta
-        let last_eta = previous_slot.last_eta;
+        let last_eta = self.get_last_eta()?;
         let total_tokens = previous_slot.total_tokens + previous_slot.reward;
         let reward = 0;
         let slot = Slot::new(id, previous, pid, last_eta, total_tokens, reward);

+ 8 - 3
src/validator/mod.rs

@@ -148,8 +148,11 @@ impl Validator {
         // If node participates in consensus and holds any forks, iterate over them
         // to verify transaction validity in their overlays
         for fork in self.consensus.forks.iter_mut() {
+            // Clone forks' overlay
+            let overlay = fork.overlay.lock().unwrap().full_clone()?;
+
             // Verify transaction
-            let erroneous_txs = verify_transactions(&fork.overlay, &time_keeper, &tx_vec).await?;
+            let erroneous_txs = verify_transactions(&overlay, &time_keeper, &tx_vec).await?;
             if !erroneous_txs.is_empty() {
                 continue
             }
@@ -201,9 +204,11 @@ impl Validator {
             // If node participates in consensus and holds any forks, iterate over them
             // to verify transaction validity in their overlays
             for fork in self.consensus.forks.iter_mut() {
+                // Clone forks' overlay
+                let overlay = fork.overlay.lock().unwrap().full_clone()?;
+
                 // Verify transaction
-                let erroneous_txs =
-                    verify_transactions(&fork.overlay, &time_keeper, &tx_vec).await?;
+                let erroneous_txs = verify_transactions(&overlay, &time_keeper, &tx_vec).await?;
                 if erroneous_txs.is_empty() {
                     valid = true;
                     continue