utils.rs
1 /* This file is part of DarkFi (https://dark.fi) 2 * 3 * Copyright (C) 2020-2025 Dyne.org foundation 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation, either version 3 of the 8 * License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Affero General Public License for more details. 14 * 15 * You should have received a copy of the GNU Affero General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 use darkfi_sdk::{ 20 crypto::{DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID}, 21 tx::TransactionHash, 22 }; 23 use num_bigint::BigUint; 24 use randomx::{RandomXCache, RandomXFlags, RandomXVM}; 25 use tracing::info; 26 27 use crate::{ 28 blockchain::{BlockInfo, BlockchainOverlayPtr, Header}, 29 runtime::vm_runtime::Runtime, 30 validator::consensus::{Fork, Proposal}, 31 Error, Result, 32 }; 33 34 /// Deploy DarkFi native wasm contracts to provided blockchain overlay. 35 /// 36 /// If overlay already contains the contracts, it will just open the 37 /// necessary db and trees, and give back what it has. This means that 38 /// on subsequent runs, our native contracts will already be in a deployed 39 /// state, so what we actually do here is a redeployment. This kind of 40 /// operation should only modify the contract's state in case it wasn't 41 /// deployed before (meaning the initial run). Otherwise, it shouldn't 42 /// touch anything, or just potentially update the db schemas or whatever 43 /// is necessary. This logic should be handled in the init function of 44 /// the actual contract, so make sure the native contracts handle this well. 45 pub async fn deploy_native_contracts( 46 overlay: &BlockchainOverlayPtr, 47 block_target: u32, 48 ) -> Result<()> { 49 info!(target: "validator::utils::deploy_native_contracts", "Deploying native WASM contracts"); 50 51 // The Money contract uses an empty payload to deploy itself. 52 let money_contract_deploy_payload = vec![]; 53 54 // The DAO contract uses an empty payload to deploy itself. 55 let dao_contract_deploy_payload = vec![]; 56 57 // The Deployooor contract uses an empty payload to deploy itself. 58 let deployooor_contract_deploy_payload = vec![]; 59 60 let native_contracts = vec![ 61 ( 62 "Money Contract", 63 *MONEY_CONTRACT_ID, 64 include_bytes!("../contract/money/darkfi_money_contract.wasm").to_vec(), 65 money_contract_deploy_payload, 66 ), 67 ( 68 "DAO Contract", 69 *DAO_CONTRACT_ID, 70 include_bytes!("../contract/dao/darkfi_dao_contract.wasm").to_vec(), 71 dao_contract_deploy_payload, 72 ), 73 ( 74 "Deployooor Contract", 75 *DEPLOYOOOR_CONTRACT_ID, 76 include_bytes!("../contract/deployooor/darkfi_deployooor_contract.wasm").to_vec(), 77 deployooor_contract_deploy_payload, 78 ), 79 ]; 80 81 // Grab last known block height to verify against next one. 82 // If no blocks exist, we verify against genesis block height (0). 83 let verifying_block_height = match overlay.lock().unwrap().last() { 84 Ok((last_block_height, _)) => last_block_height + 1, 85 Err(_) => 0, 86 }; 87 88 for (call_idx, nc) in native_contracts.into_iter().enumerate() { 89 info!(target: "validator::utils::deploy_native_contracts", "Deploying {} with ContractID {}", nc.0, nc.1); 90 91 let mut runtime = Runtime::new( 92 &nc.2[..], 93 overlay.clone(), 94 nc.1, 95 verifying_block_height, 96 block_target, 97 TransactionHash::none(), 98 call_idx as u8, 99 )?; 100 101 runtime.deploy(&nc.3)?; 102 103 info!(target: "validator::utils::deploy_native_contracts", "Successfully deployed {}", nc.0); 104 } 105 106 info!(target: "validator::utils::deploy_native_contracts", "Finished deployment of native WASM contracts"); 107 108 Ok(()) 109 } 110 111 /// Verify provided header is valid for provided mining target and compute its rank. 112 /// 113 /// Header's rank is the tuple of its squared mining target distance from max 32 bytes int, 114 /// along with its squared RandomX hash number distance from max 32 bytes int. 115 /// Genesis block has rank (0, 0). 116 pub fn header_rank(header: &Header, target: &BigUint) -> Result<(BigUint, BigUint)> { 117 // Genesis header has rank 0 118 if header.height == 0 { 119 return Ok((0u64.into(), 0u64.into())) 120 } 121 122 // Setup RandomX verifier 123 let flags = RandomXFlags::default(); 124 let cache = RandomXCache::new(flags, header.previous.inner()).unwrap(); 125 let vm = RandomXVM::new(flags, &cache).unwrap(); 126 127 // Compute the output hash 128 let out_hash = vm.hash(header.hash().inner()); 129 let out_hash = BigUint::from_bytes_be(&out_hash); 130 131 // Verify hash is less than the expected mine target 132 if out_hash > *target { 133 return Err(Error::PoWInvalidOutHash) 134 } 135 136 // Grab the max 32 bytes int 137 let max = BigUint::from_bytes_be(&[0xFF; 32]); 138 139 // Compute the squared mining target distance 140 let target_distance = &max - target; 141 let target_distance_sq = &target_distance * &target_distance; 142 143 // Compute the output hash distance 144 let hash_distance = max - out_hash; 145 let hash_distance_sq = &hash_distance * &hash_distance; 146 147 Ok((target_distance_sq, hash_distance_sq)) 148 } 149 150 /// Compute a block's rank, assuming that its valid, based on provided mining target. 151 /// 152 /// Block's rank is the tuple of its squared mining target distance from max 32 bytes int, 153 /// along with its squared RandomX hash number distance from max 32 bytes int. 154 /// Genesis block has rank (0, 0). 155 pub fn block_rank(block: &BlockInfo, target: &BigUint) -> (BigUint, BigUint) { 156 // Genesis block has rank 0 157 if block.header.height == 0 { 158 return (0u64.into(), 0u64.into()) 159 } 160 161 // Grab the max 32 bytes int 162 let max = BigUint::from_bytes_be(&[0xFF; 32]); 163 164 // Compute the squared mining target distance 165 let target_distance = &max - target; 166 let target_distance_sq = &target_distance * &target_distance; 167 168 // Setup RandomX verifier 169 let flags = RandomXFlags::default(); 170 let cache = RandomXCache::new(flags, block.header.previous.inner()).unwrap(); 171 let vm = RandomXVM::new(flags, &cache).unwrap(); 172 173 // Compute the output hash distance 174 let out_hash = vm.hash(block.hash().inner()); 175 let out_hash = BigUint::from_bytes_be(&out_hash); 176 let hash_distance = max - out_hash; 177 let hash_distance_sq = &hash_distance * &hash_distance; 178 179 (target_distance_sq, hash_distance_sq) 180 } 181 182 /// Auxiliary function to calculate the middle value between provided u64 numbers 183 pub fn get_mid(a: u64, b: u64) -> u64 { 184 (a / 2) + (b / 2) + ((a - 2 * (a / 2)) + (b - 2 * (b / 2))) / 2 185 } 186 187 /// Auxiliary function to calculate the median of a given `Vec<u64>`. 188 /// The function sorts the vector internally. 189 pub fn median(mut v: Vec<u64>) -> u64 { 190 if v.len() == 1 { 191 return v[0] 192 } 193 194 let n = v.len() / 2; 195 v.sort_unstable(); 196 197 if v.len() % 2 == 0 { 198 v[n] 199 } else { 200 get_mid(v[n - 1], v[n]) 201 } 202 } 203 204 /// Given a proposal, find the index of a fork chain it extends, along with the specific 205 /// extended proposal index. Additionally, check that proposal doesn't already exists in any 206 /// fork chain. 207 pub fn find_extended_fork_index(forks: &[Fork], proposal: &Proposal) -> Result<(usize, usize)> { 208 // Grab provided proposal hash 209 let proposal_hash = proposal.hash; 210 211 // Keep track of fork and proposal indexes 212 let (mut fork_index, mut proposal_index) = (None, None); 213 214 // Loop through all the forks 215 for (f_index, fork) in forks.iter().enumerate() { 216 // Traverse fork proposals sequence in reverse 217 for (p_index, p_hash) in fork.proposals.iter().enumerate().rev() { 218 // Check we haven't already seen that proposal 219 if &proposal_hash == p_hash { 220 return Err(Error::ProposalAlreadyExists) 221 } 222 223 // Check if proposal extends this fork 224 if &proposal.block.header.previous == p_hash { 225 (fork_index, proposal_index) = (Some(f_index), Some(p_index)); 226 } 227 } 228 } 229 230 if let (Some(f_index), Some(p_index)) = (fork_index, proposal_index) { 231 return Ok((f_index, p_index)) 232 } 233 234 Err(Error::ExtendedChainIndexNotFound) 235 } 236 237 /// Auxiliary function to find best ranked fork. 238 /// 239 /// The best ranked fork is the one with the highest sum of 240 /// its blocks squared mining target distances, from max 32 241 /// bytes int. In case of a tie, the fork with the highest 242 /// sum of its blocks squared RandomX hash number distances, 243 /// from max 32 bytes int, wins. 244 pub fn best_fork_index(forks: &[Fork]) -> Result<usize> { 245 // Check if node has any forks 246 if forks.is_empty() { 247 return Err(Error::ForksNotFound) 248 } 249 250 // Find the best ranked forks 251 let mut best = &BigUint::from(0u64); 252 let mut indexes = vec![]; 253 for (f_index, fork) in forks.iter().enumerate() { 254 let rank = &fork.targets_rank; 255 256 // Fork ranks lower that current best 257 if rank < best { 258 continue 259 } 260 261 // Fork has same rank as current best 262 if rank == best { 263 indexes.push(f_index); 264 continue 265 } 266 267 // Fork ranks higher that current best 268 best = rank; 269 indexes = vec![f_index]; 270 } 271 272 // If a single best ranking fork exists, return it 273 if indexes.len() == 1 { 274 return Ok(indexes[0]) 275 } 276 277 // Break tie using their hash distances rank 278 let mut best_index = indexes[0]; 279 for index in &indexes[1..] { 280 if forks[*index].hashes_rank > forks[best_index].hashes_rank { 281 best_index = *index; 282 } 283 } 284 285 Ok(best_index) 286 }