Skip to content

Commit

Permalink
Merge pull request #20 from rustaceanrob/test-sql
Browse files Browse the repository at this point in the history
test(lib): sql handles regtest reorg
  • Loading branch information
rustaceanrob authored Jun 26, 2024
2 parents 9a54cd2 + 126fa41 commit 6580d8d
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 5 deletions.
6 changes: 3 additions & 3 deletions src/db/sqlite/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ impl HeaderStore for SqliteHeaderDb {
) -> Result<(), DatabaseError> {
let mut write_lock = self.conn.lock().await;
let tx = write_lock.transaction().map_err(|_| DatabaseError::Write)?;
for (h, header) in header_chain {
if h.ge(&height) {
for (new_height, header) in header_chain {
if new_height.ge(&height) {
let hash: String = header.block_hash().to_string();
let version: i32 = header.version.to_consensus();
let prev_hash: String = header.prev_blockhash.as_raw_hash().to_string();
Expand All @@ -155,7 +155,7 @@ impl HeaderStore for SqliteHeaderDb {
tx.execute(
stmt,
params![
height,
new_height,
hash,
version,
prev_hash,
Expand Down
117 changes: 115 additions & 2 deletions tests/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ async fn new_node(addrs: HashSet<ScriptBuf>) -> (Node, Client) {
(node, client)
}

async fn new_node_sql(addrs: HashSet<ScriptBuf>) -> (Node, Client) {
let host = (IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), PORT);
let builder = kyoto::node::builder::NodeBuilder::new(bitcoin::Network::Regtest);
let (node, client) = builder
.add_peers(vec![host])
.add_scripts(addrs)
.build_node()
.await;
(node, client)
}

// This test may be run as much as required without altering Bitcoin Core's database.
#[tokio::test]
async fn test_reorg() {
let rpc_result = initialize_client();
Expand All @@ -53,12 +65,13 @@ async fn test_reorg() {
return;
}
let rpc = rpc_result.unwrap();
// Mine some blocks
let miner = rpc.get_new_address(None, None).unwrap().assume_checked();
rpc.generate_to_address(10, &miner).unwrap();
tokio::time::sleep(Duration::from_secs(1)).await;
let best = rpc.get_best_block_hash().unwrap();
tokio::time::sleep(Duration::from_secs(1)).await;
// Make sure we sync up to the tip under usual conditions.
// Build and run a node
let mut scripts = HashSet::new();
let other = rpc.get_new_address(None, None).unwrap().assume_checked();
scripts.insert(other.into());
Expand All @@ -76,13 +89,15 @@ async fn test_reorg() {
_ => {}
}
}
// Reorganize the blocks
let old_best = best;
let old_height = rpc.get_block_count().unwrap();
rpc.invalidate_block(&best).unwrap();
tokio::time::sleep(Duration::from_secs(2)).await;
rpc.generate_to_address(2, &miner).unwrap();
tokio::time::sleep(Duration::from_secs(2)).await;
let best = rpc.get_best_block_hash().unwrap();
// Make sure the reorg was caught
while let Ok(message) = recv.recv().await {
match message {
kyoto::node::messages::NodeMessage::Dialog(d) => println!("{d}"),
Expand All @@ -104,6 +119,7 @@ async fn test_reorg() {
rpc.stop().unwrap();
}

// This test may be repeated as much as required without altering Bitcoin Core's database.
#[tokio::test]
async fn test_broadcast() {
let rpc_result = initialize_client();
Expand All @@ -112,10 +128,12 @@ async fn test_broadcast() {
println!("Bitcoin Core is not running. Skipping this test...");
return;
}
// Mine some blocks
let rpc = rpc_result.unwrap();
let miner = rpc.get_new_address(None, None).unwrap().assume_checked();
rpc.generate_to_address(105, &miner).unwrap();
tokio::time::sleep(Duration::from_secs(5)).await;
// Send to a random address
let other = rpc.get_new_address(None, None).unwrap().assume_checked();
rpc.send_to_address(
&other.clone(),
Expand All @@ -128,7 +146,9 @@ async fn test_broadcast() {
None,
)
.unwrap();
// Confirm the transaction
rpc.generate_to_address(10, &miner).unwrap();
// Get inputs and outputs for a new transaction
let inputs = rpc
.list_unspent(Some(1), None, Some(&[&other]), None, None)
.unwrap();
Expand All @@ -143,6 +163,7 @@ async fn test_broadcast() {
let burn = rpc.get_new_address(None, None).unwrap().assume_checked();
let mut outs = HashMap::new();
outs.insert(burn.to_string(), Amount::from_sat(1000));
// Create and sign the transaction
let tx = rpc
.create_raw_transaction(&raw_tx_inputs, &outs, None, None)
.unwrap();
Expand All @@ -155,6 +176,7 @@ async fn test_broadcast() {
let (mut node, mut client) = new_node(scripts.clone()).await;
tokio::task::spawn(async move { node.run().await });
let (mut sender, mut recv) = client.split();
// Broadcast the transaction to the network
sender
.broadcast_tx(TxBroadcast::new(
signed.transaction().unwrap(),
Expand Down Expand Up @@ -189,11 +211,11 @@ async fn test_long_chain() {
return;
}
let rpc = rpc_result.unwrap();
// Mine a lot of blocks
let miner = rpc.get_new_address(None, None).unwrap().assume_checked();
rpc.generate_to_address(500, &miner).unwrap();
tokio::time::sleep(Duration::from_secs(15)).await;
let best = rpc.get_best_block_hash().unwrap();
// Make sure we sync up to the tip under usual conditions.
let mut scripts = HashSet::new();
let other = rpc.get_new_address(None, None).unwrap().assume_checked();
scripts.insert(other.into());
Expand All @@ -214,3 +236,94 @@ async fn test_long_chain() {
sender.shutdown().await.unwrap();
rpc.stop().unwrap();
}

// This test requires a clean Bitcoin Core regtest instance or unchange headers from Bitcoin Core since the last test.
#[tokio::test]
async fn test_sql() {
let rpc_result = initialize_client();
// If we can't fetch the genesis block then bitcoind is not running. Just exit.
if let Err(_) = rpc_result {
println!("Bitcoin Core is not running. Skipping this test...");
return;
}
let rpc = rpc_result.unwrap();
// Mine some blocks.
let miner = rpc.get_new_address(None, None).unwrap().assume_checked();
rpc.generate_to_address(10, &miner).unwrap();
tokio::time::sleep(Duration::from_secs(1)).await;
let best = rpc.get_best_block_hash().unwrap();
tokio::time::sleep(Duration::from_secs(1)).await;
let mut scripts = HashSet::new();
let other = rpc.get_new_address(None, None).unwrap().assume_checked();
scripts.insert(other.into());
let (mut node, mut client) = new_node_sql(scripts.clone()).await;
tokio::task::spawn(async move { node.run().await });
let (_, mut recv) = client.split();
while let Ok(message) = recv.recv().await {
match message {
kyoto::node::messages::NodeMessage::Dialog(d) => println!("{d}"),
kyoto::node::messages::NodeMessage::Warning(e) => println!("{e}"),
kyoto::node::messages::NodeMessage::Synced(update) => {
println!("Done");
assert_eq!(update.tip().hash, best);
break;
}
_ => {}
}
}
client.shutdown().await.unwrap();
// Reorganize the blocks
let old_best = best;
let old_height = rpc.get_block_count().unwrap();
rpc.invalidate_block(&best).unwrap();
tokio::time::sleep(Duration::from_secs(2)).await;
rpc.generate_to_address(2, &miner).unwrap();
tokio::time::sleep(Duration::from_secs(2)).await;
let best = rpc.get_best_block_hash().unwrap();
// Spin up the node on a cold start
let (mut node, mut client) = new_node_sql(scripts.clone()).await;
tokio::task::spawn(async move { node.run().await });
let (_, mut recv) = client.split();
// Make sure the reorganization is caught after a cold start
while let Ok(message) = recv.recv().await {
match message {
kyoto::node::messages::NodeMessage::Dialog(d) => println!("{d}"),
kyoto::node::messages::NodeMessage::Warning(e) => println!("{e}"),
kyoto::node::messages::NodeMessage::BlocksDisconnected(blocks) => {
assert_eq!(blocks.len(), 1);
assert_eq!(blocks.first().unwrap().header.block_hash(), old_best);
assert_eq!(old_height as u32, blocks.first().unwrap().height);
}
kyoto::node::messages::NodeMessage::Synced(update) => {
println!("Done");
assert_eq!(update.tip().hash, best);
break;
}
_ => {}
}
}
client.shutdown().await.unwrap();
// Mine more blocks
rpc.generate_to_address(2, &miner).unwrap();
tokio::time::sleep(Duration::from_secs(2)).await;
let best = rpc.get_best_block_hash().unwrap();
// Make sure the node does not have any corrupted headers
let (mut node, mut client) = new_node_sql(scripts.clone()).await;
tokio::task::spawn(async move { node.run().await });
let (_, mut recv) = client.split();
// The node properly syncs after persisting a reorg
while let Ok(message) = recv.recv().await {
match message {
kyoto::node::messages::NodeMessage::Dialog(d) => println!("{d}"),
kyoto::node::messages::NodeMessage::Warning(e) => println!("{e}"),
kyoto::node::messages::NodeMessage::Synced(update) => {
println!("Done");
assert_eq!(update.tip().hash, best);
break;
}
_ => {}
}
}
client.shutdown().await.unwrap();
rpc.stop().unwrap();
}

0 comments on commit 6580d8d

Please sign in to comment.