/* 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::{
crypto::{
constants::MERKLE_DEPTH,
contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
schnorr::{SchnorrPublic, SchnorrSecret},
MerkleNode, PublicKey, SecretKey,
},
db::SMART_CONTRACT_ZKAS_DB_NAME,
incrementalmerkletree::{bridgetree::BridgeTree, Tree},
pasta::{group::ff::PrimeField, pallas},
};
use darkfi_serial::{deserialize, serialize, Decodable, Encodable, WriteExt};
use log::{debug, error, info, warn};
use rand::rngs::OsRng;
use serde_json::json;
use super::{
constants,
leadcoin::LeadCoin,
state::{ConsensusState, Fork, SlotCheckpoint, StateCheckpoint},
BlockInfo, BlockProposal, Header, LeadInfo, LeadProof,
};
use crate::{
blockchain::Blockchain,
rpc::jsonrpc::JsonNotification,
runtime::vm_runtime::Runtime,
system::{Subscriber, SubscriberPtr},
tx::Transaction,
util::time::Timestamp,
wallet::WalletPtr,
zk::{
proof::{ProvingKey, VerifyingKey},
vm::ZkCircuit,
vm_stack::empty_witnesses,
},
zkas::ZkBinary,
Error, Result,
};
/// Atomic pointer to validator state.
pub type ValidatorStatePtr = Arc>;
type VerifyingKeyMap = Arc>>>;
/// This struct represents the state of a validator node.
pub struct ValidatorState {
/// Leader proof proving key
pub lead_proving_key: Option,
/// Leader proof verifying key
pub lead_verifying_key: VerifyingKey,
/// Hot/Live data used by the consensus algorithm
pub consensus: ConsensusState,
/// Canonical (finalized) blockchain
pub blockchain: Blockchain,
/// Pending transactions
pub unconfirmed_txs: Vec,
/// A map of various subscribers exporting live info from the blockchain
/// TODO: Instead of JsonNotification, it can be an enum of internal objects,
/// and then we don't have to deal with json in this module but only
// externally.
pub subscribers: HashMap<&'static str, SubscriberPtr>,
/// ZK proof verifying keys for smart contract calls
pub verifying_keys: VerifyingKeyMap,
/// Wallet interface
pub wallet: WalletPtr,
}
impl ValidatorState {
pub async fn new(
db: &sled::Db, // <-- TODO: Avoid this with some wrapping, sled should only be in blockchain
bootstrap_ts: Timestamp,
genesis_ts: Timestamp,
genesis_data: blake3::Hash,
initial_distribution: u64,
wallet: WalletPtr,
faucet_pubkeys: Vec,
enable_participation: bool,
) -> Result {
debug!(target: "consensus::validator", "Initializing ValidatorState");
debug!(target: "consensus::validator", "Initializing wallet tables for consensus");
// TODO: TESTNET: The stuff is kept entirely in memory for now, what should we write
// to disk/wallet?
//let consensus_tree_init_query = include_str!("../../script/sql/consensus_tree.sql");
//let consensus_keys_init_query = include_str!("../../script/sql/consensus_keys.sql");
//wallet.exec_sql(consensus_tree_init_query).await?;
//wallet.exec_sql(consensus_keys_init_query).await?;
debug!(target: "consensus::validator", "Generating leader proof keys with k: {}", constants::LEADER_PROOF_K);
let bincode = include_bytes!("../../proof/lead.zk.bin");
let zkbin = ZkBinary::decode(bincode)?;
let witnesses = empty_witnesses(&zkbin);
let circuit = ZkCircuit::new(witnesses, zkbin);
let lead_verifying_key = VerifyingKey::build(constants::LEADER_PROOF_K, &circuit);
// We only need this proving key if we're going to participate in the consensus.
let lead_proving_key = if enable_participation {
Some(ProvingKey::build(constants::LEADER_PROOF_K, &circuit))
} else {
None
};
let blockchain = Blockchain::new(db, genesis_ts, genesis_data)?;
let consensus = ConsensusState::new(
blockchain.clone(),
bootstrap_ts,
genesis_ts,
genesis_data,
initial_distribution,
)?;
let unconfirmed_txs = vec![];
// -----NATIVE WASM CONTRACTS-----
// This is the current place where native contracts are being deployed.
// When the `Blockchain` object is created, it doesn't care whether it
// already has the contract data or not. If there's existing data, it
// will just open the necessary db and trees, and give back what it has.
// This means that on subsequent runs our native contracts will already
// be in a deployed state, so what we actually do here is a redeployment.
// This kind of operation should only modify the contract's state in case
// it wasn't deployed before (meaning the initial run). Otherwise, it
// shouldn't touch anything, or just potentially update the db schemas or
// whatever is necessary. This logic should be handled in the init function
// of the actual contract, so make sure the native contracts handle this well.
// The faucet pubkeys are pubkeys which are allowed to create clear inputs
// in the money contract.
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",
*MONEY_CONTRACT_ID,
include_bytes!("../contract/money/money_contract.wasm").to_vec(),
money_contract_deploy_payload,
),
(
"DAO Contract",
*DAO_CONTRACT_ID,
include_bytes!("../contract/dao/dao_contract.wasm").to_vec(),
dao_contract_deploy_payload,
),
];
info!(target: "consensus::validator", "Deploying native wasm contracts");
for nc in native_contracts {
info!(target: "consensus::validator", "Deploying {} with ContractID {}", nc.0, nc.1);
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-----
// Here we initialize various subscribers that can export live consensus/blockchain data.
let mut subscribers = HashMap::new();
let block_subscriber = Subscriber::new();
subscribers.insert("blocks", block_subscriber);
let state = Arc::new(RwLock::new(ValidatorState {
lead_proving_key,
lead_verifying_key,
consensus,
blockchain,
unconfirmed_txs,
subscribers,
verifying_keys: Arc::new(RwLock::new(verifying_keys)),
wallet,
}));
Ok(state)
}
/// The node retrieves a transaction, validates its state transition,
/// and appends it to the unconfirmed transactions list.
pub async fn append_tx(&mut self, tx: Transaction) -> bool {
let tx_hash = blake3::hash(&serialize(&tx));
let tx_in_txstore = match self.blockchain.transactions.contains(&tx_hash) {
Ok(v) => v,
Err(e) => {
error!(target: "consensus::validator", "append_tx(): Failed querying txstore: {}", e);
return false
}
};
if self.unconfirmed_txs.contains(&tx) || tx_in_txstore {
info!(target: "consensus::validator", "append_tx(): We have already seen this tx.");
return false
}
info!(target: "consensus::validator", "append_tx(): Starting state transition validation");
if let Err(e) = self.verify_transactions(&[tx.clone()], false).await {
error!(target: "consensus::validator", "append_tx(): Failed to verify transaction: {}", e);
return false
};
info!(target: "consensus::validator", "append_tx(): Appended tx to mempool");
self.unconfirmed_txs.push(tx);
true
}
/// Generate a block proposal for the current slot, containing all
/// unconfirmed transactions. Proposal extends the longest fork
/// chain the node is holding.
pub fn propose(
&mut self,
slot: u64,
fork_index: i64,
coin_index: usize,
sigma1: pallas::Base,
sigma2: pallas::Base,
derived_blind: pallas::Scalar,
) -> Result