|
|
@@ -16,20 +16,15 @@
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
*/
|
|
|
|
|
|
-use std::{collections::HashMap, io::Cursor};
|
|
|
-
|
|
|
use async_std::sync::{Arc, RwLock};
|
|
|
-use darkfi_sdk::{blockchain::Slot, crypto::PublicKey, pasta::pallas};
|
|
|
-use darkfi_serial::{Decodable, Encodable, WriteExt};
|
|
|
-use log::{debug, error, info, warn};
|
|
|
+use darkfi_sdk::{blockchain::Slot, crypto::PublicKey};
|
|
|
+use log::{debug, info, warn};
|
|
|
|
|
|
use crate::{
|
|
|
- blockchain::{BlockInfo, Blockchain, BlockchainOverlay, BlockchainOverlayPtr},
|
|
|
+ blockchain::{BlockInfo, Blockchain, BlockchainOverlay},
|
|
|
error::TxVerifyFailed,
|
|
|
- runtime::vm_runtime::Runtime,
|
|
|
tx::Transaction,
|
|
|
util::time::TimeKeeper,
|
|
|
- zk::VerifyingKey,
|
|
|
Error, Result,
|
|
|
};
|
|
|
|
|
|
@@ -37,6 +32,10 @@ use crate::{
|
|
|
pub mod consensus;
|
|
|
use consensus::Consensus;
|
|
|
|
|
|
+/// Verification functions
|
|
|
+pub mod verification;
|
|
|
+use verification::{verify_block, verify_transactions};
|
|
|
+
|
|
|
/// Helper utilities
|
|
|
pub mod utils;
|
|
|
use utils::deploy_native_contracts;
|
|
|
@@ -79,31 +78,14 @@ impl Validator {
|
|
|
info!(target: "validator", "Initializing Blockchain");
|
|
|
let blockchain = Blockchain::new(db)?;
|
|
|
|
|
|
- info!(target: "validator", "Initializing Consensus");
|
|
|
- let consensus = Consensus::new(blockchain.clone(), config.time_keeper.clone());
|
|
|
-
|
|
|
- // Create the actual state
|
|
|
- let mut state = Self { blockchain: blockchain.clone(), consensus };
|
|
|
-
|
|
|
// Create an overlay over whole blockchain so we can write stuff
|
|
|
let blockchain_overlay = BlockchainOverlay::new(&blockchain)?;
|
|
|
|
|
|
// Add genesis block if blockchain is empty
|
|
|
- let genesis_block = match blockchain.genesis() {
|
|
|
- Ok((_, hash)) => hash,
|
|
|
- Err(_) => {
|
|
|
- info!(target: "validator", "Appending genesis block");
|
|
|
- state
|
|
|
- .add_blocks(
|
|
|
- blockchain_overlay.clone(),
|
|
|
- &config.time_keeper,
|
|
|
- &[config.genesis_block.clone()],
|
|
|
- )
|
|
|
- .await?;
|
|
|
- config.genesis_block.blockhash()
|
|
|
- }
|
|
|
+ if blockchain.genesis().is_err() {
|
|
|
+ info!(target: "validator", "Appending genesis block");
|
|
|
+ verify_block(blockchain_overlay.clone(), &config.genesis_block, &None)?;
|
|
|
};
|
|
|
- state.consensus.genesis_block = genesis_block;
|
|
|
|
|
|
// Deploy native wasm contracts
|
|
|
deploy_native_contracts(
|
|
|
@@ -115,9 +97,14 @@ impl Validator {
|
|
|
// Write the changes to the actual chain db
|
|
|
blockchain_overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
|
|
|
|
|
|
+ info!(target: "validator", "Initializing Consensus");
|
|
|
+ let consensus = Consensus::new(blockchain.clone(), config.time_keeper);
|
|
|
+
|
|
|
+ // Create the actual state
|
|
|
+ let state = Arc::new(RwLock::new(Self { blockchain, consensus }));
|
|
|
info!(target: "validator", "Finished initializing validator");
|
|
|
|
|
|
- Ok(Arc::new(RwLock::new(state)))
|
|
|
+ Ok(state)
|
|
|
}
|
|
|
|
|
|
// ==========================
|
|
|
@@ -132,159 +119,28 @@ impl Validator {
|
|
|
// 2) When a transaction is being broadcasted to us
|
|
|
// ==========================
|
|
|
|
|
|
- /// Append provided blocks to the provided overlay. Block sequence must be valid,
|
|
|
- /// meaning that each block and its transactions are valid, in order.
|
|
|
- pub async fn add_blocks(
|
|
|
- &self,
|
|
|
- overlay: BlockchainOverlayPtr,
|
|
|
- _time_keeper: &TimeKeeper,
|
|
|
- blocks: &[BlockInfo],
|
|
|
- ) -> Result<()> {
|
|
|
+ /// Validate a set of [`BlockInfo`] in sequence and apply them if all are valid.
|
|
|
+ pub async fn add_blocks(&self, blocks: &[BlockInfo]) -> Result<()> {
|
|
|
+ debug!(target: "validator", "Instantiating BlockchainOverlay");
|
|
|
+ let overlay = BlockchainOverlay::new(&self.blockchain)?;
|
|
|
+
|
|
|
// Retrieve last block
|
|
|
let lock = overlay.lock().unwrap();
|
|
|
let mut previous = if !lock.is_empty()? { Some(lock.last_block()?) } else { None };
|
|
|
// Validate and insert each block
|
|
|
for block in blocks {
|
|
|
- // Check if block already exists
|
|
|
- if lock.has_block(block)? {
|
|
|
- return Err(Error::BlockAlreadyExists(block.blockhash().to_string()))
|
|
|
- }
|
|
|
-
|
|
|
- // This will be true for every insert, apart from genesis
|
|
|
- if let Some(p) = previous {
|
|
|
- block.validate(&p)?;
|
|
|
- }
|
|
|
-
|
|
|
- // TODO: Add rest block verifications here
|
|
|
- /*
|
|
|
- let current_slot = self.consensus.time_keeper.current_slot();
|
|
|
- if slot.id > current_slot {
|
|
|
- return Err(Error::FutureSlotReceived(slot.id))
|
|
|
- }
|
|
|
- */
|
|
|
-
|
|
|
- // Insert block
|
|
|
- lock.add_block(block)?;
|
|
|
+ if verify_block(overlay.clone(), block, &previous).is_err() {
|
|
|
+ warn!(target: "validator", "Erroneous block found in set");
|
|
|
+ overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
|
|
|
+ return Err(Error::BlockIsInvalid(block.blockhash().to_string()))
|
|
|
+ };
|
|
|
|
|
|
// Use last inserted block as next iteration previous
|
|
|
previous = Some(block.clone());
|
|
|
}
|
|
|
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- /// Validate WASM execution, signatures, and ZK proofs for a given [`Transaction`].
|
|
|
- async fn verify_transaction(
|
|
|
- &self,
|
|
|
- blockchain_overlay: BlockchainOverlayPtr,
|
|
|
- tx: &Transaction,
|
|
|
- time_keeper: &TimeKeeper,
|
|
|
- verifying_keys: &mut HashMap<[u8; 32], HashMap<String, VerifyingKey>>,
|
|
|
- ) -> Result<()> {
|
|
|
- let tx_hash = tx.hash();
|
|
|
- debug!(target: "validator", "Validating transaction {}", tx_hash);
|
|
|
-
|
|
|
- // Table of public inputs used for ZK proof verification
|
|
|
- let mut zkp_table = vec![];
|
|
|
- // Table of public keys used for signature verification
|
|
|
- let mut sig_table = vec![];
|
|
|
-
|
|
|
- // Iterate over all calls to get the metadata
|
|
|
- for (idx, call) in tx.calls.iter().enumerate() {
|
|
|
- debug!(target: "validator", "Executing contract call {}", idx);
|
|
|
-
|
|
|
- // Write the actual payload data
|
|
|
- let mut payload = vec![];
|
|
|
- payload.write_u32(idx as u32)?; // Call index
|
|
|
- tx.calls.encode(&mut payload)?; // Actual call data
|
|
|
-
|
|
|
- debug!(target: "validator", "Instantiating WASM runtime");
|
|
|
- let wasm = blockchain_overlay.lock().unwrap().wasm_bincode.get(call.contract_id)?;
|
|
|
-
|
|
|
- let mut runtime = Runtime::new(
|
|
|
- &wasm,
|
|
|
- blockchain_overlay.clone(),
|
|
|
- call.contract_id,
|
|
|
- time_keeper.clone(),
|
|
|
- )?;
|
|
|
-
|
|
|
- debug!(target: "validator", "Executing \"metadata\" call");
|
|
|
- let metadata = runtime.metadata(&payload)?;
|
|
|
-
|
|
|
- // Decode the metadata retrieved from the execution
|
|
|
- let mut decoder = Cursor::new(&metadata);
|
|
|
-
|
|
|
- // The tuple is (zkasa_ns, public_inputs)
|
|
|
- let zkp_pub: Vec<(String, Vec<pallas::Base>)> = Decodable::decode(&mut decoder)?;
|
|
|
- let sig_pub: Vec<PublicKey> = Decodable::decode(&mut decoder)?;
|
|
|
- // TODO: Make sure we've read all the bytes above.
|
|
|
- debug!(target: "validator", "Successfully executed \"metadata\" call");
|
|
|
-
|
|
|
- // Here we'll look up verifying keys and insert them into the per-contract map.
|
|
|
- debug!(target: "validator", "Performing VerifyingKey lookups from the sled db");
|
|
|
- for (zkas_ns, _) in &zkp_pub {
|
|
|
- let inner_vk_map = verifying_keys.get_mut(&call.contract_id.to_bytes()).unwrap();
|
|
|
-
|
|
|
- // TODO: This will be a problem in case of ::deploy, unless we force a different
|
|
|
- // namespace and disable updating existing circuit. Might be a smart idea to do
|
|
|
- // so in order to have to care less about being able to verify historical txs.
|
|
|
- if inner_vk_map.contains_key(zkas_ns.as_str()) {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- let (_, vk) = blockchain_overlay
|
|
|
- .lock()
|
|
|
- .unwrap()
|
|
|
- .contracts
|
|
|
- .get_zkas(&call.contract_id, zkas_ns)?;
|
|
|
-
|
|
|
- inner_vk_map.insert(zkas_ns.to_string(), vk);
|
|
|
- }
|
|
|
-
|
|
|
- zkp_table.push(zkp_pub);
|
|
|
- sig_table.push(sig_pub);
|
|
|
-
|
|
|
- // After getting the metadata, we run the "exec" function with the same runtime
|
|
|
- // and the same payload.
|
|
|
- debug!(target: "validator", "Executing \"exec\" call");
|
|
|
- let state_update = runtime.exec(&payload)?;
|
|
|
- debug!(target: "validator", "Successfully executed \"exec\" call");
|
|
|
-
|
|
|
- // If that was successful, we apply the state update in the ephemeral overlay.
|
|
|
- debug!(target: "validator", "Executing \"apply\" call");
|
|
|
- runtime.apply(&state_update)?;
|
|
|
- debug!(target: "validator", "Successfully executed \"apply\" call");
|
|
|
-
|
|
|
- // At this point we're done with the call and move on to the next one.
|
|
|
- }
|
|
|
-
|
|
|
- // When we're done looping and executing over the tx's contract calls, we now
|
|
|
- // move on with verification. First we verify the signatures as that's cheaper,
|
|
|
- // and then finally we verify the ZK proofs.
|
|
|
- debug!(target: "validator", "Verifying signatures for transaction {}", tx_hash);
|
|
|
- if sig_table.len() != tx.signatures.len() {
|
|
|
- error!(target: "validator", "Incorrect number of signatures in tx {}", tx_hash);
|
|
|
- return Err(TxVerifyFailed::MissingSignatures.into())
|
|
|
- }
|
|
|
-
|
|
|
- // TODO: Go through the ZK circuits that have to be verified and account for the opcodes.
|
|
|
-
|
|
|
- if let Err(e) = tx.verify_sigs(sig_table) {
|
|
|
- error!(target: "validator", "Signature verification for tx {} failed: {}", tx_hash, e);
|
|
|
- return Err(TxVerifyFailed::InvalidSignature.into())
|
|
|
- }
|
|
|
-
|
|
|
- debug!(target: "validator", "Signature verification successful");
|
|
|
-
|
|
|
- debug!(target: "validator", "Verifying ZK proofs for transaction {}", tx_hash);
|
|
|
- if let Err(e) = tx.verify_zkps(verifying_keys, zkp_table).await {
|
|
|
- error!(target: "consensus::validator", "ZK proof verification for tx {} failed: {}", tx_hash, e);
|
|
|
- return Err(TxVerifyFailed::InvalidZkProof.into())
|
|
|
- }
|
|
|
-
|
|
|
- debug!(target: "validator", "ZK proof verification successful");
|
|
|
- debug!(target: "validator", "Transaction {} verified successfully", tx_hash);
|
|
|
-
|
|
|
+ debug!(target: "validator", "Applying overlay changes");
|
|
|
+ overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
@@ -292,29 +148,14 @@ impl Validator {
|
|
|
/// In case any of the transactions fail, they will be returned to the caller.
|
|
|
/// The function takes a boolean called `write` which tells it to actually write
|
|
|
/// the state transitions to the database.
|
|
|
- pub async fn verify_transactions(
|
|
|
+ pub async fn add_transactions(
|
|
|
&self,
|
|
|
txs: &[Transaction],
|
|
|
verifying_slot: u64,
|
|
|
write: bool,
|
|
|
) -> Result<()> {
|
|
|
- debug!(target: "validator", "Verifying {} transactions", txs.len());
|
|
|
-
|
|
|
debug!(target: "validator", "Instantiating BlockchainOverlay");
|
|
|
- let blockchain_overlay = BlockchainOverlay::new(&self.blockchain)?;
|
|
|
-
|
|
|
- // Tracker for failed txs
|
|
|
- let mut erroneous_txs = vec![];
|
|
|
-
|
|
|
- // Map of ZK proof verifying keys for the current transaction batch
|
|
|
- let mut vks: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
|
|
|
-
|
|
|
- // Initialize the map
|
|
|
- for tx in txs {
|
|
|
- for call in &tx.calls {
|
|
|
- vks.insert(call.contract_id.to_bytes(), HashMap::new());
|
|
|
- }
|
|
|
- }
|
|
|
+ let overlay = BlockchainOverlay::new(&self.blockchain)?;
|
|
|
|
|
|
// Generate a time keeper using transaction verifying slot
|
|
|
let time_keeper = TimeKeeper::new(
|
|
|
@@ -324,21 +165,10 @@ impl Validator {
|
|
|
verifying_slot,
|
|
|
);
|
|
|
|
|
|
- // Iterate over transactions and attempt to verify them
|
|
|
- for tx in txs {
|
|
|
- blockchain_overlay.lock().unwrap().checkpoint();
|
|
|
- if let Err(e) = self
|
|
|
- .verify_transaction(blockchain_overlay.clone(), tx, &time_keeper, &mut vks)
|
|
|
- .await
|
|
|
- {
|
|
|
- warn!(target: "validator", "Transaction verification failed: {}", e);
|
|
|
- erroneous_txs.push(tx.clone());
|
|
|
- // TODO: verify this works as expected
|
|
|
- blockchain_overlay.lock().unwrap().revert_to_checkpoint()?;
|
|
|
- }
|
|
|
- }
|
|
|
+ // Verify all transactions and get erroneous ones
|
|
|
+ let erroneous_txs = verify_transactions(overlay.clone(), &time_keeper, txs).await?;
|
|
|
|
|
|
- let lock = blockchain_overlay.lock().unwrap();
|
|
|
+ let lock = overlay.lock().unwrap();
|
|
|
let mut overlay = lock.overlay.lock().unwrap();
|
|
|
if !erroneous_txs.is_empty() {
|
|
|
warn!(target: "validator", "Erroneous transactions found in set");
|