#![allow(dead_code)]
use blvm_node::storage::blockstore::BlockStore;
use blvm_node::storage::chainstate::ChainState;
use blvm_node::storage::txindex::TxIndex;
use blvm_node::storage::utxostore::UtxoStore;
use blvm_node::Block;
use blvm_node::BlockHeader;
use blvm_node::Hash;
use blvm_node::OutPoint;
use blvm_node::Transaction;
use blvm_node::{ByteString, TransactionInput, TransactionOutput};
use blvm_protocol::ProtocolVersion;
use std::collections::HashMap;
use tempfile::TempDir;
pub fn create_protocol_test_utxo_set() -> blvm_protocol::UtxoSet {
use std::sync::Arc;
let mut utxo_set = blvm_protocol::UtxoSet::default();
utxo_set.insert(
blvm_protocol::OutPoint {
hash: [1; 32],
index: 0,
},
Arc::new(blvm_protocol::UTXO {
value: 100_000,
script_pubkey: [0x76, 0xa9, 0x14, 0x00].repeat(20).into(),
height: 0,
is_coinbase: false,
}),
);
utxo_set
}
pub fn create_protocol_test_tx(
input_value: u64,
output_value: u64,
size: usize,
) -> blvm_protocol::Transaction {
use blvm_protocol::{OutPoint, TransactionInput, TransactionOutput};
blvm_protocol::Transaction {
version: 1,
inputs: blvm_protocol::tx_inputs![TransactionInput {
prevout: OutPoint {
hash: [1; 32],
index: 0,
},
script_sig: vec![0; size / 2],
sequence: 0xffffffff,
}],
outputs: blvm_protocol::tx_outputs![TransactionOutput {
value: output_value as i64,
script_pubkey: [0x76, 0xa9, 0x14].repeat(size / 2),
}],
lock_time: 0,
}
}
pub struct TempDb {
pub temp_dir: TempDir,
pub utxo_store: UtxoStore,
pub tx_index: TxIndex,
pub block_store: BlockStore,
pub chain_state: ChainState,
}
impl TempDb {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
let temp_dir = TempDir::new()?;
let db_path = temp_dir.path();
use blvm_node::storage::database::{create_database, default_backend, Database};
let db_arc: std::sync::Arc<dyn Database> =
std::sync::Arc::from(create_database(db_path, default_backend(), None)?);
let utxo_store = UtxoStore::new(db_arc.clone())?;
let tx_index = TxIndex::new(db_arc.clone())?;
let block_store = BlockStore::new(db_arc.clone())?;
let chain_state = ChainState::new(db_arc)?;
Ok(TempDb {
temp_dir,
utxo_store,
tx_index,
block_store,
chain_state,
})
}
}
pub struct TestTransactionBuilder {
version: u64,
inputs: Vec<TransactionInput>,
outputs: Vec<TransactionOutput>,
lock_time: u64,
}
impl Default for TestTransactionBuilder {
fn default() -> Self {
Self::new()
}
}
impl TestTransactionBuilder {
pub fn new() -> Self {
Self {
version: 1,
inputs: Vec::new(),
outputs: Vec::new(),
lock_time: 0,
}
}
pub fn add_input(mut self, prevout: OutPoint) -> Self {
self.inputs.push(TransactionInput {
prevout,
script_sig: vec![0x51], sequence: 0xffffffff,
});
self
}
pub fn add_output(mut self, value: u64, script_pubkey: ByteString) -> Self {
self.outputs.push(TransactionOutput {
value: value as i64,
script_pubkey,
});
self
}
pub fn with_version(mut self, version: i32) -> Self {
self.version = version as u64;
self
}
pub fn with_lock_time(mut self, lock_time: u32) -> Self {
self.lock_time = lock_time as u64;
self
}
pub fn build(self) -> Transaction {
Transaction {
version: self.version,
inputs: self.inputs.into(),
outputs: self.outputs.into(),
lock_time: self.lock_time,
}
}
}
pub struct TestBlockBuilder {
header: BlockHeader,
transactions: Vec<Transaction>,
}
impl Default for TestBlockBuilder {
fn default() -> Self {
Self::new()
}
}
impl TestBlockBuilder {
pub fn new() -> Self {
Self {
header: BlockHeader {
version: 1,
prev_block_hash: Hash::default(),
merkle_root: Hash::default(),
timestamp: 0,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: Vec::new(),
}
}
pub fn set_prev_hash(mut self, hash: Hash) -> Self {
self.header.prev_block_hash = hash;
self
}
pub fn set_timestamp(mut self, timestamp: u32) -> Self {
self.header.timestamp = timestamp as u64;
self
}
pub fn with_version(mut self, version: i32) -> Self {
self.header.version = version as i64;
self
}
pub fn with_bits(mut self, bits: u32) -> Self {
self.header.bits = bits as u64;
self
}
pub fn with_nonce(mut self, nonce: u32) -> Self {
self.header.nonce = nonce as u64;
self
}
pub fn add_transaction(mut self, tx: Transaction) -> Self {
self.transactions.push(tx);
self
}
pub fn add_coinbase_transaction(mut self, script_pubkey: ByteString) -> Self {
let coinbase_tx = Transaction {
version: 1,
inputs: blvm_protocol::tx_inputs![TransactionInput {
prevout: OutPoint {
hash: [0u8; 32],
index: 0xffffffff,
},
script_sig: vec![0x51], sequence: 0xffffffff,
}],
outputs: blvm_protocol::tx_outputs![TransactionOutput {
value: 5000000000, script_pubkey,
}],
lock_time: 0,
};
self.transactions.push(coinbase_tx);
self
}
pub fn build(self) -> Block {
Block {
header: self.header,
transactions: self.transactions.into_boxed_slice(),
}
}
pub fn build_header(self) -> BlockHeader {
self.header
}
}
pub struct TestUtxoSetBuilder {
utxos: HashMap<OutPoint, TransactionOutput>,
}
impl Default for TestUtxoSetBuilder {
fn default() -> Self {
Self::new()
}
}
impl TestUtxoSetBuilder {
pub fn new() -> Self {
Self {
utxos: HashMap::new(),
}
}
pub fn add_utxo(
mut self,
hash: Hash,
index: u32,
value: u64,
script_pubkey: ByteString,
) -> Self {
self.utxos.insert(
OutPoint { hash, index },
TransactionOutput {
value: value as i64,
script_pubkey,
},
);
self
}
pub fn build(self) -> HashMap<OutPoint, TransactionOutput> {
self.utxos
}
}
pub fn random_hash() -> Hash {
let mut hash = [0u8; 32];
for b in &mut hash {
*b = rand::random::<u8>();
}
Hash::from(hash)
}
pub fn random_hash20() -> [u8; 20] {
let mut hash = [0u8; 20];
for b in &mut hash {
*b = rand::random::<u8>();
}
hash
}
pub fn p2pkh_script(pubkey_hash: [u8; 20]) -> ByteString {
let mut script = Vec::new();
script.push(0x76); script.push(0xa9); script.push(0x14); script.extend_from_slice(&pubkey_hash);
script.push(0x88); script.push(0xac); script
}
pub fn valid_transaction() -> Transaction {
TestTransactionBuilder::new()
.add_input(OutPoint {
hash: random_hash(),
index: 0,
})
.add_output(1000, p2pkh_script(random_hash20()))
.build()
}
pub fn unique_transaction() -> Transaction {
TestTransactionBuilder::new()
.add_input(OutPoint {
hash: random_hash(),
index: 0,
})
.add_output(1000, p2pkh_script(random_hash20()))
.build()
}
pub fn valid_block_header() -> BlockHeader {
BlockHeader {
version: 1,
prev_block_hash: random_hash(),
merkle_root: random_hash(),
timestamp: 1234567890,
bits: 0x1d00ffff,
nonce: 0,
}
}
pub fn valid_block() -> Block {
TestBlockBuilder::new()
.add_transaction(valid_transaction())
.build()
}
pub fn large_block(transaction_count: usize) -> Block {
let mut builder = TestBlockBuilder::new();
builder = builder.add_coinbase_transaction(p2pkh_script(random_hash20()));
for _ in 0..transaction_count {
let tx = TestTransactionBuilder::new()
.add_input(OutPoint {
hash: random_hash(),
index: 0,
})
.add_output(1000, p2pkh_script(random_hash20()))
.build();
builder = builder.add_transaction(tx);
}
builder.build()
}
pub fn default_protocol_version() -> ProtocolVersion {
ProtocolVersion::Regtest
}
pub const DIFFICULTY_INTERVAL: u64 = 2016;
pub fn setup_mining_chain(
storage: &std::sync::Arc<blvm_node::storage::Storage>,
total_blocks: u64,
) -> Result<(), Box<dyn std::error::Error>> {
setup_mining_chain_on(storage.as_ref(), total_blocks)
}
pub fn setup_mining_chain_on(
storage: &blvm_node::storage::Storage,
total_blocks: u64,
) -> Result<(), Box<dyn std::error::Error>> {
use blvm_protocol::Block;
assert!(
total_blocks >= 2,
"mining chain setup needs at least genesis + one block"
);
let genesis_header = BlockHeader {
version: 1,
prev_block_hash: [0u8; 32],
merkle_root: [0u8; 32],
timestamp: 1_231_006_505,
bits: 0x0f00ffff,
nonce: 2_083_236_893,
};
storage.chain().initialize(&genesis_header)?;
let genesis_block = Block {
header: genesis_header.clone(),
transactions: vec![Transaction {
version: 1,
inputs: blvm_protocol::tx_inputs![],
outputs: blvm_protocol::tx_outputs![],
lock_time: 0,
}]
.into_boxed_slice(),
};
let genesis_hash = storage.blocks().get_block_hash(&genesis_block);
storage.blocks().store_block(&genesis_block)?;
storage.blocks().store_height(0, &genesis_hash)?;
storage
.blocks()
.store_recent_header(0, &genesis_block.header)?;
let mut prev_hash = genesis_hash;
let mut prev_timestamp = genesis_header.timestamp;
for height in 1..total_blocks {
let block_header = BlockHeader {
version: 1,
prev_block_hash: prev_hash,
merkle_root: [height as u8; 32],
timestamp: prev_timestamp + 600,
bits: 0x0f00ffff,
nonce: 0,
};
let block = Block {
header: block_header.clone(),
transactions: vec![Transaction {
version: 1,
inputs: blvm_protocol::tx_inputs![],
outputs: blvm_protocol::tx_outputs![],
lock_time: 0,
}]
.into_boxed_slice(),
};
let block_hash = storage.blocks().get_block_hash(&block);
storage.blocks().store_block(&block)?;
storage.blocks().store_height(height, &block_hash)?;
storage
.blocks()
.store_recent_header(height, &block.header)?;
storage
.chain()
.update_tip(&block_hash, &block_header, height)?;
prev_hash = block_hash;
prev_timestamp = block_header.timestamp;
}
Ok(())
}
#[inline]
pub fn test_secp256k1_scalar_one() -> [u8; 32] {
let mut k = [0u8; 32];
k[31] = 1;
k
}
#[inline]
pub fn test_secp256k1_scalar_small(n: u8) -> [u8; 32] {
debug_assert!(n >= 1, "use non-zero scalar");
let mut k = [0u8; 32];
k[31] = n;
k
}
pub fn ecdsa_compact_sig_hex_and_pubkey_hex(
seckey32: &[u8; 32],
msg32: &[u8; 32],
) -> (String, String) {
use blvm_secp256k1::ecdsa::{ecdsa_sign_compact_rfc6979, ge_to_compressed, pubkey_from_secret};
use blvm_secp256k1::scalar::Scalar;
let sig = ecdsa_sign_compact_rfc6979(msg32, seckey32).expect("ECDSA sign (test key)");
let mut sec = Scalar::zero();
assert!(
!sec.set_b32(seckey32) && !sec.is_zero(),
"invalid test seckey"
);
let pk = ge_to_compressed(&pubkey_from_secret(&sec));
(hex::encode(sig), hex::encode(pk))
}
pub fn compressed_pubkey33_from_seckey(seckey32: &[u8; 32]) -> [u8; 33] {
use blvm_secp256k1::ecdsa::{ge_to_compressed, pubkey_from_secret};
use blvm_secp256k1::scalar::Scalar;
let mut sec = Scalar::zero();
assert!(!sec.set_b32(seckey32) && !sec.is_zero());
ge_to_compressed(&pubkey_from_secret(&sec))
}