#[cfg(test)]
mod tests {
use bitcoin::consensus::deserialize;
use bitcoinkernel_covenants::{
verify, Block, BlockHash, BlockUndo, ChainParams, ChainType, ChainstateManager,
ChainstateManagerOptions, Context, ContextBuilder, KernelError,
KernelNotificationInterfaceCallbacks, Log, Logger, ScriptPubkey, Transaction, TxOut,
ValidationInterfaceCallbacks, VERIFY_ALL, VERIFY_ALL_PRE_TAPROOT,
};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::sync::{Arc, Once};
use tempdir::TempDir;
struct TestLog {}
impl Log for TestLog {
fn log(&self, message: &str) {
log::info!(
target: "libbitcoinkernel",
"{}", message.strip_suffix("\r\n").or_else(|| message.strip_suffix('\n')).unwrap_or(message));
}
}
static START: Once = Once::new();
static mut GLOBAL_LOG_CALLBACK_HOLDER: Option<Logger<TestLog>> = None;
fn setup_logging() {
let mut builder = env_logger::Builder::from_default_env();
builder.filter(None, log::LevelFilter::Info).init();
unsafe { GLOBAL_LOG_CALLBACK_HOLDER = Some(Logger::new(TestLog {}).unwrap()) };
}
fn create_context() -> Context {
let builder = ContextBuilder::new()
.chain_type(ChainType::REGTEST)
.kn_callbacks(Box::new(KernelNotificationInterfaceCallbacks {
kn_block_tip: Box::new(|_state, _block_tip, _verification_progress| {
log::info!("Received block tip.");
}),
kn_header_tip: Box::new(|_state, height, timestamp, _presync| {
assert!(timestamp > 0);
log::info!(
"Received header tip at height {} and time {}",
height,
timestamp
);
}),
kn_progress: Box::new(|_state, progress, _resume_possible| {
log::info!("Made progress: {}", progress);
}),
kn_warning_set: Box::new(|_warning, message| {
log::info!("Received warning: {message}");
}),
kn_warning_unset: Box::new(|_warning| {
log::info!("Unsetting warning.");
}),
kn_flush_error: Box::new(|message| {
log::info!("Flush error! {message}");
}),
kn_fatal_error: Box::new(|message| {
log::info!("Fatal Error! {message}");
}),
}))
.validation_interface(Box::new(ValidationInterfaceCallbacks {
block_checked: Box::new(|_block, _mode, _result| {
log::info!("Block checked!");
}),
}));
builder.build().unwrap()
}
fn testing_setup() -> (Arc<Context>, String) {
START.call_once(|| {
setup_logging();
});
let context = Arc::new(create_context());
let temp_dir = TempDir::new("test_chainman_regtest").unwrap();
let data_dir = temp_dir.path();
(context, data_dir.to_str().unwrap().to_string())
}
fn read_block_data() -> Vec<Vec<u8>> {
let file = File::open("tests/block_data.txt").unwrap();
let reader = BufReader::new(file);
let mut lines = vec![];
for line in reader.lines() {
lines.push(hex::decode(line.unwrap()).unwrap().to_vec());
}
lines
}
#[test]
fn test_reindex() {
let (context, data_dir) = testing_setup();
let blocks_dir = data_dir.clone() + "/blocks";
{
let block_data = read_block_data();
let chainman = ChainstateManager::new(
ChainstateManagerOptions::new(&context, &data_dir, &blocks_dir).unwrap(),
Arc::clone(&context),
)
.unwrap();
for raw_block in block_data.iter() {
let block = Block::try_from(raw_block.as_slice()).unwrap();
let (accepted, new_block) = chainman.process_block(&block);
assert!(accepted);
assert!(new_block);
}
}
let chainman_opts = ChainstateManagerOptions::new(&context, &data_dir, &blocks_dir)
.unwrap()
.set_wipe_db(false, true);
let chainman = ChainstateManager::new(chainman_opts, Arc::clone(&context)).unwrap();
chainman.import_blocks().unwrap();
drop(chainman);
}
#[test]
fn test_invalid_block() {
let (context, data_dir) = testing_setup();
let blocks_dir = data_dir.clone() + "/blocks";
for _ in 0..10 {
let chainman = ChainstateManager::new(
ChainstateManagerOptions::new(&context, &data_dir, &blocks_dir).unwrap(),
Arc::clone(&context),
)
.unwrap();
// Not a block
let block = Block::try_from(hex::decode("deadbeef").unwrap().as_slice());
assert!(matches!(block, Err(KernelError::Internal(_))));
drop(block);
// Invalid block
let block_1 = Block::try_from(hex::decode(
"010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd\
1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e36299\
0101000000010000000000000000000000000000000000000000000000000000000000000000ffff\
ffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec1\
1600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf62\
1e73a82cbf2342c858eeac00000000").unwrap().as_slice()
)
.unwrap();
let (accepted, new_block) = chainman.process_block(&block_1);
assert!(!accepted);
assert!(!new_block);
}
}
#[test]
fn test_scan_tx() {
#[allow(dead_code)]
#[derive(Debug)]
struct Input {
height: u32,
prevout: Vec<u8>,
script_sig: Vec<u8>,
witness: Vec<Vec<u8>>,
}
#[derive(Debug)]
struct ScanTxHelper {
ins: Vec<Input>,
#[allow(dead_code)]
outs: Vec<Vec<u8>>,
}
let (context, data_dir) = testing_setup();
let blocks_dir = data_dir.clone() + "/blocks";
let block_data = read_block_data();
let chainman = ChainstateManager::new(
ChainstateManagerOptions::new(&context, &data_dir, &blocks_dir).unwrap(),
Arc::clone(&context),
)
.unwrap();
for raw_block in block_data.iter() {
let block = Block::try_from(raw_block.as_slice()).unwrap();
let (accepted, new_block) = chainman.process_block(&block);
assert!(accepted);
assert!(new_block);
}
let block_index_genesis = chainman.get_block_index_genesis();
let height = block_index_genesis.height();
assert_eq!(height, 0);
let block_index_1 = chainman.get_next_block_index(block_index_genesis).unwrap();
let height = block_index_1.height();
assert_eq!(height, 1);
let block_index_tip = chainman.get_block_index_tip();
let raw_block_tip: Vec<u8> = chainman.read_block_data(&block_index_tip).unwrap().into();
let undo_tip = chainman.read_undo_data(&block_index_tip).unwrap();
let block_tip: bitcoin::Block = deserialize(&raw_block_tip).unwrap();
// Should be the same size minus the coinbase transaction
assert_eq!(block_tip.txdata.len() - 1, undo_tip.n_tx_undo);
let block_index_tip_prev = block_index_tip.prev().unwrap();
let raw_block: Vec<u8> = chainman
.read_block_data(&block_index_tip_prev)
.unwrap()
.into();
let undo = chainman.read_undo_data(&block_index_tip_prev).unwrap();
let block: bitcoin::Block = deserialize(&raw_block).unwrap();
// Should be the same size minus the coinbase transaction
assert_eq!(block.txdata.len() - 1, undo.n_tx_undo);
for i in 0..(block.txdata.len() - 1) {
let transaction_undo_size: u64 = undo.get_transaction_undo_size(i.try_into().unwrap());
let transaction_input_size: u64 = block.txdata[i + 1].input.len().try_into().unwrap();
assert_eq!(transaction_input_size, transaction_undo_size);
let mut helper = ScanTxHelper {
ins: vec![],
outs: block.txdata[i + 1]
.output
.iter()
.map(|output| output.script_pubkey.to_bytes())
.collect(),
};
for j in 0..transaction_input_size {
helper.ins.push(Input {
height: undo.get_prevout_height_by_index(i as u64, j).unwrap(),
prevout: undo
.get_prevout_by_index(i as u64, j)
.unwrap()
.get_script_pubkey()
.get(),
script_sig: block.txdata[i + 1].input[j as usize].script_sig.to_bytes(),
witness: block.txdata[i + 1].input[j as usize].witness.to_vec(),
});
}
println!("helper: {:?}", helper);
}
}
#[test]
fn test_process_data() {
let (context, data_dir) = testing_setup();
let blocks_dir = data_dir.clone() + "/blocks";
let block_data = read_block_data();
let chainman = ChainstateManager::new(
ChainstateManagerOptions::new(&context, &data_dir, &blocks_dir).unwrap(),
Arc::clone(&context),
)
.unwrap();
for raw_block in block_data.iter() {
let block = Block::try_from(raw_block.as_slice()).unwrap();
let (accepted, new_block) = chainman.process_block(&block);
assert!(accepted);
assert!(new_block);
}
}
#[test]
fn test_validate_any() {
let (context, data_dir) = testing_setup();
let blocks_dir = data_dir.clone() + "/blocks";
let block_data = read_block_data();
let chainman = ChainstateManager::new(
ChainstateManagerOptions::new(&context, &data_dir, &blocks_dir).unwrap(),
Arc::clone(&context),
)
.unwrap();
chainman.import_blocks().unwrap();
let block_2 = Block::try_from(block_data[1].clone().as_slice()).unwrap();
let (accepted, new_block) = chainman.process_block(&block_2);
assert!(!accepted);
assert!(!new_block);
}
#[test]
fn test_logger() {
let (_, _) = testing_setup();
let logger_1 = Some(Logger::new(TestLog {}).unwrap());
let logger_2 = Some(Logger::new(TestLog {}).unwrap());
let logger_3 = Some(Logger::new(TestLog {}).unwrap());
drop(logger_1);
drop(logger_2);
drop(logger_3);
}
#[test]
fn script_verify_test() {
// a random old-style transaction from the blockchain
verify_test (
"76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac",
"02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700",
0, 0
).unwrap();
// a random segwit transaction from the blockchain using P2SH
verify_test (
"a91434c06f8c87e355e123bdc6dda4ffabc64b6989ef87",
"01000000000101d9fd94d0ff0026d307c994d0003180a5f248146efb6371d040c5973f5f66d9df0400000017160014b31b31a6cb654cfab3c50567bcf124f48a0beaecffffffff012cbd1c000000000017a914233b74bf0823fa58bbbd26dfc3bb4ae715547167870247304402206f60569cac136c114a58aedd80f6fa1c51b49093e7af883e605c212bdafcd8d202200e91a55f408a021ad2631bc29a67bd6915b2d7e9ef0265627eabd7f7234455f6012103e7e802f50344303c76d12c089c8724c1b230e3b745693bbe16aad536293d15e300000000",
1900000, 0
).unwrap();
// a random segwit transaction from the blockchain using native segwit
verify_test(
"0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d",
"010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000",
18393430 , 0
).unwrap();
// a random old-style transaction from the blockchain - WITH WRONG SIGNATURE for the address
assert!(verify_test (
"76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ff",
"02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700",
0, 0
).is_err());
// a random segwit transaction from the blockchain using P2SH - WITH WRONG AMOUNT
assert!(verify_test (
"a91434c06f8c87e355e123bdc6dda4ffabc64b6989ef87",
"01000000000101d9fd94d0ff0026d307c994d0003180a5f248146efb6371d040c5973f5f66d9df0400000017160014b31b31a6cb654cfab3c50567bcf124f48a0beaecffffffff012cbd1c000000000017a914233b74bf0823fa58bbbd26dfc3bb4ae715547167870247304402206f60569cac136c114a58aedd80f6fa1c51b49093e7af883e605c212bdafcd8d202200e91a55f408a021ad2631bc29a67bd6915b2d7e9ef0265627eabd7f7234455f6012103e7e802f50344303c76d12c089c8724c1b230e3b745693bbe16aad536293d15e300000000",
900000, 0).is_err());
// a random segwit transaction from the blockchain using native segwit - WITH WRONG SEGWIT
assert!(verify_test(
"0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58f",
"010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000",
18393430 , 0
).is_err());
// Wrong TX Hash passed via the stack fails CTV
assert!(verify_test_covenants(
"00206c9cc4747e7d287ac1abf8c65152a78d369213583e306b7d3163e12c561abaa2",
"02000000000101e4ca625690c3118e6143098987db95b522477f3d3da64d3aebb607c63fae03130000000000000000000ae90300000000000017a9145850541d205b7b1feb1bc10112f9d739fa9a7dc287d00700000000000017a914d76b85e77e20777d5ec9d7d9998ae0c062d4669087b80b00000000000017a914c79f4a39b0580e4ec90e1b4989fc6124e4135cc187a00f00000000000017a91469635f1f4c06d66591282a02303b1435ee888d9487881300000000000017a91410d3b9b2aaffae25b6098169e2dc69928849215387701700000000000017a91456e4a3e5a3a49b9a66240288e79a8a5e8e31d05587581b00000000000017a9143936403d1a70b32c1e0e6920f9cd99cbc8a8345787401f00000000000017a9146ec4fa5a4e329f9d92ea29b65fa48c6ec1470ffa87282300000000000017a914f66743d56210c98bce8a44742b2ecfa9b7f48fdb87102700000000000017a914555c65f1c355993b46003311931eb08cf457209b870220a2638b51a692e6a8b39e87c431edde4fc1ada3dedaf9665875ac8353af44c50706b3746375685100000000",
155000,
0
).is_err());
// Check that CTV is Processed with a Taproot Spend
verify_test_covenants(
"512024f5fe807bcee7774dc515f0b7ee8d6ae39eefd1b590264c52ff867e22c49419",
"020000000001015f46c58c813808d900ec6ccc2877cfb81459aebc017aa1ff92c3564b602105f90000000000000000000ae80300000000000017a914ce0036ae7d49f06967dd92cc1ffff4a878c457f987d00700000000000017a91406e00c3b362e65e03507a2858d7b6499b668669887b80b00000000000017a9142ee42c65592c59b69bfefbd03781140c67e5232487a00f00000000000017a9146b3df16a1e6651d582ca6598900cb4f2d6c9dfb887881300000000000017a914877d55932d4f38b476d4db27e4efbe159ff0a07187701700000000000017a91441e9dc892e861d252d513d594ba833cd6bc8917087581b00000000000017a914b93075800c693dcc78b0553bf9d1cf879d76a02487401f00000000000017a914e9f0ea3a2cae0ad01114e2ec3502ef08bbc50af487282300000000000017a9149a645b5293bdf8be72cb9d1460bce7d64445cfad87102700000000000017a91451e5d6b2ee24ae128234c92245df3624620ea7d3870222209eb65498bfcd4eb90e61c2c5e323a9c16c8bfd8d53ba649915bcdb572099c12fb321c0b7e0105780185688d998a8f8438aa07637a5799755688ec80175cb26c0406e0200000000",
155000,
0
).unwrap();
// Test OP_CHECKSIGFROMSTACK, fails immediately with sig for wrong data
assert!(verify_test_covenants(
"51206e929e9354a357e9a1254feac061741a11c66508786c66b3b29edc79b9c46e19",
"0200000000010197e5046ea9335a2ce209f677f0d0a303cd266461ff42316f73c53360a92f52a20000000000ffffffff01f04902000000000022512040104c71081b266fdf4008db8b0a1c3291f2e1cb680753936de9b76dac45a6ef0340b5258eeb9df148d499d14b8e23fe5315a230b7f1dee497a04605426ffe068f2e0920c9b63ba28b1c6cea39c0e659af1658825d23e859c5ae773a0be996f1c4744520feadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef204835c505a762f5e55c2e8eda1c05437d973809a0236178510208a6ac3f7632bfcc008721c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000",
155000,
0
).is_err());
// Test OP_CHECKSIGFROMSTACK is processed correctly
verify_test_covenants(
"5120313a784205aef89c9d203c1e4cfacd2e31fa55f42dcd77e5e2db9d0513d50827",
"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f195603403c5a935ce7a3856bc3e75eae403a21ff2e5a9f919c0f6f4d6bf7f58c834c13484882fc6f98587fe48e6945a49c0ca4fc62fb5f641a216ea62ac2dbc0071976833411636865636b73696766726f6d737461636b208fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000",
155000,
0
).unwrap();
// Test OP_CAT fails by concatenating wrong data
assert!(verify_test_covenants(
"5120db5b0df050a3fb4f286c407352c95aad464a963cac194a625918541a649551b1",
"020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff00fdffffff01a086010000000000026a0004036f705f03636174097e066f705f6361748721c11b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f00000000",
155000,
0
).is_err());
// Test OP_CAT is processed correctly
verify_test_covenants(
"51205260c94f5b254b77eabc3918117831e0f29aa01f6e24e55c171a3b99536a790f",
"020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff00fdffffff01a086010000000000026a0004036f705f03636174097e066f705f6361748721c11b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f00000000",
155000,
0
).unwrap();
}
fn verify_test(
spent: &str,
spending: &str,
amount: i64,
input: u32,
) -> Result<(), KernelError> {
let outputs = vec![];
let spent_script_pubkey =
ScriptPubkey::try_from(hex::decode(spent).unwrap().as_slice()).unwrap();
let spending_tx = Transaction::try_from(hex::decode(spending).unwrap().as_slice()).unwrap();
verify(
&spent_script_pubkey,
Some(amount),
&spending_tx,
input,
Some(VERIFY_ALL_PRE_TAPROOT),
&outputs,
)
}
fn verify_test_covenants(
spent: &str,
spending: &str,
amount: i64,
input: u32,
) -> Result<(), KernelError> {
let spent_script_pubkey =
ScriptPubkey::try_from(hex::decode(spent).unwrap().as_slice()).unwrap();
let spending_tx = Transaction::try_from(hex::decode(spending).unwrap().as_slice()).unwrap();
let spent_output = TxOut::new(&spent_script_pubkey, amount);
let outputs = vec![spent_output];
verify(
&spent_script_pubkey,
Some(amount),
&spending_tx,
input,
Some(VERIFY_ALL),
&outputs,
)
}
#[test]
fn test_traits() {
fn is_sync<T: Sync>() {}
fn is_send<T: Send>() {}
is_sync::<ScriptPubkey>();
is_send::<ScriptPubkey>();
is_sync::<ChainParams>(); // compiles only if true
is_send::<ChainParams>();
is_sync::<TxOut>();
is_send::<TxOut>();
is_sync::<Transaction>();
is_send::<Transaction>();
is_sync::<Context>();
is_send::<Context>();
is_sync::<Block>();
is_send::<Block>();
is_sync::<BlockUndo>();
is_send::<BlockUndo>();
is_sync::<ChainstateManager>();
is_send::<ChainstateManager>();
is_sync::<BlockHash>();
is_send::<BlockHash>();
// is_sync::<Rc<u8>>(); // won't compile, kept as a failure case.
// is_send::<Rc<u8>>(); // won't compile, kept as a failure case.
}
}