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::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 randomx::{RandomXCache, RandomXFlags, RandomXVM};
27use tracing::info;
28
29use crate::{
30    blockchain::{BlockInfo, BlockchainOverlayPtr, Header},
31    runtime::vm_runtime::Runtime,
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        let mut runtime = Runtime::new(
102            &nc.2[..],
103            overlay.clone(),
104            nc.1,
105            verifying_block_height,
106            block_target,
107            TransactionHash::none(),
108            call_idx as u8,
109        )?;
110
111        runtime.deploy(&nc.3)?;
112
113        info!(target: "validator::utils::deploy_native_contracts", "Successfully deployed {}", nc.0);
114    }
115
116    info!(target: "validator::utils::deploy_native_contracts", "Finished deployment of native WASM contracts");
117
118    Ok(())
119}
120
121/// Verify provided header is valid for provided PoW module and compute
122/// its rank.
123/// Returns next mine difficulty, along with the computed rank.
124///
125/// Header's rank is the tuple of its squared mining target distance
126/// from max 32 bytes int, along with its squared RandomX hash number
127/// distance from max 32 bytes int.
128/// Genesis block has rank (0, 0).
129pub fn header_rank(module: &mut PoWModule, header: &Header) -> Result<(BigUint, BigUint, BigUint)> {
130    // Grab next mine target and difficulty
131    let (target, difficulty) = module.next_mine_target_and_difficulty()?;
132
133    // Genesis header has rank 0
134    if header.height == 0 {
135        return Ok((difficulty, 0u64.into(), 0u64.into()))
136    }
137
138    // Verify hash is less than the expected mine target
139    let out_hash = module.verify_block_target(header, &target)?;
140
141    // Compute the squared mining target distance
142    let target_distance = &*MAX_32_BYTES - target;
143    let target_distance_sq = &target_distance * &target_distance;
144
145    // Compute the output hash distance
146    let hash_distance = &*MAX_32_BYTES - out_hash;
147    let hash_distance_sq = &hash_distance * &hash_distance;
148
149    Ok((difficulty, target_distance_sq, hash_distance_sq))
150}
151
152/// Compute a block's rank, assuming that its valid, based on provided
153/// mining target.
154///
155/// Block's rank is the tuple of its squared mining target distance
156/// from max 32 bytes int, along with its squared RandomX hash number
157/// distance from max 32 bytes int. Genesis block has rank (0, 0).
158pub fn block_rank(block: &BlockInfo, target: &BigUint) -> Result<(BigUint, BigUint)> {
159    // Genesis block has rank 0
160    if block.header.height == 0 {
161        return Ok((0u64.into(), 0u64.into()))
162    }
163
164    // Compute the squared mining target distance
165    let target_distance = &*MAX_32_BYTES - target;
166    let target_distance_sq = &target_distance * &target_distance;
167
168    // Setup RandomX verifier
169    let flags = RandomXFlags::get_recommended_flags();
170    let cache = RandomXCache::new(flags, block.header.previous.inner())?;
171    let vm = RandomXVM::new(flags, Some(cache), None)?;
172
173    // Compute the output hash distance
174    let out_hash = vm.calculate_hash(block.hash().inner())?;
175    let out_hash = BigUint::from_bytes_le(&out_hash);
176    let hash_distance = &*MAX_32_BYTES - out_hash;
177    let hash_distance_sq = &hash_distance * &hash_distance;
178
179    Ok((target_distance_sq, hash_distance_sq))
180}
181
182/// Auxiliary function to calculate the middle value between provided
183/// u64 numbers.
184pub fn get_mid(a: u64, b: u64) -> u64 {
185    (a / 2) + (b / 2) + ((a - 2 * (a / 2)) + (b - 2 * (b / 2))) / 2
186}
187
188/// Auxiliary function to calculate the median of a given `Vec<u64>`.
189/// The function sorts the vector internally.
190pub fn median(mut v: Vec<u64>) -> u64 {
191    if v.len() == 1 {
192        return v[0]
193    }
194
195    let n = v.len() / 2;
196    v.sort_unstable();
197
198    if v.len().is_multiple_of(2) {
199        return get_mid(v[n - 1], v[n])
200    }
201    v[n]
202}
203
204/// Given a proposal, find the index of a fork chain it extends, along
205/// with the specific extended proposal index. Additionally, check that
206/// proposal doesn't already exists in any fork chain.
207pub 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 its blocks
240/// squared mining target distances, from max 32 bytes int. In case of
241/// a tie, the fork with the highest sum of its blocks squared RandomX
242/// hash number distances, from max 32 bytes int, wins.
243pub fn best_fork_index(forks: &[Fork]) -> Result<usize> {
244    // Check if node has any forks
245    if forks.is_empty() {
246        return Err(Error::ForksNotFound)
247    }
248
249    // Find the best ranked forks
250    let mut best = &BigUint::from(0u64);
251    let mut indexes = vec![];
252    for (f_index, fork) in forks.iter().enumerate() {
253        let rank = &fork.targets_rank;
254
255        // Fork ranks lower that current best
256        if rank < best {
257            continue
258        }
259
260        // Fork has same rank as current best
261        if rank == best {
262            indexes.push(f_index);
263            continue
264        }
265
266        // Fork ranks higher that current best
267        best = rank;
268        indexes = vec![f_index];
269    }
270
271    // If a single best ranking fork exists, return it
272    if indexes.len() == 1 {
273        return Ok(indexes[0])
274    }
275
276    // Break tie using their hash distances rank
277    let mut best_index = indexes[0];
278    for index in &indexes[1..] {
279        if forks[*index].hashes_rank > forks[best_index].hashes_rank {
280            best_index = *index;
281        }
282    }
283
284    Ok(best_index)
285}
286
287/// Auxiliary function to find worst ranked fork.
288///
289/// The worst ranked fork is the one with the lowest sum of its blocks
290/// squared mining target distances, from max 32 bytes int. In case of
291/// a tie, the fork with the lowest sum of its blocks squared RandomX
292/// hash number distances, from max 32 bytes int, wins.
293pub fn worst_fork_index(forks: &[Fork]) -> Result<usize> {
294    // Check if node has any forks
295    if forks.is_empty() {
296        return Err(Error::ForksNotFound)
297    }
298
299    // Find the worst ranked forks
300    let mut worst = &forks[0].targets_rank;
301    let mut indexes = vec![0];
302    for (f_index, fork) in forks[1..].iter().enumerate() {
303        let rank = &fork.targets_rank;
304
305        // Fork ranks higher that current worst
306        if rank > worst {
307            continue
308        }
309
310        // Fork has same rank as current worst
311        if rank == worst {
312            indexes.push(f_index + 1);
313            continue
314        }
315
316        // Fork ranks lower that current worst
317        worst = rank;
318        indexes = vec![f_index + 1];
319    }
320
321    // If a single worst ranking fork exists, return it
322    if indexes.len() == 1 {
323        return Ok(indexes[0])
324    }
325
326    // Break tie using their hash distances rank
327    let mut worst_index = indexes[0];
328    for index in &indexes[1..] {
329        if forks[*index].hashes_rank < forks[worst_index].hashes_rank {
330            worst_index = *index;
331        }
332    }
333
334    Ok(worst_index)
335}