/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2023 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use async_std::sync::Arc; use darkfi::{ blockchain::{BlockInfo, Header}, net::Settings, util::time::TimeKeeper, validator::{ consensus::{next_block_reward, pid::slot_pid_output}, Validator, ValidatorConfig, }, Result, }; use darkfi_contract_test_harness::{vks, Holder, TestHarness}; use darkfi_sdk::{ blockchain::{PidOutput, PreviousSlot, Slot}, pasta::{group::ff::Field, pallas}, }; use log::error; use url::Url; use crate::{ task::sync::sync_task, utils::{genesis_txs_total, spawn_consensus_p2p, spawn_sync_p2p}, Darkfid, }; pub struct HarnessConfig { pub testing_node: bool, pub alice_initial: u64, pub bob_initial: u64, } pub struct Harness { pub config: HarnessConfig, pub vks: Vec<(Vec, String, Vec)>, pub validator_config: ValidatorConfig, pub alice: Darkfid, pub bob: Darkfid, } impl Harness { pub async fn new(config: HarnessConfig, ex: &Arc>) -> Result { // Use test harness to generate genesis transactions let mut th = TestHarness::new(&["money".to_string(), "consensus".to_string()]).await?; let (genesis_stake_tx, _) = th.genesis_stake(&Holder::Alice, config.alice_initial)?; let (genesis_mint_tx, _) = th.genesis_mint(&Holder::Bob, config.bob_initial)?; // Generate default genesis block let mut genesis_block = BlockInfo::default(); // Append genesis transactions and calculate their total genesis_block.txs.push(genesis_stake_tx); genesis_block.txs.push(genesis_mint_tx); let genesis_txs_total = genesis_txs_total(&genesis_block.txs)?; genesis_block.slots[0].total_tokens = genesis_txs_total; // Generate validators configuration // NOTE: we are not using consensus constants here so we // don't get circular dependencies. let time_keeper = TimeKeeper::new(genesis_block.header.timestamp, 10, 90, 0); let validator_config = ValidatorConfig::new( time_keeper, genesis_block, genesis_txs_total, vec![], config.testing_node, ); // Generate validators using pregenerated vks let (_, vks) = vks::read_or_gen_vks_and_pks()?; let mut sync_settings = Settings::default(); sync_settings.localnet = true; let mut consensus_settings = Settings::default(); consensus_settings.localnet = true; // Alice let alice_url = Url::parse("tcp+tls://127.0.0.1:18340")?; sync_settings.inbound_addrs = vec![alice_url.clone()]; let alice_consensus_url = Url::parse("tcp+tls://127.0.0.1:18350")?; consensus_settings.inbound_addrs = vec![alice_consensus_url.clone()]; let alice = generate_node( &vks, &validator_config, &sync_settings, Some(&consensus_settings), ex, true, ) .await?; // Bob let bob_url = Url::parse("tcp+tls://127.0.0.1:18341")?; sync_settings.inbound_addrs = vec![bob_url]; sync_settings.peers = vec![alice_url]; let bob_consensus_url = Url::parse("tcp+tls://127.0.0.1:18351")?; consensus_settings.inbound_addrs = vec![bob_consensus_url]; consensus_settings.peers = vec![alice_consensus_url]; let bob = generate_node( &vks, &validator_config, &sync_settings, Some(&consensus_settings), ex, false, ) .await?; Ok(Self { config, vks, validator_config, alice, bob }) } pub async fn validate_chains(&self, total_blocks: usize, total_slots: usize) -> Result<()> { let genesis_txs_total = self.config.alice_initial + self.config.bob_initial; let alice = &self.alice.validator.read().await; let bob = &self.bob.validator.read().await; alice.validate_blockchain(genesis_txs_total, vec![]).await?; bob.validate_blockchain(genesis_txs_total, vec![]).await?; let alice_blockchain_len = alice.blockchain.len(); assert_eq!(alice_blockchain_len, bob.blockchain.len()); assert_eq!(alice_blockchain_len, total_blocks); let alice_slots_len = alice.blockchain.slots.len(); assert_eq!(alice_slots_len, bob.blockchain.slots.len()); assert_eq!(alice_slots_len, total_slots); Ok(()) } pub async fn add_blocks(&self, blocks: &[BlockInfo]) -> Result<()> { // We simply broadcast the block using Alice's sync P2P for block in blocks { self.alice.sync_p2p.broadcast(block).await; } // and then add it to her chain self.alice.validator.read().await.add_blocks(blocks).await?; Ok(()) } pub async fn generate_next_block( &self, previous: &BlockInfo, slots_count: usize, ) -> Result { let previous_hash = previous.blockhash(); // Generate empty slots let mut slots = Vec::with_capacity(slots_count); let mut previous_slot = previous.slots.last().unwrap().clone(); for i in 0..slots_count { let id = previous_slot.id + 1; // First slot in the sequence has (at least) 1 previous slot producer let producers = if i == 0 { 1 } else { 0 }; let previous = PreviousSlot::new( producers, vec![previous_hash], vec![previous.header.previous.clone()], pallas::Base::ZERO, previous_slot.pid.error, ); let (f, error, sigma1, sigma2) = slot_pid_output(&previous_slot, producers); let pid = PidOutput::new(f, error, sigma1, sigma2); let total_tokens = previous_slot.total_tokens + previous_slot.reward; // Only last slot in the sequence has a reward let reward = if i == slots_count - 1 { next_block_reward() } else { 0 }; let slot = Slot::new(id, previous, pid, total_tokens, reward); slots.push(slot.clone()); previous_slot = slot; } // We increment timestamp so we don't have to use sleep let mut timestamp = previous.header.timestamp; timestamp.add(1); // Generate header let header = Header::new( previous_hash, previous.header.epoch, slots.last().unwrap().id, timestamp, previous.header.root.clone(), ); // Generate block let block = BlockInfo::new(header, vec![], previous.producer.clone(), slots); Ok(block) } } pub async fn generate_node( vks: &Vec<(Vec, String, Vec)>, config: &ValidatorConfig, sync_settings: &Settings, consensus_settings: Option<&Settings>, ex: &Arc>, skip_sync: bool, ) -> Result { let sled_db = sled::Config::new().temporary(true).open()?; vks::inject(&sled_db, &vks)?; let validator = Validator::new(&sled_db, config.clone()).await?; let sync_p2p = spawn_sync_p2p(&sync_settings, &validator).await; let consensus_p2p = if let Some(settings) = consensus_settings { Some(spawn_consensus_p2p(settings, &validator).await) } else { None }; let node = Darkfid::new(sync_p2p.clone(), consensus_p2p.clone(), validator).await; sync_p2p.clone().start(ex.clone()).await?; let _ex = ex.clone(); ex.spawn(async move { if let Err(e) = sync_p2p.run(_ex).await { error!("Failed starting sync P2P network: {}", e); } }) .detach(); if consensus_settings.is_some() { consensus_p2p.clone().unwrap().start(ex.clone()).await?; let _ex = ex.clone(); ex.spawn(async move { if let Err(e) = consensus_p2p.unwrap().run(_ex).await { error!("Failed starting consensus P2P network: {}", e); } }) .detach(); } if !skip_sync { sync_task(&node).await?; } else { node.validator.write().await.synced = true; } Ok(node) }