commit 6944f01b20abf6922a0453960df6b728941ef4e0
parent c9693b859e564174db47c60859742a0ee314ab1b
Author: Antoine A <>
Date: Thu, 13 Jan 2022 19:16:17 +0100
First bootstrap cli draft
Diffstat:
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"