/* 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 darkfi_sdk::{
crypto::{
ecvrf::VrfProof, pasta_prelude::PrimeField, PublicKey, CONSENSUS_CONTRACT_ID,
DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
},
pasta::{group::ff::FromUniformBytes, pallas},
};
use darkfi_serial::{serialize_async, AsyncDecodable};
use log::info;
use smol::io::Cursor;
use crate::{
blockchain::{BlockInfo, BlockchainOverlayPtr},
error::TxVerifyFailed,
runtime::vm_runtime::Runtime,
tx::Transaction,
util::time::TimeKeeper,
validator::consensus::{Fork, Proposal},
Error, Result,
};
/// Deploy DarkFi native wasm contracts to provided blockchain overlay.
/// If overlay already contains the contracts, 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.
pub async fn deploy_native_contracts(
overlay: &BlockchainOverlayPtr,
time_keeper: &TimeKeeper,
faucet_pubkeys: &Vec,
) -> Result<()> {
info!(target: "validator::utils::deploy_native_contracts", "Deploying native WASM contracts");
// The faucet pubkeys are pubkeys which are allowed to create clear inputs
// in the Money contract.
let money_contract_deploy_payload = serialize_async(faucet_pubkeys).await;
// The DAO contract uses an empty payload to deploy itself.
let dao_contract_deploy_payload = vec![];
// The Consensus contract uses an empty payload to deploy itself.
let consensus_contract_deploy_payload = vec![];
let native_contracts = vec![
(
"Money Contract",
*MONEY_CONTRACT_ID,
include_bytes!("../contract/money/darkfi_money_contract.wasm").to_vec(),
money_contract_deploy_payload,
),
(
"DAO Contract",
*DAO_CONTRACT_ID,
include_bytes!("../contract/dao/darkfi_dao_contract.wasm").to_vec(),
dao_contract_deploy_payload,
),
(
"Consensus Contract",
*CONSENSUS_CONTRACT_ID,
include_bytes!("../contract/consensus/darkfi_consensus_contract.wasm").to_vec(),
consensus_contract_deploy_payload,
),
];
for nc in native_contracts {
info!(target: "validator::utils::deploy_native_contracts", "Deploying {} with ContractID {}", nc.0, nc.1);
let mut runtime = Runtime::new(&nc.2[..], overlay.clone(), nc.1, time_keeper.clone())?;
runtime.deploy(&nc.3)?;
info!(target: "validator::utils::deploy_native_contracts", "Successfully deployed {}", nc.0);
}
info!(target: "validator::utils::deploy_native_contracts", "Finished deployment of native WASM contracts");
Ok(())
}
/// Compute a block's rank, assuming the its valid.
/// Genesis block has rank 0.
/// First 2 blocks rank is equal to their nonce, since their previous
/// previous block producer doesn't exist or have a VRF.
pub async fn block_rank(
block: &BlockInfo,
previous_previous: &BlockInfo,
pos_testing_mode: bool,
) -> Result {
// Genesis block has rank 0
if block.header.height == 0 {
return Ok(0)
}
// Compute nonce u64
let mut nonce = [0u8; 8];
nonce.copy_from_slice(&block.header.nonce.to_repr()[..8]);
let nonce = u64::from_be_bytes(nonce);
// First 2 blocks or testing ones have rank equal to their nonce
if block.header.height < 3 || pos_testing_mode {
return Ok(nonce)
}
// Extract VRF proof from the previous previous producer transaction
let tx = previous_previous.txs.last().unwrap();
let data = &tx.calls[0].data.data;
let position = match previous_previous.header.version {
// PoW uses MoneyPoWRewardParamsV1
1 => 563,
// PoS uses ConsensusProposalParamsV1
2 => 490,
_ => return Err(Error::BlockVersionIsInvalid(previous_previous.header.version)),
};
let mut decoder = Cursor::new(&data);
decoder.set_position(position);
let vrf_proof: VrfProof = AsyncDecodable::decode_async(&mut decoder).await?;
// Compute VRF u64
let mut vrf = [0u8; 64];
vrf[..blake3::OUT_LEN].copy_from_slice(vrf_proof.hash_output().as_bytes());
let vrf_pallas = pallas::Base::from_uniform_bytes(&vrf);
let mut vrf = [0u8; 8];
vrf.copy_from_slice(&vrf_pallas.to_repr()[..8]);
let vrf = u64::from_be_bytes(vrf);
// Finally, compute the rank
let rank = nonce % vrf;
Ok(rank)
}
/// Auxiliary function to calculate the middle value between provided u64 numbers
pub fn get_mid(a: u64, b: u64) -> u64 {
(a / 2) + (b / 2) + ((a - 2 * (a / 2)) + (b - 2 * (b / 2))) / 2
}
/// Auxiliary function to calculate the median of a given `Vec`.
/// The function sorts the vector internally.
pub fn median(mut v: Vec) -> u64 {
if v.len() == 1 {
return v[0]
}
let n = v.len() / 2;
v.sort_unstable();
if v.len() % 2 == 0 {
v[n]
} else {
get_mid(v[n - 1], v[n])
}
}
/// Auxiliary function to calculate the total amount of minted tokens in provided
/// genesis transactions set. This includes both staked and normal tokens.
/// If a non-genesis transaction is found, execution fails.
/// Set must also include the genesis transaction(empty) at last position.
pub async fn genesis_txs_total(txs: &[Transaction]) -> Result {
let mut total = 0;
if txs.is_empty() {
return Ok(total)
}
// Iterate transactions, exluding producer(last) one
for tx in &txs[..txs.len() - 1] {
// Transaction must contain a single Consensus::GenesisStake (0x00)
// or Money::GenesisMint (0x01) call
if tx.calls.len() != 1 {
return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
}
let call = &tx.calls[0];
let data = &call.data.data;
let function = data[0];
if !(call.data.contract_id == *CONSENSUS_CONTRACT_ID ||
call.data.contract_id == *MONEY_CONTRACT_ID) ||
(call.data.contract_id == *CONSENSUS_CONTRACT_ID && function != 0x00_u8) ||
(call.data.contract_id == *MONEY_CONTRACT_ID && function != 0x01_u8)
{
return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
}
// Extract transaction input value.
// Consensus::GenesisStake uses ConsensusGenesisStakeParamsV1, while
// Money::GenesisMint uses MoneyGenesisMintParamsV1. Both params structs
// have the value at same position (1).
let position = 1;
let mut decoder = Cursor::new(&data);
decoder.set_position(position);
let value: u64 = AsyncDecodable::decode_async(&mut decoder).await?;
total += value;
}
let tx = txs.last().unwrap();
if tx != &Transaction::default() {
return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
}
Ok(total)
}
/// Retrieve previous slot producers, last proposal hashes,
/// and their second to last hashes, from all provided forks.
pub fn previous_slot_info(
forks: &Vec,
slot: u64,
) -> Result<(u64, Vec, Vec)> {
let mut producers = 0;
let mut last_hashes = vec![];
let mut second_to_last_hashes = vec![];
for fork in forks {
let last_proposal = fork.last_proposal()?;
if last_proposal.block.header.height == slot {
producers += 1;
}
last_hashes.push(last_proposal.hash);
second_to_last_hashes.push(last_proposal.block.header.previous);
}
Ok((producers, last_hashes, second_to_last_hashes))
}
/// Given a proposal, find the index of the fork chain it extends, along with the specific
/// extended proposal index.
pub fn find_extended_fork_index(forks: &[Fork], proposal: &Proposal) -> Result<(usize, usize)> {
for (f_index, fork) in forks.iter().enumerate() {
// Traverse fork proposals sequence in reverse
for (p_index, p_hash) in fork.proposals.iter().enumerate().rev() {
if &proposal.block.header.previous == p_hash {
return Ok((f_index, p_index))
}
}
}
Err(Error::ExtendedChainIndexNotFound)
}
/// Auxiliary function to find best ranked forks indexes.
pub fn best_forks_indexes(forks: &[Fork]) -> Result> {
// Check if node has any forks
if forks.is_empty() {
return Err(Error::ForksNotFound)
}
// Find the best ranked forks
let mut best = 0;
let mut indexes = vec![];
for (f_index, fork) in forks.iter().enumerate() {
let rank = fork.rank;
// Fork ranks lower that current best
if rank < best {
continue
}
// Fork has same rank as current best
if rank == best {
indexes.push(f_index);
continue
}
// Fork ranks higher that current best
best = rank;
indexes = vec![f_index];
}
Ok(indexes)
}