Skip to content

Commit

Permalink
Refactor test code and introduce TestSyncStore
Browse files Browse the repository at this point in the history
.. which asserts that all `KVStore` implementations operatate
synchronously, i.e., yield identical results given the same inputs.
  • Loading branch information
tnull committed Aug 24, 2023
1 parent 89e97ab commit e1d081c
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 134 deletions.
144 changes: 12 additions & 132 deletions src/test/functional_tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::builder::NodeBuilder;
use crate::io::KVStore;
use crate::test::utils::*;
use crate::test::utils::{expect_event, random_config};
use crate::test::utils::{expect_event, random_config, setup_two_nodes};
use crate::{Error, Event, Node, PaymentDirection, PaymentStatus};

use bitcoin::Amount;
Expand All @@ -11,45 +11,14 @@ use electrsd::ElectrsD;
#[test]
fn channel_full_cycle() {
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
println!("== Node A ==");
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
let config_a = random_config();
let mut builder_a = NodeBuilder::from_config(config_a);
builder_a.set_esplora_server(esplora_url.clone());
let node_a = builder_a.build().unwrap();
node_a.start().unwrap();

println!("\n== Node B ==");
let config_b = random_config();
let mut builder_b = NodeBuilder::from_config(config_b);
builder_b.set_esplora_server(esplora_url);
let node_b = builder_b.build().unwrap();
node_b.start().unwrap();

let (node_a, node_b) = setup_two_nodes(&electrsd, false);
do_channel_full_cycle(node_a, node_b, &bitcoind, &electrsd, false);
}

#[test]
fn channel_full_cycle_0conf() {
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
println!("== Node A ==");
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
let config_a = random_config();
let mut builder_a = NodeBuilder::from_config(config_a);
builder_a.set_esplora_server(esplora_url.clone());
let node_a = builder_a.build().unwrap();
node_a.start().unwrap();

println!("\n== Node B ==");
let mut config_b = random_config();
config_b.trusted_peers_0conf.push(node_a.node_id());

let mut builder_b = NodeBuilder::from_config(config_b);
builder_b.set_esplora_server(esplora_url.clone());
let node_b = builder_b.build().unwrap();

node_b.start().unwrap();

let (node_a, node_b) = setup_two_nodes(&electrsd, true);
do_channel_full_cycle(node_a, node_b, &bitcoind, &electrsd, true)
}

Expand Down Expand Up @@ -271,21 +240,9 @@ fn do_channel_full_cycle<K: KVStore + Sync + Send>(
#[test]
fn channel_open_fails_when_funds_insufficient() {
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
println!("== Node A ==");
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
let config_a = random_config();
let mut builder_a = NodeBuilder::from_config(config_a);
builder_a.set_esplora_server(esplora_url.clone());
let node_a = builder_a.build().unwrap();
node_a.start().unwrap();
let addr_a = node_a.new_onchain_address().unwrap();
let (node_a, node_b) = setup_two_nodes(&electrsd, false);

println!("\n== Node B ==");
let config_b = random_config();
let mut builder_b = NodeBuilder::from_config(config_b);
builder_b.set_esplora_server(esplora_url);
let node_b = builder_b.build().unwrap();
node_b.start().unwrap();
let addr_a = node_a.new_onchain_address().unwrap();
let addr_b = node_b.new_onchain_address().unwrap();

let premine_amount_sat = 100_000;
Expand Down Expand Up @@ -329,26 +286,22 @@ fn connect_to_public_testnet_esplora() {
#[test]
fn start_stop_reinit() {
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
let config = random_config();
let mut builder = NodeBuilder::from_config(config.clone());
builder.set_esplora_server(esplora_url.clone());
let node = builder.build().unwrap();
let node = setup_node(&electrsd, config.clone());

let expected_node_id = node.node_id();
assert_eq!(node.start(), Err(Error::AlreadyRunning));

let funding_address = node.new_onchain_address().unwrap();
let expected_amount = Amount::from_sat(100000);

premine_and_distribute_funds(&bitcoind, &electrsd, vec![funding_address], expected_amount);
assert_eq!(node.total_onchain_balance_sats().unwrap(), 0);

node.start().unwrap();
assert_eq!(node.start(), Err(Error::AlreadyRunning));

node.sync_wallets().unwrap();
assert_eq!(node.spendable_onchain_balance_sats().unwrap(), expected_amount.to_sat());

let log_file_symlink = format!("{}/logs/ldk_node_latest.log", config.storage_dir_path);
let log_file_symlink = format!("{}/logs/ldk_node_latest.log", config.clone().storage_dir_path);
assert!(std::path::Path::new(&log_file_symlink).is_symlink());

node.stop().unwrap();
Expand All @@ -361,66 +314,9 @@ fn start_stop_reinit() {
assert_eq!(node.stop(), Err(Error::NotRunning));
drop(node);

let mut new_builder = NodeBuilder::from_config(config);
new_builder.set_esplora_server(esplora_url);
let reinitialized_node = builder.build().unwrap();
assert_eq!(reinitialized_node.node_id(), expected_node_id);

reinitialized_node.start().unwrap();

assert_eq!(
reinitialized_node.spendable_onchain_balance_sats().unwrap(),
expected_amount.to_sat()
);

reinitialized_node.sync_wallets().unwrap();
assert_eq!(
reinitialized_node.spendable_onchain_balance_sats().unwrap(),
expected_amount.to_sat()
);

reinitialized_node.stop().unwrap();
}

#[test]
fn start_stop_reinit_fs_store() {
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
let config = random_config();
let mut builder = NodeBuilder::from_config(config.clone());
builder.set_esplora_server(esplora_url.clone());
let node = builder.build_with_fs_store().unwrap();
let expected_node_id = node.node_id();

let funding_address = node.new_onchain_address().unwrap();
let expected_amount = Amount::from_sat(100000);

premine_and_distribute_funds(&bitcoind, &electrsd, vec![funding_address], expected_amount);
assert_eq!(node.total_onchain_balance_sats().unwrap(), 0);

node.start().unwrap();
assert_eq!(node.start(), Err(Error::AlreadyRunning));

node.sync_wallets().unwrap();
assert_eq!(node.spendable_onchain_balance_sats().unwrap(), expected_amount.to_sat());

node.stop().unwrap();
assert_eq!(node.stop(), Err(Error::NotRunning));

node.start().unwrap();
assert_eq!(node.start(), Err(Error::AlreadyRunning));

node.stop().unwrap();
assert_eq!(node.stop(), Err(Error::NotRunning));
drop(node);

let mut new_builder = NodeBuilder::from_config(config);
new_builder.set_esplora_server(esplora_url);
let reinitialized_node = builder.build_with_fs_store().unwrap();
let reinitialized_node = setup_node(&electrsd, config);
assert_eq!(reinitialized_node.node_id(), expected_node_id);

reinitialized_node.start().unwrap();

assert_eq!(
reinitialized_node.spendable_onchain_balance_sats().unwrap(),
expected_amount.to_sat()
Expand All @@ -438,20 +334,9 @@ fn start_stop_reinit_fs_store() {
#[test]
fn onchain_spend_receive() {
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
let (node_a, node_b) = setup_two_nodes(&electrsd, false);

let config_a = random_config();
let mut builder_a = NodeBuilder::from_config(config_a);
builder_a.set_esplora_server(esplora_url.clone());
let node_a = builder_a.build().unwrap();
node_a.start().unwrap();
let addr_a = node_a.new_onchain_address().unwrap();

let config_b = random_config();
let mut builder_b = NodeBuilder::from_config(config_b);
builder_b.set_esplora_server(esplora_url);
let node_b = builder_b.build().unwrap();
node_b.start().unwrap();
let addr_b = node_b.new_onchain_address().unwrap();

premine_and_distribute_funds(
Expand Down Expand Up @@ -494,13 +379,8 @@ fn onchain_spend_receive() {
#[test]
fn sign_verify_msg() {
let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd();
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
let config = random_config();
let mut builder = NodeBuilder::from_config(config.clone());
builder.set_esplora_server(esplora_url.clone());
let node = builder.build().unwrap();

node.start().unwrap();
let node = setup_node(&electrsd, config);

// Tests arbitrary message signing and later verification
let msg = "OK computer".as_bytes();
Expand Down
143 changes: 141 additions & 2 deletions src/test/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::io::KVStore;
use crate::Config;
use crate::builder::NodeBuilder;
use crate::io::{FilesystemStore, KVStore, SqliteStore};
use crate::{Config, Node};
use lightning::util::logger::{Level, Logger, Record};
use lightning::util::persist::KVStorePersister;
use lightning::util::ser::Writeable;
Expand Down Expand Up @@ -133,6 +134,118 @@ impl KVStorePersister for TestStore {
}
}

// A `KVStore` impl for testing purposes that wraps all our `KVStore`s and asserts their synchronicity.
pub(crate) struct TestSyncStore {
fs_store: FilesystemStore,
sqlite_store: SqliteStore,
}

impl TestSyncStore {
pub(crate) fn new(dest_dir: PathBuf) -> Self {
let fs_store = FilesystemStore::new(dest_dir.clone());
let sqlite_store = SqliteStore::new(dest_dir);
Self { fs_store, sqlite_store }
}
}

impl KVStore for TestSyncStore {
type Reader = io::Cursor<Vec<u8>>;

fn read(&self, namespace: &str, key: &str) -> std::io::Result<Self::Reader> {
// For now, we only assert `Ok` with the `fs_reader` here, as it's too complicated to track
// the read status of both seperately, however, the `Reader` concept is going away anyways
// at which point we can assert on simply on the returned values of `KVStore::read`.
let fs_res = self.fs_store.read(namespace, key);
let sqlite_res = self.sqlite_store.read(namespace, key);

match sqlite_res {
Ok(read) => {
assert!(fs_res.is_ok());
Ok(read)
}
Err(e) => {
assert!(fs_res.is_err());
assert_eq!(e.kind(), unsafe { fs_res.unwrap_err_unchecked().kind() });
Err(e)
}
}
}

fn write(&self, namespace: &str, key: &str, buf: &[u8]) -> std::io::Result<()> {
let fs_res = self.fs_store.write(namespace, key, buf);
let sqlite_res = self.sqlite_store.write(namespace, key, buf);

assert!(self.list(namespace).unwrap().contains(&key.to_string()));

match fs_res {
Ok(()) => {
assert!(sqlite_res.is_ok());
Ok(())
}
Err(e) => {
assert!(sqlite_res.is_err());
Err(e)
}
}
}

fn remove(&self, namespace: &str, key: &str) -> std::io::Result<()> {
let fs_res = self.fs_store.remove(namespace, key);
let sqlite_res = self.sqlite_store.remove(namespace, key);

match fs_res {
Ok(()) => {
assert!(sqlite_res.is_ok());
Ok(())
}
Err(e) => {
assert!(sqlite_res.is_err());
Err(e)
}
}
}

fn list(&self, namespace: &str) -> std::io::Result<Vec<String>> {
let fs_res = self.fs_store.list(namespace);
let sqlite_res = self.sqlite_store.list(namespace);

match fs_res {
Ok(list) => {
assert_eq!(list, sqlite_res.unwrap());
Ok(list)
}
Err(e) => {
assert!(sqlite_res.is_err());
Err(e)
}
}
}
}

impl KVStorePersister for TestSyncStore {
fn persist<W: Writeable>(&self, prefixed_key: &str, object: &W) -> std::io::Result<()> {
let msg = format!("Could not persist file for key {}.", prefixed_key);
let dest_file = PathBuf::from_str(prefixed_key).map_err(|_| {
lightning::io::Error::new(lightning::io::ErrorKind::InvalidInput, msg.clone())
})?;

let parent_directory = dest_file.parent().ok_or(lightning::io::Error::new(
lightning::io::ErrorKind::InvalidInput,
msg.clone(),
))?;
let namespace = parent_directory.display().to_string();

let dest_without_namespace = dest_file
.strip_prefix(&namespace)
.map_err(|_| lightning::io::Error::new(lightning::io::ErrorKind::InvalidInput, msg))?;
let key = dest_without_namespace.display().to_string();

let data = object.encode();
self.write(&namespace, &key, &data)?;
Ok(())
}
}

// Copied over from upstream LDK
#[allow(dead_code)]
pub struct TestLogger {
Expand Down Expand Up @@ -268,6 +381,32 @@ pub fn setup_bitcoind_and_electrsd() -> (BitcoinD, ElectrsD) {
(bitcoind, electrsd)
}

pub(crate) fn setup_two_nodes(
electrsd: &ElectrsD, allow_0conf: bool,
) -> (Node<TestSyncStore>, Node<TestSyncStore>) {
println!("== Node A ==");
let config_a = random_config();
let node_a = setup_node(electrsd, config_a);

println!("\n== Node B ==");
let mut config_b = random_config();
if allow_0conf {
config_b.trusted_peers_0conf.push(node_a.node_id());
}
let node_b = setup_node(electrsd, config_b);
(node_a, node_b)
}

pub(crate) fn setup_node(electrsd: &ElectrsD, config: Config) -> Node<TestSyncStore> {
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
let mut builder = NodeBuilder::from_config(config.clone());
builder.set_esplora_server(esplora_url.clone());
let test_sync_store = Arc::new(TestSyncStore::new(config.storage_dir_path.into()));
let node = builder.build_with_store(test_sync_store).unwrap();
node.start().unwrap();
node
}

pub fn generate_blocks_and_wait(bitcoind: &BitcoinD, electrsd: &ElectrsD, num: usize) {
let cur_height = bitcoind.client.get_block_count().expect("failed to get current block height");
let address = bitcoind
Expand Down

0 comments on commit e1d081c

Please sign in to comment.