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