depolymerization

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

commit 8090f9ccf69dd88aafde6aaf6b01dc74a8bd4b4a
parent 5f43dd660186d631fcd05e505ee8fd2a5393a113
Author: Antoine A <>
Date:   Mon, 27 Dec 2021 20:21:27 +0100

Parse bitcoin config file

Diffstat:
MCargo.lock | 1+
Mbtc-wire/Cargo.toml | 1+
Mbtc-wire/src/bin/btc-wire-cli.rs | 52++++++++--------------------------------------------
Mbtc-wire/src/bin/test.rs | 39++++++++++++++++++++-------------------
Abtc-wire/src/config.rs | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbtc-wire/src/lib.rs | 1+
Mbtc-wire/src/main.rs | 49++++++++++++++-----------------------------------
Mbtc-wire/src/rpc.rs | 30+++++++++++++++---------------
Mbtc-wire/src/rpc_utils.rs | 23++---------------------
Mscript/setup.sh | 7+++++--
Mtaler-config/Cargo.toml | 2--
Mtaler-config/src/lib.rs | 2--
Mtest.conf | 1-
13 files changed, 144 insertions(+), 141 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -159,6 +159,7 @@ dependencies = [ "owo-colors", "postgres", "rand", + "rust-ini", "serde", "serde_json", "serde_repr", diff --git a/btc-wire/Cargo.toml b/btc-wire/Cargo.toml @@ -40,6 +40,7 @@ base64 = "0.13.0" taler-api = { path = "../taler-api" } taler-config = { path = "../taler-config" } taler-log = { path = "../taler-log" } +rust-ini = "0.17.0" [dev-dependencies] # statistics-driven micro-benchmarks diff --git a/btc-wire/src/bin/btc-wire-cli.rs b/btc-wire/src/bin/btc-wire-cli.rs @@ -2,8 +2,9 @@ use std::path::PathBuf; use bitcoin::{Address, Amount, Network}; use btc_wire::{ + config::BitcoinConfig, rpc::{BtcRpc, Error, ErrorCode}, - rpc_utils::{default_data_dir, rpc_dir}, + rpc_utils::default_data_dir, test::rand_key, }; @@ -22,8 +23,6 @@ struct Args { enum Cmd { Transfer(TransferCmd), NextBlock(NextBlockCmd), - Init(InitCmd), - Reset(ResetCmd), } #[derive(argh::FromArgs)] @@ -56,33 +55,18 @@ struct NextBlockCmd { to: String, } -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "init")] -/// Init test state -struct InitCmd {} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "reset")] -/// Reset regtest -struct ResetCmd {} - struct App { - data_dir: PathBuf, - network: Network, + config: BitcoinConfig, client: BtcRpc, } impl App { pub fn start(data_dir: Option<PathBuf>) -> Self { let data_dir = data_dir.unwrap_or_else(default_data_dir); - let network = Network::Regtest; - let client = BtcRpc::common(&data_dir, network).unwrap(); + let config = BitcoinConfig::load(data_dir).unwrap(); + let client = BtcRpc::common(&config).unwrap(); - Self { - data_dir, - network, - client, - } + Self { config, client } } pub fn auto_wallet(&self, name: &str) -> (BtcRpc, Address) { @@ -93,7 +77,7 @@ impl App { e => Err(e).unwrap(), } } - let wallet = BtcRpc::wallet(&self.data_dir, self.network, name).unwrap(); + let wallet = BtcRpc::wallet(&self.config, name).unwrap(); let addr = wallet .get_new_address() .expect(&format!("Failed to get wallet address {}", name)); @@ -101,7 +85,7 @@ impl App { } pub fn next_block(&self, wallet: &str) { - match self.network { + match self.config.network { Network::Regtest => { // Manually mine a block let (_, addr) = self.auto_wallet(wallet); @@ -113,31 +97,11 @@ impl App { } } } - - pub fn init(&self) { - for wallet in ["wire", "client", "reserve"] { - self.client.create_wallet(wallet).ok(); - } - if self.network == Network::Regtest { - let (client, client_addr) = self.auto_wallet("client"); - client.generate(101, &client_addr).unwrap(); - } - } } fn main() { let args: Args = argh::from_env(); match args.cmd { - Cmd::Init(_) => App::start(args.datadir).init(), - Cmd::Reset(_) => { - let path = args - .datadir - .unwrap_or_else(default_data_dir) - .join(rpc_dir(Network::Regtest)); - if path.exists() { - std::fs::remove_dir_all(path).unwrap(); - } - } Cmd::Transfer(TransferCmd { key: _key, from, diff --git a/btc-wire/src/bin/test.rs b/btc-wire/src/bin/test.rs @@ -3,8 +3,9 @@ use std::{collections::HashSet, iter::repeat_with, panic::AssertUnwindSafe}; use bitcoin::{Address, Amount, Network, Txid}; use btc_wire::{ + config::BitcoinConfig, rpc::{self, BtcRpc, Category}, - rpc_utils::{default_data_dir, rpc_dir, CLIENT, WIRE}, + rpc_utils::{default_data_dir, CLIENT, WIRE}, test::rand_key, BounceErr, }; @@ -16,9 +17,9 @@ const RESERVE: &str = "reserve"; pub fn main() { let test_amount = Amount::from_sat(1500); let data_dir = default_data_dir(); + let config = BitcoinConfig::load(&data_dir).unwrap(); // Network check - let network = Network::Regtest; - match network { + match config.network { Network::Bitcoin => { panic!("Do not run tests on the mainnet, you are going to loose money") } @@ -30,16 +31,15 @@ pub fn main() { // Wallet check { - let existing_wallets: HashSet<String> = - std::fs::read_dir(data_dir.join(rpc_dir(network)).join("wallets")) - .unwrap() - .filter_map(|it| it.ok()) - .map(|it| it.file_name().to_string_lossy().to_string()) - .collect(); + let existing_wallets: HashSet<String> = std::fs::read_dir(config.dir.join("wallets")) + .unwrap() + .filter_map(|it| it.ok()) + .map(|it| it.file_name().to_string_lossy().to_string()) + .collect(); let wallets = [CLIENT, WIRE, RESERVE]; - let rpc = BtcRpc::common(&data_dir, network).unwrap(); + let rpc = BtcRpc::common(&config).unwrap(); if !existing_wallets.contains(CLIENT) || !existing_wallets.contains(WIRE) || !existing_wallets.contains(RESERVE) @@ -64,15 +64,15 @@ pub fn main() { } // Client initialization - let client_rpc = BtcRpc::wallet(&data_dir, network, CLIENT).unwrap(); - let wire_rpc = BtcRpc::wallet(&data_dir, network, WIRE).unwrap(); - let reserve_rpc = BtcRpc::wallet(&data_dir, network, RESERVE).unwrap(); + let client_rpc = BtcRpc::wallet(&config, CLIENT).unwrap(); + let wire_rpc = BtcRpc::wallet(&config, WIRE).unwrap(); + let reserve_rpc = BtcRpc::wallet(&config, RESERVE).unwrap(); let client_addr = client_rpc.get_new_address().unwrap(); let wire_addr = wire_rpc.get_new_address().unwrap(); let reserve_addr = reserve_rpc.get_new_address().unwrap(); let next_block = || { - match network { + match config.network { Network::Regtest => { // Manually mine a block reserve_rpc.generate(1, &reserve_addr).unwrap(); @@ -120,9 +120,9 @@ pub fn main() { ) .yellow() ); - match network { + match config.network { Network::Bitcoin | Network::Testnet | Network::Signet => { - if network == Network::Bitcoin { + if config.network == Network::Bitcoin { println!("Send coins to this address: {}", client_addr); } else { println!("Request coins from a faucet such as https://bitcoinfaucet.uo1.net/send.php to this address: {}", client_addr); @@ -296,9 +296,10 @@ pub fn main() { /// Check a specific transaction exist in a wallet historic fn tx_exist(rpc: &BtcRpc, id: &Txid, min_confirmation: i32, detail: Category) -> rpc::Result<bool> { let result = rpc.list_since_block(None, 1, false).unwrap(); - let found = result.transactions.into_iter().any(|tx| { - tx.category == detail && tx.confirmations >= min_confirmation && tx.txid == *id - }); + let found = result + .transactions + .into_iter() + .any(|tx| tx.category == detail && tx.confirmations >= min_confirmation && tx.txid == *id); Ok(found) } diff --git a/btc-wire/src/config.rs b/btc-wire/src/config.rs @@ -0,0 +1,77 @@ +use std::{ + net::SocketAddr, + path::{Path, PathBuf}, + process::exit, + str::FromStr, +}; + +use bitcoin::Network; +use taler_log::log::error; + +fn rpc_dir(network: Network) -> &'static str { + match network { + Network::Bitcoin => "main", + Network::Testnet => "testnet3", + Network::Regtest => "regtest", + Network::Signet => "signet", + } +} + +fn rpc_port(network: Network) -> u16 { + match network { + Network::Bitcoin => 8332, + Network::Testnet => 18332, + Network::Regtest => 18443, + Network::Signet => 38333, + } +} + +#[derive(Clone)] +pub struct BitcoinConfig { + pub network: Network, + pub dir: PathBuf, + pub addr: SocketAddr, +} + +impl BitcoinConfig { + pub fn load(data_dir: impl AsRef<Path>) -> Result<Self, ini::Error> { + let conf = ini::Ini::load_from_file(data_dir.as_ref().join("bitcoin.conf"))?; + + let section = conf.general_section(); + + if !section.contains_key("txindex") { + error!("btc_wire require a bitcoin core node running with 'txindex' option"); + exit(1); + } + + let network = if let Some("1") = section.get("testnet") { + Network::Testnet + } else if let Some("1") = section.get("signet") { + Network::Signet + } else if let Some("1") = section.get("regtest") { + Network::Regtest + } else { + Network::Bitcoin + }; + + let port = if let Some(addr) = section.get("rpcport") { + addr.parse() + .expect("bitcoin config value 'rpcport' is not a valid port number") + } else { + rpc_port(network) + }; + + let addr = if let Some(addr) = section.get("rpcbind") { + SocketAddr::from_str(addr) + .expect("bitcoin config value 'rpcbind' is not a valid socket address") + } else { + ([127, 0, 0, 1], port).into() + }; + + Ok(Self { + network, + addr, + dir: data_dir.as_ref().join(rpc_dir(network)), + }) + } +} diff --git a/btc-wire/src/lib.rs b/btc-wire/src/lib.rs @@ -9,6 +9,7 @@ pub mod rpc; pub mod rpc_utils; pub mod segwit; pub mod test; +pub mod config; #[derive(Debug, thiserror::Error)] pub enum BounceErr { diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs @@ -1,5 +1,6 @@ -use bitcoin::{hashes::Hash, Address, Amount as BtcAmount, BlockHash, Network, SignedAmount, Txid}; +use bitcoin::{hashes::Hash, Address, Amount as BtcAmount, BlockHash, SignedAmount, Txid}; use btc_wire::{ + config::BitcoinConfig, rpc::{BtcRpc, Category}, rpc_utils::{default_data_dir, sender_address}, segwit::DecodeSegWitErr, @@ -9,7 +10,7 @@ use postgres::{fallible_iterator::FallibleIterator, Client, NoTls}; use rand::{rngs::OsRng, RngCore}; use std::{ collections::HashMap, - path::{Path, PathBuf}, + path::PathBuf, str::FromStr, time::{Duration, SystemTime}, }; @@ -123,34 +124,26 @@ mod test { struct AutoReconnectRPC { delay: Duration, - data_dir: PathBuf, + config: BitcoinConfig, wallet: String, client: BtcRpc, - network: Network, } impl AutoReconnectRPC { - pub fn new( - data_dir: impl Into<PathBuf>, - network: Network, - wallet: impl Into<String>, - delay: Duration, - ) -> Self { + pub fn new(config: BitcoinConfig, wallet: impl Into<String>, delay: Duration) -> Self { let wallet: String = wallet.into(); - let data_dir: PathBuf = data_dir.into(); Self { - client: Self::connect(&data_dir, network, &wallet, delay), - data_dir, + client: Self::connect(&config, &wallet, delay), wallet, delay, - network, + config, } } /// Connect a new client, loop on error - fn connect(data_dir: &Path, network: Network, wallet: &str, delay: Duration) -> BtcRpc { + fn connect(config: &BitcoinConfig, wallet: &str, delay: Duration) -> BtcRpc { loop { - match BtcRpc::wallet(data_dir, network, wallet) { + match BtcRpc::wallet(config, wallet) { Ok(new) => match new.net_info() { Ok(_) => return new, Err(err) => { @@ -168,7 +161,7 @@ impl AutoReconnectRPC { pub fn client(&mut self) -> &mut BtcRpc { if self.client.net_info().is_err() { - self.client = Self::connect(&self.data_dir, self.network, &self.wallet, self.delay); + self.client = Self::connect(&self.config, &self.wallet, self.delay); } &mut self.client } @@ -509,29 +502,15 @@ fn main() { .unwrap_or_else(default_data_dir); let config = taler_config::Config::from_path("test.conf"); let config: &'static Config = Box::leak(Box::new(config)); - let network = match config.btc_chain.as_str() { - "main" => Network::Bitcoin, - "test" => Network::Testnet, - "regtest" => Network::Regtest, - chain => { - error!("Unsupported chain {}", chain); - std::process::exit(1); - } - }; - let rpc = BtcRpc::common(&data_dir, network).unwrap(); + let btc_config = BitcoinConfig::load(&data_dir).unwrap(); + let rpc = BtcRpc::common(&btc_config).unwrap(); rpc.load_wallet(&config.btc_wallet).ok(); let rpc_listener = AutoReconnectRPC::new( - &data_dir, - network, - &config.btc_wallet, - Duration::from_secs(5), - ); - let rpc_worker = AutoReconnectRPC::new( - &data_dir, - network, + btc_config.clone(), &config.btc_wallet, Duration::from_secs(5), ); + let rpc_worker = AutoReconnectRPC::new(btc_config, &config.btc_wallet, Duration::from_secs(5)); let db_listener = AutoReconnectSql::new(&config.db_url, Duration::from_secs(5)); let db_worker = AutoReconnectSql::new(&config.db_url, Duration::from_secs(5)); diff --git a/btc-wire/src/rpc.rs b/btc-wire/src/rpc.rs @@ -1,15 +1,15 @@ -use crate::rpc_utils::{rpc_dir, rpc_url}; -use bitcoin::{hashes::hex::ToHex, Address, Amount, BlockHash, Network, SignedAmount, Txid}; +use bitcoin::{hashes::hex::ToHex, Address, Amount, BlockHash, SignedAmount, Txid}; use serde_json::{json, Value}; use std::{ fmt::Debug, io::{self, BufRead, BufReader, ErrorKind, Write}, net::{SocketAddr, TcpStream}, - path::Path, sync::atomic::{AtomicU64, Ordering}, time::{Duration, Instant}, }; +use crate::config::BitcoinConfig; + // This is a very simple RPC client designed only for a specific bitcoincore version // and to use on an secure localhost connection to a trusted node @@ -55,24 +55,24 @@ pub struct BtcRpc { } impl BtcRpc { - pub fn common(data_dir: &Path, network: Network) -> io::Result<Self> { - Self::new(data_dir, rpc_url(network), "/".into(), network) + pub fn common(config: &BitcoinConfig) -> io::Result<Self> { + Self::new(config, None) } - pub fn wallet(data_dir: &Path, network: Network, wallet: &str) -> io::Result<Self> { - Self::new( - data_dir, - rpc_url(network), - format!("/wallet/{}", wallet), - network, - ) + pub fn wallet(config: &BitcoinConfig, wallet: &str) -> io::Result<Self> { + Self::new(config, Some(wallet)) } - 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"); + fn new(config: &BitcoinConfig, wallet: Option<&str>) -> io::Result<Self> { + let path = if let Some(wallet) = wallet { + format!("/wallet/{}", wallet) + } else { + String::from("/") + }; + let cookie_path = config.dir.join(".cookie"); let cookie = std::fs::read(cookie_path)?; Ok(Self { - addr, + addr: config.addr, path, id: AtomicU64::new(0), cookie: format!("Basic {}", base64::encode(&cookie)), diff --git a/btc-wire/src/rpc_utils.rs b/btc-wire/src/rpc_utils.rs @@ -1,31 +1,12 @@ -use std::{net::SocketAddr, path::PathBuf, str::FromStr}; +use std::{path::PathBuf, str::FromStr}; -use bitcoin::{Address, Amount, Network}; +use bitcoin::{Address, Amount}; use crate::rpc::{self, BtcRpc, TransactionFull}; pub const CLIENT: &str = "client"; pub const WIRE: &str = "wire"; -pub fn rpc_dir(network: Network) -> &'static str { - match network { - Network::Bitcoin => "main", - Network::Testnet => "testnet3", - Network::Regtest => "regtest", - _ => unreachable!("Unsupported network"), - } -} - -pub fn rpc_url(network: Network) -> SocketAddr { - let port = match network { - Network::Bitcoin => 8332, - Network::Testnet => 18332, - Network::Regtest => 18443, - _ => unreachable!("Unsupported network"), - }; - ([127, 0, 0, 1], port).into() -} - pub fn default_data_dir() -> 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") { diff --git a/script/setup.sh b/script/setup.sh @@ -18,15 +18,18 @@ function reset_db() { # Start a bitcoind regtest server in a temporary directory function init_btc() { BTC_DIR=$(mktemp -d) + echo "regtest=1" > $BTC_DIR/bitcoin.conf + echo "txindex=1" >> $BTC_DIR/bitcoin.conf + echo "fallbackfee=0.00000001" >> $BTC_DIR/bitcoin.conf BTC_CLI="bitcoin-cli -regtest -datadir=$BTC_DIR" - bitcoind -datadir=$BTC_DIR -txindex -regtest -fallbackfee=0.00000001 &> btc.log & + bitcoind -datadir=$BTC_DIR &> 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 &>> btc.log & + bitcoind -datadir=$BTC_DIR -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/taler-config/Cargo.toml b/taler-config/Cargo.toml @@ -3,8 +3,6 @@ name = "taler-config" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] # Url format url = { version = "2.2.2", features = ["serde"] } diff --git a/taler-config/src/lib.rs b/taler-config/src/lib.rs @@ -9,7 +9,6 @@ pub struct Config { pub payto: Url, pub confirmation: u8, pub btc_wallet: String, - pub btc_chain: String, pub bounce_fee: u64, } @@ -37,7 +36,6 @@ impl Config { .parse() .expect("Config CONFIRMATION is not a number"), btc_wallet: self_conf.get("BTC_WALLET").unwrap_or("wire").to_string(), - btc_chain: self_conf.get("BTC_CHAIN").unwrap_or("regtest").to_string(), bounce_fee: self_conf .get("BOUNCE_FEE") .unwrap_or("0") diff --git a/test.conf b/test.conf @@ -9,5 +9,4 @@ PAYTO = payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj CONFIRMATION = 1 BTC_WALLET = wire BTC_DATA_DIR = ~/.bitcoin -BTC_CHAIN = regtest BOUNCE_FEE = 0 \ No newline at end of file