darkfi/validator/utils.rs
1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 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
19use std::sync::{Arc, LazyLock};
20
21use darkfi_sdk::{
22 crypto::{DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID},
23 tx::TransactionHash,
24};
25use num_bigint::BigUint;
26use parking_lot::Mutex;
27use tracing::info;
28
29use crate::{
30 blockchain::{BlockInfo, BlockchainOverlayPtr, Header},
31 runtime::vm_runtime::{Runtime, TxLocalState},
32 validator::{
33 consensus::{Fork, Proposal},
34 pow::PoWModule,
35 },
36 Error, Result,
37};
38
39/// Max 32 bytes integer, used in rank calculations.
40/// Cached to avoid repeated allocation.
41pub static MAX_32_BYTES: LazyLock<BigUint> = LazyLock::new(|| BigUint::from_bytes_le(&[0xFF; 32]));
42
43/// Deploy DarkFi native wasm contracts to provided blockchain overlay.
44///
45/// If overlay already contains the contracts, it will just open the
46/// necessary db and trees, and give back what it has. This means that
47/// on subsequent runs, our native contracts will already be in a
48/// deployed state, so what we actually do here is a redeployment. This
49/// kind of operation should only modify the contract's state in case
50/// it wasn't deployed before (meaning the initial run). Otherwise, it
51/// shouldn't touch anything, or just potentially update the db schemas
52/// or whatever is necessary. This logic should be handled in the init
53/// function of the actual contract, so make sure the native contracts
54/// handle this well.
55pub async fn deploy_native_contracts(
56 overlay: &BlockchainOverlayPtr,
57 block_target: u32,
58) -> Result<()> {
59 info!(target: "validator::utils::deploy_native_contracts", "Deploying native WASM contracts");
60
61 // The Money contract uses an empty payload to deploy itself.
62 let money_contract_deploy_payload = vec![];
63
64 // The DAO contract uses an empty payload to deploy itself.
65 let dao_contract_deploy_payload = vec![];
66
67 // The Deployooor contract uses an empty payload to deploy itself.
68 let deployooor_contract_deploy_payload = vec![];
69
70 let native_contracts = vec![
71 (
72 "Money Contract",
73 *MONEY_CONTRACT_ID,
74 include_bytes!("../contract/money/darkfi_money_contract.wasm").to_vec(),
75 money_contract_deploy_payload,
76 ),
77 (
78 "DAO Contract",
79 *DAO_CONTRACT_ID,
80 include_bytes!("../contract/dao/darkfi_dao_contract.wasm").to_vec(),
81 dao_contract_deploy_payload,
82 ),
83 (
84 "Deployooor Contract",
85 *DEPLOYOOOR_CONTRACT_ID,
86 include_bytes!("../contract/deployooor/darkfi_deployooor_contract.wasm").to_vec(),
87 deployooor_contract_deploy_payload,
88 ),
89 ];
90
91 // Grab last known block height to verify against next one.
92 // If no blocks exist, we verify against genesis block height (0).
93 let verifying_block_height = match overlay.lock().unwrap().last() {
94 Ok((last_block_height, _)) => last_block_height + 1,
95 Err(_) => 0,
96 };
97
98 for (call_idx, nc) in native_contracts.into_iter().enumerate() {
99 info!(target: "validator::utils::deploy_native_contracts", "Deploying {} with ContractID {}", nc.0, nc.1);
100
101 // Create tx-local state. Here it remains unused since native
102 // contract deployments do not use tx-local state.
103 let tx_local_state = Arc::new(Mutex::new(TxLocalState::new()));
104
105 let mut runtime = Runtime::new(
106 &nc.2[..],
107 overlay.clone(),
108 tx_local_state,
109 nc.1,
110 verifying_block_height,
111 block_target,
112 TransactionHash::none(),
113 call_idx as u8,
114 )?;
115
116 runtime.deploy(&nc.3)?;
117
118 info!(target: "validator::utils::deploy_native_contracts", "Successfully deployed {}", nc.0);
119 }
120
121 info!(target: "validator::utils::deploy_native_contracts", "Finished deployment of native WASM contracts");
122
123 Ok(())
124}
125
126/// Verify provided header is valid for provided PoW module and compute
127/// its rank.
128/// Returns next mine difficulty, along with the computed rank.
129///
130/// Header's rank is the tuple of its squared mining target distance
131/// from max 32 bytes int, along with its squared RandomX hash number
132/// distance from max 32 bytes int.
133/// Genesis block has rank (0, 0).
134pub fn header_rank(module: &mut PoWModule, header: &Header) -> Result<(BigUint, BigUint, BigUint)> {
135 // Grab next mine target and difficulty
136 let (target, difficulty) = module.next_mine_target_and_difficulty()?;
137
138 // Genesis header has rank 0
139 if header.height == 0 {
140 return Ok((difficulty, 0u64.into(), 0u64.into()))
141 }
142
143 // Verify hash is less than the expected mine target
144 let out_hash = module.verify_block_target(header, &target)?;
145
146 // Compute the squared mining target distance
147 let target_distance = &*MAX_32_BYTES - target;
148 let target_distance_sq = &target_distance * &target_distance;
149
150 // Compute the squared output hash distance
151 let hash_distance = &*MAX_32_BYTES - out_hash;
152 let hash_distance_sq = &hash_distance * &hash_distance;
153
154 Ok((difficulty, target_distance_sq, hash_distance_sq))
155}
156
157/// Compute a block's rank, assuming that its valid, for provided PoW
158/// module.
159/// Returns next mine difficulty, along with the computed rank.
160///
161/// Block's rank is the tuple of its squared mining target distance
162/// from max 32 bytes int, along with its squared RandomX hash number
163/// distance from max 32 bytes int. Genesis block has rank (0, 0).
164pub fn block_rank(
165 module: &mut PoWModule,
166 block: &BlockInfo,
167) -> Result<(BigUint, BigUint, BigUint)> {
168 // Grab next mine target and difficulty
169 let (target, difficulty) = module.next_mine_target_and_difficulty()?;
170
171 // Genesis block has rank 0
172 if block.header.height == 0 {
173 return Ok((difficulty, 0u64.into(), 0u64.into()))
174 }
175
176 // Compute the block header hash based on its PoW data
177 let out_hash = module.calculate_hash(&block.header)?;
178
179 // Compute the squared mining target distance
180 let target_distance = &*MAX_32_BYTES - target;
181 let target_distance_sq = &target_distance * &target_distance;
182
183 // Compute the squared output hash distance
184 let hash_distance = &*MAX_32_BYTES - out_hash;
185 let hash_distance_sq = &hash_distance * &hash_distance;
186
187 Ok((difficulty, target_distance_sq, hash_distance_sq))
188}
189
190/// Auxiliary function to calculate the middle value between provided
191/// u64 numbers.
192pub fn get_mid(a: u64, b: u64) -> u64 {
193 (a / 2) + (b / 2) + ((a - 2 * (a / 2)) + (b - 2 * (b / 2))) / 2
194}
195
196/// Auxiliary function to calculate the median of a given `Vec<u64>`.
197/// The function sorts the vector internally.
198pub fn median(mut v: Vec<u64>) -> u64 {
199 if v.len() == 1 {
200 return v[0]
201 }
202
203 let n = v.len() / 2;
204 v.sort_unstable();
205
206 if v.len().is_multiple_of(2) {
207 return get_mid(v[n - 1], v[n])
208 }
209 v[n]
210}
211
212/// Given a proposal, find the index of a fork chain it extends, along
213/// with the specific extended proposal index. Additionally, check that
214/// proposal doesn't already exists in any fork chain.
215pub fn find_extended_fork_index(forks: &[Fork], proposal: &Proposal) -> Result<(usize, usize)> {
216 // Grab provided proposal hash
217 let proposal_hash = proposal.hash;
218
219 // Keep track of fork and proposal indexes
220 let (mut fork_index, mut proposal_index) = (None, None);
221
222 // Loop through all the forks
223 for (f_index, fork) in forks.iter().enumerate() {
224 // Traverse fork proposals sequence in reverse
225 for (p_index, p_hash) in fork.proposals.iter().enumerate().rev() {
226 // Check we haven't already seen that proposal
227 if proposal_hash == *p_hash {
228 return Err(Error::ProposalAlreadyExists)
229 }
230
231 // Check if proposal extends this fork
232 if proposal.block.header.previous == *p_hash {
233 (fork_index, proposal_index) = (Some(f_index), Some(p_index));
234 }
235 }
236 }
237
238 if let (Some(f_index), Some(p_index)) = (fork_index, proposal_index) {
239 return Ok((f_index, p_index))
240 }
241
242 Err(Error::ExtendedChainIndexNotFound)
243}
244
245/// Auxiliary function to find best ranked fork.
246///
247/// The best ranked fork is the one with the highest sum of its blocks
248/// squared mining target distances, from max 32 bytes int. In case of
249/// a tie, the fork with the highest sum of its blocks squared RandomX
250/// hash number distances, from max 32 bytes int, wins.
251pub fn best_fork_index(forks: &[Fork]) -> Result<usize> {
252 // Check if node has any forks
253 if forks.is_empty() {
254 return Err(Error::ForksNotFound)
255 }
256
257 // Find the best ranked forks
258 let mut best = &BigUint::from(0u64);
259 let mut indexes = vec![];
260 for (f_index, fork) in forks.iter().enumerate() {
261 let rank = &fork.targets_rank;
262
263 // Fork ranks lower that current best
264 if rank < best {
265 continue
266 }
267
268 // Fork has same rank as current best
269 if rank == best {
270 indexes.push(f_index);
271 continue
272 }
273
274 // Fork ranks higher that current best
275 best = rank;
276 indexes = vec![f_index];
277 }
278
279 // If a single best ranking fork exists, return it
280 if indexes.len() == 1 {
281 return Ok(indexes[0])
282 }
283
284 // Break tie using their hash distances rank
285 let mut best_index = indexes[0];
286 for index in &indexes[1..] {
287 if forks[*index].hashes_rank > forks[best_index].hashes_rank {
288 best_index = *index;
289 }
290 }
291
292 Ok(best_index)
293}
294
295/// Auxiliary function to find worst ranked fork.
296///
297/// The worst ranked fork is the one with the lowest sum of its blocks
298/// squared mining target distances, from max 32 bytes int. In case of
299/// a tie, the fork with the lowest sum of its blocks squared RandomX
300/// hash number distances, from max 32 bytes int, wins.
301pub fn worst_fork_index(forks: &[Fork]) -> Result<usize> {
302 // Check if node has any forks
303 if forks.is_empty() {
304 return Err(Error::ForksNotFound)
305 }
306
307 // Find the worst ranked forks
308 let mut worst = &forks[0].targets_rank;
309 let mut indexes = vec![0];
310 for (f_index, fork) in forks[1..].iter().enumerate() {
311 let rank = &fork.targets_rank;
312
313 // Fork ranks higher that current worst
314 if rank > worst {
315 continue
316 }
317
318 // Fork has same rank as current worst
319 if rank == worst {
320 indexes.push(f_index + 1);
321 continue
322 }
323
324 // Fork ranks lower that current worst
325 worst = rank;
326 indexes = vec![f_index + 1];
327 }
328
329 // If a single worst ranking fork exists, return it
330 if indexes.len() == 1 {
331 return Ok(indexes[0])
332 }
333
334 // Break tie using their hash distances rank
335 let mut worst_index = indexes[0];
336 for index in &indexes[1..] {
337 if forks[*index].hashes_rank < forks[worst_index].hashes_rank {
338 worst_index = *index;
339 }
340 }
341
342 Ok(worst_index)
343}