/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2026 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::sync::LazyLock;
use darkfi_sdk::{
crypto::{DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID},
tx::TransactionHash,
};
use num_bigint::BigUint;
use randomx::{RandomXCache, RandomXFlags, RandomXVM};
use tracing::info;
use crate::{
blockchain::{BlockInfo, BlockchainOverlayPtr, Header},
runtime::vm_runtime::Runtime,
validator::{
consensus::{Fork, Proposal},
pow::PoWModule,
},
Error, Result,
};
/// Max 32 bytes integer, used in rank calculations.
/// Cached to avoid repeated allocation.
pub static MAX_32_BYTES: LazyLock = LazyLock::new(|| BigUint::from_bytes_le(&[0xFF; 32]));
/// 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,
block_target: u32,
) -> Result<()> {
info!(target: "validator::utils::deploy_native_contracts", "Deploying native WASM contracts");
// The Money contract uses an empty payload to deploy itself.
let money_contract_deploy_payload = vec![];
// The DAO contract uses an empty payload to deploy itself.
let dao_contract_deploy_payload = vec![];
// The Deployooor contract uses an empty payload to deploy itself.
let deployooor_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,
),
(
"Deployooor Contract",
*DEPLOYOOOR_CONTRACT_ID,
include_bytes!("../contract/deployooor/darkfi_deployooor_contract.wasm").to_vec(),
deployooor_contract_deploy_payload,
),
];
// Grab last known block height to verify against next one.
// If no blocks exist, we verify against genesis block height (0).
let verifying_block_height = match overlay.lock().unwrap().last() {
Ok((last_block_height, _)) => last_block_height + 1,
Err(_) => 0,
};
for (call_idx, nc) in native_contracts.into_iter().enumerate() {
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,
verifying_block_height,
block_target,
TransactionHash::none(),
call_idx as u8,
)?;
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(())
}
/// Verify provided header is valid for provided PoW module and compute
/// its rank.
/// Returns next mine difficulty, along with the computed rank.
///
/// Header's rank is the tuple of its squared mining target distance
/// from max 32 bytes int, along with its squared RandomX hash number
/// distance from max 32 bytes int.
/// Genesis block has rank (0, 0).
pub fn header_rank(module: &mut PoWModule, header: &Header) -> Result<(BigUint, BigUint, BigUint)> {
// Grab next mine target and difficulty
let (target, difficulty) = module.next_mine_target_and_difficulty()?;
// Genesis header has rank 0
if header.height == 0 {
return Ok((difficulty, 0u64.into(), 0u64.into()))
}
// Verify hash is less than the expected mine target
let out_hash = module.verify_block_target(header, &target)?;
// Compute the squared mining target distance
let target_distance = &*MAX_32_BYTES - target;
let target_distance_sq = &target_distance * &target_distance;
// Compute the output hash distance
let hash_distance = &*MAX_32_BYTES - out_hash;
let hash_distance_sq = &hash_distance * &hash_distance;
Ok((difficulty, target_distance_sq, hash_distance_sq))
}
/// Compute a block's rank, assuming that its valid, based on provided
/// mining target.
///
/// Block's rank is the tuple of its squared mining target distance
/// from max 32 bytes int, along with its squared RandomX hash number
/// distance from max 32 bytes int. Genesis block has rank (0, 0).
pub fn block_rank(block: &BlockInfo, target: &BigUint) -> Result<(BigUint, BigUint)> {
// Genesis block has rank 0
if block.header.height == 0 {
return Ok((0u64.into(), 0u64.into()))
}
// Compute the squared mining target distance
let target_distance = &*MAX_32_BYTES - target;
let target_distance_sq = &target_distance * &target_distance;
// Setup RandomX verifier
let flags = RandomXFlags::get_recommended_flags();
let cache = RandomXCache::new(flags, block.header.previous.inner())?;
let vm = RandomXVM::new(flags, Some(cache), None)?;
// Compute the output hash distance
let out_hash = vm.calculate_hash(block.hash().inner())?;
let out_hash = BigUint::from_bytes_le(&out_hash);
let hash_distance = &*MAX_32_BYTES - out_hash;
let hash_distance_sq = &hash_distance * &hash_distance;
Ok((target_distance_sq, hash_distance_sq))
}
/// 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().is_multiple_of(2) {
return get_mid(v[n - 1], v[n])
}
v[n]
}
/// Given a proposal, find the index of a fork chain it extends, along
/// with the specific extended proposal index. Additionally, check that
/// proposal doesn't already exists in any fork chain.
pub fn find_extended_fork_index(forks: &[Fork], proposal: &Proposal) -> Result<(usize, usize)> {
// Grab provided proposal hash
let proposal_hash = proposal.hash;
// Keep track of fork and proposal indexes
let (mut fork_index, mut proposal_index) = (None, None);
// Loop through all the forks
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() {
// Check we haven't already seen that proposal
if proposal_hash == *p_hash {
return Err(Error::ProposalAlreadyExists)
}
// Check if proposal extends this fork
if proposal.block.header.previous == *p_hash {
(fork_index, proposal_index) = (Some(f_index), Some(p_index));
}
}
}
if let (Some(f_index), Some(p_index)) = (fork_index, proposal_index) {
return Ok((f_index, p_index))
}
Err(Error::ExtendedChainIndexNotFound)
}
/// Auxiliary function to find best ranked fork.
///
/// The best ranked fork is the one with the highest sum of its blocks
/// squared mining target distances, from max 32 bytes int. In case of
/// a tie, the fork with the highest sum of its blocks squared RandomX
/// hash number distances, from max 32 bytes int, wins.
pub fn best_fork_index(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 = &BigUint::from(0u64);
let mut indexes = vec![];
for (f_index, fork) in forks.iter().enumerate() {
let rank = &fork.targets_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];
}
// If a single best ranking fork exists, return it
if indexes.len() == 1 {
return Ok(indexes[0])
}
// Break tie using their hash distances rank
let mut best_index = indexes[0];
for index in &indexes[1..] {
if forks[*index].hashes_rank > forks[best_index].hashes_rank {
best_index = *index;
}
}
Ok(best_index)
}
/// Auxiliary function to find worst ranked fork.
///
/// The worst ranked fork is the one with the lowest sum of its blocks
/// squared mining target distances, from max 32 bytes int. In case of
/// a tie, the fork with the lowest sum of its blocks squared RandomX
/// hash number distances, from max 32 bytes int, wins.
pub fn worst_fork_index(forks: &[Fork]) -> Result {
// Check if node has any forks
if forks.is_empty() {
return Err(Error::ForksNotFound)
}
// Find the worst ranked forks
let mut worst = &forks[0].targets_rank;
let mut indexes = vec![0];
for (f_index, fork) in forks[1..].iter().enumerate() {
let rank = &fork.targets_rank;
// Fork ranks higher that current worst
if rank > worst {
continue
}
// Fork has same rank as current worst
if rank == worst {
indexes.push(f_index + 1);
continue
}
// Fork ranks lower that current worst
worst = rank;
indexes = vec![f_index + 1];
}
// If a single worst ranking fork exists, return it
if indexes.len() == 1 {
return Ok(indexes[0])
}
// Break tie using their hash distances rank
let mut worst_index = indexes[0];
for index in &indexes[1..] {
if forks[*index].hashes_rank < forks[worst_index].hashes_rank {
worst_index = *index;
}
}
Ok(worst_index)
}