/ src / validator / utils.rs
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  }