depolymerization

wire gateway for Bitcoin/Ethereum
Log | Files | Refs | Submodules | README | LICENSE

commit 8e866a37cbfbea19b7da6c062fea493efa26acdc
parent ccade06e09b68cf7ee79a91ddf9413760925da14
Author: Antoine A <>
Date:   Wed, 17 Nov 2021 12:36:38 +0100

Network agnostic instrumentation test

Diffstat:
MCargo.lock | 7+++++++
MCargo.toml | 2++
Asrc/bin/test.rs | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib.rs | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.rs | 163+++++--------------------------------------------------------------------------
5 files changed, 230 insertions(+), 153 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -292,6 +292,7 @@ dependencies = [ "bitcoincore-rpc", "criterion", "fastrand", + "owo-colors", "rand", "rustyline", "serde", @@ -524,6 +525,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] +name = "owo-colors" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9ad6d222cdc2351ccabb7af4f68bfaecd601b33c5f10d410ec89d2a273f6fff" + +[[package]] name = "plotters" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -20,6 +20,8 @@ rand = { version = "0.8.4", features = ["getrandom"] } fastrand = "1.5.0" # Serialization library serde = { version = "1.0.130", features = ["derive"] } +# Zero allocation terminal color +owo-colors = "3.1.0" [dev-dependencies] # statistics-driven micro-benchmarks diff --git a/src/bin/test.rs b/src/bin/test.rs @@ -0,0 +1,90 @@ +use core::panic; +use std::panic::AssertUnwindSafe; + +use bitcoincore_rpc::{ + bitcoin::{Address, Amount}, + Client, RpcApi, +}; +use depolymerization::{ + rpc::{common_rpc, dirty_guess_network, last_transaction, wallet_rpc, Network, CLIENT, WIRE}, + utils::rand_key, + ClientExtended, +}; +use owo_colors::OwoColorize; + +/// Instrumentation test tests +/// +/// Rust tests are run in parallel and are supposed to run in isolation. +/// As we test against an bitcoin JSON-RPC server and ordering of transaction is important +/// we want our tests to run sequentially and share commons rpc connections. +pub fn main() { + let network = dirty_guess_network(); + + match network { + Network::MainNet => { + panic!("Do not run tests on the mainnet, you are going to loose money") + } + Network::TestNet => println!("Running on testnet, slow network mining"), + Network::RegTest => println!("Running on regtest, fast manual mining"), + } + let rpc = common_rpc(network).unwrap(); + rpc.load_wallet(&WIRE).ok(); + rpc.load_wallet(&CLIENT).ok(); + let client_rpc = wallet_rpc(network, CLIENT); + let wire_rpc = wallet_rpc(network, WIRE); + let client_addr = client_rpc.get_new_address(None, None).unwrap(); + let wire_addr = wire_rpc.get_new_address(None, None).unwrap(); + println!( + "Initial state:\n{} {}\n{} {}", + WIRE, + wire_rpc.get_balance(None, None).unwrap(), + CLIENT, + client_rpc.get_balance(None, None).unwrap() + ); + + run_test("OpReturn metadata", || { + let msg = "J'aime le chocolat".as_bytes(); + client_rpc + .send_op_return(&wire_addr, Amount::from_sat(4200), msg) + .unwrap(); + next_block(network, &client_rpc, &client_addr); + let last = last_transaction(&wire_rpc).unwrap(); + let (_, decoded) = wire_rpc.get_tx_op_return(&last).unwrap(); + assert_eq!(&msg, &decoded.as_slice()); + }); + + run_test("SegWit metadata", || { + let key = rand_key(); + client_rpc + .send_segwit_key(&wire_addr, Amount::from_sat(4200), &key) + .unwrap(); + next_block(network, &client_rpc, &client_addr); + let last = last_transaction(&wire_rpc).unwrap(); + let (_, decoded) = wire_rpc.get_tx_segwit_key(&last).unwrap(); + assert_eq!(key, decoded); + }); +} + +fn next_block(network: Network, rpc: &Client, address: &Address) { + match network { + Network::TestNet => { + // Manually mine a block + rpc.generate_to_address(1, address).unwrap(); + } + _ => { + // Wait for the next block + rpc.wait_for_new_block(60 * 60 * 1000).unwrap(); + } + } +} + +fn run_test(name: &str, test: impl FnOnce() -> ()) -> bool { + println!("{}", name.cyan()); + let result = std::panic::catch_unwind(AssertUnwindSafe(test)); + if result.is_ok() { + println!("{}", "Ok".green()) + } else { + println!("{}", "Err".red()) + } + return result.is_ok(); +} diff --git a/src/lib.rs b/src/lib.rs @@ -309,6 +309,127 @@ pub mod utils { } } +pub mod rpc { + use std::{path::PathBuf, str::FromStr}; + + use bitcoincore_rpc::{ + bitcoin::{BlockHash, Txid}, + json::{GetTransactionResultDetailCategory, ListTransactionResult}, + Auth, Client, RpcApi, + }; + + pub const CLIENT: &str = "client"; + pub const WIRE: &str = "wire"; + pub const RPC_URL: &str = "http://localhost:18443"; + + /// Bitcoin networks + #[derive(Debug, Clone, Copy)] + pub enum Network { + MainNet, + TestNet, + RegTest, + } + + impl Network { + pub fn dir(&self) -> &'static str { + match self { + Network::MainNet => "main", + Network::TestNet => "testnet3", + Network::RegTest => "regtest", + } + } + } + + pub fn data_dir_path() -> PathBuf { + // https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md#:~:text=By%20default%2C%20the%20configuration%20file,file%3E%20option%20in%20the%20bitcoin. + if cfg!(target_os = "windows") { + PathBuf::from_str(&std::env::var("APPDATA").unwrap()) + .unwrap() + .join("Bitcoin") + } else if cfg!(target_os = "linux") { + PathBuf::from_str(&std::env::var("HOME").unwrap()) + .unwrap() + .join(".bitcoin") + } else if cfg!(target_os = "macos") { + PathBuf::from_str(&std::env::var("HOME").unwrap()) + .unwrap() + .join("Library/Application Support/Bitcoin") + } else { + unimplemented!("Only windows, linux or macos") + } + } + + pub fn network_dir_path(network: Network) -> PathBuf { + data_dir_path().join(network.dir()) + } + + pub fn common_rpc(network: Network) -> bitcoincore_rpc::Result<Client> { + Client::new( + RPC_URL, + Auth::CookieFile(network_dir_path(network).join(".cookie")), + ) + } + + pub fn wallet_rpc(network: Network, wallet: &str) -> Client { + Client::new( + &format!("{}/wallet/{}", RPC_URL, wallet), + Auth::CookieFile(network_dir_path(network).join(".cookie")), + ) + .expect(&format!("Failed to open wallet '{}' client", wallet)) + } + + pub fn last_transaction(rpc: &Client) -> bitcoincore_rpc::Result<Txid> { + Ok(rpc + .list_transactions(None, None, None, None)? + .last() + .unwrap() + .info + .txid) + } + + pub fn received_since( + rpc: &Client, + hash: Option<&BlockHash>, + ) -> bitcoincore_rpc::Result<(Vec<Txid>, BlockHash)> { + let result = rpc.list_since_block(hash, Some(1), None, None)?; + let mut received: Vec<&ListTransactionResult> = result + .transactions + .iter() + .filter(|it| { + it.info.confirmations > 0 + && it.detail.category == GetTransactionResultDetailCategory::Receive + }) + .collect(); + received.sort_unstable_by_key(|it| it.info.time); + received.reverse(); + Ok(( + received.into_iter().map(|it| it.info.txid).collect(), + result.lastblock, + )) + } + + pub fn dirty_guess_network() -> Network { + let result_reg = common_rpc(Network::RegTest).and_then(|rpc| rpc.get_network_info()); + if result_reg.is_ok() { + return Network::RegTest; + } + let result_test = common_rpc(Network::TestNet).and_then(|rpc| rpc.get_network_info()); + if result_test.is_ok() { + return Network::TestNet; + } + + let result_main = common_rpc(Network::MainNet).and_then(|rpc| rpc.get_network_info()); + if result_main.is_ok() { + return Network::MainNet; + } + + unreachable!( + "Failed to connect to any chain\nreg: {:?}\ntest: {:?}\nmain: {:?}", + result_reg, result_main, result_test + ); + } +} + #[cfg(test)] mod test { use crate::{ diff --git a/src/main.rs b/src/main.rs @@ -1,57 +1,13 @@ -use std::{collections::HashSet, path::PathBuf, str::FromStr, time::Duration}; - -use bitcoincore_rpc::{ - bitcoin::{Amount, BlockHash, Transaction, Txid}, - json::{GetTransactionResultDetailCategory, ListSinceBlockResult, ListTransactionResult}, - jsonrpc::serde_json::error::Category, - Auth, Client, RpcApi, +use std::{collections::HashSet, str::FromStr, time::Duration}; + +use bitcoincore_rpc::{bitcoin::Amount, RpcApi}; +use depolymerization::{ + rpc::{ + common_rpc, dirty_guess_network, network_dir_path, received_since, + wallet_rpc, CLIENT, WIRE, + }, + ClientExtended, }; -use depolymerization::{utils::rand_key, ClientExtended}; - -const CLIENT: &str = "client"; -const WIRE: &str = "wire"; -const RPC_URL: &str = "http://localhost:18443"; - -/// Bitcoin networks -#[derive(Debug, Clone, Copy)] -pub enum Network { - MainNet, - TestNet, - RegTest, -} - -impl Network { - pub fn dir(&self) -> &'static str { - match self { - Network::MainNet => "main", - Network::TestNet => "testnet3", - Network::RegTest => "regtest", - } - } -} - -fn data_dir_path() -> PathBuf { - // https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md#:~:text=By%20default%2C%20the%20configuration%20file,file%3E%20option%20in%20the%20bitcoin. - if cfg!(target_os = "windows") { - PathBuf::from_str(&std::env::var("APPDATA").unwrap()) - .unwrap() - .join("Bitcoin") - } else if cfg!(target_os = "linux") { - PathBuf::from_str(&std::env::var("HOME").unwrap()) - .unwrap() - .join(".bitcoin") - } else if cfg!(target_os = "macos") { - PathBuf::from_str(&std::env::var("HOME").unwrap()) - .unwrap() - .join("Library/Application Support/Bitcoin") - } else { - unimplemented!("Only windows, linux or macos") - } -} - -fn network_dir_path(network: Network) -> PathBuf { - data_dir_path().join(network.dir()) -} #[derive(argh::FromArgs)] /// Bitcoin metadata tester @@ -61,11 +17,6 @@ struct Args { } #[derive(argh::FromArgs)] -/// Test library -#[argh(subcommand, name = "test")] -struct TestCmd {} - -#[derive(argh::FromArgs)] /// Mine block for the given wallet #[argh(subcommand, name = "mine")] struct MineCmd { @@ -147,79 +98,12 @@ struct MsgCmd { #[argh(subcommand)] enum Cmd { Mine(MineCmd), - Test(TestCmd), Msg(MsgCmd), } -fn common_rpc(network: Network) -> bitcoincore_rpc::Result<Client> { - Client::new( - RPC_URL, - Auth::CookieFile(network_dir_path(network).join(".cookie")), - ) -} - -fn wallet_rpc(network: Network, wallet: &str) -> Client { - Client::new( - &format!("{}/wallet/{}", RPC_URL, wallet), - Auth::CookieFile(network_dir_path(network).join(".cookie")), - ) - .expect(&format!("Failed to open wallet '{}' client", wallet)) -} - -fn last_transaction(rpc: &Client) -> bitcoincore_rpc::Result<Txid> { - Ok(rpc - .list_transactions(None, None, None, None)? - .last() - .unwrap() - .info - .txid) -} - -fn received_since( - rpc: &Client, - hash: Option<&BlockHash>, -) -> bitcoincore_rpc::Result<(Vec<Txid>, BlockHash)> { - let result = rpc.list_since_block(hash, Some(1), None, None)?; - let mut received: Vec<&ListTransactionResult> = result - .transactions - .iter() - .filter(|it| { - it.info.confirmations > 0 - && it.detail.category == GetTransactionResultDetailCategory::Receive - }) - .collect(); - received.sort_unstable_by_key(|it| it.info.time); - received.reverse(); - Ok(( - received.into_iter().map(|it| it.info.txid).collect(), - result.lastblock, - )) -} - fn main() { // Guess network by trying to connect to a JSON RPC server - let network = (|| { - let result_main = common_rpc(Network::MainNet).and_then(|rpc| rpc.get_network_info()); - if result_main.is_ok() { - println!("Connected to the main chain"); - return Network::MainNet; - } - let result_test = common_rpc(Network::TestNet).and_then(|rpc| rpc.get_network_info()); - if result_test.is_ok() { - println!("Connected to the test chain"); - return Network::TestNet; - } - let result_reg = common_rpc(Network::RegTest).and_then(|rpc| rpc.get_network_info()); - if result_reg.is_ok() { - println!("Connected to the reg chain"); - return Network::RegTest; - } - - unreachable!( - "Failed to connect to any chain\nmain: {:?}\ntest: {:?}\nreg: {:?}", - result_main, result_test, result_reg - ); - })(); + let network = dirty_guess_network(); { let existing_wallets: HashSet<String> = std::fs::read_dir(network_dir_path(network).join("wallets")) @@ -274,33 +158,6 @@ fn main() { } } } - Cmd::Test(_) => { - let client_rpc = wallet_rpc(network, CLIENT); - let wire_rpc = wallet_rpc(network, WIRE); - let client_addr = client_rpc.get_new_address(None, None).unwrap(); - let wire_addr = wire_rpc.get_new_address(None, None).unwrap(); - // OP RETURN test - let msg = "J'aime le chocolat".as_bytes(); - client_rpc - .send_op_return(&wire_addr, Amount::from_sat(4200), msg) - .unwrap(); - client_rpc.generate_to_address(1, &client_addr).unwrap(); - let last = last_transaction(&wire_rpc).unwrap(); - let (_, decoded) = wire_rpc.get_tx_op_return(&last).unwrap(); - assert_eq!(&msg, &decoded.as_slice()); - - // Segwit test - let key = rand_key(); - client_rpc - .send_segwit_key(&wire_addr, Amount::from_sat(4200), &key) - .unwrap(); - client_rpc.generate_to_address(1, &client_addr).unwrap(); - let last = last_transaction(&wire_rpc).unwrap(); - let (_, decoded) = wire_rpc.get_tx_segwit_key(&last).unwrap(); - assert_eq!(key, decoded); - - println!("Test ok"); - } Cmd::Msg(MsgCmd { role, method }) => { println!("Initial state:"); let client_rpc = wallet_rpc(network, CLIENT);