depolymerization

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

commit 5f43dd660186d631fcd05e505ee8fd2a5393a113
parent ca6277fe082e3e025cc83d6e2366238c639874c8
Author: Antoine A <>
Date:   Mon, 27 Dec 2021 18:43:50 +0100

Lower level rpc and fix failing test

Diffstat:
MCargo.lock | 133+------------------------------------------------------------------------------
Mbtc-wire/Cargo.toml | 1-
Mbtc-wire/src/rpc.rs | 137++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mbtc-wire/src/rpc_utils.rs | 6+++---
Mscript/setup.sh | 4++--
Mscript/test_btc_fail.sh | 1-
Mscript/test_btc_stress.sh | 9++++-----
7 files changed, 87 insertions(+), 204 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -166,7 +166,6 @@ dependencies = [ "taler-config", "taler-log", "thiserror", - "ureq", "uri-pack", "url", ] @@ -211,12 +210,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chunked_transfer" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" - -[[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -237,15 +230,6 @@ dependencies = [ ] [[package]] -name = "crc32fast" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" -dependencies = [ - "cfg-if", -] - -[[package]] name = "criterion" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -477,18 +461,6 @@ dependencies = [ ] [[package]] -name = "flate2" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" -dependencies = [ - "cfg-if", - "crc32fast", - "libc", - "miniz_oxide 0.4.4", -] - -[[package]] name = "flexi_logger" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -862,16 +834,6 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "miniz_oxide" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" @@ -930,12 +892,6 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" - -[[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1233,21 +1189,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] name = "rust-ini" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1267,18 +1208,6 @@ dependencies = [ ] [[package]] -name = "rustls" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" -dependencies = [ - "log", - "ring", - "sct", - "webpki", -] - -[[package]] name = "rustversion" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1306,16 +1235,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] name = "secp256k1" version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1467,12 +1386,6 @@ dependencies = [ ] [[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] name = "stringprep" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1741,31 +1654,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "ureq" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9399fa2f927a3d327187cbd201480cee55bee6ac5d3c77dd27f0c6814cff16d5" -dependencies = [ - "base64", - "chunked_transfer", - "flate2", - "log", - "once_cell", - "rustls", - "serde", - "serde_json", - "url", - "webpki", - "webpki-roots", -] - -[[package]] name = "uri-pack" version = "0.1.0" dependencies = [ @@ -1896,25 +1784,6 @@ dependencies = [ ] [[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c475786c6f47219345717a043a37ec04cb4bc185e28853adcc4fa0a947eba630" -dependencies = [ - "webpki", -] - -[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1953,7 +1822,7 @@ dependencies = [ "deadpool-postgres", "hyper", "listenfd", - "miniz_oxide 0.5.1", + "miniz_oxide", "rand", "serde", "serde_json", diff --git a/btc-wire/Cargo.toml b/btc-wire/Cargo.toml @@ -35,7 +35,6 @@ uri-pack = { path = "../uri-pack" } url = { version = "2.2.2", features = ["serde"] } # Ansi color owo-colors = "3.1.1" -ureq = { version = "2.3.1", features = ["json"] } base64 = "0.13.0" # Taler libs taler-api = { path = "../taler-api" } diff --git a/btc-wire/src/rpc.rs b/btc-wire/src/rpc.rs @@ -3,14 +3,15 @@ use bitcoin::{hashes::hex::ToHex, Address, Amount, BlockHash, Network, SignedAmo use serde_json::{json, Value}; use std::{ fmt::Debug, - io, + io::{self, BufRead, BufReader, ErrorKind, Write}, + net::{SocketAddr, TcpStream}, path::Path, sync::atomic::{AtomicU64, Ordering}, - time::Duration, + time::{Duration, Instant}, }; // This is a very simple RPC client designed only for a specific bitcoincore version -// and to un on a secure localhost +// and to use on an secure localhost connection to a trusted node #[derive(Debug, serde::Serialize)] struct BtcRequest<'a, T: serde::Serialize> { @@ -34,8 +35,8 @@ struct BtcErr { #[derive(Debug, thiserror::Error)] pub enum Error { - #[error(transparent)] - Transport(#[from] ureq::Transport), + #[error("{0:?}")] + Transport(#[from] std::io::Error), #[error("{code:?} - {msg}")] RPC { code: ErrorCode, msg: String }, #[error("JSON: {0}")] @@ -44,67 +45,93 @@ pub enum Error { pub type Result<T> = std::result::Result<T, Error>; +const EMPTY: [(); 0] = []; + pub struct BtcRpc { + addr: SocketAddr, path: String, - agent: ureq::Agent, id: AtomicU64, cookie: String, } impl BtcRpc { pub fn common(data_dir: &Path, network: Network) -> io::Result<Self> { - Self::new(data_dir, rpc_url(network), network) + Self::new(data_dir, rpc_url(network), "/".into(), network) } pub fn wallet(data_dir: &Path, network: Network, wallet: &str) -> io::Result<Self> { Self::new( data_dir, - format!("{}/wallet/{}", rpc_url(network), wallet), + rpc_url(network), + format!("/wallet/{}", wallet), network, ) } - fn new(data_dir: &Path, path: String, network: Network) -> io::Result<Self> { + fn new(data_dir: &Path, addr: SocketAddr, path: String, network: Network) -> io::Result<Self> { let cookie_path = data_dir.join(rpc_dir(network)).join(".cookie"); let cookie = std::fs::read(cookie_path)?; - let agent = ureq::builder().redirects(0).build(); Ok(Self { + addr, path, - agent, id: AtomicU64::new(0), cookie: format!("Basic {}", base64::encode(&cookie)), }) } - fn call<T>( - &self, - method: &str, - params: &impl serde::Serialize, - timeout: Option<Duration>, - ) -> Result<T> + fn call<T>(&self, method: &str, params: &impl serde::Serialize) -> Result<T> where T: serde::de::DeserializeOwned + Debug, { let id = self.id.fetch_add(1, Ordering::SeqCst); let request = BtcRequest { method, id, params }; - let body = - serde_json::to_vec(&request).expect("Serialization into a vec should never fail"); - let result = self - .agent - .post(&self.path) - .set("Authorization", &self.cookie) - .set("Content-Type", "application/json-rpc") - .set("Accept", "application/json-rpc") - .timeout(timeout.unwrap_or(Duration::from_secs(10))) - .send_bytes(&body); - let response = match result { - Ok(it) => it, - Err(it) => match it { - ureq::Error::Status(_, it) => it, - ureq::Error::Transport(it) => return Err(it.into()), - }, - }; - let response: BtcResponse<T> = serde_json::from_reader(response.into_reader())?; + + // Some call might hang waiting for a new block to be mined + let timeout = Duration::from_secs(666); + let request_deadline = Instant::now() + timeout; + + // Open connection + let mut sock = TcpStream::connect_timeout(&self.addr, timeout)?; + sock.set_read_timeout(Some(timeout))?; + sock.set_write_timeout(Some(timeout))?; + + // Serialize the body first so we can set the Content-Length header. + let body = serde_json::to_vec(&request)?; + let mut buf = Vec::new(); + // Write HTTP request + { + // Send HTTP request + writeln!(buf, "POST {} HTTP/1.1\r", self.path)?; + // Write headers + writeln!(buf, "Accept: application/json-rpc\r")?; + writeln!(buf, "Connection: close\r")?; + writeln!(buf, "Authorization: {}\r", self.cookie)?; + writeln!(buf, "Content-Type: application/json-rpc\r")?; + writeln!(buf, "Content-Length: {}\r", body.len())?; + // Write separator + writeln!(buf, "\r")?; + sock.write_all(&buf).unwrap(); + buf.clear(); + // Write body + sock.write_all(&body).unwrap(); + sock.flush().unwrap(); + } + // Receive response + let mut reader = BufReader::new(sock); + // Skip response + loop { + let amount = reader.read_until(b'\n', &mut buf).unwrap(); + let sep = buf[..amount] == [b'\r', b'\n']; + buf.clear(); + if sep { + break; + } else if Instant::now() > request_deadline { + Err(std::io::Error::new(ErrorKind::TimedOut, "Timeout"))? + } + } + // Read body + let amount = reader.read_until(b'\n', &mut buf).unwrap(); + let response: BtcResponse<T> = serde_json::from_slice(&buf[..amount])?; assert_eq!(id, response.id); if let Some(ok) = response.result { Ok(ok) @@ -118,42 +145,37 @@ impl BtcRpc { } pub fn net_info(&self) -> Result<Empty> { - let params: [(); 0] = []; - self.call("getnetworkinfo", &params, None) + self.call("getnetworkinfo", &EMPTY) } pub fn load_wallet(&self, name: &str) -> Result<Wallet> { - self.call("loadwallet", &[name], None) + self.call("loadwallet", &[name]) } pub fn create_wallet(&self, name: &str) -> Result<Wallet> { - self.call("createwallet", &[name], None) + self.call("createwallet", &[name]) } pub fn get_new_address(&self) -> Result<Address> { - self.call("getnewaddress", &[()], None) + self.call("getnewaddress", &EMPTY) } pub fn generate(&self, nb: u16, address: &Address) -> Result<Vec<BlockHash>> { - self.call( - "generatetoaddress", - &(nb, address), - Some(Duration::from_secs(10 * 60 + 10)), - ) + self.call("generatetoaddress", &(nb, address)) } pub fn wait_for_new_block(&self, timeout: u64) -> Result<Empty> { - self.call("waitfornewblock", &[timeout], None) + self.call("waitfornewblock", &[timeout]) } pub fn get_balance(&self) -> Result<Amount> { - let btc: f64 = self.call("getbalance", &[()], None)?; + let btc: f64 = self.call("getbalance", &EMPTY)?; Ok(Amount::from_btc(btc).unwrap()) } pub fn send(&self, address: &Address, amount: &Amount, subtract_fee: bool) -> Result<Txid> { let btc = amount.as_btc(); - self.call("sendtoaddress", &(address, btc, (), (), subtract_fee), None) + self.call("sendtoaddress", &(address, btc, (), (), subtract_fee)) } /// Send transaction to multiple recipients @@ -167,7 +189,7 @@ impl BtcRpc { .map(|(addr, amount)| (addr.to_string(), amount.as_btc().into())) .collect(), ); - self.call("sendmany", &("", amounts), None) + self.call("sendmany", &("", amounts)) } pub fn send_custom<'a, 'b, 'c>( @@ -197,11 +219,10 @@ impl BtcRpc { vec }), ], - None, )?; - let funded: HexWrapper = self.call("fundrawtransaction", &[hex], None)?; - let signed: HexWrapper = self.call("signrawtransactionwithwallet", &[&funded.hex], None)?; - self.call("sendrawtransaction", &[&signed.hex], None) + let funded: HexWrapper = self.call("fundrawtransaction", &[hex])?; + let signed: HexWrapper = self.call("signrawtransactionwithwallet", &[&funded.hex])?; + self.call("sendrawtransaction", &[&signed.hex]) } pub fn list_since_block( @@ -210,19 +231,15 @@ impl BtcRpc { confirmation: u8, include_remove: bool, ) -> Result<ListSinceBlock> { - self.call( - "listsinceblock", - &(hash, confirmation, (), include_remove), - None, - ) + self.call("listsinceblock", &(hash, confirmation, (), include_remove)) } pub fn get_tx(&self, id: &Txid) -> Result<TransactionFull> { - self.call("gettransaction", &(id, (), true), None) + self.call("gettransaction", &(id, (), true)) } pub fn get_raw(&self, id: &Txid) -> Result<RawTransaction> { - self.call("getrawtransaction", &(id, true), None) + self.call("getrawtransaction", &(id, true)) } } diff --git a/btc-wire/src/rpc_utils.rs b/btc-wire/src/rpc_utils.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, str::FromStr}; +use std::{net::SocketAddr, path::PathBuf, str::FromStr}; use bitcoin::{Address, Amount, Network}; @@ -16,14 +16,14 @@ pub fn rpc_dir(network: Network) -> &'static str { } } -pub fn rpc_url(network: Network) -> String { +pub fn rpc_url(network: Network) -> SocketAddr { let port = match network { Network::Bitcoin => 8332, Network::Testnet => 18332, Network::Regtest => 18443, _ => unreachable!("Unsupported network"), }; - format!("http://127.0.0.1:{}", port) + ([127, 0, 0, 1], port).into() } pub fn default_data_dir() -> PathBuf { diff --git a/script/setup.sh b/script/setup.sh @@ -19,14 +19,14 @@ function reset_db() { function init_btc() { BTC_DIR=$(mktemp -d) BTC_CLI="bitcoin-cli -regtest -datadir=$BTC_DIR" - bitcoind -datadir=$BTC_DIR -txindex -regtest -fallbackfee=0.00000001 -rpcworkqueue=64 &> btc.log & + bitcoind -datadir=$BTC_DIR -txindex -regtest -fallbackfee=0.00000001 &> btc.log & $BTC_CLI -rpcwait getnetworkinfo > /dev/null } # Start a bitcoind regest server in a previously created temporary directory and load wallets function restart_btc() { BTC_CLI="bitcoin-cli -regtest -datadir=$BTC_DIR" - bitcoind -datadir=$BTC_DIR -txindex -regtest -fallbackfee=0.00000001 -rpcworkqueue=64 &>> btc.log & + bitcoind -datadir=$BTC_DIR -txindex -regtest -fallbackfee=0.00000001 &>> btc.log & $BTC_CLI -rpcwait getnetworkinfo > /dev/null for wallet in wire client reserve; do $BTC_CLI loadwallet $wallet > /dev/null diff --git a/script/test_btc_fail.sh b/script/test_btc_fail.sh @@ -65,7 +65,6 @@ for n in `$SEQ`; do -b $BANK_ENDPOINT \ -C payto://bitcoin/$CLIENT \ -a BTC:0.0000$n > /dev/null - mine_btc done sleep 15 mine_btc # Mine transactions diff --git a/script/test_btc_stress.sh b/script/test_btc_stress.sh @@ -33,7 +33,7 @@ echo "Start gateway" gateway echo "" -SEQ="seq 10 50" +SEQ="seq 10 99" function check() { check_delta "$1?delta=-100" "$SEQ" @@ -56,7 +56,7 @@ check incoming echo " OK" echo -n "Check balance:" -check_balance 9.99844569 1.00123000 +check_balance 9.99438310 1.00490500 echo " OK" echo "----- Handle outgoing -----" @@ -67,7 +67,6 @@ for n in `$SEQ`; do -b $BANK_ENDPOINT \ -C payto://bitcoin/$CLIENT \ -a BTC:0.0000$n > /dev/null - mine_btc done sleep 10 # Give time for btc_wire sender to process next_btc # Mine transactions @@ -80,7 +79,7 @@ check outgoing echo " OK" echo -n "Check balance:" -check_balance 9.99967569 +check_balance 9.99928810 echo " OK" next_btc # Mine transactions @@ -101,7 +100,7 @@ echo " OK" echo -n "Check balance:" # Balance should not have changed -check_balance 9.99967569 +check_balance 9.99928810 echo " OK" echo "All tests passed" \ No newline at end of file