depolymerization

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

commit 6944f01b20abf6922a0453960df6b728941ef4e0
parent c9693b859e564174db47c60859742a0ee314ab1b
Author: Antoine A <>
Date:   Thu, 13 Jan 2022 19:16:17 +0100

First bootstrap cli draft

Diffstat:
Mbtc-wire/src/bin/btc-wire-cli.rs | 192+++++++++++++++++++++----------------------------------------------------------
Abtc-wire/src/bin/btc-wire-utils.rs | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbtc-wire/src/config.rs | 2++
Mbtc-wire/src/main.rs | 8++++----
Mdb/btc.sql | 2--
Mmakefile | 2+-
Mtaler-common/src/config.rs | 20++++++++++++++++++--
Mtest/btc/conflict.sh | 6+++---
Mtest/btc/fail.sh | 2+-
Mtest/btc/hell.sh | 10+++++-----
Mtest/btc/lifetime.sh | 2+-
Mtest/btc/reconnect.sh | 4++--
Mtest/btc/reorg.sh | 2+-
Mtest/btc/stress.sh | 2+-
Mtest/btc/wire.sh | 2+-
Mtest/common.sh | 9++++++---
Mtest/conf/taler_lifetime.conf | 1-
Mtest/conf/taler_test.conf | 1-
Mtest/gateway/api.sh | 2+-
19 files changed, 259 insertions(+), 172 deletions(-)

diff --git a/btc-wire/src/bin/btc-wire-cli.rs b/btc-wire/src/bin/btc-wire-cli.rs @@ -1,142 +1,50 @@ -use std::path::PathBuf; - -use bitcoin::{Address, Amount, Network}; -use btc_wire::{ - config::BitcoinConfig, - rpc::{BtcRpc, Category, Error, ErrorCode}, - rpc_utils::default_data_dir, - test::rand_key, -}; - -#[derive(argh::FromArgs)] -/// Bitcoin wire test client -struct Args { - #[argh(option, short = 'd')] - /// specify data directory - datadir: Option<PathBuf>, - #[argh(subcommand)] - cmd: Cmd, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand)] -enum Cmd { - Transfer(TransferCmd), - NextBlock(NextBlockCmd), - Abandon(AbandonCmd), -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "transfer")] -/// Wait or mine the next block -struct TransferCmd { - #[argh(option, short = 'k')] - /// reserve public key - key: Option<String>, - - #[argh(option, short = 'f', default = "String::from(\"client\")")] - /// sender wallet - from: String, - - #[argh(option, short = 't', default = "String::from(\"wire\")")] - /// receiver wallet - to: String, - - #[argh(positional)] - /// amount to send in btc - amount: f64, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "nblock")] -/// Wait or mine the next block -struct NextBlockCmd { - #[argh(positional, default = "String::from(\"wire\")")] - /// receiver wallet - to: String, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "abandon")] -/// Abandon all unconfirmed transaction -struct AbandonCmd { - #[argh(positional, default = "String::from(\"wire\")")] - /// sender wallet - from: String, -} - -struct App { - 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 config = BitcoinConfig::load(data_dir).unwrap(); - let client = BtcRpc::common(&config).unwrap(); - - Self { config, client } - } - - pub fn auto_wallet(&mut self, name: &str) -> (BtcRpc, Address) { - // Auto load - if let Err(err) = self.client.load_wallet(name) { - match err { - Error::RPC { code, .. } if code == ErrorCode::RpcWalletAlreadyLoaded => {} - e => Err(e).unwrap(), - } - } - let mut wallet = BtcRpc::wallet(&self.config, name).unwrap(); - let addr = wallet - .get_new_address() - .expect(&format!("Failed to get wallet address {}", name)); - (wallet, addr) - } - - pub fn next_block(&mut self, wallet: &str) { - match self.config.network { - Network::Regtest => { - // Manually mine a block - let (_, addr) = self.auto_wallet(wallet); - self.client.generate(1, &addr).unwrap(); - } - _ => { - // Wait for next network block - self.client.wait_for_new_block(0).ok(); - } - } - } -} - -fn main() { - let args: Args = argh::from_env(); - let mut app = App::start(args.datadir); - match args.cmd { - Cmd::Transfer(TransferCmd { - key: _key, - from, - to, - amount, - }) => { - let (mut client, _) = app.auto_wallet(&from); - let (_, to) = app.auto_wallet(&to); - let tx = client - .send_segwit_key(&to, &Amount::from_btc(amount).unwrap(), &rand_key()) - .unwrap(); - println!("{}", tx); - } - Cmd::NextBlock(NextBlockCmd { to }) => { - app.next_block(&to); - } - Cmd::Abandon(AbandonCmd { from }) => { - let (mut wire, _) = app.auto_wallet(&from); - let list = wire.list_since_block(None, 1, false).unwrap(); - for tx in list.transactions { - if tx.category == Category::Send && tx.confirmations == 0 { - wire.abandon_tx(&tx.txid).unwrap(); - } - } - } - } -} +use btc_wire::{ + config::{BitcoinConfig, WIRE_WALLET_NAME}, + rpc::{BtcRpc, Error, ErrorCode}, + rpc_utils::default_data_dir, +}; +use postgres::{Client, NoTls}; + +fn main() { + let args: Vec<_> = std::env::args().collect(); + let config = taler_common::config::InitConfig::load_from_file(&args[2]); + + match args[1].as_str() { + "initdb" => { + let mut client = Client::connect(&config.db_url, NoTls).unwrap(); + client + .batch_execute(include_str!("../../../db/btc.sql")) + .unwrap(); + println!("Database initialised"); + } + "initwallet" => { + let btc_conf = + BitcoinConfig::load(config.btc_data_dir.unwrap_or(default_data_dir())).unwrap(); + let mut rpc = BtcRpc::common(&btc_conf).unwrap(); + let created = match rpc.create_wallet(WIRE_WALLET_NAME) { + Err(Error::RPC { code, .. }) if code == ErrorCode::RpcWalletError => false, + Err(e) => panic!("{}", e), + Ok(_) => true, + }; + + rpc.load_wallet(WIRE_WALLET_NAME).ok(); + // TODO idempotent (store address in database ?) + // TODO init last_hash to skip previous database sync + let address = BtcRpc::wallet(&btc_conf, WIRE_WALLET_NAME) + .unwrap() + .get_new_address() + .unwrap(); + + if created { + println!("Created new wallet"); + } else { + println!("Found already existing wallet") + } + println!("Address is {}", &address); + println!("Add the following line into taler.conf:"); + println!("[depolymerizer-bitcoin]"); + println!("PAYTO = payto://bitcoin/{}", address); + } + cmd => panic!("Unknown command {}", cmd), + } +} diff --git a/btc-wire/src/bin/btc-wire-utils.rs b/btc-wire/src/bin/btc-wire-utils.rs @@ -0,0 +1,162 @@ +use std::path::PathBuf; + +use bitcoin::{Address, Amount, Network}; +use btc_wire::{ + config::BitcoinConfig, + rpc::{BtcRpc, Category, Error, ErrorCode}, + rpc_utils::default_data_dir, + test::rand_key, +}; +use postgres::{Client, NoTls}; +use taler_common::config::Config; + +#[derive(argh::FromArgs)] +/// Bitcoin wire test client +struct Args { + #[argh(option, short = 'd')] + /// specify data directory + datadir: Option<PathBuf>, + #[argh(subcommand)] + cmd: Cmd, +} + +#[derive(argh::FromArgs)] +#[argh(subcommand)] +enum Cmd { + Transfer(TransferCmd), + NextBlock(NextBlockCmd), + Abandon(AbandonCmd), + ClearDB(ClearCmd), +} + +#[derive(argh::FromArgs)] +#[argh(subcommand, name = "transfer")] +/// Wait or mine the next block +struct TransferCmd { + #[argh(option, short = 'k')] + /// reserve public key + key: Option<String>, + + #[argh(option, short = 'f', default = "String::from(\"client\")")] + /// sender wallet + from: String, + + #[argh(option, short = 't', default = "String::from(\"wire\")")] + /// receiver wallet + to: String, + + #[argh(positional)] + /// amount to send in btc + amount: f64, +} + +#[derive(argh::FromArgs)] +#[argh(subcommand, name = "nblock")] +/// Wait or mine the next block +struct NextBlockCmd { + #[argh(positional, default = "String::from(\"wire\")")] + /// receiver wallet + to: String, +} + +#[derive(argh::FromArgs)] +#[argh(subcommand, name = "abandon")] +/// Abandon all unconfirmed transaction +struct AbandonCmd { + #[argh(positional, default = "String::from(\"wire\")")] + /// sender wallet + from: String, +} + +#[derive(argh::FromArgs)] +#[argh(subcommand, name = "cleardb")] +/// Clear database +struct ClearCmd { + #[argh(positional)] + /// taler config + config: String, +} + +struct App { + 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 config = BitcoinConfig::load(data_dir).unwrap(); + let client = BtcRpc::common(&config).unwrap(); + + Self { config, client } + } + + pub fn auto_wallet(&mut self, name: &str) -> (BtcRpc, Address) { + // Auto load + if let Err(err) = self.client.load_wallet(name) { + match err { + Error::RPC { code, .. } if code == ErrorCode::RpcWalletAlreadyLoaded => {} + e => Err(e).unwrap(), + } + } + let mut wallet = BtcRpc::wallet(&self.config, name).unwrap(); + let addr = wallet + .get_new_address() + .expect(&format!("Failed to get wallet address {}", name)); + (wallet, addr) + } + + pub fn next_block(&mut self, wallet: &str) { + match self.config.network { + Network::Regtest => { + // Manually mine a block + let (_, addr) = self.auto_wallet(wallet); + self.client.generate(1, &addr).unwrap(); + } + _ => { + // Wait for next network block + self.client.wait_for_new_block(0).ok(); + } + } + } +} + +fn main() { + let args: Args = argh::from_env(); + match args.cmd { + Cmd::Transfer(TransferCmd { + key: _key, + from, + to, + amount, + }) => { + let mut app = App::start(args.datadir); + let (mut client, _) = app.auto_wallet(&from); + let (_, to) = app.auto_wallet(&to); + let tx = client + .send_segwit_key(&to, &Amount::from_btc(amount).unwrap(), &rand_key()) + .unwrap(); + println!("{}", tx); + } + Cmd::NextBlock(NextBlockCmd { to }) => { + let mut app = App::start(args.datadir); + app.next_block(&to); + } + Cmd::Abandon(AbandonCmd { from }) => { + let mut app = App::start(args.datadir); + let (mut wire, _) = app.auto_wallet(&from); + let list = wire.list_since_block(None, 1, false).unwrap(); + for tx in list.transactions { + if tx.category == Category::Send && tx.confirmations == 0 { + wire.abandon_tx(&tx.txid).unwrap(); + } + } + } + Cmd::ClearDB(ClearCmd { config }) => { + let config = Config::load_from_file(&config); + let mut db = Client::connect(&config.db_url, NoTls).unwrap(); + db.execute("DROP TABLE IF EXISTS state, tx_in, tx_out, bounce", &[]) + .unwrap(); + } + } +} diff --git a/btc-wire/src/config.rs b/btc-wire/src/config.rs @@ -8,6 +8,8 @@ use std::{ use bitcoin::Network; use taler_common::log::log::error; +pub const WIRE_WALLET_NAME: &str = "wire"; + /// Default chain dir <https://github.com/bitcoin/bitcoin/blob/master/doc/files.md#data-directory-location> fn chain_dir(network: Network) -> &'static str { match network { diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs @@ -1,6 +1,6 @@ use bitcoin::{hashes::Hash, Address, Amount as BtcAmount, BlockHash, Network, Txid}; use btc_wire::{ - config::BitcoinConfig, + config::{BitcoinConfig, WIRE_WALLET_NAME}, rpc::{self, BtcRpc, Category, ErrorCode}, rpc_utils::{default_data_dir, sender_address}, GetOpReturnErr, GetSegwitErr, @@ -576,9 +576,9 @@ fn main() { info!("Running on {} chain", chain_name); let mut rpc = BtcRpc::common(&btc_config).unwrap(); - rpc.load_wallet(&config.btc_wallet).ok(); - let rpc_listener = AutoReconnectRPC::new(btc_config.clone(), &config.btc_wallet); - let rpc_worker = AutoReconnectRPC::new(btc_config, &config.btc_wallet); + rpc.load_wallet(WIRE_WALLET_NAME).ok(); + let rpc_listener = AutoReconnectRPC::new(btc_config.clone(), WIRE_WALLET_NAME); + let rpc_worker = AutoReconnectRPC::new(btc_config, WIRE_WALLET_NAME); let db_listener = AutoReconnectSql::new(&config.db_url); let db_worker = AutoReconnectSql::new(&config.db_url); diff --git a/db/btc.sql b/db/btc.sql @@ -1,5 +1,3 @@ -DROP TABLE IF EXISTS state, tx_in, tx_out, bounce; - -- Key value state CREATE TABLE state ( name TEXT PRIMARY KEY, diff --git a/makefile b/makefile @@ -1,5 +1,5 @@ install: - cargo install --path btc-wire --bin btc-wire-cli btc-wire + cargo install --path btc-wire --bin btc-wire-cli --bin btc-wire-utils --bin btc-wire cargo install --path wire-gateway test_gateway: diff --git a/taler-common/src/config.rs b/taler-common/src/config.rs @@ -5,6 +5,24 @@ use std::{ }; use url::Url; +pub struct InitConfig { + pub db_url: String, + pub btc_data_dir: Option<PathBuf>, +} + +impl InitConfig { + /// Load from a file + pub fn load_from_file(config_file: impl AsRef<Path>) -> Self { + let conf = ini::Ini::load_from_file(config_file).unwrap(); + assert(section(&conf, "taler"), "CURRENCY", "BTC"); + let self_conf = section(&conf, "depolymerizer-bitcoin"); + Self { + db_url: require(self_conf, "DB_URL", string), + btc_data_dir: path(self_conf, "BTC_DATA_DIR"), + } + } +} + /// Taler config with depolymerizer config #[derive(Debug, Clone, PartialEq, Eq)] pub struct Config { @@ -15,7 +33,6 @@ pub struct Config { pub btc_data_dir: Option<PathBuf>, pub payto: Url, pub confirmation: u8, - pub btc_wallet: String, pub bounce_fee: u64, pub btc_lifetime: Option<u64>, pub http_lifetime: Option<u64>, @@ -36,7 +53,6 @@ impl Config { btc_data_dir: path(self_conf, "BTC_DATA_DIR"), payto: require(self_conf, "PAYTO", url), confirmation: nb(self_conf, "CONFIRMATION").unwrap_or(6), - btc_wallet: string(self_conf, "BTC_WALLET").unwrap_or_else(|| "wire".to_string()), bounce_fee: nb(self_conf, "BOUNCE_FEE").unwrap_or(1000), btc_lifetime: nb(self_conf, "BTC_LIFETIME") .and_then(|nb| (nb != 0).then(|| Some(nb))) diff --git a/test/btc/conflict.sh b/test/btc/conflict.sh @@ -25,7 +25,7 @@ echo "" echo "----- Conflict send -----" echo -n "Making wire transfer to exchange:" -btc-wire-cli -d $BTC_DIR transfer 0.042 > /dev/null +btc-wire-utils -d $BTC_DIR transfer 0.042 > /dev/null next_btc check_balance 9.95799209 0.04200000 echo " OK" @@ -41,7 +41,7 @@ echo " OK" echo -n "Abandon pending transaction:" restart_btc -minrelaytxfee=0.0001 -btc-wire-cli -d $BTC_DIR abandon +btc-wire-utils -d $BTC_DIR abandon check_balance 9.95799209 0.04200000 echo " OK" @@ -92,7 +92,7 @@ echo " OK" echo -n "Abandon pending transaction:" restart_btc -minrelaytxfee=0.0001 -btc-wire-cli -d $BTC_DIR abandon +btc-wire-utils -d $BTC_DIR abandon check_balance 9.95999859 0.04000000 echo " OK" diff --git a/test/btc/fail.sh b/test/btc/fail.sh @@ -26,7 +26,7 @@ echo "----- Handle incoming -----" echo -n "Making wire transfer to exchange:" for n in `$SEQ`; do - btc-wire-cli -d $BTC_DIR transfer 0.000$n > /dev/null + btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc_wire diff --git a/test/btc/hell.sh b/test/btc/hell.sh @@ -37,7 +37,7 @@ echo "Loose second bitcoin node" btc2_deco echo -n "Gen incoming transactions:" -btc-wire-cli -d $BTC_DIR transfer 0.0042 > /dev/null +btc-wire-utils -d $BTC_DIR transfer 0.0042 > /dev/null next_btc # Trigger btc_wire check_balance 9.99579209 0.00420000 echo " OK" @@ -57,8 +57,8 @@ echo " OK" echo -n "Generate conflict:" restart_btc -minrelaytxfee=0.0001 -btc-wire-cli -d $BTC_DIR abandon client -btc-wire-cli -d $BTC_DIR transfer 0.0054 > /dev/null +btc-wire-utils -d $BTC_DIR abandon client +btc-wire-utils -d $BTC_DIR transfer 0.0054 > /dev/null next_btc check_balance 9.99457382 0.00540000 echo " OK" @@ -121,8 +121,8 @@ echo " OK" echo -n "Generate conflict:" restart_btc -minrelaytxfee=0.0001 -btc-wire-cli -d $BTC_DIR abandon client -btc-wire-cli -d $BTC_DIR transfer 0.054 > /dev/null +btc-wire-utils -d $BTC_DIR abandon client +btc-wire-utils -d $BTC_DIR transfer 0.054 > /dev/null next_btc check_balance 9.94597382 0.05400000 echo " OK" diff --git a/test/btc/lifetime.sh b/test/btc/lifetime.sh @@ -33,7 +33,7 @@ echo " OK" echo -n "Do some work:" for n in `$SEQ`; do - btc-wire-cli -d $BTC_DIR transfer 0.000$n > /dev/null + btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc_wire diff --git a/test/btc/reconnect.sh b/test/btc/reconnect.sh @@ -22,7 +22,7 @@ echo "" echo "----- With DB -----" echo "Making wire transfer to exchange:" -btc-wire-cli -d $BTC_DIR transfer 0.000042 > /dev/null +btc-wire-utils -d $BTC_DIR transfer 0.000042 > /dev/null next_btc check_balance 9.99995009 0.00004200 echo -n "Requesting exchange incoming transaction list:" @@ -35,7 +35,7 @@ pg_ctl stop -D $DB_DIR > /dev/null echo "Making incomplete wire transfer to exchange" $BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.00042 &> /dev/null echo -n "Making wire transfer to exchange:" -btc-wire-cli -d $BTC_DIR transfer 0.00004 > /dev/null +btc-wire-utils -d $BTC_DIR transfer 0.00004 > /dev/null next_btc check_balance 9.99948077 0.00050200 echo " OK" diff --git a/test/btc/reorg.sh b/test/btc/reorg.sh @@ -40,7 +40,7 @@ btc2_deco echo -n "Gen incoming transactions:" for n in `$SEQ`; do - btc-wire-cli -d $BTC_DIR transfer 0.000$n > /dev/null + btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc_wire diff --git a/test/btc/stress.sh b/test/btc/stress.sh @@ -26,7 +26,7 @@ echo "----- Handle incoming -----" echo -n "Making wire transfer to exchange:" for n in `$SEQ`; do - btc-wire-cli -d $BTC_DIR transfer 0.000$n > /dev/null + btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null mine_btc # Mine transactions done sleep 3 # Give time for btc_wire worker to process diff --git a/test/btc/wire.sh b/test/btc/wire.sh @@ -26,7 +26,7 @@ echo "----- Receive -----" echo -n "Making wire transfer to exchange:" for n in `$SEQ`; do - btc-wire-cli -d $BTC_DIR transfer 0.000$n > /dev/null + btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc_wire diff --git a/test/common.sh b/test/common.sh @@ -70,12 +70,13 @@ function setup_db() { echo "port=5454" >> $DB_DIR/postgresql.conf pg_ctl start -D $DB_DIR >> log/postgres.log echo "CREATE ROLE postgres LOGIN SUPERUSER PASSWORD 'password'" | psql -p 5454 postgres > /dev/null - psql $DB_URL < db/${SCHEMA:-common.sql} &> /dev/null + btc-wire-cli initdb $CONF > /dev/null } # Erase database function reset_db() { - psql $DB_URL < db/${SCHEMA:-common.sql} > /dev/null + btc-wire-utils cleardb $CONF + btc-wire-cli initdb $CONF } # ----- Bitcoin node ----- # @@ -87,8 +88,10 @@ function init_btc() { BTC_PID="$!" # Wait for RPC server to be online $BTC_CLI -rpcwait getnetworkinfo > /dev/null + # Create wire wallet + btc-wire-cli initwallet $CONF > /dev/null # Load wallets - for wallet in wire client reserve; do + for wallet in client reserve; do $BTC_CLI createwallet $wallet > /dev/null done # Generate addresses diff --git a/test/conf/taler_lifetime.conf b/test/conf/taler_lifetime.conf @@ -9,7 +9,6 @@ DB_URL = postgres://localhost:5454/postgres?user=postgres&password=passwo PORT = 8060 PAYTO = payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj CONFIRMATION = 3 -BTC_WALLET = wire BOUNCE_FEE = 1000 HTTP_LIFETIME = 10 BTC_LIFETIME = 10 \ No newline at end of file diff --git a/test/conf/taler_test.conf b/test/conf/taler_test.conf @@ -9,5 +9,4 @@ DB_URL = postgres://localhost:5454/postgres?user=postgres&password=passwor PORT = 8060 PAYTO = payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj CONFIRMATION = 3 -BTC_WALLET = wire BOUNCE_FEE = 1000 \ No newline at end of file diff --git a/test/gateway/api.sh b/test/gateway/api.sh @@ -18,7 +18,7 @@ trap cleanup EXIT source "${BASH_SOURCE%/*}/../common.sh" ADDRESS=mpTJZxWPerz1Gife6mQSdHT8mMuJK6FP85 -SCHEMA=btc.sql +SCHEMA=btc.sq echo "----- Setup -----" echo "Load config file"