use crate::{
storage::{rocksdb::RocksDB, Storage},
LedgerState,
};
use snarkvm::dpc::{prelude::*, testnet2::Testnet2};
use rand::{thread_rng, Rng};
use std::sync::atomic::AtomicBool;
fn temp_dir() -> std::path::PathBuf {
tempfile::tempdir().expect("Failed to open temporary directory").into_path()
}
fn create_new_ledger<N: Network, S: Storage>() -> LedgerState<N> {
LedgerState::open_writer_with_increment::<S, _>(temp_dir(), 1).expect("Failed to initialize ledger")
}
#[test]
fn test_genesis() {
let ledger = create_new_ledger::<Testnet2, RocksDB>();
let genesis = Testnet2::genesis_block();
let mut ledger_tree = LedgerTree::<Testnet2>::new().expect("Failed to initialize ledger tree");
ledger_tree.add(&genesis.hash()).expect("Failed to add to ledger tree");
assert_eq!(0, ledger.latest_block_height());
assert_eq!(genesis.height(), ledger.latest_block_height());
assert_eq!(genesis.hash(), ledger.latest_block_hash());
assert_eq!(genesis.timestamp(), ledger.latest_block_timestamp());
assert_eq!(genesis.difficulty_target(), ledger.latest_block_difficulty_target());
assert_eq!(genesis, &ledger.latest_block());
assert_eq!(Some(&(genesis.hash(), None)), ledger.latest_block_locators().get(&genesis.height()));
assert_eq!(ledger_tree.root(), ledger.latest_ledger_root());
}
#[test]
fn test_add_next_block() {
let rng = &mut thread_rng();
let terminator = AtomicBool::new(false);
let ledger = create_new_ledger::<Testnet2, RocksDB>();
assert_eq!(0, ledger.latest_block_height());
let mut ledger_tree = LedgerTree::<Testnet2>::new().expect("Failed to initialize ledger tree");
ledger_tree
.add(&Testnet2::genesis_block().hash())
.expect("Failed to add to ledger tree");
let account = Account::<Testnet2>::new(&mut thread_rng());
let address = account.address();
let (block, _) = ledger
.mine_next_block(address, true, &[], &terminator, rng)
.expect("Failed to mine");
ledger.add_next_block(&block).expect("Failed to add next block to ledger");
ledger_tree.add(&block.hash()).expect("Failed to add hash to ledger tree");
assert_eq!(1, ledger.latest_block_height());
assert_eq!(block.height(), ledger.latest_block_height());
assert_eq!(block.hash(), ledger.latest_block_hash());
assert_eq!(block.timestamp(), ledger.latest_block_timestamp());
assert_eq!(block.difficulty_target(), ledger.latest_block_difficulty_target());
assert_eq!(block, ledger.latest_block());
assert_eq!(ledger_tree.root(), ledger.latest_ledger_root());
let genesis = Testnet2::genesis_block();
let block_locators = ledger.latest_block_locators();
assert_eq!(2, block_locators.len());
assert_eq!(
Some(&(block.hash(), Some(block.header().clone()))),
block_locators.get(&block.height())
);
assert_eq!(Some(&(genesis.hash(), None)), block_locators.get(&genesis.height()));
}
#[test]
fn test_remove_last_block() {
let rng = &mut thread_rng();
let terminator = AtomicBool::new(false);
let ledger = create_new_ledger::<Testnet2, RocksDB>();
assert_eq!(0, ledger.latest_block_height());
let mut ledger_tree = LedgerTree::<Testnet2>::new().expect("Failed to initialize ledger tree");
ledger_tree
.add(&Testnet2::genesis_block().hash())
.expect("Failed to add to ledger tree");
let account = Account::<Testnet2>::new(&mut thread_rng());
let address = account.address();
let (block, _) = ledger
.mine_next_block(address, true, &[], &terminator, rng)
.expect("Failed to mine");
ledger.add_next_block(&block).expect("Failed to add next block to ledger");
assert_eq!(1, ledger.latest_block_height());
let blocks = ledger
.revert_to_block_height(ledger.latest_block_height() - 1)
.expect("Failed to remove the last block");
assert_eq!(vec![block], blocks);
let genesis = Testnet2::genesis_block();
assert_eq!(0, ledger.latest_block_height());
assert_eq!(genesis.height(), ledger.latest_block_height());
assert_eq!(genesis.hash(), ledger.latest_block_hash());
assert_eq!(genesis.timestamp(), ledger.latest_block_timestamp());
assert_eq!(genesis.difficulty_target(), ledger.latest_block_difficulty_target());
assert_eq!(genesis, &ledger.latest_block());
assert_eq!(Some(&(genesis.hash(), None)), ledger.latest_block_locators().get(&genesis.height()));
assert_eq!(ledger_tree.root(), ledger.latest_ledger_root());
}
#[test]
fn test_remove_last_2_blocks() {
let rng = &mut thread_rng();
let terminator = AtomicBool::new(false);
let ledger = create_new_ledger::<Testnet2, RocksDB>();
assert_eq!(0, ledger.latest_block_height());
let mut ledger_tree = LedgerTree::<Testnet2>::new().expect("Failed to initialize ledger tree");
ledger_tree
.add(&Testnet2::genesis_block().hash())
.expect("Failed to add to ledger tree");
let account = Account::<Testnet2>::new(&mut thread_rng());
let address = account.address();
let (block_1, _) = ledger
.mine_next_block(address, true, &[], &terminator, rng)
.expect("Failed to mine");
ledger.add_next_block(&block_1).expect("Failed to add next block to ledger");
assert_eq!(1, ledger.latest_block_height());
let (block_2, _) = ledger
.mine_next_block(address, true, &[], &terminator, rng)
.expect("Failed to mine");
ledger.add_next_block(&block_2).expect("Failed to add next block to ledger");
assert_eq!(2, ledger.latest_block_height());
let blocks = ledger
.revert_to_block_height(ledger.latest_block_height() - 2)
.expect("Failed to remove the last two blocks");
assert_eq!(vec![block_1, block_2], blocks);
let genesis = Testnet2::genesis_block();
assert_eq!(0, ledger.latest_block_height());
assert_eq!(genesis.height(), ledger.latest_block_height());
assert_eq!(genesis.hash(), ledger.latest_block_hash());
assert_eq!(genesis.timestamp(), ledger.latest_block_timestamp());
assert_eq!(genesis.difficulty_target(), ledger.latest_block_difficulty_target());
assert_eq!(genesis, &ledger.latest_block());
assert_eq!(Some(&(genesis.hash(), None)), ledger.latest_block_locators().get(&genesis.height()));
assert_eq!(ledger_tree.root(), ledger.latest_ledger_root());
}
#[test]
fn test_get_block_locators() {
let rng = &mut thread_rng();
let terminator = AtomicBool::new(false);
let ledger = create_new_ledger::<Testnet2, RocksDB>();
assert_eq!(0, ledger.latest_block_height());
let mut ledger_tree = LedgerTree::<Testnet2>::new().expect("Failed to initialize ledger tree");
ledger_tree
.add(&Testnet2::genesis_block().hash())
.expect("Failed to add to ledger tree");
let account = Account::<Testnet2>::new(&mut thread_rng());
let address = account.address();
let (block_1, _) = ledger
.mine_next_block(address, true, &[], &terminator, rng)
.expect("Failed to mine");
ledger.add_next_block(&block_1).expect("Failed to add next block to ledger");
assert_eq!(1, ledger.latest_block_height());
let block_locators = ledger
.get_block_locators(ledger.latest_block_height())
.expect("Failed to get block locators");
assert!(
ledger
.check_block_locators(&block_locators)
.expect("Failed to check block locators")
);
let (block_2, _) = ledger
.mine_next_block(address, true, &[], &terminator, rng)
.expect("Failed to mine");
ledger.add_next_block(&block_2).expect("Failed to add next block to ledger");
assert_eq!(2, ledger.latest_block_height());
let block_locators = ledger
.get_block_locators(ledger.latest_block_height())
.expect("Failed to get block locators");
assert!(
ledger
.check_block_locators(&block_locators)
.expect("Failed to check block locators")
);
let (block_3, _) = ledger
.mine_next_block(address, true, &[], &terminator, rng)
.expect("Failed to mine");
ledger.add_next_block(&block_3).expect("Failed to add next block to ledger");
assert_eq!(3, ledger.latest_block_height());
let block_locators = ledger
.get_block_locators(ledger.latest_block_height())
.expect("Failed to get block locators");
assert!(
ledger
.check_block_locators(&block_locators)
.expect("Failed to check block locators")
);
}
#[test]
fn test_transaction_fees() {
let rng = &mut thread_rng();
let terminator = AtomicBool::new(false);
let ledger = create_new_ledger::<Testnet2, RocksDB>();
assert_eq!(0, ledger.latest_block_height());
let account = Account::<Testnet2>::new(&mut thread_rng());
let private_key = account.private_key();
let view_key = account.view_key();
let address = account.address();
let (block, _record) = ledger
.mine_next_block(address, true, &[], &terminator, rng)
.expect("Failed to mine");
ledger.add_next_block(&block).expect("Failed to add next block to ledger");
let coinbase_transaction = &block.transactions()[0];
let available_balance = AleoAmount::from_i64(-coinbase_transaction.value_balance().0);
let fee = AleoAmount::from_i64(rng.gen_range(1..available_balance.0));
let amount = available_balance.sub(fee);
let coinbase_record = coinbase_transaction.to_decrypted_records(&view_key.into()).collect::<Vec<_>>();
let ledger_proof = ledger.get_ledger_inclusion_proof(coinbase_record[0].commitment()).unwrap();
let recipient_account = Account::<Testnet2>::new(rng);
let recipient_view_key = recipient_account.view_key();
let recipient = recipient_account.address();
let transfer_request = Request::new_transfer(
private_key,
coinbase_record,
vec![ledger_proof, LedgerProof::default()],
recipient,
amount,
fee,
true,
rng,
)
.unwrap();
let (vm, _response) = VirtualMachine::new(ledger.latest_ledger_root())
.unwrap()
.execute(&transfer_request, rng)
.unwrap();
let new_transaction = vm.finalize().unwrap();
let (block_2, _record) = ledger
.mine_next_block(address, true, &[new_transaction], &terminator, rng)
.expect("Failed to mine");
ledger.add_next_block(&block_2).expect("Failed to add next block to ledger");
assert_eq!(2, ledger.latest_block_height());
let expected_block_reward = Block::<Testnet2>::block_reward(2).add(fee);
let output_record = &block_2.transactions()[0]
.to_decrypted_records(&recipient_view_key.into())
.collect::<Vec<_>>()[0];
let new_coinbase_record = &block_2.transactions()[1].to_decrypted_records(&view_key.into()).collect::<Vec<_>>()[0];
assert_eq!(new_coinbase_record.value(), expected_block_reward);
assert_eq!(output_record.value(), amount);
}