darkfi/validator/
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
19use darkfi_sdk::{
20    crypto::{DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID},
21    tx::TransactionHash,
22};
23use log::info;
24use num_bigint::BigUint;
25use randomx::{RandomXCache, RandomXFlags, RandomXVM};
26
27use 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.
45pub 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).
116pub 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).
155pub 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
183pub 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.
189pub 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.
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
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.
244pub 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}