Quellcode durchsuchen

consensus: Dynamically load VerifyingKey for zkas circuits from sled.

parazyd vor 3 Jahren
Ursprung
Commit
473d2f8707
5 geänderte Dateien mit 105 neuen und 67 gelöschten Zeilen
  1. 4 4
      Cargo.lock
  2. 2 1
      Cargo.toml
  3. 78 43
      src/consensus/validator.rs
  4. 2 2
      src/runtime/vm_runtime.rs
  5. 19 17
      src/tx/mod.rs

+ 4 - 4
Cargo.lock

@@ -3347,9 +3347,9 @@ dependencies = [
 
 [[package]]
 name = "rayon"
-version = "1.6.1"
+version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
+checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
 dependencies = [
  "either",
  "rayon-core",
@@ -3357,9 +3357,9 @@ dependencies = [
 
 [[package]]
 name = "rayon-core"
-version = "1.10.2"
+version = "1.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
+checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
 dependencies = [
  "crossbeam-channel",
  "crossbeam-deque",

+ 2 - 1
Cargo.toml

@@ -85,6 +85,7 @@ structopt-toml = {version= "0.5.1", optional = true}
 toml = {version = "0.7.1", optional = true}
 # Big float high precision arithmetics
 dashu = { version = "0.3.0", optional=true }
+
 # Utilities
 # TODO: check chrono usage and impl our own
 chrono = {version = "0.4.23", optional = true}
@@ -149,6 +150,7 @@ blockchain = [
     "blake3",
     "bs58", # <-- remove after we get rid of json for notifications
     "chrono",
+    "crypto_api_chachapoly",
     "dashu",
     "halo2_proofs",
     "lazy_static",
@@ -156,7 +158,6 @@ blockchain = [
     "sled",
     "sqlx",
     "url",
-    "crypto_api_chachapoly",
 
     "async-runtime",
     "darkfi-sdk",

+ 78 - 43
src/consensus/validator.rs

@@ -26,7 +26,6 @@ use darkfi_sdk::{
         schnorr::{SchnorrPublic, SchnorrSecret},
         MerkleNode, PublicKey, SecretKey,
     },
-    db::SMART_CONTRACT_ZKAS_DB_NAME,
     incrementalmerkletree::{bridgetree::BridgeTree, Tree},
     pasta::{group::ff::PrimeField, pallas},
 };
@@ -46,7 +45,7 @@ use super::{
 use crate::{
     blockchain::Blockchain,
     rpc::jsonrpc::JsonNotification,
-    runtime::vm_runtime::Runtime,
+    runtime::vm_runtime::{Runtime, SMART_CONTRACT_ZKAS_DB_NAME},
     system::{Subscriber, SubscriberPtr},
     tx::Transaction,
     util::time::Timestamp,
@@ -63,8 +62,6 @@ use crate::{
 /// Atomic pointer to validator state.
 pub type ValidatorStatePtr = Arc<RwLock<ValidatorState>>;
 
-type VerifyingKeyMap = Arc<RwLock<HashMap<[u8; 32], Vec<(String, VerifyingKey)>>>>;
-
 /// This struct represents the state of a validator node.
 pub struct ValidatorState {
     /// Leader proof proving key
@@ -82,8 +79,6 @@ pub struct ValidatorState {
     ///       and then we don't have to deal with json in this module but only
     //        externally.
     pub subscribers: HashMap<&'static str, SubscriberPtr<JsonNotification>>,
-    /// ZK proof verifying keys for smart contract calls
-    pub verifying_keys: VerifyingKeyMap,
     /// Wallet interface
     pub wallet: WalletPtr,
     /// Flag signalling node has finished initial sync
@@ -159,10 +154,6 @@ impl ValidatorState {
         let money_contract_deploy_payload = serialize(&faucet_pubkeys);
         let dao_contract_deploy_payload = vec![];
 
-        // In this hashmap, we keep references to ZK proof verifying keys needed
-        // for the circuits our native contracts provide.
-        let mut verifying_keys = HashMap::new();
-
         let native_contracts = vec![
             (
                 "Money Contract",
@@ -184,36 +175,10 @@ impl ValidatorState {
             let mut runtime = Runtime::new(&nc.2[..], blockchain.clone(), nc.1)?;
             runtime.deploy(&nc.3)?;
             info!(target: "consensus::validator", "Successfully deployed {}", nc.0);
-
-            // When deployed, we can do a lookup for the zkas circuits and
-            // initialize verifying keys for them.
-            info!(target: "consensus::validator", "Creating ZK verifying keys for {} zkas circuits", nc.0);
-            info!(target: "consensus::validator", "Looking up zkas db for {} (ContractID: {})", nc.0, nc.1);
-            let zkas_db = blockchain.contracts.lookup(
-                &blockchain.sled_db,
-                &nc.1,
-                SMART_CONTRACT_ZKAS_DB_NAME,
-            )?;
-
-            let mut vks = vec![];
-            for i in zkas_db.iter() {
-                info!(target: "consensus::validator", "Iterating over zkas db");
-                let (zkas_ns, zkas_bincode) = i?;
-                info!(target: "consensus::validator", "Deserializing namespace");
-                let zkas_ns: String = deserialize(&zkas_ns)?;
-                info!(target: "consensus::validator", "Creating VerifyingKey for zkas circuit with namespace {}", zkas_ns);
-                let zkbin = ZkBinary::decode(&zkas_bincode)?;
-                let circuit = ZkCircuit::new(empty_witnesses(&zkbin), zkbin);
-                // FIXME: This k=13 man...
-                let vk = VerifyingKey::build(13, &circuit);
-                vks.push((zkas_ns, vk));
-            }
-
-            info!(target: "consensus::validator", "Finished creating VerifyingKey objects for {} (ContractID: {})", nc.0, nc.1);
-            verifying_keys.insert(nc.1.to_bytes(), vks);
         }
+
         info!(target: "consensus::validator", "Finished deployment of native wasm contracts");
-        // -----NATIVE WASM CONTRACTS-----
+        // -----END NATIVE WASM CONTRACTS-----
 
         // Here we initialize various subscribers that can export live consensus/blockchain data.
         let mut subscribers = HashMap::new();
@@ -227,7 +192,6 @@ impl ValidatorState {
             blockchain,
             unconfirmed_txs,
             subscribers,
-            verifying_keys: Arc::new(RwLock::new(verifying_keys)),
             wallet,
             synced: false,
             single_node,
@@ -882,7 +846,14 @@ impl ValidatorState {
     // TODO: Currently we keep erroneous transactions in the vector and blocks,
     //       in order to apply max fee logic in the future, to prevent spamming.
     // TODO: This should be paralellized as if even one tx in the batch fails to verify,
-    //       we can skip it.
+    //       we can skip it. When things are parallel, make sure to write in a deterministic
+    //       order.
+    // TODO: This function should be refactored to be more readable and efficient.
+    //       1. Get metadata
+    //       2. Verify signatures
+    //       3. Verify execution
+    //       4. Verify ZK proofs
+    //       5. (optionally) write
     pub async fn verify_transactions(
         &self,
         txs: &[Transaction],
@@ -890,6 +861,7 @@ impl ValidatorState {
     ) -> Result<Vec<Transaction>> {
         info!(target: "consensus::validator", "Verifying {} transaction(s)", txs.len());
         let mut erroneous_txs = vec![];
+
         for tx in txs {
             let tx_hash = blake3::hash(&serialize(tx));
             info!(target: "consensus::validator", "Verifying transaction {}", tx_hash);
@@ -898,8 +870,17 @@ impl ValidatorState {
             let mut zkp_table = vec![];
             // Table of public keys used for signature verification
             let mut sig_table = vec![];
-            // State updates produced by contract execcution
+            // State updates produced by contract execution
             let mut updates = vec![];
+            // Map of zk proof verifying keys for the current transaction
+            //let mut verifying_keys: HashMap<[u8; 32], Vec<(String, VerifyingKey)>> = HashMap::new();
+            let mut verifying_keys: HashMap<[u8; 32], HashMap<String, VerifyingKey>> =
+                HashMap::new();
+
+            // Initialize the map
+            for call in tx.calls.iter() {
+                verifying_keys.insert(call.contract_id.to_bytes(), HashMap::new());
+            }
 
             // Iterate over all calls to get the metadata
             let mut skip = false;
@@ -953,6 +934,8 @@ impl ValidatorState {
 
                 // Decode the metadata retrieved from the execution
                 let mut decoder = Cursor::new(&metadata);
+
+                //                (zkas_ns, public_inputs)
                 let zkp_pub: Vec<(String, Vec<pallas::Base>)> = match Decodable::decode(
                     &mut decoder,
                 ) {
@@ -972,9 +955,61 @@ impl ValidatorState {
                         break
                     }
                 };
-
                 // TODO: Make sure we've read all the bytes above.
                 info!(target: "consensus::validator", "Successfully executed \"metadata\" call");
+
+                // Here we'll look up verifying keys and insert them into the per-contract map.
+                // TODO: This should be abstracted
+                info!(target: "consensus::validator", "Performing VerifyingKey lookups from the sled db");
+                let zkas_tree = match self.blockchain.contracts.lookup(
+                    &self.blockchain.sled_db,
+                    &call.contract_id,
+                    SMART_CONTRACT_ZKAS_DB_NAME,
+                ) {
+                    Ok(v) => v,
+                    Err(e) => {
+                        error!(target: "consensus::validator", "Failed to lookup zkas db for contract {}: {}", call.contract_id, e);
+                        skip = true;
+                        break
+                    }
+                };
+
+                for (zkas_ns, _) in &zkp_pub {
+                    let inner_vk_map =
+                        verifying_keys.get_mut(&call.contract_id.to_bytes()).unwrap();
+
+                    if inner_vk_map.contains_key(zkas_ns.as_str()) {
+                        continue
+                    }
+
+                    let Some(zkas_encoded_bytes) = zkas_tree.get(&serialize(&zkas_ns.as_str())).expect("get") else {
+                        error!(target: "consensus::validator", "Failed to find reference to zkas in sled");
+                        skip = true;
+                        break
+                    };
+
+                    let (_, vk_bin): (Vec<u8>, Vec<u8>) = match deserialize(&zkas_encoded_bytes) {
+                        Ok(v) => v,
+                        Err(e) => {
+                            error!(target: "consensus::validator", "Failed to deserialize zkas data: {}", e);
+                            skip = true;
+                            break
+                        }
+                    };
+
+                    let mut vk_buf = Cursor::new(vk_bin);
+                    let vk = match VerifyingKey::read::<Cursor<Vec<u8>>, ZkCircuit>(&mut vk_buf) {
+                        Ok(v) => v,
+                        Err(e) => {
+                            error!(target: "consensus::validator", "Failed to decode VerifyingKey: {}", e);
+                            skip = true;
+                            break
+                        }
+                    };
+
+                    inner_vk_map.insert(zkas_ns.to_string(), vk);
+                }
+
                 zkp_table.push(zkp_pub);
                 sig_table.push(sig_pub);
 
@@ -1032,7 +1067,7 @@ impl ValidatorState {
             // inside of this function. This can be kinda expensive, so open to
             // alternatives.
             info!(target: "consensus::validator", "Verifying ZK proofs for transaction {}", tx_hash);
-            match tx.verify_zkps(self.verifying_keys.clone(), zkp_table).await {
+            match tx.verify_zkps(verifying_keys.clone(), zkp_table).await {
                 Ok(()) => {
                     info!(target: "consensus::validator", "ZK proof verification for tx {} successful", tx_hash)
                 }

+ 2 - 2
src/runtime/vm_runtime.rs

@@ -21,7 +21,7 @@ use std::{
     sync::Arc,
 };
 
-use darkfi_sdk::{crypto::ContractId, db::SMART_CONTRACT_ZKAS_DB_NAME, entrypoint};
+use darkfi_sdk::{crypto::ContractId, entrypoint};
 use darkfi_serial::serialize;
 use log::{debug, error, info};
 use wasmer::{
@@ -44,7 +44,7 @@ const MEMORY: &str = "memory";
 const GAS_LIMIT: u64 = 200000000;
 
 /// The hardcoded db name for the zkas circuits database tree
-const SMART_CONTRACT_ZKAS_DB_NAME: &str = "_zkas";
+pub const SMART_CONTRACT_ZKAS_DB_NAME: &str = "_zkas";
 
 #[derive(Clone, Copy, PartialEq)]
 pub enum ContractSection {

+ 19 - 17
src/tx/mod.rs

@@ -18,7 +18,6 @@
 
 use std::collections::HashMap;
 
-use async_std::sync::{Arc, RwLock};
 use darkfi_sdk::{
     crypto::{
         schnorr::{SchnorrPublic, SchnorrSecret, Signature},
@@ -57,13 +56,13 @@ pub struct Transaction {
 }
 // ANCHOR_END: transaction
 
-type VerifyingKeyMap = Arc<RwLock<HashMap<[u8; 32], Vec<(String, VerifyingKey)>>>>;
+//type VerifyingKeyMap = Arc<RwLock<HashMap<[u8; 32], Vec<(String, VerifyingKey)>>>>;
 
 impl Transaction {
     /// Verify ZK proofs for the entire transaction.
     pub async fn verify_zkps(
         &self,
-        verifying_keys: VerifyingKeyMap,
+        verifying_keys: HashMap<[u8; 32], HashMap<String, VerifyingKey>>,
         zkp_table: Vec<Vec<(String, Vec<pallas::Base>)>>,
     ) -> Result<()> {
         // TODO: Are we sure we should assert here?
@@ -73,22 +72,25 @@ impl Transaction {
         for (call, (proofs, pubvals)) in zip!(self.calls, self.proofs, zkp_table) {
             assert_eq!(proofs.len(), pubvals.len());
 
+            let Some(contract_map) = verifying_keys.get(&call.contract_id.to_bytes()) else {
+                error!("Verifying keys not found for contract {}", call.contract_id);
+                return Err(VerifyFailed::ProofVerifyFailed("VKs not found for contract".to_string()).into())
+            };
+
             for (proof, (zk_ns, public_vals)) in proofs.iter().zip(pubvals.iter()) {
-                if let Some(vks) = verifying_keys.read().await.get(&call.contract_id.to_bytes()) {
-                    if let Some(vk) = vks.iter().find(|x| &x.0 == zk_ns) {
-                        // We have a verifying key for this
-                        debug!("public inputs: {:#?}", public_vals);
-                        if let Err(e) = proof.verify(&vk.1, public_vals) {
-                            error!(
-                                target: "",
-                                "Failed verifying {}::{} ZK proof: {:#?}",
-                                call.contract_id, zk_ns, e
-                            );
-                            return Err(VerifyFailed::ProofVerifyFailed(e.to_string()).into())
-                        }
-                        debug!("Successfully verified {}::{} ZK proof", call.contract_id, zk_ns);
-                        continue
+                if let Some(vk) = contract_map.get(zk_ns) {
+                    // We have a verifying key for this
+                    debug!("public inputs: {:#?}", public_vals);
+                    if let Err(e) = proof.verify(&vk, public_vals) {
+                        error!(
+                            target: "",
+                            "Failed verifying {}::{} ZK proof: {:#?}",
+                            call.contract_id, zk_ns, e
+                        );
+                        return Err(VerifyFailed::ProofVerifyFailed(e.to_string()).into())
                     }
+                    debug!("Successfully verified {}::{} ZK proof", call.contract_id, zk_ns);
+                    continue
                 }
 
                 let e = format!("{}:{} circuit VK nonexistent", call.contract_id, zk_ns);