From 257d7313d91a15293f8f9e673dd2f6f1db385cde Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Thu, 2 Dec 2021 12:22:01 +0100 Subject: [PATCH 1/7] PSET integration test with elements_core. blinded and unblinded PSET --- Cargo.toml | 7 +- src/pset/mod.rs | 3 +- .../data}/pset_swap_tutorial.hex | 0 tests/pset.rs | 142 ++++++++++++++++++ 4 files changed, 149 insertions(+), 3 deletions(-) rename {tests_data => tests/data}/pset_swap_tutorial.hex (100%) create mode 100644 tests/pset.rs diff --git a/Cargo.toml b/Cargo.toml index 01f0971f..793c6430 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ documentation = "https://docs.rs/elements/" [features] default = [ "json-contract" ] +integration = [ "elementsd" ] json-contract = [ "serde_json" ] "serde-feature" = [ @@ -34,13 +35,17 @@ serde_json = { version = "<=1.0.44", optional = true } serde = { version = "1.0", features=["derive"], optional = true } +# This should be an optional dev-dependency (only needed for integration tests), +# but dev-dependency cannot be optional, and without optionality older toolchain try to compile it and fails +elementsd = { version="0.3.0", features=["0_21_0","bitcoind_22_0"], optional = true } + [dev-dependencies] rand = "0.6.5" serde_test = "1.0" serde_json = "<=1.0.44" ryu = "<1.0.5" bincode = "1.3" - +base64 = "0.13.0" [[example]] name = "pset_blind_coinjoin" diff --git a/src/pset/mod.rs b/src/pset/mod.rs index cf6434f3..2da73783 100644 --- a/src/pset/mod.rs +++ b/src/pset/mod.rs @@ -654,7 +654,6 @@ mod tests { fn pset_rtt(pset_hex: &str) { let pset: PartiallySignedTransaction = encode::deserialize(&Vec::::from_hex(pset_hex).unwrap()[..]).unwrap(); - assert_eq!(encode::serialize_hex(&pset), pset_hex); } @@ -800,7 +799,7 @@ mod tests { #[test] fn pset_from_elements() { - let pset_str = include_str!("../../tests_data/pset_swap_tutorial.hex"); + let pset_str = include_str!("../../tests/data/pset_swap_tutorial.hex"); let bytes = Vec::::from_hex(pset_str).unwrap(); let pset = encode::deserialize::(&bytes).unwrap(); diff --git a/tests_data/pset_swap_tutorial.hex b/tests/data/pset_swap_tutorial.hex similarity index 100% rename from tests_data/pset_swap_tutorial.hex rename to tests/data/pset_swap_tutorial.hex diff --git a/tests/pset.rs b/tests/pset.rs new file mode 100644 index 00000000..1146633c --- /dev/null +++ b/tests/pset.rs @@ -0,0 +1,142 @@ +extern crate elements; + +#[cfg(feature = "integration")] +extern crate elementsd; + +#[cfg(all(test, feature = "integration"))] +mod tests { + use elements::encode::{deserialize, serialize}; + use elements::pset::PartiallySignedTransaction; + use elementsd::bitcoincore_rpc::jsonrpc::serde_json::{json, Value}; + use elementsd::bitcoincore_rpc::RpcApi; + use elementsd::bitcoind::BitcoinD; + use elementsd::{bitcoind, ElementsD}; + + trait Call { + fn call(&self, cmd: &str, args: &[Value]) -> Value; + fn decode_psbt(&self, psbt: &str) -> Option; + fn get_new_address(&self) -> String; + fn wallet_create_funded_psbt(&self, address: &str) -> String; + fn expected_next(&self, psbt: &str) -> String; + fn wallet_process_psbt(&self, psbt: &str) -> String; + } + + /* + issuance + reissueance + pegin + */ + + #[cfg_attr(feature = "integration", test)] + fn tx_unblinded() { + let (elementsd, _bitcoind) = setup(false); + + let address = elementsd.get_new_address(); + let psbt_base64 = elementsd.wallet_create_funded_psbt(&address); + assert_eq!(elementsd.expected_next(&psbt_base64), "blinder"); + psbt_rtt(&elementsd, &psbt_base64); + } + + #[cfg_attr(feature = "integration", test)] + fn tx_blinded() { + let (elementsd, _bitcoind) = setup(false); + + let address = elementsd.get_new_address(); + let psbt_base64 = elementsd.wallet_create_funded_psbt(&address); + assert_eq!(elementsd.expected_next(&psbt_base64), "blinder"); + let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); + assert_eq!(elementsd.expected_next(&psbt_base64), "finalizer"); + psbt_rtt(&elementsd, &psbt_base64); + } + + fn psbt_rtt(elementsd: &ElementsD, base64: &str) { + let a = elementsd.decode_psbt(&base64).unwrap(); + + let b_psbt = psbt_from_base64(&base64); + let mut b_bytes = serialize(&b_psbt); + let b_base64 = base64::encode(&b_bytes); + let b = elementsd.decode_psbt(&b_base64).unwrap(); + + assert_eq!(a, b); + + let mut tests = 0; + for i in 0..b_bytes.len() { + // ensuring decode prints all data inside psbt, changing all bytes, if the results is still + // decodable it should not be equal to initial value + b_bytes[i] = b_bytes[i].wrapping_add(1); + let base64 = base64::encode(&b_bytes); + if let Some(decoded) = elementsd.decode_psbt(&base64) { + assert_ne!(a, decoded); + tests += 1; + } + b_bytes[i] = b_bytes[i].wrapping_sub(1); + } + assert!(tests > 0) + } + + impl Call for ElementsD { + fn call(&self, cmd: &str, args: &[Value]) -> Value { + self.client().call::(cmd, args).unwrap() + } + + fn decode_psbt(&self, psbt: &str) -> Option { + self.client().call::("decodepsbt", &[psbt.into()]).ok() + } + + fn get_new_address(&self) -> String { + self.call("getnewaddress", &[]) + .as_str() + .unwrap() + .to_string() + } + + fn wallet_create_funded_psbt(&self, address: &str) -> String { + let value = self.call( + "walletcreatefundedpsbt", + &[json!([]), json!([{address.to_string(): "1"}])], + ); + value.get("psbt").unwrap().as_str().unwrap().to_string() + } + + fn expected_next(&self, base64: &str) -> String { + let value = self.call("analyzepsbt", &[base64.into()]); + value.get("next").unwrap().as_str().unwrap().to_string() + } + + fn wallet_process_psbt(&self, base64: &str) -> String { + let value = self.call("walletprocesspsbt", &[base64.into()]); + value.get("psbt").unwrap().as_str().unwrap().to_string() + } + } + + fn psbt_from_base64(base64: &str) -> PartiallySignedTransaction { + let bytes = base64::decode(&base64).unwrap(); + deserialize(&bytes).unwrap() + } + + fn setup(validate_pegin: bool) -> (ElementsD, Option) { + let mut bitcoind = None; + if validate_pegin { + let bitcoind_exe = bitcoind::exe_path().unwrap(); + let bitcoind_conf = bitcoind::Conf::default(); + bitcoind = Some(bitcoind::BitcoinD::with_conf(&bitcoind_exe, &bitcoind_conf).unwrap()); + } + + let conf = elementsd::Conf::new(bitcoind.as_ref()); + + let elementsd = ElementsD::with_conf(elementsd::exe_path().unwrap(), &conf).unwrap(); + + let create = elementsd.call("createwallet", &["wallet".into()]); + assert_eq!(create.get("name").unwrap(), "wallet"); + + let rescan = elementsd.call("rescanblockchain", &[]); + assert_eq!(rescan.get("stop_height").unwrap(), 0); + + let balances = elementsd.call("getbalances", &[]); + let mine = balances.get("mine").unwrap(); + let trusted = mine.get("trusted").unwrap(); + assert_eq!(trusted.get("bitcoin").unwrap(), 21.0); + + (elementsd, bitcoind) + } +} \ No newline at end of file From 14c65a4fcc348c9426f81ba0668db57f1c63540c Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Fri, 3 Dec 2021 14:52:15 +0100 Subject: [PATCH 2/7] Include integration test in CI --- .github/workflows/rust.yml | 2 ++ contrib/test.sh | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e985c78d..be46460b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -7,11 +7,13 @@ jobs: name: Tests runs-on: ubuntu-latest strategy: + fail-fast: false matrix: include: - rust: stable env: DO_FUZZ: true + DO_INTEGRATION: true - rust: beta env: DUMMY: true diff --git a/contrib/test.sh b/contrib/test.sh index cddb21cd..1e187873 100755 --- a/contrib/test.sh +++ b/contrib/test.sh @@ -32,4 +32,12 @@ then cargo test --verbose ./travis-fuzz.sh ) -fi \ No newline at end of file +fi + +# Do integration test if told to +if [ "$DO_INTEGRATION" = true ] +then + ( + cargo test --features integration + ) +fi From fcb34787f667e911525177c3b030002f3236a39b Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Fri, 3 Dec 2021 16:47:41 +0100 Subject: [PATCH 3/7] remove serde_json semver requirement --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 793c6430..a1576f41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ slip21 = "0.2.0" bitcoin_hashes = "0.10.0" # Used for ContractHash::from_json_contract. -serde_json = { version = "<=1.0.44", optional = true } +serde_json = { version = "1.0", optional = true } serde = { version = "1.0", features=["derive"], optional = true } @@ -42,7 +42,7 @@ elementsd = { version="0.3.0", features=["0_21_0","bitcoind_22_0"], optional = t [dev-dependencies] rand = "0.6.5" serde_test = "1.0" -serde_json = "<=1.0.44" +serde_json = "1.0" ryu = "<1.0.5" bincode = "1.3" base64 = "0.13.0" From e352e8196dd455a80e9d7799d5ce20c3d963d69c Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Mon, 6 Dec 2021 08:28:17 +0100 Subject: [PATCH 4/7] add issuance to pset integration tests --- tests/pset.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/tests/pset.rs b/tests/pset.rs index 1146633c..9b208b09 100644 --- a/tests/pset.rs +++ b/tests/pset.rs @@ -5,8 +5,12 @@ extern crate elementsd; #[cfg(all(test, feature = "integration"))] mod tests { + use elements::bitcoin::hashes::hex::FromHex; + use elements::bitcoin::hashes::Hash; use elements::encode::{deserialize, serialize}; use elements::pset::PartiallySignedTransaction; + use elements::Txid; + use elements::{AssetId, ContractHash, OutPoint}; use elementsd::bitcoincore_rpc::jsonrpc::serde_json::{json, Value}; use elementsd::bitcoincore_rpc::RpcApi; use elementsd::bitcoind::BitcoinD; @@ -15,15 +19,16 @@ mod tests { trait Call { fn call(&self, cmd: &str, args: &[Value]) -> Value; fn decode_psbt(&self, psbt: &str) -> Option; + fn analyze_psbt(&self, psbt: &str) -> Value; fn get_new_address(&self) -> String; fn wallet_create_funded_psbt(&self, address: &str) -> String; fn expected_next(&self, psbt: &str) -> String; fn wallet_process_psbt(&self, psbt: &str) -> String; + fn finalize_psbt(&self, psbt: &str) -> String; + fn get_first_prevout(&self) -> OutPoint; } /* - issuance - reissueance pegin */ @@ -49,6 +54,51 @@ mod tests { psbt_rtt(&elementsd, &psbt_base64); } + #[cfg_attr(feature = "integration", test)] + fn tx_issuance() { + let (elementsd, _bitcoind) = setup(false); + + let address_asset = elementsd.get_new_address(); + let address_reissuance = elementsd.get_new_address(); + let address_lbtc = elementsd.get_new_address(); + let prevout = elementsd.get_first_prevout(); + + let contract_hash = ContractHash::from_inner([0u8; 32]); + let entropy = AssetId::generate_asset_entropy(prevout, contract_hash); + let asset_id = AssetId::from_entropy(entropy.clone()); + assert_eq!( + asset_id.to_string(), + "78b4920005fa0156ae3779129338bc2707e4d07cf8a0c2593583e3c1da3bb58c" + ); + let reissuance_id = AssetId::reissuance_token_from_entropy(entropy, true); + assert_eq!( + reissuance_id.to_string(), + "78b4920005fa0156ae3779129338bc2707e4d07cf8a0c2593583e3c1da3bb58c" + ); + + let value = elementsd.call( + "createpsbt", + &[ + json!([{ "txid": prevout.txid, "vout": prevout.vout, "issuance_amount": 1000, "issuance_tokens": 1}]), + json!([ + {address_asset: "1000", "asset": asset_id.to_string(), "blinder_index": 0}, + {address_reissuance: "1", "asset": reissuance_id.to_string(), "blinder_index": 0}, + {address_lbtc: "20.9", "blinder_index": 0}, + {"fee": "0.1" } + ]), + 0.into(), + false.into(), + ], + ); + let psbt_base64 = value.as_str().unwrap().to_string(); + let value = elementsd.analyze_psbt(&psbt_base64); + + assert_eq!(elementsd.expected_next(&psbt_base64), "updater"); + let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); + assert_eq!(elementsd.expected_next(&psbt_base64), "finalizer"); + psbt_rtt(&elementsd, &psbt_base64); + } + fn psbt_rtt(elementsd: &ElementsD, base64: &str) { let a = elementsd.decode_psbt(&base64).unwrap(); @@ -80,7 +130,13 @@ mod tests { } fn decode_psbt(&self, psbt: &str) -> Option { - self.client().call::("decodepsbt", &[psbt.into()]).ok() + self.client() + .call::("decodepsbt", &[psbt.into()]) + .ok() + } + + fn analyze_psbt(&self, psbt: &str) -> Value { + self.call("decodepsbt", &[psbt.into()]) } fn get_new_address(&self) -> String { @@ -107,6 +163,20 @@ mod tests { let value = self.call("walletprocesspsbt", &[base64.into()]); value.get("psbt").unwrap().as_str().unwrap().to_string() } + + fn finalize_psbt(&self, base64: &str) -> String { + let value = self.call("finalizepsbt", &[base64.into()]); + value.get("hex").unwrap().as_str().unwrap().to_string() + } + + fn get_first_prevout(&self) -> OutPoint { + let value = self.call("listunspent", &[]); + let first = value.get(0).unwrap(); + let txid = first.get("txid").unwrap().as_str().unwrap(); + let vout = first.get("vout").unwrap().as_u64().unwrap(); + + OutPoint::new(Txid::from_hex(txid).unwrap(), vout as u32) + } } fn psbt_from_base64(base64: &str) -> PartiallySignedTransaction { @@ -139,4 +209,4 @@ mod tests { (elementsd, bitcoind) } -} \ No newline at end of file +} From b9578efb8c81bba1a127f50a7ff3c42ca6c7c222 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Mon, 6 Dec 2021 11:22:33 +0100 Subject: [PATCH 5/7] add pegin pset in integration tests --- tests/pset.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 8 deletions(-) diff --git a/tests/pset.rs b/tests/pset.rs index 9b208b09..3ea099ac 100644 --- a/tests/pset.rs +++ b/tests/pset.rs @@ -1,10 +1,14 @@ extern crate elements; +extern crate bitcoin; #[cfg(feature = "integration")] extern crate elementsd; +extern crate rand; #[cfg(all(test, feature = "integration"))] mod tests { + use bitcoin::hashes::hex::ToHex; + use bitcoin::{Address, Amount}; use elements::bitcoin::hashes::hex::FromHex; use elements::bitcoin::hashes::Hash; use elements::encode::{deserialize, serialize}; @@ -15,23 +19,24 @@ mod tests { use elementsd::bitcoincore_rpc::RpcApi; use elementsd::bitcoind::BitcoinD; use elementsd::{bitcoind, ElementsD}; + use rand::distributions::{Distribution, Uniform}; + use std::str::FromStr; trait Call { fn call(&self, cmd: &str, args: &[Value]) -> Value; fn decode_psbt(&self, psbt: &str) -> Option; fn analyze_psbt(&self, psbt: &str) -> Value; fn get_new_address(&self) -> String; + fn get_pegin_address(&self) -> (String, String); fn wallet_create_funded_psbt(&self, address: &str) -> String; fn expected_next(&self, psbt: &str) -> String; fn wallet_process_psbt(&self, psbt: &str) -> String; fn finalize_psbt(&self, psbt: &str) -> String; fn get_first_prevout(&self) -> OutPoint; + fn generate(&self, blocks: u32); + fn get_balances(&self) -> Value; } - /* - pegin - */ - #[cfg_attr(feature = "integration", test)] fn tx_unblinded() { let (elementsd, _bitcoind) = setup(false); @@ -73,7 +78,7 @@ mod tests { let reissuance_id = AssetId::reissuance_token_from_entropy(entropy, true); assert_eq!( reissuance_id.to_string(), - "78b4920005fa0156ae3779129338bc2707e4d07cf8a0c2593583e3c1da3bb58c" + "7655162a372496330d25abc9f9f99668fc9293c5ab4548189f054d20cb3d02fb" ); let value = elementsd.call( @@ -99,6 +104,76 @@ mod tests { psbt_rtt(&elementsd, &psbt_base64); } + #[cfg_attr(feature = "integration", test)] + fn tx_pegin() { + let (elementsd, bitcoind) = setup(true); + let bitcoind = bitcoind.unwrap(); + let mainchain_address = bitcoind.client.get_new_address(None, None).unwrap(); + let address_lbtc = elementsd.get_new_address(); + bitcoind + .client + .generate_to_address(101, &mainchain_address) + .unwrap(); + let (pegin_address, claim_script) = elementsd.get_pegin_address(); + let txid = bitcoind + .client + .send_to_address( + &Address::from_str(&pegin_address).unwrap(), + Amount::from_sat(100_000_000), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + let tx = bitcoind.client.get_raw_transaction(&txid, None).unwrap(); + let tx_bytes = serialize(&tx); + let vout = tx + .output + .iter() + .position(|o| { + Address::from_script(&o.script_pubkey, bitcoin::Network::Regtest) + .unwrap() + .to_string() + == pegin_address + }) + .unwrap(); + + bitcoind + .client + .generate_to_address(101, &mainchain_address) + .unwrap(); + let proof = bitcoind.client.get_tx_out_proof(&[txid], None).unwrap(); + elementsd.generate(2); + println!("{:#}", elementsd.get_balances()); + let prevout = elementsd.get_first_prevout(); + let inputs = json!([ {"txid":txid, "vout": vout,"pegin_bitcoin_tx": tx_bytes.to_hex(), "pegin_txout_proof": proof.to_hex(), "pegin_claim_script": claim_script } ]); + println!("{:#}", inputs); + + // "pegin_bitcoin_tx": "hex", (string, required) The raw bitcoin transaction (in hex) depositing bitcoin to the mainchain_address generated by getpeginaddress + // "pegin_txout_proof": "hex", (string, required) A rawtxoutproof (in hex) generated by the mainchain daemon's `gettxoutproof` containing a proof of only bitcoin_tx + // "pegin_claim_script": "hex", (string, required) The witness program generated by getpeginaddress. + let value = elementsd.call( + "createpsbt", + &[ + inputs, + json!([ + {address_lbtc: "0.9", "blinder_index": 0}, + {"fee": "0.1" } + ]), + 0.into(), + false.into(), + ], + ); + let psbt_base64 = value.as_str().unwrap().to_string(); + assert_eq!(elementsd.expected_next(&psbt_base64), "updater"); + let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); + assert_eq!(elementsd.expected_next(&psbt_base64), "extractor"); + psbt_rtt(&elementsd, &psbt_base64); + } + fn psbt_rtt(elementsd: &ElementsD, base64: &str) { let a = elementsd.decode_psbt(&base64).unwrap(); @@ -109,10 +184,13 @@ mod tests { assert_eq!(a, b); + let mut rng = rand::thread_rng(); + let die = Uniform::from(0..b_bytes.len()); let mut tests = 0; - for i in 0..b_bytes.len() { - // ensuring decode prints all data inside psbt, changing all bytes, if the results is still - // decodable it should not be equal to initial value + for _ in 0..100 { + let i = die.sample(&mut rng); + // ensuring decode prints all data inside psbt, randomly changing a byte, + // if the results is still decodable it should not be equal to initial value b_bytes[i] = b_bytes[i].wrapping_add(1); let base64 = base64::encode(&b_bytes); if let Some(decoded) = elementsd.decode_psbt(&base64) { @@ -146,6 +224,29 @@ mod tests { .to_string() } + fn get_pegin_address(&self) -> (String, String) { + let value = self.call("getpeginaddress", &[]); + ( + value + .get("mainchain_address") + .unwrap() + .as_str() + .unwrap() + .to_string(), + value + .get("claim_script") + .unwrap() + .as_str() + .unwrap() + .to_string(), + ) + } + + fn generate(&self, blocks: u32) { + let address = self.get_new_address(); + let _value = self.call("generatetoaddress", &[blocks.into(), address.into()]); + } + fn wallet_create_funded_psbt(&self, address: &str) -> String { let value = self.call( "walletcreatefundedpsbt", @@ -177,6 +278,9 @@ mod tests { OutPoint::new(Txid::from_hex(txid).unwrap(), vout as u32) } + fn get_balances(&self) -> Value { + self.call("getbalances", &[]) + } } fn psbt_from_base64(base64: &str) -> PartiallySignedTransaction { From c901aacda27b51a0488b35ec78ede751ae2e93d7 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Tue, 7 Dec 2021 09:19:38 +0100 Subject: [PATCH 6/7] add final testmempoolaccept --- tests/pset.rs | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/tests/pset.rs b/tests/pset.rs index 3ea099ac..ee5f17c8 100644 --- a/tests/pset.rs +++ b/tests/pset.rs @@ -32,6 +32,7 @@ mod tests { fn expected_next(&self, psbt: &str) -> String; fn wallet_process_psbt(&self, psbt: &str) -> String; fn finalize_psbt(&self, psbt: &str) -> String; + fn test_mempool_accept(&self, hex: &str) -> bool; fn get_first_prevout(&self) -> OutPoint; fn generate(&self, blocks: u32); fn get_balances(&self) -> Value; @@ -57,6 +58,9 @@ mod tests { let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); assert_eq!(elementsd.expected_next(&psbt_base64), "finalizer"); psbt_rtt(&elementsd, &psbt_base64); + + let tx_hex = elementsd.finalize_psbt(&rtt(&psbt_base64)); + assert!(elementsd.test_mempool_accept(&tx_hex)); } #[cfg_attr(feature = "integration", test)] @@ -96,12 +100,14 @@ mod tests { ], ); let psbt_base64 = value.as_str().unwrap().to_string(); - let value = elementsd.analyze_psbt(&psbt_base64); assert_eq!(elementsd.expected_next(&psbt_base64), "updater"); let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); assert_eq!(elementsd.expected_next(&psbt_base64), "finalizer"); psbt_rtt(&elementsd, &psbt_base64); + + let tx_hex = elementsd.finalize_psbt(&rtt(&psbt_base64)); + assert!(elementsd.test_mempool_accept(&tx_hex)); } #[cfg_attr(feature = "integration", test)] @@ -147,14 +153,7 @@ mod tests { .unwrap(); let proof = bitcoind.client.get_tx_out_proof(&[txid], None).unwrap(); elementsd.generate(2); - println!("{:#}", elementsd.get_balances()); - let prevout = elementsd.get_first_prevout(); let inputs = json!([ {"txid":txid, "vout": vout,"pegin_bitcoin_tx": tx_bytes.to_hex(), "pegin_txout_proof": proof.to_hex(), "pegin_claim_script": claim_script } ]); - println!("{:#}", inputs); - - // "pegin_bitcoin_tx": "hex", (string, required) The raw bitcoin transaction (in hex) depositing bitcoin to the mainchain_address generated by getpeginaddress - // "pegin_txout_proof": "hex", (string, required) A rawtxoutproof (in hex) generated by the mainchain daemon's `gettxoutproof` containing a proof of only bitcoin_tx - // "pegin_claim_script": "hex", (string, required) The witness program generated by getpeginaddress. let value = elementsd.call( "createpsbt", &[ @@ -171,7 +170,16 @@ mod tests { assert_eq!(elementsd.expected_next(&psbt_base64), "updater"); let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); assert_eq!(elementsd.expected_next(&psbt_base64), "extractor"); - psbt_rtt(&elementsd, &psbt_base64); + + // TODO this fails because elements decodepsbt is not printing TxOut::asset (PSET_IN_WITNESS_UTXO) + //psbt_rtt(&elementsd, &psbt_base64); + + let tx_hex = elementsd.finalize_psbt(&rtt(&psbt_base64)); + assert!(elementsd.test_mempool_accept(&tx_hex)); + } + + fn rtt(base64: &str) -> String { + base64::encode(serialize(&psbt_from_base64(&base64))) } fn psbt_rtt(elementsd: &ElementsD, base64: &str) { @@ -187,14 +195,14 @@ mod tests { let mut rng = rand::thread_rng(); let die = Uniform::from(0..b_bytes.len()); let mut tests = 0; - for _ in 0..100 { + for _ in 0..1_000 { let i = die.sample(&mut rng); // ensuring decode prints all data inside psbt, randomly changing a byte, // if the results is still decodable it should not be equal to initial value b_bytes[i] = b_bytes[i].wrapping_add(1); let base64 = base64::encode(&b_bytes); if let Some(decoded) = elementsd.decode_psbt(&base64) { - assert_ne!(a, decoded); + assert_ne!(a, decoded, "{} with changed byte {}", b_bytes.to_hex(), i); tests += 1; } b_bytes[i] = b_bytes[i].wrapping_sub(1); @@ -281,6 +289,17 @@ mod tests { fn get_balances(&self) -> Value { self.call("getbalances", &[]) } + + fn test_mempool_accept(&self, hex: &str) -> bool { + let result = self.call("testmempoolaccept", &[json!([hex])]); + result + .get(0) + .unwrap() + .get("allowed") + .unwrap() + .as_bool() + .unwrap() + } } fn psbt_from_base64(base64: &str) -> PartiallySignedTransaction { From 682eb8a5bc972e28ccea8997ec582aa2d28769a1 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Thu, 9 Dec 2021 18:34:43 +0100 Subject: [PATCH 7/7] address review feedback --- tests/pset.rs | 538 ++++++++++++++++++++++++-------------------------- 1 file changed, 263 insertions(+), 275 deletions(-) diff --git a/tests/pset.rs b/tests/pset.rs index ee5f17c8..6f256cfc 100644 --- a/tests/pset.rs +++ b/tests/pset.rs @@ -1,3 +1,5 @@ +#![cfg(all(test, feature = "integration"))] + extern crate elements; extern crate bitcoin; @@ -5,87 +7,77 @@ extern crate bitcoin; extern crate elementsd; extern crate rand; -#[cfg(all(test, feature = "integration"))] -mod tests { - use bitcoin::hashes::hex::ToHex; - use bitcoin::{Address, Amount}; - use elements::bitcoin::hashes::hex::FromHex; - use elements::bitcoin::hashes::Hash; - use elements::encode::{deserialize, serialize}; - use elements::pset::PartiallySignedTransaction; - use elements::Txid; - use elements::{AssetId, ContractHash, OutPoint}; - use elementsd::bitcoincore_rpc::jsonrpc::serde_json::{json, Value}; - use elementsd::bitcoincore_rpc::RpcApi; - use elementsd::bitcoind::BitcoinD; - use elementsd::{bitcoind, ElementsD}; - use rand::distributions::{Distribution, Uniform}; - use std::str::FromStr; - - trait Call { - fn call(&self, cmd: &str, args: &[Value]) -> Value; - fn decode_psbt(&self, psbt: &str) -> Option; - fn analyze_psbt(&self, psbt: &str) -> Value; - fn get_new_address(&self) -> String; - fn get_pegin_address(&self) -> (String, String); - fn wallet_create_funded_psbt(&self, address: &str) -> String; - fn expected_next(&self, psbt: &str) -> String; - fn wallet_process_psbt(&self, psbt: &str) -> String; - fn finalize_psbt(&self, psbt: &str) -> String; - fn test_mempool_accept(&self, hex: &str) -> bool; - fn get_first_prevout(&self) -> OutPoint; - fn generate(&self, blocks: u32); - fn get_balances(&self) -> Value; - } +use bitcoin::hashes::hex::ToHex; +use bitcoin::{Address, Amount}; +use elements::bitcoin::hashes::hex::FromHex; +use elements::bitcoin::hashes::Hash; +use elements::encode::{deserialize, serialize}; +use elements::pset::PartiallySignedTransaction; +use elements::Txid; +use elements::{AssetId, ContractHash, OutPoint}; +use elementsd::bitcoincore_rpc::jsonrpc::serde_json::{json, Value}; +use elementsd::bitcoincore_rpc::RpcApi; +use elementsd::bitcoind::BitcoinD; +use elementsd::{bitcoind, ElementsD}; +use rand::distributions::{Distribution, Uniform}; +use std::str::FromStr; + +trait Call { + fn call(&self, cmd: &str, args: &[Value]) -> Value; + fn decode_psbt(&self, psbt: &str) -> Option; + fn analyze_psbt(&self, psbt: &str) -> Value; + fn get_new_address(&self) -> String; + fn get_pegin_address(&self) -> (String, String); + fn wallet_create_funded_psbt(&self, address: &str) -> String; + fn expected_next(&self, psbt: &str) -> String; + fn wallet_process_psbt(&self, psbt: &str) -> String; + fn finalize_psbt(&self, psbt: &str) -> String; + fn test_mempool_accept(&self, hex: &str) -> bool; + fn get_first_prevout(&self) -> OutPoint; + fn generate(&self, blocks: u32); + fn get_balances(&self) -> Value; +} - #[cfg_attr(feature = "integration", test)] - fn tx_unblinded() { - let (elementsd, _bitcoind) = setup(false); +#[test] +fn tx_unblinded() { + let (elementsd, _bitcoind) = setup(false); - let address = elementsd.get_new_address(); - let psbt_base64 = elementsd.wallet_create_funded_psbt(&address); - assert_eq!(elementsd.expected_next(&psbt_base64), "blinder"); - psbt_rtt(&elementsd, &psbt_base64); - } + let address = elementsd.get_new_address(); + let psbt_base64 = elementsd.wallet_create_funded_psbt(&address); + assert_eq!(elementsd.expected_next(&psbt_base64), "blinder"); + psbt_rtt(&elementsd, &psbt_base64); +} - #[cfg_attr(feature = "integration", test)] - fn tx_blinded() { - let (elementsd, _bitcoind) = setup(false); +#[test] +fn tx_blinded() { + let (elementsd, _bitcoind) = setup(false); - let address = elementsd.get_new_address(); - let psbt_base64 = elementsd.wallet_create_funded_psbt(&address); - assert_eq!(elementsd.expected_next(&psbt_base64), "blinder"); - let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); - assert_eq!(elementsd.expected_next(&psbt_base64), "finalizer"); - psbt_rtt(&elementsd, &psbt_base64); + let address = elementsd.get_new_address(); + let psbt_base64 = elementsd.wallet_create_funded_psbt(&address); + assert_eq!(elementsd.expected_next(&psbt_base64), "blinder"); + let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); + assert_eq!(elementsd.expected_next(&psbt_base64), "finalizer"); + psbt_rtt(&elementsd, &psbt_base64); - let tx_hex = elementsd.finalize_psbt(&rtt(&psbt_base64)); - assert!(elementsd.test_mempool_accept(&tx_hex)); - } + let tx_hex = elementsd.finalize_psbt(&rtt(&psbt_base64)); + assert!(elementsd.test_mempool_accept(&tx_hex)); +} - #[cfg_attr(feature = "integration", test)] - fn tx_issuance() { - let (elementsd, _bitcoind) = setup(false); - - let address_asset = elementsd.get_new_address(); - let address_reissuance = elementsd.get_new_address(); - let address_lbtc = elementsd.get_new_address(); - let prevout = elementsd.get_first_prevout(); - - let contract_hash = ContractHash::from_inner([0u8; 32]); - let entropy = AssetId::generate_asset_entropy(prevout, contract_hash); - let asset_id = AssetId::from_entropy(entropy.clone()); - assert_eq!( - asset_id.to_string(), - "78b4920005fa0156ae3779129338bc2707e4d07cf8a0c2593583e3c1da3bb58c" - ); - let reissuance_id = AssetId::reissuance_token_from_entropy(entropy, true); - assert_eq!( - reissuance_id.to_string(), - "7655162a372496330d25abc9f9f99668fc9293c5ab4548189f054d20cb3d02fb" - ); +#[test] +fn tx_issuance() { + let (elementsd, _bitcoind) = setup(false); - let value = elementsd.call( + let address_asset = elementsd.get_new_address(); + let address_reissuance = elementsd.get_new_address(); + let address_lbtc = elementsd.get_new_address(); + let prevout = elementsd.get_first_prevout(); + + let contract_hash = ContractHash::from_inner([0u8; 32]); + let entropy = AssetId::generate_asset_entropy(prevout, contract_hash); + let asset_id = AssetId::from_entropy(entropy.clone()); + let reissuance_id = AssetId::reissuance_token_from_entropy(entropy, true); + + let value = elementsd.call( "createpsbt", &[ json!([{ "txid": prevout.txid, "vout": prevout.vout, "issuance_amount": 1000, "issuance_tokens": 1}]), @@ -99,237 +91,233 @@ mod tests { false.into(), ], ); - let psbt_base64 = value.as_str().unwrap().to_string(); + let psbt_base64 = value.as_str().unwrap().to_string(); - assert_eq!(elementsd.expected_next(&psbt_base64), "updater"); - let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); - assert_eq!(elementsd.expected_next(&psbt_base64), "finalizer"); - psbt_rtt(&elementsd, &psbt_base64); + assert_eq!(elementsd.expected_next(&psbt_base64), "updater"); + let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); + assert_eq!(elementsd.expected_next(&psbt_base64), "finalizer"); + psbt_rtt(&elementsd, &psbt_base64); - let tx_hex = elementsd.finalize_psbt(&rtt(&psbt_base64)); - assert!(elementsd.test_mempool_accept(&tx_hex)); - } + let tx_hex = elementsd.finalize_psbt(&rtt(&psbt_base64)); + assert!(elementsd.test_mempool_accept(&tx_hex)); +} - #[cfg_attr(feature = "integration", test)] - fn tx_pegin() { - let (elementsd, bitcoind) = setup(true); - let bitcoind = bitcoind.unwrap(); - let mainchain_address = bitcoind.client.get_new_address(None, None).unwrap(); - let address_lbtc = elementsd.get_new_address(); - bitcoind - .client - .generate_to_address(101, &mainchain_address) - .unwrap(); - let (pegin_address, claim_script) = elementsd.get_pegin_address(); - let txid = bitcoind - .client - .send_to_address( - &Address::from_str(&pegin_address).unwrap(), - Amount::from_sat(100_000_000), - None, - None, - None, - None, - None, - None, - ) - .unwrap(); - let tx = bitcoind.client.get_raw_transaction(&txid, None).unwrap(); - let tx_bytes = serialize(&tx); - let vout = tx - .output - .iter() - .position(|o| { - Address::from_script(&o.script_pubkey, bitcoin::Network::Regtest) - .unwrap() - .to_string() - == pegin_address - }) - .unwrap(); - - bitcoind - .client - .generate_to_address(101, &mainchain_address) - .unwrap(); - let proof = bitcoind.client.get_tx_out_proof(&[txid], None).unwrap(); - elementsd.generate(2); - let inputs = json!([ {"txid":txid, "vout": vout,"pegin_bitcoin_tx": tx_bytes.to_hex(), "pegin_txout_proof": proof.to_hex(), "pegin_claim_script": claim_script } ]); - let value = elementsd.call( - "createpsbt", - &[ - inputs, - json!([ - {address_lbtc: "0.9", "blinder_index": 0}, - {"fee": "0.1" } - ]), - 0.into(), - false.into(), - ], - ); - let psbt_base64 = value.as_str().unwrap().to_string(); - assert_eq!(elementsd.expected_next(&psbt_base64), "updater"); - let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); - assert_eq!(elementsd.expected_next(&psbt_base64), "extractor"); +#[test] +fn tx_pegin() { + let (elementsd, bitcoind) = setup(true); + let bitcoind = bitcoind.unwrap(); + let mainchain_address = bitcoind.client.get_new_address(None, None).unwrap(); + let address_lbtc = elementsd.get_new_address(); + bitcoind + .client + .generate_to_address(101, &mainchain_address) + .unwrap(); + let (pegin_address, claim_script) = elementsd.get_pegin_address(); + let txid = bitcoind + .client + .send_to_address( + &Address::from_str(&pegin_address).unwrap(), + Amount::from_sat(100_000_000), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + let tx = bitcoind.client.get_raw_transaction(&txid, None).unwrap(); + let tx_bytes = serialize(&tx); + let vout = tx + .output + .iter() + .position(|o| { + Address::from_script(&o.script_pubkey, bitcoin::Network::Regtest) + .unwrap() + .to_string() + == pegin_address + }) + .unwrap(); + + bitcoind + .client + .generate_to_address(101, &mainchain_address) + .unwrap(); + let proof = bitcoind.client.get_tx_out_proof(&[txid], None).unwrap(); + elementsd.generate(2); + let inputs = json!([ {"txid":txid, "vout": vout,"pegin_bitcoin_tx": tx_bytes.to_hex(), "pegin_txout_proof": proof.to_hex(), "pegin_claim_script": claim_script } ]); + let value = elementsd.call( + "createpsbt", + &[ + inputs, + json!([ + {address_lbtc: "0.9", "blinder_index": 0}, + {"fee": "0.1" } + ]), + 0.into(), + false.into(), + ], + ); + let psbt_base64 = value.as_str().unwrap().to_string(); + assert_eq!(elementsd.expected_next(&psbt_base64), "updater"); + let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); + assert_eq!(elementsd.expected_next(&psbt_base64), "extractor"); + + // TODO this fails because elements decodepsbt is not printing TxOut::asset (PSET_IN_WITNESS_UTXO) + //psbt_rtt(&elementsd, &psbt_base64); + + let tx_hex = elementsd.finalize_psbt(&rtt(&psbt_base64)); + assert!(elementsd.test_mempool_accept(&tx_hex)); +} - // TODO this fails because elements decodepsbt is not printing TxOut::asset (PSET_IN_WITNESS_UTXO) - //psbt_rtt(&elementsd, &psbt_base64); +fn rtt(base64: &str) -> String { + base64::encode(serialize(&psbt_from_base64(&base64))) +} - let tx_hex = elementsd.finalize_psbt(&rtt(&psbt_base64)); - assert!(elementsd.test_mempool_accept(&tx_hex)); +fn psbt_rtt(elementsd: &ElementsD, base64: &str) { + let a = elementsd.decode_psbt(&base64).unwrap(); + + let b_psbt = psbt_from_base64(&base64); + let mut b_bytes = serialize(&b_psbt); + let b_base64 = base64::encode(&b_bytes); + let b = elementsd.decode_psbt(&b_base64).unwrap(); + + assert_eq!(a, b); + + let mut rng = rand::thread_rng(); + let die = Uniform::from(0..b_bytes.len()); + for _ in 0..1_000 { + let i = die.sample(&mut rng); + // ensuring decode prints all data inside psbt, randomly changing a byte, + // if the results is still decodable it should not be equal to initial value + b_bytes[i] = b_bytes[i].wrapping_add(1); + let base64 = base64::encode(&b_bytes); + if let Some(decoded) = elementsd.decode_psbt(&base64) { + assert_ne!(a, decoded, "{} with changed byte {}", b_bytes.to_hex(), i); + } + b_bytes[i] = b_bytes[i].wrapping_sub(1); } +} - fn rtt(base64: &str) -> String { - base64::encode(serialize(&psbt_from_base64(&base64))) +impl Call for ElementsD { + fn call(&self, cmd: &str, args: &[Value]) -> Value { + self.client().call::(cmd, args).unwrap() } - fn psbt_rtt(elementsd: &ElementsD, base64: &str) { - let a = elementsd.decode_psbt(&base64).unwrap(); - - let b_psbt = psbt_from_base64(&base64); - let mut b_bytes = serialize(&b_psbt); - let b_base64 = base64::encode(&b_bytes); - let b = elementsd.decode_psbt(&b_base64).unwrap(); - - assert_eq!(a, b); - - let mut rng = rand::thread_rng(); - let die = Uniform::from(0..b_bytes.len()); - let mut tests = 0; - for _ in 0..1_000 { - let i = die.sample(&mut rng); - // ensuring decode prints all data inside psbt, randomly changing a byte, - // if the results is still decodable it should not be equal to initial value - b_bytes[i] = b_bytes[i].wrapping_add(1); - let base64 = base64::encode(&b_bytes); - if let Some(decoded) = elementsd.decode_psbt(&base64) { - assert_ne!(a, decoded, "{} with changed byte {}", b_bytes.to_hex(), i); - tests += 1; - } - b_bytes[i] = b_bytes[i].wrapping_sub(1); - } - assert!(tests > 0) + fn decode_psbt(&self, psbt: &str) -> Option { + self.client() + .call::("decodepsbt", &[psbt.into()]) + .ok() } - impl Call for ElementsD { - fn call(&self, cmd: &str, args: &[Value]) -> Value { - self.client().call::(cmd, args).unwrap() - } - - fn decode_psbt(&self, psbt: &str) -> Option { - self.client() - .call::("decodepsbt", &[psbt.into()]) - .ok() - } + fn analyze_psbt(&self, psbt: &str) -> Value { + self.call("decodepsbt", &[psbt.into()]) + } - fn analyze_psbt(&self, psbt: &str) -> Value { - self.call("decodepsbt", &[psbt.into()]) - } + fn get_new_address(&self) -> String { + self.call("getnewaddress", &[]) + .as_str() + .unwrap() + .to_string() + } - fn get_new_address(&self) -> String { - self.call("getnewaddress", &[]) + fn get_pegin_address(&self) -> (String, String) { + let value = self.call("getpeginaddress", &[]); + ( + value + .get("mainchain_address") + .unwrap() .as_str() .unwrap() - .to_string() - } - - fn get_pegin_address(&self) -> (String, String) { - let value = self.call("getpeginaddress", &[]); - ( - value - .get("mainchain_address") - .unwrap() - .as_str() - .unwrap() - .to_string(), - value - .get("claim_script") - .unwrap() - .as_str() - .unwrap() - .to_string(), - ) - } - - fn generate(&self, blocks: u32) { - let address = self.get_new_address(); - let _value = self.call("generatetoaddress", &[blocks.into(), address.into()]); - } + .to_string(), + value + .get("claim_script") + .unwrap() + .as_str() + .unwrap() + .to_string(), + ) + } - fn wallet_create_funded_psbt(&self, address: &str) -> String { - let value = self.call( - "walletcreatefundedpsbt", - &[json!([]), json!([{address.to_string(): "1"}])], - ); - value.get("psbt").unwrap().as_str().unwrap().to_string() - } + fn generate(&self, blocks: u32) { + let address = self.get_new_address(); + let _value = self.call("generatetoaddress", &[blocks.into(), address.into()]); + } - fn expected_next(&self, base64: &str) -> String { - let value = self.call("analyzepsbt", &[base64.into()]); - value.get("next").unwrap().as_str().unwrap().to_string() - } + fn wallet_create_funded_psbt(&self, address: &str) -> String { + let value = self.call( + "walletcreatefundedpsbt", + &[json!([]), json!([{address.to_string(): "1"}])], + ); + value.get("psbt").unwrap().as_str().unwrap().to_string() + } - fn wallet_process_psbt(&self, base64: &str) -> String { - let value = self.call("walletprocesspsbt", &[base64.into()]); - value.get("psbt").unwrap().as_str().unwrap().to_string() - } + fn expected_next(&self, base64: &str) -> String { + let value = self.call("analyzepsbt", &[base64.into()]); + value.get("next").unwrap().as_str().unwrap().to_string() + } - fn finalize_psbt(&self, base64: &str) -> String { - let value = self.call("finalizepsbt", &[base64.into()]); - value.get("hex").unwrap().as_str().unwrap().to_string() - } + fn wallet_process_psbt(&self, base64: &str) -> String { + let value = self.call("walletprocesspsbt", &[base64.into()]); + value.get("psbt").unwrap().as_str().unwrap().to_string() + } - fn get_first_prevout(&self) -> OutPoint { - let value = self.call("listunspent", &[]); - let first = value.get(0).unwrap(); - let txid = first.get("txid").unwrap().as_str().unwrap(); - let vout = first.get("vout").unwrap().as_u64().unwrap(); + fn finalize_psbt(&self, base64: &str) -> String { + let value = self.call("finalizepsbt", &[base64.into()]); + value.get("hex").unwrap().as_str().unwrap().to_string() + } - OutPoint::new(Txid::from_hex(txid).unwrap(), vout as u32) - } - fn get_balances(&self) -> Value { - self.call("getbalances", &[]) - } + fn get_first_prevout(&self) -> OutPoint { + let value = self.call("listunspent", &[]); + let first = value.get(0).unwrap(); + let txid = first.get("txid").unwrap().as_str().unwrap(); + let vout = first.get("vout").unwrap().as_u64().unwrap(); - fn test_mempool_accept(&self, hex: &str) -> bool { - let result = self.call("testmempoolaccept", &[json!([hex])]); - result - .get(0) - .unwrap() - .get("allowed") - .unwrap() - .as_bool() - .unwrap() - } + OutPoint::new(Txid::from_hex(txid).unwrap(), vout as u32) + } + fn get_balances(&self) -> Value { + self.call("getbalances", &[]) } - fn psbt_from_base64(base64: &str) -> PartiallySignedTransaction { - let bytes = base64::decode(&base64).unwrap(); - deserialize(&bytes).unwrap() + fn test_mempool_accept(&self, hex: &str) -> bool { + let result = self.call("testmempoolaccept", &[json!([hex])]); + result + .get(0) + .unwrap() + .get("allowed") + .unwrap() + .as_bool() + .unwrap() } +} - fn setup(validate_pegin: bool) -> (ElementsD, Option) { - let mut bitcoind = None; - if validate_pegin { - let bitcoind_exe = bitcoind::exe_path().unwrap(); - let bitcoind_conf = bitcoind::Conf::default(); - bitcoind = Some(bitcoind::BitcoinD::with_conf(&bitcoind_exe, &bitcoind_conf).unwrap()); - } +fn psbt_from_base64(base64: &str) -> PartiallySignedTransaction { + let bytes = base64::decode(&base64).unwrap(); + deserialize(&bytes).unwrap() +} + +fn setup(validate_pegin: bool) -> (ElementsD, Option) { + let mut bitcoind = None; + if validate_pegin { + let bitcoind_exe = bitcoind::exe_path().unwrap(); + let bitcoind_conf = bitcoind::Conf::default(); + bitcoind = Some(bitcoind::BitcoinD::with_conf(&bitcoind_exe, &bitcoind_conf).unwrap()); + } - let conf = elementsd::Conf::new(bitcoind.as_ref()); + let conf = elementsd::Conf::new(bitcoind.as_ref()); - let elementsd = ElementsD::with_conf(elementsd::exe_path().unwrap(), &conf).unwrap(); + let elementsd = ElementsD::with_conf(elementsd::exe_path().unwrap(), &conf).unwrap(); - let create = elementsd.call("createwallet", &["wallet".into()]); - assert_eq!(create.get("name").unwrap(), "wallet"); + let create = elementsd.call("createwallet", &["wallet".into()]); + assert_eq!(create.get("name").unwrap(), "wallet"); - let rescan = elementsd.call("rescanblockchain", &[]); - assert_eq!(rescan.get("stop_height").unwrap(), 0); + let rescan = elementsd.call("rescanblockchain", &[]); + assert_eq!(rescan.get("stop_height").unwrap(), 0); - let balances = elementsd.call("getbalances", &[]); - let mine = balances.get("mine").unwrap(); - let trusted = mine.get("trusted").unwrap(); - assert_eq!(trusted.get("bitcoin").unwrap(), 21.0); + let balances = elementsd.call("getbalances", &[]); + let mine = balances.get("mine").unwrap(); + let trusted = mine.get("trusted").unwrap(); + assert_eq!(trusted.get("bitcoin").unwrap(), 21.0); - (elementsd, bitcoind) - } + (elementsd, bitcoind) }