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 }