/* 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 . */ 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 crate::{ blockchain::{BlockInfo, Blockchain, BlockchainOverlay, BlockchainOverlayPtr}, error::TxVerifyFailed, runtime::vm_runtime::Runtime, tx::Transaction, util::time::TimeKeeper, zk::VerifyingKey, Error, Result, }; /// DarkFi consensus module pub mod consensus; use consensus::Consensus; /// Helper utilities pub mod utils; use utils::deploy_native_contracts; /// Configuration for initializing [`Validator`] pub struct ValidatorConfig { /// Helper structure to calculate time related operations pub time_keeper: TimeKeeper, /// Genesis block pub genesis_block: BlockInfo, /// Whitelisted faucet pubkeys (testnet stuff) pub faucet_pubkeys: Vec, } impl ValidatorConfig { pub fn new( time_keeper: TimeKeeper, genesis_block: BlockInfo, faucet_pubkeys: Vec, ) -> Self { Self { time_keeper, genesis_block, faucet_pubkeys } } } /// Atomic pointer to validator. pub type ValidatorPtr = Arc>; /// This struct represents a DarkFi validator node. pub struct Validator { /// Canonical (finalized) blockchain pub blockchain: Blockchain, /// Hot/Live data used by the consensus algorithm pub consensus: Consensus, } impl Validator { pub async fn new(db: &sled::Db, config: ValidatorConfig) -> Result { info!(target: "validator", "Initializing 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() } }; state.consensus.genesis_block = genesis_block; // Deploy native wasm contracts deploy_native_contracts( blockchain_overlay.clone(), &config.time_keeper, &config.faucet_pubkeys, )?; // Write the changes to the actual chain db blockchain_overlay.lock().unwrap().overlay.lock().unwrap().apply()?; info!(target: "validator", "Finished initializing validator"); Ok(Arc::new(RwLock::new(state))) } // ========================== // State transition functions // ========================== // TODO TESTNET: Write down all cases below // State transition checks should be happening in the following cases for a sync node: // 1) When a finalized block is received // 2) When a transaction is being broadcasted to us // State transition checks should be happening in the following cases for a consensus participating node: // 1) When a finalized block is received // 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<()> { // 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)?; // 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>, ) -> 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)> = Decodable::decode(&mut decoder)?; let sig_pub: Vec = 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); Ok(()) } /// Validate a set of [`Transaction`] in sequence and apply them if all are valid. /// 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( &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> = HashMap::new(); // Initialize the map for tx in txs { for call in &tx.calls { vks.insert(call.contract_id.to_bytes(), HashMap::new()); } } // Generate a time keeper using transaction verifying slot let time_keeper = TimeKeeper::new( self.consensus.time_keeper.genesis_ts, self.consensus.time_keeper.epoch_length, self.consensus.time_keeper.slot_time, 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()?; } } let lock = blockchain_overlay.lock().unwrap(); let mut overlay = lock.overlay.lock().unwrap(); if !erroneous_txs.is_empty() { warn!(target: "validator", "Erroneous transactions found in set"); overlay.purge_new_trees()?; return Err(TxVerifyFailed::ErroneousTxs(erroneous_txs).into()) } if !write { debug!(target: "validator", "Skipping apply of state updates because write=false"); overlay.purge_new_trees()?; return Ok(()) } debug!(target: "validator", "Applying overlay changes"); overlay.apply()?; Ok(()) } /// Append to canonical state received slot. /// This should be only used for test purposes. pub async fn receive_test_slot(&mut self, slot: &Slot) -> Result<()> { debug!(target: "validator", "receive_slot(): Appending slot to ledger"); self.blockchain.slots.insert(&[slot.clone()])?; Ok(()) } }