commit 8e866a37cbfbea19b7da6c062fea493efa26acdc
parent ccade06e09b68cf7ee79a91ddf9413760925da14
Author: Antoine A <>
Date: Wed, 17 Nov 2021 12:36:38 +0100
Network agnostic instrumentation test
Diffstat:
| M | Cargo.lock | | | 7 | +++++++ |
| M | Cargo.toml | | | 2 | ++ |
| A | src/bin/test.rs | | | 90 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/lib.rs | | | 121 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/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);