/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2024 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 std::sync::Arc;
use darkfi::{net::Settings, validator::utils::best_fork_index, Result};
use darkfi_contract_test_harness::init_logger;
use darkfi_sdk::num_traits::One;
use num_bigint::BigUint;
use smol::Executor;
use url::Url;
mod harness;
use harness::{generate_node, Harness, HarnessConfig};
mod forks;
mod sync_forks;
async fn sync_blocks_real(ex: Arc>) -> Result<()> {
init_logger();
// Initialize harness in testing mode
let pow_target = 90;
let pow_fixed_difficulty = Some(BigUint::one());
let config = HarnessConfig {
pow_target,
pow_fixed_difficulty: pow_fixed_difficulty.clone(),
finalization_threshold: 3,
alice_initial: 1000,
bob_initial: 500,
};
let th = Harness::new(config, true, &ex).await?;
// Retrieve genesis block
let genesis = th.alice.validator.blockchain.last_block()?;
// Generate next blocks
let block1 = th.generate_next_block(&genesis).await?;
let block2 = th.generate_next_block(&block1).await?;
let block3 = th.generate_next_block(&block2).await?;
let block4 = th.generate_next_block(&block3).await?;
// Add them to nodes
th.add_blocks(&vec![block1, block2, block3.clone(), block4.clone()]).await?;
// Nodes must have one fork with 2 blocks
th.validate_fork_chains(1, vec![2]).await;
// Extend current fork sequence
let block5 = th.generate_next_block(&block4).await?;
// Create a new fork extending canonical
let block6 = th.generate_next_block(&block3).await?;
// Add them to nodes
th.add_blocks(&vec![block5, block6.clone()]).await?;
// Grab current best fork index
let forks = th.alice.validator.consensus.forks.read().await;
// If index corresponds to the small fork, finalization
// did not occur, as it's size is not over the threshold.
let small_best = best_fork_index(&forks)? == 1;
drop(forks);
if small_best {
// Nodes must have one fork with 3 blocks and one with 2 blocks
th.validate_fork_chains(2, vec![3, 2]).await;
} else {
// Nodes must have one fork with 2 blocks and one with 1 block
th.validate_fork_chains(2, vec![2, 1]).await;
}
// We are going to create a third node and try to sync from Bob
let mut settings = Settings { localnet: true, inbound_connections: 3, ..Default::default() };
let charlie_url = Url::parse("tcp+tls://127.0.0.1:18342")?;
settings.inbound_addrs = vec![charlie_url];
let bob_url = th.bob.p2p.settings().inbound_addrs[0].clone();
settings.peers = vec![bob_url];
let charlie =
generate_node(&th.vks, &th.validator_config, &settings, &ex, false, false).await?;
// Verify node synced
let alice = &th.alice.validator;
let charlie = &charlie.validator;
charlie.validate_blockchain(pow_target, pow_fixed_difficulty.clone()).await?;
assert_eq!(alice.blockchain.len(), charlie.blockchain.len());
// Node must have just the best fork
let forks = alice.consensus.forks.read().await;
let best_fork = &forks[best_fork_index(&forks)?];
let charlie_forks = charlie.consensus.forks.read().await;
assert_eq!(charlie_forks.len(), 1);
assert_eq!(charlie_forks[0].proposals.len(), best_fork.proposals.len());
assert_eq!(charlie_forks[0].diffs.len(), best_fork.diffs.len());
drop(forks);
drop(charlie_forks);
// Extend the small fork sequence and add it to nodes
let block7 = th.generate_next_block(&block6).await?;
th.add_blocks(&vec![block7.clone()]).await?;
// Nodes must have two forks with 2 blocks each
th.validate_fork_chains(2, vec![2, 2]).await;
// Check charlie has the correct forks
let charlie_forks = charlie.consensus.forks.read().await;
if small_best {
// If Charlie already had the small fork as its best,
// it will have a single fork with 2 blocks.
assert_eq!(charlie_forks.len(), 1);
assert_eq!(charlie_forks[0].proposals.len(), 2);
assert_eq!(charlie_forks[0].diffs.len(), 2);
} else {
// Charlie didn't originaly have the fork, but it
// should be synced when its proposal was received
assert_eq!(charlie_forks.len(), 2);
assert_eq!(charlie_forks[0].proposals.len(), 2);
assert_eq!(charlie_forks[0].diffs.len(), 2);
assert_eq!(charlie_forks[1].proposals.len(), 2);
assert_eq!(charlie_forks[1].diffs.len(), 2);
}
drop(charlie_forks);
// Since the don't know if the second fork was the best,
// we extend it until it becomes best and a finalization
// occurred.
let mut fork_sequence = vec![block6, block7];
loop {
let proposal = th.generate_next_block(fork_sequence.last().unwrap()).await?;
th.add_blocks(&vec![proposal.clone()]).await?;
fork_sequence.push(proposal);
// Check if finalization occured
if th.alice.validator.blockchain.len() > 4 {
break
}
}
// Nodes must have executed finalization, so we validate their chains
th.validate_chains(4 + (fork_sequence.len() - 2)).await?;
let bob = &th.bob.validator;
let last = alice.blockchain.last()?.1;
assert_eq!(last, fork_sequence[fork_sequence.len() - 3].hash()?);
assert_eq!(last, bob.blockchain.last()?.1);
// Nodes must have one fork with 2 blocks
th.validate_fork_chains(1, vec![2]).await;
let last_proposal = alice.consensus.forks.read().await[0].proposals[1];
assert_eq!(last_proposal, fork_sequence.last().unwrap().hash()?);
assert_eq!(last_proposal, bob.consensus.forks.read().await[0].proposals[1]);
// Same for Charlie
charlie.finalization().await?;
charlie.validate_blockchain(pow_target, pow_fixed_difficulty).await?;
assert_eq!(alice.blockchain.len(), charlie.blockchain.len());
assert_eq!(last, charlie.blockchain.last()?.1);
let charlie_forks = charlie.consensus.forks.read().await;
assert_eq!(charlie_forks.len(), 1);
assert_eq!(charlie_forks[0].proposals.len(), 2);
assert_eq!(charlie_forks[0].diffs.len(), 2);
assert_eq!(last_proposal, charlie_forks[0].proposals[1]);
// Thanks for reading
Ok(())
}
#[test]
fn sync_blocks() -> Result<()> {
let ex = Arc::new(Executor::new());
let (signal, shutdown) = smol::channel::unbounded::<()>();
easy_parallel::Parallel::new().each(0..4, |_| smol::block_on(ex.run(shutdown.recv()))).finish(
|| {
smol::block_on(async {
sync_blocks_real(ex.clone()).await.unwrap();
drop(signal);
})
},
);
Ok(())
}