depolymerization

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

btc.rs (35032B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022-2025, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use std::{
     18     net::{IpAddr, Ipv4Addr, SocketAddr},
     19     ops::{Deref, DerefMut},
     20     path::Path,
     21     time::Duration,
     22 };
     23 
     24 use bitcoin::{Address, Amount};
     25 use depolymerizer_bitcoin::{
     26     CONFIG_SOURCE,
     27     config::{RpcAuth, RpcCfg, ServeCfg, WorkerCfg},
     28     payto::BtcWallet,
     29     rpc::{Category, Error, ErrorCode, Rpc},
     30     rpc_utils::segwit_min_amount,
     31     taler_utils::btc_to_taler,
     32 };
     33 use ini::Ini;
     34 use taler_common::{
     35     api::{EddsaPublicKey, ShortHashCode},
     36     config::Config,
     37     types::{
     38         base32::Base32,
     39         payto::{Payto, PaytoImpl},
     40     },
     41 };
     42 
     43 use crate::{
     44     retry, retry_opt,
     45     utils::{ChildGuard, TalerCtx, TestCtx, cmd_redirect, patch_config, transfer, unused_port},
     46 };
     47 
     48 pub struct BtcCtx {
     49     btc_node: ChildGuard,
     50     _btc_node2: ChildGuard,
     51     common_rpc: Rpc,
     52     common_rpc2: Rpc,
     53     wire_rpc: Rpc,
     54     client_rpc: Rpc,
     55     reserve_rpc: Rpc,
     56     wire_addr: Address,
     57     pub client_addr: Address,
     58     reserve_addr: Address,
     59     worker_cfg: WorkerCfg,
     60     serve_cfg: ServeCfg,
     61     conf: u16,
     62     ctx: TalerCtx,
     63     node2_addr: String,
     64 }
     65 
     66 impl Deref for BtcCtx {
     67     type Target = TalerCtx;
     68 
     69     fn deref(&self) -> &Self::Target {
     70         &self.ctx
     71     }
     72 }
     73 
     74 impl DerefMut for BtcCtx {
     75     fn deref_mut(&mut self) -> &mut Self::Target {
     76         &mut self.ctx
     77     }
     78 }
     79 
     80 impl BtcCtx {
     81     pub async fn config(
     82         ctx: &TestCtx,
     83         btc_patch: impl FnOnce(&mut Ini, &Path),
     84         cfg_patch: impl FnOnce(&mut Ini, &Path),
     85     ) {
     86         // Patch configs
     87         let port = unused_port();
     88         let rpc_port = unused_port();
     89 
     90         patch_config(
     91             "testbench/conf/bitcoin.conf",
     92             ctx.dir.join("bitcoin.conf"),
     93             |cfg| {
     94                 cfg.with_section(Some("regtest"))
     95                     .set("bind", format!("127.0.0.1:{port}"))
     96                     .set("rpcport", format!("{rpc_port}"));
     97                 btc_patch(cfg, &ctx.dir)
     98             },
     99         );
    100         patch_config(
    101             "testbench/conf/taler_btc.conf",
    102             ctx.dir.join("config.conf"),
    103             |cfg| {
    104                 cfg.with_section(Some("depolymerizer-bitcoin-worker"))
    105                     .set("RPC_BIND", format!("127.0.0.1:{rpc_port}"));
    106                 cfg_patch(cfg, &ctx.dir)
    107             },
    108         );
    109         // Load config
    110         let cfg = Config::load(CONFIG_SOURCE, Some(ctx.dir.join("config.conf"))).unwrap();
    111         let rpc_cfg = RpcCfg::parse(&cfg).unwrap();
    112         // Start bitcoin nodes
    113         let _node = cmd_redirect(
    114             "bitcoind",
    115             &[&format!("-datadir={}", ctx.dir.to_string_lossy())],
    116             ctx.log("bitcoind"),
    117         );
    118         // Connect
    119         retry_opt!(async {
    120             let mut client = Rpc::common(&rpc_cfg).await?;
    121             client.get_blockchain_info().await?;
    122             Ok::<_, anyhow::Error>(())
    123         });
    124     }
    125 
    126     fn patch_btc_config(from: impl AsRef<Path>, to: impl AsRef<Path>, port: u16, rpc_port: u16) {
    127         patch_config(from, to, |cfg| {
    128             cfg.with_section(Some("regtest"))
    129                 .set("bind", format!("127.0.0.1:{port}"))
    130                 .set("rpcport", format!("{rpc_port}"));
    131         })
    132     }
    133 
    134     pub async fn setup(ctx: &TestCtx, config: &str, stressed: bool) -> Self {
    135         let mut ctx = TalerCtx::new(ctx, "depolymerizer-bitcoin", config, stressed);
    136 
    137         // Choose unused port
    138         let btc_port = unused_port();
    139         let btc_rpc_port = unused_port();
    140         let btc2_port = unused_port();
    141         let btc2_rpc_port = unused_port();
    142 
    143         // Bitcoin config
    144         Self::patch_btc_config(
    145             "testbench/conf/bitcoin.conf",
    146             ctx.wire_dir.join("bitcoin.conf"),
    147             btc_port,
    148             btc_rpc_port,
    149         );
    150         Self::patch_btc_config(
    151             "testbench/conf/bitcoin.conf",
    152             ctx.wire2_dir.join("bitcoin.conf"),
    153             btc2_port,
    154             btc2_rpc_port,
    155         );
    156         patch_config(&ctx.conf, &ctx.conf, |cfg| {
    157             cfg.with_section(Some("depolymerizer-bitcoin-worker"))
    158                 .set("RPC_BIND", format!("127.0.0.1:{btc_rpc_port}"))
    159                 .set("WALLET_NAME", "wire")
    160                 .set(
    161                     "RPC_COOKIE_FILE",
    162                     ctx.wire_dir.join("regtest/.cookie").to_string_lossy(),
    163                 );
    164         });
    165 
    166         // Load config
    167         let config = Config::load(CONFIG_SOURCE, Some(&ctx.conf)).unwrap();
    168         let cfg = WorkerCfg::parse(&config).unwrap();
    169         // Start bitcoin nodes
    170         let btc_node = cmd_redirect(
    171             "bitcoind",
    172             &[&format!("-datadir={}", ctx.wire_dir.to_string_lossy())],
    173             ctx.log("bitcoind"),
    174         );
    175         let _btc_node2 = cmd_redirect(
    176             "bitcoind",
    177             &[&format!("-datadir={}", ctx.wire2_dir.to_string_lossy())],
    178             ctx.log("bitcoind2"),
    179         );
    180 
    181         // Setup wallets
    182         let mut common_rpc = retry_opt!(Rpc::common(&cfg.rpc_cfg));
    183         retry_opt!(common_rpc.get_blockchain_info());
    184         let node2_addr = format!("127.0.0.1:{btc2_port}");
    185         common_rpc.add_node(&node2_addr).await.unwrap();
    186 
    187         for name in ["wire", "client", "reserve"] {
    188             if let Err(e) = common_rpc.load_wallet(name).await {
    189                 if let Error::RPC {
    190                     code: ErrorCode::RpcWalletNotFound,
    191                     ..
    192                 } = e
    193                 {
    194                     common_rpc.create_wallet(name, "").await.unwrap();
    195                 } else {
    196                     break;
    197                 }
    198             }
    199         }
    200         let common_rpc2 = retry_opt!(Rpc::common(&RpcCfg {
    201             addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), btc2_rpc_port),
    202             auth: RpcAuth::Cookie(
    203                 ctx.wire2_dir
    204                     .join("regtest/.cookie")
    205                     .to_string_lossy()
    206                     .to_string(),
    207             ),
    208         }));
    209 
    210         // Generate money
    211         let mut reserve_rpc = Rpc::wallet(&cfg.rpc_cfg, "reserve").await.unwrap();
    212         let mut client_rpc = Rpc::wallet(&cfg.rpc_cfg, "client").await.unwrap();
    213         let mut wire_rpc = Rpc::wallet(&cfg.rpc_cfg, "wire").await.unwrap();
    214         let reserve_addr = reserve_rpc.gen_addr().await.unwrap();
    215         let client_addr = client_rpc.gen_addr().await.unwrap();
    216         let wire_addr = wire_rpc.gen_addr().await.unwrap();
    217         common_rpc.mine(101, &reserve_addr).await.unwrap();
    218         reserve_rpc
    219             .send(&client_addr, &(Amount::ONE_BTC * 10), None, false)
    220             .await
    221             .unwrap();
    222         common_rpc.mine(1, &reserve_addr).await.unwrap();
    223 
    224         patch_config(&ctx.conf, &ctx.conf, |cfg| {
    225             cfg.with_section(Some("depolymerizer-bitcoin"))
    226                 .set("NAME", "Exchange Owner")
    227                 .set("WALLET", wire_addr.to_string());
    228         });
    229 
    230         let config = Config::load(CONFIG_SOURCE, Some(&ctx.conf)).unwrap();
    231         let serve_cfg = ServeCfg::parse(&config).unwrap();
    232 
    233         // Setup & run
    234         ctx.dbinit();
    235         ctx.setup();
    236         ctx.run().await;
    237 
    238         Self {
    239             ctx,
    240             btc_node,
    241             common_rpc,
    242             wire_rpc,
    243             client_rpc,
    244             reserve_rpc,
    245             wire_addr,
    246             client_addr,
    247             reserve_addr,
    248             conf: cfg.conf as u16,
    249             worker_cfg: cfg,
    250             serve_cfg,
    251             _btc_node2,
    252             common_rpc2,
    253             node2_addr,
    254         }
    255     }
    256 
    257     pub fn reset_db(&mut self) {
    258         self.ctx.reset_db();
    259         self.ctx.setup();
    260     }
    261 
    262     pub async fn stop_node(&mut self) {
    263         // We need to kill bitcoin gracefully to avoid corruption
    264         self.common_rpc.stop().await.unwrap();
    265         self.btc_node.0.wait().unwrap();
    266     }
    267 
    268     pub async fn cluster_deco(&mut self) {
    269         self.common_rpc
    270             .disconnect_node(&self.node2_addr)
    271             .await
    272             .unwrap();
    273     }
    274 
    275     pub async fn cluster_fork(&mut self) -> u16 {
    276         let node1_height = self.common_rpc.get_blockchain_info().await.unwrap().blocks;
    277         let node2_height = self.common_rpc2.get_blockchain_info().await.unwrap().blocks;
    278         let diff = node1_height - node2_height;
    279         self.common_rpc2
    280             .mine(diff as u16 + 1, &self.reserve_addr)
    281             .await
    282             .unwrap();
    283         self.common_rpc.add_node(&self.node2_addr).await.unwrap();
    284         diff as u16 + 2
    285     }
    286 
    287     pub async fn restart_node(&mut self, additional_args: &[&str]) {
    288         self.stop_node().await;
    289         self.resume_node(additional_args).await;
    290     }
    291 
    292     pub async fn resume_node(&mut self, additional_args: &[&str]) {
    293         let datadir = format!("-datadir={}", self.ctx.wire_dir.to_string_lossy());
    294         let mut args = vec![datadir.as_str()];
    295         args.extend_from_slice(additional_args);
    296         self.btc_node = cmd_redirect("bitcoind", &args, self.ctx.log("bitcoind"));
    297         self.common_rpc = retry_opt!(Rpc::common(&self.worker_cfg.rpc_cfg));
    298         self.common_rpc.add_node(&self.node2_addr).await.unwrap();
    299         for name in ["client", "reserve", "wire"] {
    300             self.common_rpc.load_wallet(name).await.ok();
    301         }
    302 
    303         self.reserve_rpc = Rpc::wallet(&self.worker_cfg.rpc_cfg, "reserve")
    304             .await
    305             .unwrap();
    306         self.client_rpc = Rpc::wallet(&self.worker_cfg.rpc_cfg, "client")
    307             .await
    308             .unwrap();
    309         self.wire_rpc = Rpc::wallet(&self.worker_cfg.rpc_cfg, "wire").await.unwrap();
    310         tokio::time::sleep(Duration::from_millis(100)).await;
    311     }
    312 
    313     /* ----- Transaction ------ */
    314 
    315     pub async fn credit(&mut self, amount: Amount, metadata: &EddsaPublicKey) {
    316         while let Err(e) = self
    317             .client_rpc
    318             .send_segwit_key(&self.wire_addr, &amount, metadata)
    319             .await
    320         {
    321             match e {
    322                 Error::RPC {
    323                     code: ErrorCode::RpcWalletError,
    324                     ..
    325                 } => {
    326                     self.mine(1).await;
    327                 }
    328                 _ => panic!("{e:?}"),
    329             }
    330         }
    331     }
    332 
    333     pub async fn debit(&mut self, amount: Amount, metadata: &ShortHashCode) {
    334         transfer(
    335             &self.ctx.gateway_url,
    336             metadata,
    337             Payto::new(BtcWallet(self.client_addr.clone())).as_full_uri("name"),
    338             &btc_to_taler(&amount.to_signed().unwrap(), &self.worker_cfg.currency),
    339         )
    340         .await
    341     }
    342 
    343     pub async fn malformed_credit(&mut self, amount: &Amount) {
    344         self.client_rpc
    345             .send(&self.wire_addr, amount, None, false)
    346             .await
    347             .unwrap();
    348     }
    349 
    350     pub async fn reset_wallet(&mut self) {
    351         let amount = self.wire_balance().await;
    352         self.wire_rpc
    353             .send(&self.client_addr, &amount, None, true)
    354             .await
    355             .unwrap();
    356         self.next_block().await;
    357     }
    358 
    359     async fn abandon(rpc: &mut Rpc) {
    360         let list = rpc.list_since_block(None, 1).await.unwrap();
    361         for tx in list.transactions {
    362             if tx.category == Category::Send && tx.confirmations == 0 {
    363                 rpc.abandon_tx(&tx.txid).await.unwrap();
    364             }
    365         }
    366     }
    367 
    368     pub async fn abandon_wire(&mut self) {
    369         Self::abandon(&mut self.wire_rpc).await;
    370     }
    371 
    372     pub async fn abandon_client(&mut self) {
    373         Self::abandon(&mut self.client_rpc).await;
    374     }
    375 
    376     /* ----- Mining ----- */
    377 
    378     async fn mine(&mut self, nb: u16) {
    379         self.common_rpc.mine(nb, &self.reserve_addr).await.unwrap();
    380     }
    381 
    382     pub async fn next_conf(&mut self) {
    383         self.mine(self.conf).await
    384     }
    385 
    386     pub async fn next_block(&mut self) {
    387         self.mine(1).await
    388     }
    389 
    390     pub async fn mine_pending(&mut self) {
    391         while self.common_rpc.get_mempool_info().await.unwrap().size > 0 {
    392             self.mine(1).await
    393         }
    394     }
    395 
    396     pub async fn mine_conf(&mut self) {
    397         self.mine_pending().await;
    398         self.next_conf().await;
    399     }
    400 
    401     /* ----- Balances ----- */
    402 
    403     pub async fn client_balance(&mut self) -> Amount {
    404         self.client_rpc.get_balance().await.unwrap()
    405     }
    406 
    407     pub async fn wire_balance(&mut self) -> Amount {
    408         self.wire_rpc.get_balance().await.unwrap()
    409     }
    410 
    411     pub async fn expect_c_balance(&mut self, balance: Amount, mine: bool) {
    412         retry!(async {
    413             let check = balance == self.client_balance().await;
    414             if !check && mine {
    415                 self.mine_conf().await;
    416             }
    417             check
    418         });
    419     }
    420 
    421     pub async fn expect_w_balance(&mut self, balance: Amount, mine: bool) {
    422         retry!(async {
    423             let check = balance == self.wire_balance().await;
    424             if !check && mine {
    425                 self.mine_conf().await;
    426             }
    427             check
    428         });
    429     }
    430 
    431     pub async fn expect_w_balance_less(&mut self, balance: Amount, mine: bool) {
    432         retry!(async {
    433             let check = self.wire_balance().await < balance;
    434             if !check && mine {
    435                 self.mine_conf().await;
    436             }
    437             check
    438         });
    439     }
    440 
    441     /* ----- Wire Gateway ----- */
    442 
    443     pub async fn expect_credits(&mut self, txs: &[(EddsaPublicKey, Amount)], mine: bool) {
    444         let txs: Vec<_> = txs
    445             .iter()
    446             .map(|(metadata, amount)| {
    447                 (
    448                     metadata.clone(),
    449                     btc_to_taler(&amount.to_signed().unwrap(), &self.worker_cfg.currency),
    450                 )
    451             })
    452             .collect();
    453         retry!(async {
    454             let check = self.ctx.expect_credits(&txs).await;
    455             if !check && mine {
    456                 self.mine_conf().await;
    457             }
    458             check
    459         });
    460     }
    461 
    462     pub async fn expect_debits(&mut self, txs: &[(ShortHashCode, Amount)], mine: bool) {
    463         let txs: Vec<_> = txs
    464             .iter()
    465             .map(|(metadata, amount)| {
    466                 (
    467                     metadata.clone(),
    468                     btc_to_taler(&amount.to_signed().unwrap(), &self.worker_cfg.currency),
    469                 )
    470             })
    471             .collect();
    472         retry!(async {
    473             let check = self.ctx.expect_debits(&txs).await;
    474             if !check && mine {
    475                 self.mine_conf().await;
    476             }
    477             check
    478         });
    479     }
    480 }
    481 
    482 /// Test btc-wire correctly receive and send transactions on the blockchain
    483 pub async fn wire(ctx: TestCtx) {
    484     ctx.step("Setup");
    485     let mut ctx = BtcCtx::setup(&ctx, "taler_btc.conf", false).await;
    486 
    487     ctx.step("Credit");
    488     {
    489         // Send transactions
    490         let mut balance = ctx.wire_balance().await;
    491         let mut txs = Vec::new();
    492         for n in 10..100 {
    493             let metadata = EddsaPublicKey::rand();
    494             let amount = Amount::from_sat(n * 1000);
    495             ctx.credit(amount, &metadata).await;
    496             txs.push((metadata, amount));
    497             balance += amount;
    498         }
    499         ctx.expect_credits(&txs, true).await;
    500         ctx.expect_w_balance(balance, false).await;
    501     };
    502 
    503     ctx.step("Debit");
    504     {
    505         let mut balance = ctx.client_balance().await;
    506         let mut txs = Vec::new();
    507         for n in 10..100 {
    508             let metadata = Base32::rand();
    509             let amount = Amount::from_sat(n * 100);
    510             balance += amount;
    511             ctx.debit(amount, &metadata).await;
    512             txs.push((metadata, amount));
    513         }
    514         ctx.expect_debits(&txs, true).await;
    515         ctx.expect_c_balance(balance, false).await;
    516     }
    517 
    518     ctx.step("Bounce");
    519     {
    520         ctx.reset_wallet().await;
    521         let mut balance = ctx.wire_balance().await;
    522         for n in 10..40 {
    523             ctx.malformed_credit(&Amount::from_sat(n * 1000)).await;
    524             balance += ctx.worker_cfg.bounce_fee;
    525         }
    526         ctx.expect_w_balance(balance, true).await;
    527     }
    528 }
    529 
    530 /// Check btc-wire and wire-gateway correctly stop when a lifetime limit is configured
    531 pub async fn lifetime(ctx: TestCtx) {
    532     ctx.step("Setup");
    533     let mut ctx = BtcCtx::setup(&ctx, "taler_btc_lifetime.conf", false).await;
    534     ctx.step("Check lifetime");
    535     // Start up
    536     retry!(async { ctx.wire_running() && ctx.gateway_running() });
    537     // Consume wire lifetime
    538     for _ in 0..ctx.worker_cfg.lifetime.unwrap() {
    539         ctx.mine(1).await;
    540     }
    541     retry!(async {
    542         let check = !ctx.wire_running() && ctx.gateway_running();
    543         if !check {
    544             ctx.mine(1).await;
    545         }
    546         check
    547     });
    548     // Consume gateway lifetime
    549     for _ in 0..=ctx.serve_cfg.lifetime.unwrap() {
    550         ctx.debit(segwit_min_amount(), &Base32::rand()).await;
    551     }
    552     // End down
    553     retry!(async { !ctx.wire_running() && !ctx.gateway_running() });
    554 }
    555 
    556 /// Check the capacity of wire-gateway and btc-wire to recover from database and node loss
    557 pub async fn reconnect(ctx: TestCtx) {
    558     // TODO check recover metadata
    559     ctx.step("Setup");
    560     let mut ctx = BtcCtx::setup(&ctx, "taler_btc.conf", false).await;
    561 
    562     let mut credits = Vec::new();
    563     let mut debits = Vec::new();
    564 
    565     ctx.step("With DB");
    566     {
    567         let metadata = EddsaPublicKey::rand();
    568         let amount = Amount::from_sat(42000);
    569         ctx.credit(amount, &metadata).await;
    570         credits.push((metadata, amount));
    571         ctx.mine_conf().await;
    572         ctx.expect_credits(&credits, false).await;
    573     };
    574 
    575     ctx.step("Without DB");
    576     {
    577         ctx.stop_db();
    578         ctx.malformed_credit(&Amount::from_sat(24000)).await;
    579         let metadata = EddsaPublicKey::rand();
    580         let amount = Amount::from_sat(40000);
    581         ctx.credit(amount, &metadata).await;
    582         credits.push((metadata, amount));
    583         ctx.stop_node().await;
    584         ctx.expect_gateway_down().await;
    585     }
    586 
    587     ctx.step("Reconnect DB");
    588     {
    589         ctx.resume_db();
    590         ctx.resume_node(&[]).await;
    591         let metadata = Base32::rand();
    592         let amount = Amount::from_sat(2000);
    593         ctx.debit(amount, &metadata).await;
    594         debits.push((metadata, amount));
    595         ctx.expect_debits(&debits, true).await;
    596         ctx.expect_credits(&credits, false).await;
    597     }
    598 
    599     ctx.step("Recover DB");
    600     {
    601         let balance = ctx.wire_balance().await;
    602         ctx.reset_db();
    603         ctx.expect_debits(&debits, false).await;
    604         ctx.expect_credits(&credits, false).await;
    605         ctx.expect_w_balance(balance, false).await;
    606     }
    607 }
    608 
    609 /// Test btc-wire ability to recover from errors in correctness critical paths and prevent concurrent sending
    610 pub async fn stress(ctx: TestCtx) {
    611     ctx.step("Setup");
    612     let mut ctx = BtcCtx::setup(&ctx, "taler_btc.conf", true).await;
    613 
    614     let mut credits = Vec::new();
    615     let mut debits = Vec::new();
    616 
    617     ctx.step("Credit");
    618     {
    619         let mut balance = ctx.wire_balance().await;
    620         for n in 10..30 {
    621             let metadata = EddsaPublicKey::rand();
    622             let amount = Amount::from_sat(n * 10000);
    623             ctx.credit(amount, &metadata).await;
    624             credits.push((metadata, amount));
    625             balance += amount;
    626         }
    627         ctx.expect_credits(&credits, true).await;
    628         ctx.expect_w_balance(balance, false).await;
    629     };
    630 
    631     ctx.step("Debit");
    632     {
    633         let mut balance = ctx.client_balance().await;
    634         for n in 10..30 {
    635             let metadata = Base32::rand();
    636             let amount = Amount::from_sat(n * 100);
    637             balance += amount;
    638             ctx.debit(amount, &metadata).await;
    639             debits.push((metadata, amount));
    640         }
    641         ctx.expect_debits(&debits, true).await;
    642         ctx.expect_c_balance(balance, false).await;
    643     }
    644 
    645     ctx.step("Bounce");
    646     {
    647         ctx.reset_wallet().await;
    648         let mut balance = ctx.wire_balance().await;
    649         for n in 10..30 {
    650             ctx.malformed_credit(&Amount::from_sat(n * 1000)).await;
    651             balance += ctx.worker_cfg.bounce_fee;
    652         }
    653         ctx.expect_w_balance(balance, true).await;
    654     }
    655 
    656     ctx.step("Recover DB");
    657     {
    658         let balance = ctx.wire_balance().await;
    659         ctx.reset_db();
    660         ctx.expect_debits(&debits, false).await;
    661         ctx.expect_credits(&credits, false).await;
    662         ctx.expect_w_balance(balance, false).await;
    663     }
    664 }
    665 
    666 /// Test btc-wire ability to handle conflicting outgoing transactions
    667 pub async fn conflict(tctx: TestCtx) {
    668     tctx.step("Setup");
    669     let mut ctx = BtcCtx::setup(&tctx, "taler_btc.conf", false).await;
    670 
    671     ctx.step("Conflict send");
    672     {
    673         // Perform credit
    674         let amount = Amount::from_sat(4200000);
    675         ctx.credit(amount, &EddsaPublicKey::rand()).await;
    676         ctx.next_conf().await;
    677         ctx.expect_w_balance(amount, false).await;
    678         let client = ctx.client_balance().await;
    679         let wire = ctx.wire_balance().await;
    680 
    681         // Perform debit
    682         ctx.debit(Amount::from_sat(400000), &Base32::rand()).await;
    683         ctx.expect_w_balance_less(wire, false).await;
    684 
    685         // Abandon pending transaction
    686         ctx.restart_node(&["-minrelaytxfee=0.0001"]).await;
    687         ctx.abandon_wire().await;
    688         ctx.expect_c_balance(client, false).await;
    689         ctx.expect_w_balance(wire, false).await;
    690 
    691         // Generate conflict
    692         ctx.debit(Amount::from_sat(500000), &Base32::rand()).await;
    693         ctx.expect_w_balance_less(wire, false).await;
    694 
    695         // Resend conflicting transaction
    696         let wire = ctx.wire_balance().await;
    697         ctx.restart_node(&[]).await;
    698         ctx.expect_w_balance_less(wire, true).await;
    699     }
    700 
    701     ctx.step("Setup");
    702     drop(ctx);
    703     let mut ctx = BtcCtx::setup(&tctx, "taler_btc.conf", false).await;
    704     ctx.credit(Amount::from_sat(3000000), &EddsaPublicKey::rand())
    705         .await;
    706     ctx.next_block().await;
    707 
    708     ctx.step("Conflict bounce");
    709     {
    710         // Perform bounce
    711         let wire = ctx.wire_balance().await;
    712         let bounce_amount = Amount::from_sat(4000000);
    713         ctx.malformed_credit(&bounce_amount).await;
    714         ctx.next_conf().await;
    715         let fee = ctx.worker_cfg.bounce_fee;
    716         ctx.expect_w_balance(wire + fee, true).await;
    717 
    718         // Abandon pending transaction
    719         ctx.restart_node(&["-minrelaytxfee=0.0001"]).await;
    720         ctx.abandon_wire().await;
    721         ctx.expect_w_balance(wire + bounce_amount, false).await;
    722 
    723         // Generate conflict
    724         ctx.debit(Amount::from_sat(50000), &Base32::rand()).await;
    725         ctx.expect_w_balance_less(wire + bounce_amount, false).await;
    726 
    727         // Resend conflicting transaction
    728         let wire = ctx.wire_balance().await;
    729         ctx.restart_node(&[]).await;
    730         ctx.expect_w_balance_less(wire, true).await;
    731     }
    732 }
    733 
    734 /// Test btc-wire correctness when a blockchain reorganization occurs
    735 pub async fn reorg(ctx: TestCtx) {
    736     ctx.step("Setup");
    737     let mut ctx = BtcCtx::setup(&ctx, "taler_btc.conf", false).await;
    738 
    739     ctx.step("Handle reorg incoming transactions");
    740     {
    741         // Loose second bitcoin node
    742         ctx.cluster_deco().await;
    743 
    744         // Perform credits
    745         let before = ctx.wire_balance().await;
    746         for n in 10..21 {
    747             ctx.credit(Amount::from_sat(n * 10000), &EddsaPublicKey::rand())
    748                 .await;
    749         }
    750         ctx.mine_conf().await;
    751         let after = ctx.wire_balance().await;
    752 
    753         // Perform fork and check btc-wire hard error
    754         ctx.expect_gateway_up().await;
    755         let fork = ctx.cluster_fork().await;
    756         ctx.expect_w_balance(before, false).await;
    757         ctx.expect_gateway_down().await;
    758 
    759         // Recover orphaned transaction
    760         ctx.mine(fork).await;
    761         ctx.expect_w_balance(after, false).await;
    762         ctx.expect_gateway_up().await;
    763     }
    764 
    765     ctx.step("Handle reorg outgoing transactions");
    766     {
    767         // Loose second bitcoin node
    768         ctx.cluster_deco().await;
    769 
    770         // Perform debits
    771         let before = ctx.client_balance().await;
    772         let mut after = ctx.client_balance().await;
    773         for n in 10..21 {
    774             let amount = Amount::from_sat(n * 100);
    775             ctx.debit(amount, &Base32::rand()).await;
    776             after += amount;
    777         }
    778         ctx.expect_c_balance(after, true).await;
    779 
    780         // Perform fork and check btc-wire still up
    781         ctx.expect_gateway_up().await;
    782         ctx.cluster_fork().await;
    783         ctx.expect_c_balance(before, false).await;
    784         ctx.expect_gateway_up().await;
    785 
    786         // Recover orphaned transaction
    787         ctx.next_conf().await;
    788         ctx.expect_c_balance(after, false).await;
    789     }
    790 
    791     ctx.step("Handle reorg bounce");
    792     {
    793         ctx.reset_wallet().await;
    794 
    795         // Loose second bitcoin node
    796         ctx.cluster_deco().await;
    797 
    798         // Perform bounce
    799         let before = ctx.wire_balance().await;
    800         let mut after = ctx.wire_balance().await;
    801         for n in 10..21 {
    802             ctx.malformed_credit(&Amount::from_sat(n * 1000)).await;
    803             after += ctx.worker_cfg.bounce_fee;
    804         }
    805         ctx.expect_w_balance(after, true).await;
    806 
    807         // Perform fork and check btc-wire hard error
    808         ctx.expect_gateway_up().await;
    809         let fork = ctx.cluster_fork().await;
    810         ctx.expect_w_balance(before, false).await;
    811         ctx.expect_gateway_down().await;
    812 
    813         // Recover orphaned transaction
    814         ctx.mine(fork).await;
    815         ctx.expect_w_balance(after, false).await;
    816         ctx.expect_gateway_up().await;
    817     }
    818 }
    819 
    820 /// Test btc-wire correctness when a blockchain reorganization occurs leading to past incoming transaction conflict
    821 pub async fn hell(tctx: TestCtx) {
    822     macro_rules! step {
    823         ($ctx:ident, $action:expr) => {
    824             // Loose second bitcoin node
    825             $ctx.cluster_deco().await;
    826 
    827             // Perform action
    828             $action;
    829 
    830             // Perform fork and check btc-wire hard error
    831             $ctx.expect_gateway_up().await;
    832             $ctx.cluster_fork().await;
    833             $ctx.expect_gateway_down().await;
    834 
    835             // Generate conflict
    836             $ctx.restart_node(&["-minrelaytxfee=0.001"]).await;
    837             $ctx.abandon_client().await;
    838             let amount = Amount::from_sat(54000);
    839             $ctx.credit(amount, &EddsaPublicKey::rand()).await;
    840             $ctx.expect_w_balance(amount, true).await;
    841 
    842             // Check btc-wire suspend operation
    843             let bounce_amount = Amount::from_sat(34000);
    844             $ctx.malformed_credit(&bounce_amount).await;
    845             $ctx.next_conf().await;
    846             $ctx.expect_w_balance(amount + bounce_amount, true).await;
    847             $ctx.expect_gateway_down().await;
    848         };
    849     }
    850 
    851     tctx.step("Setup");
    852     let mut ctx = BtcCtx::setup(&tctx, "taler_btc.conf", false).await;
    853     ctx.step("Handle reorg conflicting incoming credit");
    854     step!(ctx, {
    855         let amount = Amount::from_sat(420000);
    856         ctx.credit(amount, &EddsaPublicKey::rand()).await;
    857         ctx.mine_conf().await;
    858         ctx.expect_w_balance(amount, true).await;
    859     });
    860 
    861     drop(ctx);
    862     tctx.step("Setup");
    863     let mut ctx = BtcCtx::setup(&tctx, "taler_btc.conf", false).await;
    864     ctx.step("Handle reorg conflicting bounce");
    865     step!(ctx, {
    866         let amount = Amount::from_sat(420000);
    867         ctx.malformed_credit(&amount).await;
    868         ctx.mine_conf().await;
    869         let fee = ctx.worker_cfg.bounce_fee;
    870         ctx.expect_w_balance(fee, true).await;
    871     });
    872 }
    873 
    874 /// Test btc-wire ability to learn and protect itself from blockchain behavior
    875 pub async fn analysis(ctx: TestCtx) {
    876     ctx.step("Setup");
    877     let mut ctx = BtcCtx::setup(&ctx, "taler_btc.conf", false).await;
    878 
    879     ctx.step("Learn from reorg");
    880 
    881     // Loose second bitcoin node
    882     ctx.cluster_deco().await;
    883 
    884     // Perform credit
    885     let before = ctx.wire_balance().await;
    886     ctx.credit(Amount::from_sat(42000), &EddsaPublicKey::rand())
    887         .await;
    888     ctx.mine_conf().await;
    889     let after = ctx.wire_balance().await;
    890 
    891     // Perform fork and check btc-wire hard error
    892     ctx.expect_gateway_up().await;
    893     ctx.cluster_fork().await;
    894     ctx.expect_w_balance(before, false).await;
    895     ctx.expect_gateway_down().await;
    896 
    897     // Recover orphaned transaction
    898     ctx.mine_conf().await;
    899     ctx.next_block().await; // Conf have changed
    900     ctx.expect_w_balance(after, false).await;
    901     ctx.expect_gateway_up().await;
    902 
    903     // Loose second bitcoin node
    904     ctx.cluster_deco().await;
    905 
    906     // Perform credit
    907     let before = ctx.wire_balance().await;
    908     ctx.credit(Amount::from_sat(42000), &EddsaPublicKey::rand())
    909         .await;
    910     ctx.mine_conf().await;
    911 
    912     // Perform fork and check btc-wire learned from previous attack
    913     ctx.expect_gateway_up().await;
    914     ctx.cluster_fork().await;
    915     ctx.expect_w_balance(before, false).await;
    916     ctx.expect_gateway_up().await;
    917 }
    918 
    919 /// Test btc-wire ability to handle stuck transaction correctly
    920 pub async fn bumpfee(tctx: TestCtx) {
    921     tctx.step("Setup");
    922     let mut ctx = BtcCtx::setup(&tctx, "taler_btc_bump.conf", false).await;
    923 
    924     // Perform credits to allow wire to perform debits latter
    925     for n in 10..13 {
    926         ctx.credit(Amount::from_sat(n * 100000), &EddsaPublicKey::rand())
    927             .await;
    928     }
    929     ctx.mine_conf().await;
    930 
    931     ctx.step("Bump fee");
    932     {
    933         // Perform debit
    934         let mut client = ctx.client_balance().await;
    935         let wire = ctx.wire_balance().await;
    936         let amount = Amount::from_sat(40000);
    937         ctx.debit(amount, &Base32::rand()).await;
    938         ctx.expect_w_balance_less(wire, false).await;
    939 
    940         // Bump min relay fee making the previous debit stuck
    941         ctx.restart_node(&["-minrelaytxfee=0.0001"]).await;
    942 
    943         // Check bump happen
    944         client += amount;
    945         ctx.expect_c_balance(client, true).await;
    946     }
    947 
    948     ctx.step("Bump fee reorg");
    949     {
    950         // Loose second bitcoin node
    951         ctx.cluster_deco().await;
    952 
    953         // Perform debit
    954         let mut client = ctx.client_balance().await;
    955         let wire = ctx.wire_balance().await;
    956         let amount = Amount::from_sat(40000);
    957         ctx.debit(amount, &Base32::rand()).await;
    958         ctx.expect_w_balance_less(wire, false).await;
    959 
    960         // Bump min relay fee and fork making the previous debit stuck and problematic
    961         ctx.cluster_fork().await;
    962         ctx.restart_node(&["-minrelaytxfee=0.0001"]).await;
    963 
    964         // Check bump happen
    965         client += amount;
    966         ctx.expect_c_balance(client, true).await;
    967     }
    968     ctx.step("Setup");
    969     drop(ctx);
    970     let mut ctx = BtcCtx::setup(&tctx, "taler_btc_bump.conf", true).await;
    971 
    972     // Perform credits to allow wire to perform debits latter
    973     for n in 10..61 {
    974         ctx.credit(Amount::from_sat(n * 100000), &EddsaPublicKey::rand())
    975             .await;
    976     }
    977     ctx.mine_conf().await;
    978 
    979     ctx.step("Bump fee stress");
    980     {
    981         // Loose second bitcoin node
    982         ctx.cluster_deco().await;
    983 
    984         // Perform debits
    985         let client = ctx.client_balance().await;
    986         let wire = ctx.wire_balance().await;
    987         let mut total_amount = Amount::ZERO;
    988         for n in 10..30 {
    989             let amount = Amount::from_sat(n * 10000);
    990             total_amount += amount;
    991             ctx.debit(amount, &Base32::rand()).await;
    992         }
    993         ctx.expect_w_balance_less(wire - total_amount, false).await;
    994 
    995         // Bump min relay fee making the previous debits stuck
    996         ctx.restart_node(&["-minrelaytxfee=0.0001"]).await;
    997 
    998         // Check bump happen
    999         ctx.expect_c_balance(client + total_amount, true).await;
   1000     }
   1001 }
   1002 
   1003 /// Test btc-wire handle transaction fees exceeding limits
   1004 pub async fn maxfee(ctx: TestCtx) {
   1005     ctx.step("Setup");
   1006     let mut ctx = BtcCtx::setup(&ctx, "taler_btc.conf", false).await;
   1007 
   1008     // Perform credits to allow wire to perform debits latter
   1009     for n in 10..31 {
   1010         ctx.credit(Amount::from_sat(n * 100000), &EddsaPublicKey::rand())
   1011             .await;
   1012     }
   1013     ctx.mine_pending().await;
   1014 
   1015     let client = ctx.client_balance().await;
   1016     let wire = ctx.wire_balance().await;
   1017     let mut total_amount = Amount::ZERO;
   1018 
   1019     ctx.step("Too high fee");
   1020     {
   1021         // Change fee config
   1022         ctx.restart_node(&["-maxtxfee=0.0000001", "-minrelaytxfee=0.0000001"])
   1023             .await;
   1024 
   1025         // Perform debits
   1026         for n in 10..31 {
   1027             let amount = Amount::from_sat(n * 10000);
   1028             total_amount += amount;
   1029             ctx.debit(amount, &Base32::rand()).await;
   1030         }
   1031         ctx.mine_pending().await;
   1032 
   1033         // Check no transaction happen
   1034         ctx.expect_w_balance(wire, false).await;
   1035         ctx.expect_c_balance(client, false).await;
   1036     }
   1037 
   1038     ctx.step("Good feed");
   1039     {
   1040         // Restore default config
   1041         ctx.restart_node(&[]).await;
   1042 
   1043         // Check transaction now have been made
   1044         ctx.expect_c_balance(client + total_amount, true).await;
   1045     }
   1046 }
   1047 
   1048 /// Test btc-wire ability to configure itself from bitcoin configuration
   1049 pub async fn config(ctx: TestCtx) {
   1050     // Connect with cookie files
   1051     ctx.step("Cookie");
   1052     BtcCtx::config(
   1053         &ctx,
   1054         |btc, dir| {
   1055             btc.with_section(None::<&str>).set(
   1056                 "rpccookiefile",
   1057                 dir.join("catch_me_if_you_can").to_string_lossy(),
   1058             );
   1059         },
   1060         |cfg, dir| {
   1061             cfg.with_section(Some("depolymerizer-bitcoin-worker")).set(
   1062                 "RPC_COOKIE_FILE",
   1063                 dir.join("catch_me_if_you_can").to_string_lossy(),
   1064             );
   1065         },
   1066     )
   1067     .await;
   1068 
   1069     // Connect with password
   1070     ctx.step("Password");
   1071     BtcCtx::config(
   1072         &ctx,
   1073         |btc, _| {
   1074             btc.with_section(None::<&str>)
   1075                 .set("rpcuser", "bob")
   1076                 .set("rpcpassword", "password");
   1077         },
   1078         |cfg, _| {
   1079             cfg.with_section(Some("depolymerizer-bitcoin-worker"))
   1080                 .set("RPC_AUTH_METHOD", "basic")
   1081                 .set("RPC_USERNAME", "bob")
   1082                 .set("RPC_PASSWORD", "password");
   1083         },
   1084     )
   1085     .await;
   1086 
   1087     // Connect with token
   1088     ctx.step("Token");
   1089     BtcCtx::config(
   1090         &ctx,
   1091         |btc, _| {
   1092             btc.with_section(None::<&str>)
   1093                 .set("rpcauth", "bob:9641cec731e1fad1ded02e1d31536e44$36b8b8af0a38104997a57f017805ff56bf8963ae4a2ed40252ca0e0e070fc19e");
   1094         },
   1095         |cfg, _| {
   1096             cfg.with_section(Some("depolymerizer-bitcoin-worker"))
   1097                 .set("RPC_AUTH_METHOD", "basic")
   1098                 .set("RPC_USERNAME", "bob")
   1099                 .set("RPC_PASSWORD", "password");
   1100         },
   1101     ).await;
   1102 }