use std::sync::Arc;
use blake2::Blake2b;
pub use block_spec::{BlockSpec, BlockSpecs};
use digest::consts::U32;
use rand::{Rng, rngs::OsRng};
use tari_common::configuration::Network;
use tari_common_sqlite::connection::DbConnection;
use tari_common_types::{
tari_address::TariAddress,
types::{CompressedPublicKey, PrivateKey},
};
use tari_comms::{
PeerManager,
multiaddr::Multiaddr,
net_address::{MultiaddressesWithStats, PeerAddressSource},
peer_manager::{
NodeId,
Peer,
PeerFeatures,
PeerFlags,
database::{MIGRATIONS, PeerDatabaseSql},
},
types::{CommsPublicKey, TransportProtocol},
};
use tari_crypto::keys::SecretKey;
use tari_node_components::blocks::{Block, BlockHeader, BlockHeaderAccumulatedData, ChainHeader};
use tari_transaction_components::{
MicroMinotari,
consensus::consensus_constants::ConsensusConstants,
generate_coinbase_with_wallet_output,
key_manager::{KeyManager, TariKeyId, TransactionKeyManagerInterface},
tari_proof_of_work::Difficulty,
transaction_components::{
CoinBaseExtra,
RangeProofType,
Transaction,
WalletOutput,
memo_field::{MemoField, TxType},
},
};
use tari_utilities::epoch_time::EpochTime;
use crate::{
blocks::BlockHeaderAccumulatedDataBuilder,
chain_storage::{BlockchainBackend, BlockchainDatabase},
consensus::BaseNodeConsensusManager,
proof_of_work::{AchievedTargetDifficulty, sha3x_difficulty},
};
#[macro_use]
mod block_spec;
pub mod blockchain;
pub fn create_consensus_rules() -> BaseNodeConsensusManager {
BaseNodeConsensusManager::builder(Network::LocalNet).build().unwrap()
}
pub fn create_consensus_constants(height: u64) -> ConsensusConstants {
create_consensus_rules().consensus_constants(height).clone()
}
pub fn create_orphan_block(
block_height: u64,
transactions: Vec<Transaction>,
consensus: &BaseNodeConsensusManager,
) -> Block {
let mut header = BlockHeader::new(consensus.consensus_constants(block_height).blockchain_version().into());
header.height = block_height;
header.into_builder().with_transactions(transactions).build()
}
pub fn default_coinbase_entities(key_manager: &KeyManager) -> (TariKeyId, TariAddress) {
let wallet_private_spend_key = PrivateKey::random(&mut OsRng);
let wallet_private_view_key = PrivateKey::random(&mut OsRng);
let _key = key_manager
.create_encrypted_key(wallet_private_view_key.clone(), None)
.unwrap();
let script_key_id = key_manager
.create_encrypted_key(wallet_private_spend_key.clone(), None)
.unwrap();
let wallet_payment_address = TariAddress::new_dual_address_with_default_features(
CompressedPublicKey::from_secret_key(&wallet_private_view_key),
CompressedPublicKey::from_secret_key(&wallet_private_spend_key),
Network::LocalNet,
)
.unwrap();
(script_key_id, wallet_payment_address)
}
pub fn create_block<TDB: BlockchainBackend>(
db: &BlockchainDatabase<TDB>,
rules: &BaseNodeConsensusManager,
prev_block: &Block,
spec: BlockSpec,
km: &KeyManager,
script_key_id: &TariKeyId,
wallet_payment_address: &TariAddress,
range_proof_type: Option<RangeProofType>,
) -> (Block, WalletOutput) {
let mut header = BlockHeader::from_previous(&prev_block.header);
header.version = rules.consensus_constants(header.height).blockchain_version().into();
let block_height = spec.height_override.unwrap_or(prev_block.header.height + 1);
header.height = block_height;
let reward = spec.reward_override.unwrap_or_else(|| {
rules
.calculate_coinbase_and_fees(
header.height,
&spec
.transactions
.iter()
.flat_map(|tx| tx.body.kernels().clone())
.collect::<Vec<_>>(),
)
.unwrap()
});
let (coinbase_transaction, _, _, coinbase_wallet_output) = generate_coinbase_with_wallet_output(
MicroMinotari::from(0),
reward,
header.height,
&CoinBaseExtra::default(),
km,
script_key_id,
wallet_payment_address,
false,
rules.consensus_constants(header.height),
range_proof_type.unwrap_or(RangeProofType::BulletProofPlus),
MemoField::new_open(vec![], TxType::Coinbase).expect("Should never fail since the vector is empty"),
)
.unwrap();
let mut block = header
.into_builder()
.with_transactions(
Some(coinbase_transaction)
.filter(|_| !spec.skip_coinbase)
.into_iter()
.chain(spec.transactions)
.collect(),
)
.build();
block.header.timestamp = prev_block
.header
.timestamp
.checked_add(EpochTime::from(spec.block_time))
.unwrap();
let mut block = apply_mmr_to_block(db, block);
block.header.output_smt_size = prev_block.header.output_smt_size + block.body.outputs().len() as u64;
block.header.kernel_mmr_size = prev_block.header.kernel_mmr_size + block.body.kernels().len() as u64;
(block, coinbase_wallet_output)
}
pub fn apply_mmr_to_block<TDB: BlockchainBackend>(db: &BlockchainDatabase<TDB>, block: Block) -> Block {
let res = block.clone();
let (mut block, mmr_roots) = match db.calculate_mmr_roots(block) {
Ok(mmr_roots) => mmr_roots,
Err(_) => {
return res;
},
};
block.header.output_mr = mmr_roots.output_mr;
block
}
pub fn mine_to_difficulty(mut block: Block, difficulty: Difficulty) -> Result<Block, String> {
block.header.nonce = rand::thread_rng().r#gen();
for _i in 0..20000 {
if sha3x_difficulty(&block.header).map_err(|e| e.to_string())? == difficulty {
return Ok(block);
}
block.header.nonce += 1;
}
Err("Could not mine to difficulty in 20000 iterations".to_string())
}
fn create_test_peer() -> Peer {
let mut rng = rand::rngs::OsRng;
let (_sk, pk) = CommsPublicKey::random_keypair(&mut rng);
let node_id = NodeId::from_key(&pk);
let addresses = MultiaddressesWithStats::from_addresses_with_source(
vec!["/ip4/123.0.0.123/tcp/8000".parse::<Multiaddr>().unwrap()],
&PeerAddressSource::Config,
);
Peer::new(
pk,
node_id,
addresses,
PeerFlags::default(),
PeerFeatures::empty(),
Default::default(),
Default::default(),
)
}
pub fn create_peer_manager() -> Arc<PeerManager> {
let db_connection = DbConnection::connect_temp_file_and_migrate(MIGRATIONS).unwrap();
let peers_db = PeerDatabaseSql::new(db_connection, &create_test_peer()).unwrap();
Arc::new(PeerManager::new(peers_db, TransportProtocol::get_all()).unwrap())
}
pub fn create_chain_header(header: BlockHeader, prev_accum: &BlockHeaderAccumulatedData) -> ChainHeader {
let achieved_target_diff = AchievedTargetDifficulty::try_construct(
header.pow_algo(),
Difficulty::from_u64(Difficulty::min().as_u64() + 1).unwrap(),
Difficulty::from_u64(Difficulty::min().as_u64() + 1).unwrap(),
)
.unwrap();
let accumulated_data = BlockHeaderAccumulatedDataBuilder::from_previous(prev_accum)
.with_hash(header.hash())
.with_achieved_target_difficulty(achieved_target_diff)
.with_total_kernel_offset(header.total_kernel_offset.clone())
.build(&create_consensus_constants(header.height))
.unwrap();
ChainHeader::try_construct(header, accumulated_data).unwrap()
}
pub fn new_public_key() -> CompressedPublicKey {
CompressedPublicKey::random_keypair(&mut OsRng).1
}
pub fn make_hash<T: AsRef<[u8]>>(preimage: T) -> [u8; 32] {
use digest::Digest;
Blake2b::<U32>::default()
.chain_update(preimage.as_ref())
.finalize()
.into()
}
pub fn make_hash2<T: AsRef<[u8]>, U: AsRef<[u8]>>(preimage1: T, preimage2: U) -> [u8; 32] {
use digest::Digest;
Blake2b::<U32>::default()
.chain_update(preimage1.as_ref())
.chain_update(preimage2.as_ref())
.finalize()
.into()
}