diff --git a/.github/workflows/check_lint_build.yaml b/.github/workflows/check_lint_build.yaml index c1ea82d..b26cd17 100644 --- a/.github/workflows/check_lint_build.yaml +++ b/.github/workflows/check_lint_build.yaml @@ -61,6 +61,16 @@ jobs: chmod +x bitcoin-patched-bins/bitcoin-cli popd + - name: Download bitcoin unpatched + run: | + pushd .. + wget https://bitcoincore.org/bin/bitcoin-core-30.2/bitcoin-30.2-x86_64-linux-gnu.tar.gz + tar -xf bitcoin-30.2-x86_64-linux-gnu.tar.gz + rm bitcoin-30.2-x86_64-linux-gnu.tar.gz + mv bitcoin-30.2/bin bitcoin-unpatched-bins + rm -r bitcoin-30.2 + popd + - name: Download latest bip300301_enforcer run: | pushd .. @@ -113,6 +123,7 @@ jobs: run: | export BIP300301_ENFORCER='../bip300301-enforcer' export BITCOIND='../bitcoin-patched-bins/bitcoind' + export BITCOIND_UNPATCHED='../bitcoin-unpatched-bins/bitcoind' export BITCOIN_CLI='../bitcoin-patched-bins/bitcoin-cli' export ELECTRS='../electrs/target/release/electrs' export THUNDER_APP='target/debug/thunder_app' diff --git a/Cargo.lock b/Cargo.lock index d3c6503..6f263a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -562,9 +562,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.4" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" dependencies = [ "aws-lc-sys", "zeroize", @@ -572,9 +572,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" dependencies = [ "cc", "cmake", @@ -753,7 +753,7 @@ dependencies = [ [[package]] name = "bip300301_enforcer_integration_tests" version = "0.3.4" -source = "git+https://github.com/LayerTwo-Labs/bip300301_enforcer?rev=a8d169ee34aeddf6d354aa52376d847831d7fbb3#a8d169ee34aeddf6d354aa52376d847831d7fbb3" +source = "git+https://github.com/LayerTwo-Labs/bip300301_enforcer?rev=9c3eb0bdcb5b9a458e6f92cafb8bdbf76a860f74#9c3eb0bdcb5b9a458e6f92cafb8bdbf76a860f74" dependencies = [ "anyhow", "bdk_wallet", @@ -786,7 +786,7 @@ dependencies = [ [[package]] name = "bip300301_enforcer_lib" version = "0.3.4" -source = "git+https://github.com/LayerTwo-Labs/bip300301_enforcer?rev=a8d169ee34aeddf6d354aa52376d847831d7fbb3#a8d169ee34aeddf6d354aa52376d847831d7fbb3" +source = "git+https://github.com/LayerTwo-Labs/bip300301_enforcer?rev=9c3eb0bdcb5b9a458e6f92cafb8bdbf76a860f74#9c3eb0bdcb5b9a458e6f92cafb8bdbf76a860f74" dependencies = [ "aes-gcm", "argon2", @@ -1110,9 +1110,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "calloop" @@ -3609,9 +3609,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] @@ -5210,9 +5210,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "fastbloom", @@ -5701,9 +5701,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "aws-lc-rs", "ring", @@ -6025,7 +6025,7 @@ version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ - "errno 0.3.14", + "errno 0.2.8", "libc", ] @@ -6628,9 +6628,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.46" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -6649,9 +6649,9 @@ checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", diff --git a/Cargo.toml b/Cargo.toml index 75b294b..2a4b7eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,12 +39,12 @@ uuid = "1.13.1" [workspace.dependencies.bip300301_enforcer_lib] default-features = false git = "https://github.com/LayerTwo-Labs/bip300301_enforcer" -rev = "a8d169ee34aeddf6d354aa52376d847831d7fbb3" +rev = "9c3eb0bdcb5b9a458e6f92cafb8bdbf76a860f74" [workspace.dependencies.bip300301_enforcer_integration_tests] default-features = false git = "https://github.com/LayerTwo-Labs/bip300301_enforcer" -rev = "a8d169ee34aeddf6d354aa52376d847831d7fbb3" +rev = "9c3eb0bdcb5b9a458e6f92cafb8bdbf76a860f74" [workspace.dependencies.l2l-openapi] git = "https://github.com/Ash-L2L/l2l-openapi" diff --git a/app/gui/withdrawals.rs b/app/gui/withdrawals.rs index 617a9db..446e870 100644 --- a/app/gui/withdrawals.rs +++ b/app/gui/withdrawals.rs @@ -10,7 +10,7 @@ impl Withdrawals { pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { ui.heading("Pending withdrawals"); let bundle = app.and_then(|app| { - app.node.get_pending_withdrawal_bundle().ok().flatten() + app.node.try_get_pending_withdrawal_bundle().ok().flatten() }); if let Some(bundle) = bundle { let mut spent_utxos: Vec<_> = bundle.spend_utxos().iter().collect(); diff --git a/app/rpc_server.rs b/app/rpc_server.rs index b49a45c..8b69a12 100644 --- a/app/rpc_server.rs +++ b/app/rpc_server.rs @@ -206,7 +206,7 @@ impl RpcServer for RpcServerImpl { ) -> RpcResult> { self.app .node - .get_pending_withdrawal_bundle() + .try_get_pending_withdrawal_bundle() .map_err(custom_err) } diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index b4833d3..d009b67 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -18,7 +18,7 @@ jsonrpsee = { workspace = true } libtest-mimic = "0.8.1" reserve-port = "2.0.1" thiserror = { workspace = true } -thunder = { path = "../lib" } +thunder = { path = "../lib", features = ["clap"] } thunder_app_rpc_api = { path = "../rpc-api" } tracing = { workspace = true } tracing-indicatif = "0.3.8" diff --git a/integration_tests/example.env b/integration_tests/example.env index 98b32d8..1cdeca4 100644 --- a/integration_tests/example.env +++ b/integration_tests/example.env @@ -1,5 +1,6 @@ BIP300301_ENFORCER='../bip300301_enforcer/target/debug/bip300301_enforcer' -BITCOIND='../bitcoin/build/src/bitcoind' +BITCOIND='../bitcoin-patched/build/src/bitcoind' +BITCOIND_UNPATCHED='../bitcoin/build/src/bitcoind' BITCOIN_CLI='../bitcoin/build/src/bitcoin-cli' ELECTRS='../electrs/target/release/electrs' THUNDER_APP='target/debug/thunder_app' \ No newline at end of file diff --git a/integration_tests/ibd.rs b/integration_tests/ibd.rs index 3011961..f9f5151 100644 --- a/integration_tests/ibd.rs +++ b/integration_tests/ibd.rs @@ -5,8 +5,9 @@ use std::net::SocketAddr; use bip300301_enforcer_integration_tests::{ integration_test::{activate_sidechain, fund_enforcer, propose_sidechain}, setup::{ - Mode, Network, PostSetup as EnforcerPostSetup, Sidechain as _, - setup as setup_enforcer, + Mode, Network, PostSetup as EnforcerPostSetup, + PreSetup as EnforcerPreSetup, SetupOpts as EnforcerSetupOpts, + Sidechain as _, }, util::{AbortOnDrop, AsyncTrial, TestFailureCollector, TestFileRegistry}, }; @@ -33,13 +34,14 @@ async fn setup( bin_paths: BinPaths, res_tx: mpsc::UnboundedSender>, ) -> anyhow::Result<(EnforcerPostSetup, ThunderNodes)> { - let mut enforcer_post_setup = setup_enforcer( - &bin_paths.others, - Network::Regtest, - Mode::Mempool, - res_tx.clone(), - ) - .await?; + let enforcer_pre_setup = + EnforcerPreSetup::new(bin_paths.others, Network::Regtest)?; + let mut enforcer_post_setup = { + let setup_opts: EnforcerSetupOpts = Default::default(); + enforcer_pre_setup + .setup(Mode::Mempool, setup_opts, res_tx.clone()) + .await? + }; let sidechain_sender = PostSetup::setup( Init { thunder_app: bin_paths.thunder.clone(), diff --git a/integration_tests/integration_test.rs b/integration_tests/integration_test.rs index 4f43515..f1fd83c 100644 --- a/integration_tests/integration_test.rs +++ b/integration_tests/integration_test.rs @@ -1,8 +1,15 @@ use bip300301_enforcer_integration_tests::{ - setup::{Mode, Network}, + integration_test as bip300301_enforcer_integration_test, + setup::{ + Mode, Network, PostSetup as EnforcerPostSetup, + PreSetup as EnforcerPreSetup, SetupOpts as EnforcerSetupOpts, + Sidechain as _, + }, util::{AsyncTrial, TestFailureCollector, TestFileRegistry}, }; -use futures::{FutureExt, future::BoxFuture}; +use bip300301_enforcer_lib::bins::CommandExt; +use futures::{FutureExt, channel::mpsc::UnboundedSender, future::BoxFuture}; +use thunder_app_rpc_api::RpcClient as _; use crate::{ ibd::ibd_trial, @@ -11,7 +18,112 @@ use crate::{ util::BinPaths, }; -fn deposit_withdraw_roundtrip( +#[allow(clippy::significant_drop_tightening, reason = "false positive")] +pub async fn deposit_withdraw_roundtrip_task( + post_setup: &mut EnforcerPostSetup, + res_tx: UnboundedSender>, + init: Init, +) -> anyhow::Result { + use bip300301_enforcer_integration_test::{ + activate_sidechain, deposit, fund_enforcer, propose_sidechain, + wait_for_wallet_sync, withdraw_succeed, + }; + use bitcoin::Amount; + + const DEPOSIT_AMOUNT: Amount = Amount::from_sat(21_000_000); + const DEPOSIT_FEE: Amount = Amount::from_sat(1_000_000); + const WITHDRAW_AMOUNT: Amount = Amount::from_sat(18_000_000); + const WITHDRAW_FEE: Amount = Amount::from_sat(1_000_000); + + let mut sidechain = PostSetup::setup(init, post_setup, res_tx).await?; + tracing::info!("Setup successfully"); + let () = propose_sidechain::(post_setup).await?; + tracing::info!("Proposed sidechain successfully"); + let () = activate_sidechain::(post_setup).await?; + tracing::info!("Activated sidechain successfully"); + let () = fund_enforcer::(post_setup).await?; + tracing::info!("Funded enforcer successfully"); + let deposit_address = sidechain.get_deposit_address().await?; + let () = deposit( + post_setup, + &mut sidechain, + &deposit_address, + DEPOSIT_AMOUNT, + DEPOSIT_FEE, + ) + .await?; + tracing::info!("Deposited to sidechain successfully"); + // Wait for mempool to catch up before attempting second deposit + tracing::debug!("Waiting for wallet sync..."); + let () = wait_for_wallet_sync().await?; + tracing::info!("Attempting second deposit"); + let () = deposit( + post_setup, + &mut sidechain, + &deposit_address, + DEPOSIT_AMOUNT, + DEPOSIT_FEE, + ) + .await?; + tracing::info!("Deposited to sidechain successfully"); + let sidechain_block_count = sidechain.rpc_client.getblockcount().await?; + let target_sidechain_block_height = 5; + tracing::info!( + sidechain_block_count, + target_sidechain_block_height, + "BMMing sidechain blocks..." + ); + sidechain + .bmm( + post_setup, + target_sidechain_block_height - sidechain_block_count, + ) + .await?; + let () = withdraw_succeed( + post_setup, + &mut sidechain, + WITHDRAW_AMOUNT, + WITHDRAW_FEE, + Amount::ZERO, + ) + .await?; + tracing::info!("Withdrawal succeeded"); + let mainchain_block_count = post_setup + .bitcoin_cli + .command::([], "getblockcount", []) + .run_utf8() + .await?; + let sidechain_block_count = sidechain.rpc_client.getblockcount().await?; + tracing::info!(%mainchain_block_count, sidechain_block_count); + Ok(sidechain) +} + +async fn deposit_withdraw_roundtrip( + mut post_setup: EnforcerPostSetup, + init: Init, + res_tx: UnboundedSender>, +) -> anyhow::Result<()> { + let sidechain_post_setup = + deposit_withdraw_roundtrip_task(&mut post_setup, res_tx, init).await?; + // check that everything is ok after BMM'ing 3 blocks + let mut block_count_pre = + sidechain_post_setup.rpc_client.getblockcount().await?; + sidechain_post_setup.bmm_single(&mut post_setup).await?; + let mut block_count_post = + sidechain_post_setup.rpc_client.getblockcount().await?; + anyhow::ensure!(block_count_post == block_count_pre + 1); + block_count_pre = block_count_post; + sidechain_post_setup.bmm_single(&mut post_setup).await?; + block_count_post = sidechain_post_setup.rpc_client.getblockcount().await?; + anyhow::ensure!(block_count_post == block_count_pre + 1); + block_count_pre = block_count_post; + sidechain_post_setup.bmm_single(&mut post_setup).await?; + block_count_post = sidechain_post_setup.rpc_client.getblockcount().await?; + anyhow::ensure!(block_count_post == block_count_pre + 1); + Ok(()) +} + +fn deposit_withdraw_roundtrip_trial( bin_paths: BinPaths, file_registry: TestFileRegistry, failure_collector: TestFailureCollector, @@ -20,20 +132,25 @@ fn deposit_withdraw_roundtrip( "deposit_withdraw_roundtrip", async move { let (res_tx, _) = futures::channel::mpsc::unbounded(); - let post_setup = bip300301_enforcer_integration_tests::setup::setup( - &bin_paths.others, - Network::Regtest, - Mode::Mempool, - res_tx - ).await?; - bip300301_enforcer_integration_tests::integration_test::deposit_withdraw_roundtrip::( - post_setup, - Init { - thunder_app: bin_paths.thunder, - data_dir_suffix: None, - }, - ).await - }.boxed(), + let pre_setup = + EnforcerPreSetup::new(bin_paths.others, Network::Regtest)?; + let post_setup = { + let setup_opts: EnforcerSetupOpts = Default::default(); + pre_setup + .setup(Mode::Mempool, setup_opts, res_tx.clone()) + .await? + }; + deposit_withdraw_roundtrip( + post_setup, + Init { + thunder_app: bin_paths.thunder, + data_dir_suffix: None, + }, + res_tx, + ) + .await + } + .boxed(), file_registry, failure_collector, ) @@ -45,7 +162,7 @@ pub fn tests( failure_collector: TestFailureCollector, ) -> Vec>>> { vec![ - deposit_withdraw_roundtrip( + deposit_withdraw_roundtrip_trial( bin_paths.clone(), file_registry.clone(), failure_collector.clone(), diff --git a/integration_tests/main.rs b/integration_tests/main.rs index 2ae88c2..c66bb8c 100644 --- a/integration_tests/main.rs +++ b/integration_tests/main.rs @@ -51,6 +51,7 @@ fn set_tracing_subscriber(log_level: tracing::Level) -> anyhow::Result<()> { let targets_filter = { let default_directives_str = targets_directive_str([ ("", saturating_pred_level(log_level)), + ("bip300301_enforcer_integration_tests", log_level), ("integration_tests", log_level), ]); let directives_str = diff --git a/integration_tests/setup.rs b/integration_tests/setup.rs index 54ba877..291a43c 100644 --- a/integration_tests/setup.rs +++ b/integration_tests/setup.rs @@ -12,7 +12,7 @@ use bip300301_enforcer_lib::types::SidechainNumber; use futures::{TryFutureExt as _, channel::mpsc, future}; use reserve_port::ReservedPort; use thiserror::Error; -use thunder::types::{OutputContent, PointedOutput}; +use thunder::types::{Network, OutputContent, PointedOutput}; use thunder_app_rpc_api::RpcClient as _; use tokio::time::sleep; @@ -167,6 +167,7 @@ impl Sidechain for PostSetup { .enforcer_serve_grpc .port(), net_port: reserved_ports.net.port(), + network: Network::Regtest, rpc_port: reserved_ports.rpc.port(), }; let thunder_app_task = thunder_app @@ -268,12 +269,10 @@ impl Sidechain for PostSetup { .latest_failed_withdrawal_bundle_height() .await? .unwrap_or(0); - match WITHDRAWAL_BUNDLE_FAILURE_GAP.saturating_sub( + let blocks_to_mine = WITHDRAWAL_BUNDLE_FAILURE_GAP.saturating_sub( block_height - latest_failed_withdrawal_bundle_height, - ) { - 0 => WITHDRAWAL_BUNDLE_FAILURE_GAP + 1, - blocks_to_mine => blocks_to_mine, - } + ); + std::cmp::max(1, blocks_to_mine) }; tracing::debug!( "Mining thunder blocks until withdrawal bundle is broadcast" diff --git a/integration_tests/unknown_withdrawal.rs b/integration_tests/unknown_withdrawal.rs index a51305a..c9a62f1 100644 --- a/integration_tests/unknown_withdrawal.rs +++ b/integration_tests/unknown_withdrawal.rs @@ -6,10 +6,14 @@ use bip300301_enforcer_integration_tests::{ withdraw_succeed, }, setup::{ - Mode, Network, PostSetup as EnforcerPostSetup, Sidechain as _, - setup as setup_enforcer, + Mode, Network, PostSetup as EnforcerPostSetup, + PreSetup as EnforcerPreSetup, SetupOpts as EnforcerSetupOpts, + Sidechain as _, + }, + util::{ + AbortOnDrop, AsyncTrial, BinPaths as EnforcerBinPaths, + TestFailureCollector, TestFileRegistry, }, - util::{AbortOnDrop, AsyncTrial, TestFailureCollector, TestFileRegistry}, }; use futures::{ FutureExt as _, StreamExt as _, channel::mpsc, future::BoxFuture, @@ -26,16 +30,17 @@ use crate::{ /// Initial setup for the test async fn setup( - bin_paths: &BinPaths, + enforcer_bin_paths: EnforcerBinPaths, res_tx: mpsc::UnboundedSender>, ) -> anyhow::Result { - let mut enforcer_post_setup = setup_enforcer( - &bin_paths.others, - Network::Regtest, - Mode::Mempool, - res_tx.clone(), - ) - .await?; + let enforcer_pre_setup = + EnforcerPreSetup::new(enforcer_bin_paths, Network::Regtest)?; + let mut enforcer_post_setup = { + let setup_opts: EnforcerSetupOpts = Default::default(); + enforcer_pre_setup + .setup(Mode::Mempool, setup_opts, res_tx.clone()) + .await? + }; let () = propose_sidechain::(&mut enforcer_post_setup).await?; tracing::info!("Proposed sidechain successfully"); let () = activate_sidechain::(&mut enforcer_post_setup).await?; @@ -53,7 +58,8 @@ async fn unknown_withdrawal_task( bin_paths: BinPaths, res_tx: mpsc::UnboundedSender>, ) -> anyhow::Result<()> { - let mut enforcer_post_setup = setup(&bin_paths, res_tx.clone()).await?; + let mut enforcer_post_setup = + setup(bin_paths.others, res_tx.clone()).await?; let mut sidechain_withdrawer = PostSetup::setup( Init { thunder_app: bin_paths.thunder.clone(), diff --git a/integration_tests/util.rs b/integration_tests/util.rs index af29a6c..48f8e0e 100644 --- a/integration_tests/util.rs +++ b/integration_tests/util.rs @@ -7,6 +7,7 @@ use bip300301_enforcer_integration_tests::util::{ AbortOnDrop, BinPaths as EnforcerBinPaths, VarError, get_env_var, spawn_command_with_args, }; +use thunder::types::Network; fn load_env_var_from_string(s: &str) -> Result<(), VarError> { dotenvy::from_read_override(s.as_bytes()) @@ -39,6 +40,7 @@ pub struct ThunderApp { pub mainchain_grpc_port: u16, /// Port to use for P2P networking pub net_port: u16, + pub network: Network, /// Port to use for the RPC server pub rpc_port: u16, } @@ -65,6 +67,7 @@ impl ThunderApp { format!("http://127.0.0.1:{}", self.mainchain_grpc_port), "--net-addr".to_owned(), format!("127.0.0.1:{}", self.net_port), + format!("--network={}", self.network), "--rpc-addr".to_owned(), format!("127.0.0.1:{}", self.rpc_port), ]; diff --git a/lib/net/mod.rs b/lib/net/mod.rs index 1ac575e..1620a38 100644 --- a/lib/net/mod.rs +++ b/lib/net/mod.rs @@ -326,19 +326,6 @@ impl Net { .map_err(|err| DbError::from(err).into()) } - pub const SEED_NODE_ADDRS: &[SocketAddr] = { - const SIGNET_MINING_SERVER: SocketAddr = SocketAddr::new( - std::net::IpAddr::V4(std::net::Ipv4Addr::new(172, 105, 148, 135)), - 4000 + THIS_SIDECHAIN as u16, - ); - // thunder.bip300.xyz - const BIP300_XYZ: SocketAddr = SocketAddr::new( - std::net::IpAddr::V4(std::net::Ipv4Addr::new(95, 217, 243, 12)), - 4000 + THIS_SIDECHAIN as u16, - ); - &[SIGNET_MINING_SERVER, BIP300_XYZ] - }; - pub fn new( env: &sneed::Env, archive: Archive, diff --git a/lib/node/mod.rs b/lib/node/mod.rs index b15f4f6..44c1333 100644 --- a/lib/node/mod.rs +++ b/lib/node/mod.rs @@ -497,13 +497,13 @@ where Ok((returned_transactions, fee)) } - pub fn get_pending_withdrawal_bundle( + pub fn try_get_pending_withdrawal_bundle( &self, ) -> Result, Error> { - let txn = self.env.read_txn().map_err(EnvError::from)?; + let rotxn = self.env.read_txn().map_err(EnvError::from)?; let bundle = self .state - .get_pending_withdrawal_bundle(&txn)? + .try_get_pending_withdrawal_bundle(&rotxn)? .map(|(bundle, _)| bundle); Ok(bundle) } @@ -645,7 +645,7 @@ where return Ok(false); }; let rotxn = self.env.read_txn().map_err(EnvError::from)?; - let bundle = self.state.get_pending_withdrawal_bundle(&rotxn)?; + let bundle = self.state.try_get_pending_withdrawal_bundle(&rotxn)?; if let Some((bundle, _)) = bundle { let m6id = bundle.compute_m6id(); let mut cusf_mainchain_wallet_lock = diff --git a/lib/state/block.rs b/lib/state/block.rs index 89c9411..abebacc 100644 --- a/lib/state/block.rs +++ b/lib/state/block.rs @@ -68,7 +68,7 @@ pub fn prevalidate( for (outpoint, utxo_hash) in &transaction.inputs { let key = OutPointKey::from(outpoint); let spent_output = - state.utxos.try_get(rotxn, &key)?.ok_or(Error::NoUtxo { + state.utxos.try_get(rotxn, &key)?.ok_or(error::NoUtxo { outpoint: *outpoint, })?; all_input_keys.push(OutPointKey::from(outpoint)); @@ -219,7 +219,7 @@ pub fn connect_prevalidated( .utxos .try_get(rwtxn, &OutPointKey::from(outpoint)) .map_err(DbError::from)? - .ok_or(Error::NoUtxo { + .ok_or(error::NoUtxo { outpoint: *outpoint, })?; state @@ -449,7 +449,7 @@ pub fn connect( { let key = OutPointKey::from(outpoint); let spent_output = - state.utxos.try_get(rwtxn, &key)?.ok_or(Error::NoUtxo { + state.utxos.try_get(rwtxn, &key)?.ok_or(error::NoUtxo { outpoint: *outpoint, })?; @@ -549,10 +549,10 @@ pub fn disconnect_tip( }; accumulator_diff.remove((&pointed_output).into()); let key = OutPointKey::from(&outpoint); - if state.utxos.delete(rwtxn, &key).map_err(DbError::from)? { - Ok(()) + if state.utxos.delete(rwtxn, &key)? { + Ok::<_, Error>(()) } else { - Err(Error::NoUtxo { outpoint }) + Err(error::NoUtxo { outpoint }.into()) } }, )?; @@ -590,10 +590,10 @@ pub fn disconnect_tip( }; accumulator_diff.remove((&pointed_output).into()); let key = OutPointKey::from(&outpoint); - if state.utxos.delete(rwtxn, &key).map_err(DbError::from)? { - Ok(()) + if state.utxos.delete(rwtxn, &key)? { + Ok::<_, Error>(()) } else { - Err(Error::NoUtxo { outpoint }) + Err(error::NoUtxo { outpoint }.into()) } })?; let height = state diff --git a/lib/state/error.rs b/lib/state/error.rs index 5068567..2d5efd5 100644 --- a/lib/state/error.rs +++ b/lib/state/error.rs @@ -8,6 +8,90 @@ use crate::types::{ WithdrawalBundleError, }; +#[derive(Debug, Error)] +#[error("utxo {outpoint} doesn't exist")] +pub struct NoUtxo { + pub outpoint: OutPoint, +} + +#[derive(Debug, Error)] +#[error("pending withdrawal bundle {0} unknown in withdrawal_bundles")] +#[repr(transparent)] +pub struct PendingWithdrawalBundleUnknown(pub M6id); + +#[allow(clippy::duplicated_attributes)] +#[derive(Debug, Error, Transitive)] +#[transitive( + from(db::Delete, db::Error), + from(db::Put, db::Error), + from(db::TryGet, db::Error) +)] +pub enum ConnectWithdrawalBundleSubmitted { + #[error( + "confirmed withdrawal bundle {} resubmitted in {}", + .m6id, + .event_block_hash, + )] + ConfirmedResubmitted { + event_block_hash: bitcoin::BlockHash, + m6id: M6id, + }, + #[error(transparent)] + Db(Box), + #[error( + "dropped withdrawal bundle {0} marked as pending in withdrawal_bundles" + )] + DroppedPending(M6id), + #[error(transparent)] + NoUtxo(#[from] NoUtxo), + #[error(transparent)] + PendingWithdrawalBundleUnknown(#[from] PendingWithdrawalBundleUnknown), + #[error( + "withdrawal bundle {} submitted in {} resubmitted in {}", + m6id, + submitted_block_height, + event_block_hash + )] + Resubmitted { + event_block_hash: bitcoin::BlockHash, + m6id: M6id, + submitted_block_height: u32, + }, + #[error( + "unknown confirmed withdrawal bundle {} marked as failed in {}", + .m6id, + .failed_block_height, + )] + UnknownConfirmedFailed { + m6id: M6id, + failed_block_height: u32, + }, + #[error( + "unknown withdrawal bundle {} marked as dropped in {}", + .m6id, + .dropped_block_height, + )] + UnknownDropped { + m6id: M6id, + dropped_block_height: u32, + }, + #[error( + "unknown withdrawal bundle {} marked as pending in {}", + .m6id, + .pending_block_height, + )] + UnknownPending { + m6id: M6id, + pending_block_height: u32, + }, +} + +impl From for ConnectWithdrawalBundleSubmitted { + fn from(err: db::Error) -> Self { + Self::Db(Box::new(err)) + } +} + #[derive(Debug, Error)] pub enum InvalidHeader { #[error("expected block hash {expected}, but computed {computed}")] @@ -29,6 +113,7 @@ pub enum InvalidHeader { #[transitive(from(db::Clear, db::Error))] #[transitive(from(db::Delete, db::Error))] #[transitive(from(db::Error, sneed::Error))] +#[transitive(from(db::Get, db::Error))] #[transitive(from(db::IterInit, db::Error))] #[transitive(from(db::IterItem, db::Error))] #[transitive(from(db::Last, db::Error))] @@ -51,6 +136,8 @@ pub enum Error { #[error(transparent)] BorshSerialize(borsh::io::Error), #[error(transparent)] + ConnectWithdrawalBundleSubmitted(#[from] ConnectWithdrawalBundleSubmitted), + #[error(transparent)] ComputeMerkleRoot(#[from] ComputeMerkleRootError), #[error(transparent)] Db(#[from] sneed::Error), @@ -73,11 +160,13 @@ pub enum Error { NoStxo { outpoint: OutPoint }, #[error("value in is less than value out")] NotEnoughValueIn, - #[error("utxo {outpoint} doesn't exist")] - NoUtxo { outpoint: OutPoint }, + #[error(transparent)] + NoUtxo(#[from] NoUtxo), #[error("Withdrawal bundle event block doesn't exist")] NoWithdrawalBundleEventBlock, #[error(transparent)] + PendingWithdrawalBundleUnknown(#[from] PendingWithdrawalBundleUnknown), + #[error(transparent)] Utreexo(#[from] UtreexoError), #[error("Utreexo proof verification failed for tx {txid}")] UtreexoProofFailed { txid: Txid }, diff --git a/lib/state/mod.rs b/lib/state/mod.rs index 25ed6ab..75f4baf 100644 --- a/lib/state/mod.rs +++ b/lib/state/mod.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeMap, HashMap, HashSet}; -use fallible_iterator::FallibleIterator; +use fallible_iterator::FallibleIterator as _; use futures::Stream; use heed::types::SerdeBincode; use rustreexo::accumulator::{node_hash::BitcoinNodeHash, proof::Proof}; @@ -79,9 +79,8 @@ pub struct State { height: DatabaseUnique>, pub utxos: DatabaseUnique>, pub stxos: DatabaseUnique>, - /// Pending withdrawal bundle and block height - pub pending_withdrawal_bundle: - DatabaseUnique>, + /// Pending withdrawal bundle. MUST exist in withdrawal_bundles + pub pending_withdrawal_bundle: DatabaseUnique>, /// Latest failed (known) withdrawal bundle latest_failed_withdrawal_bundle: DatabaseUnique>>, @@ -224,10 +223,23 @@ impl State { }; let latest_failed_m6id = latest_failed_m6id.latest().value; let (_bundle, bundle_status) = self.withdrawal_bundles.try_get(rotxn, &latest_failed_m6id)? - .expect("Inconsistent DBs: latest failed m6id should exist in withdrawal_bundles"); - let bundle_status = bundle_status.latest(); - assert_eq!(bundle_status.value, WithdrawalBundleStatus::Failed); - Ok(Some((bundle_status.height, latest_failed_m6id))) + .unwrap_or_else(|| + panic!("Inconsistent DBs: latest failed m6id {latest_failed_m6id} should exist in withdrawal_bundles") + ); + let failed_height = bundle_status + .iter() + .rev() + .find_map(|status| match status.value { + WithdrawalBundleStatus::Failed => Some(status.height), + WithdrawalBundleStatus::Confirmed + | WithdrawalBundleStatus::Dropped + | WithdrawalBundleStatus::Pending + | WithdrawalBundleStatus::Submitted => None, + }) + .unwrap_or_else(|| { + panic!("missing failure status for {latest_failed_m6id}") + }); + Ok(Some((failed_height, latest_failed_m6id))) } /// Get the current Utreexo accumulator @@ -281,7 +293,7 @@ impl State { for (outpoint, _) in &transaction.inputs { let key = OutPointKey::from(outpoint); let utxo = - self.utxos.try_get(rotxn, &key)?.ok_or(Error::NoUtxo { + self.utxos.try_get(rotxn, &key)?.ok_or(error::NoUtxo { outpoint: *outpoint, })?; spent_utxos.push(utxo); @@ -307,14 +319,25 @@ impl State { } /// Get pending withdrawal bundle and block height - pub fn get_pending_withdrawal_bundle( + pub fn try_get_pending_withdrawal_bundle( &self, - txn: &RoTxn, + rotxn: &RoTxn, ) -> Result, Error> { - Ok(self - .pending_withdrawal_bundle - .try_get(txn, &()) - .map_err(DbError::from)?) + let Some(m6id) = self.pending_withdrawal_bundle.try_get(rotxn, &())? + else { + return Ok(None); + }; + let (bundle_info, bundle_status) = + self.withdrawal_bundles.get(rotxn, &m6id)?; + let bundle = match bundle_info { + WithdrawalBundleInfo::Known(bundle) => bundle, + WithdrawalBundleInfo::Unknown + | WithdrawalBundleInfo::UnknownConfirmed { spend_utxos: _ } => { + return Err(error::PendingWithdrawalBundleUnknown(m6id).into()); + } + }; + let height = bundle_status.latest().height; + Ok(Some((bundle, height))) } pub fn validate_filled_transaction( diff --git a/lib/state/rollback.rs b/lib/state/rollback.rs index 3ef4fa6..aba6b65 100644 --- a/lib/state/rollback.rs +++ b/lib/state/rollback.rs @@ -41,10 +41,16 @@ impl RollBack { } /// Returns the earliest value + #[allow(dead_code)] pub fn earliest(&self) -> &HeightStamped { self.0.first() } + /// Iterate values, earliest to latest + pub fn iter(&self) -> impl DoubleEndedIterator> { + self.0.iter() + } + /// Returns the most recent value pub fn latest(&self) -> &HeightStamped { self.0.last() diff --git a/lib/state/two_way_peg_data.rs b/lib/state/two_way_peg_data.rs index f6c19ab..d64abcf 100644 --- a/lib/state/two_way_peg_data.rs +++ b/lib/state/two_way_peg_data.rs @@ -8,13 +8,13 @@ use sneed::{RoTxn, RwTxn, db::error::Error as DbError}; use crate::{ state::{ Error, State, WITHDRAWAL_BUNDLE_FAILURE_GAP, WithdrawalBundleInfo, - rollback::RollBack, + error, rollback::RollBack, }, types::{ AccumulatorDiff, AggregatedWithdrawal, AmountOverflowError, InPoint, M6id, OutPoint, OutPointKey, Output, OutputContent, PointedOutput, PointedOutputRef, SpentOutput, WithdrawalBundle, WithdrawalBundleEvent, - WithdrawalBundleStatus, hash, + WithdrawalBundleEventStatus, WithdrawalBundleStatus, hash, proto::mainchain::{BlockEvent, TwoWayPegData}, }, }; @@ -109,19 +109,28 @@ fn connect_withdrawal_bundle_submitted( accumulator_diff: &mut AccumulatorDiff, event_block_hash: &bitcoin::BlockHash, m6id: M6id, -) -> Result<(), Error> { - if let Some((bundle, bundle_block_height)) = state - .pending_withdrawal_bundle - .try_get(rwtxn, &()) - .map_err(DbError::from)? - && bundle.compute_m6id() == m6id +) -> Result<(), error::ConnectWithdrawalBundleSubmitted> { + if let Some(bundle_m6id) = + state.pending_withdrawal_bundle.try_get(rwtxn, &())? + && bundle_m6id == m6id { - assert_eq!(bundle_block_height, block_height - 1); tracing::debug!( %block_height, %m6id, - "Withdrawal bundle successfully submitted" + "Pending withdrawal bundle submission confirmed" ); + let (bundle, mut bundle_status) = state + .withdrawal_bundles + .try_get(rwtxn, &m6id)? + .ok_or(error::PendingWithdrawalBundleUnknown(m6id))?; + let bundle = match bundle { + WithdrawalBundleInfo::Known(bundle) => bundle, + WithdrawalBundleInfo::Unknown + | WithdrawalBundleInfo::UnknownConfirmed { spend_utxos: _ } => { + let err = error::PendingWithdrawalBundleUnknown(m6id); + return Err(err.into()); + } + }; for (outpoint, spend_output) in bundle.spend_utxos() { let utxo_hash = hash(&PointedOutputRef { outpoint: *outpoint, @@ -129,64 +138,128 @@ fn connect_withdrawal_bundle_submitted( }); accumulator_diff.remove(utxo_hash.into()); let key = OutPointKey::from(outpoint); - state.utxos.delete(rwtxn, &key).map_err(DbError::from)?; + if !state.utxos.delete(rwtxn, &key)? { + return Err(error::NoUtxo { + outpoint: *outpoint, + } + .into()); + }; let spent_output = SpentOutput { output: spend_output.clone(), inpoint: InPoint::Withdrawal { m6id }, }; - state - .stxos - .put(rwtxn, &key, &spent_output) - .map_err(DbError::from)?; + state.stxos.put(rwtxn, &key, &spent_output)?; } - state - .withdrawal_bundles - .put( - rwtxn, - &m6id, - &( - WithdrawalBundleInfo::Known(bundle), - RollBack::new( - WithdrawalBundleStatus::Submitted, - block_height, - ), - ), - ) - .map_err(DbError::from)?; - state - .pending_withdrawal_bundle - .delete(rwtxn, &()) - .map_err(DbError::from)?; - } else if let Some((_bundle, bundle_status)) = state - .withdrawal_bundles - .try_get(rwtxn, &m6id) - .map_err(DbError::from)? - { - // Already applied assert_eq!( - bundle_status.earliest().value, - WithdrawalBundleStatus::Submitted + bundle_status.latest().value, + WithdrawalBundleStatus::Pending ); + bundle_status + .push(WithdrawalBundleStatus::Submitted, block_height) + .expect("push submitted status should be valid"); + state.withdrawal_bundles.put( + rwtxn, + &m6id, + &(WithdrawalBundleInfo::Known(bundle), bundle_status), + )?; + state.pending_withdrawal_bundle.delete(rwtxn, &())?; + } else if let Some((bundle, mut bundle_status)) = + state.withdrawal_bundles.try_get(rwtxn, &m6id)? + { + match (&bundle, bundle_status.latest().value) { + (_, WithdrawalBundleStatus::Confirmed) => { + let err = error::ConnectWithdrawalBundleSubmitted::ConfirmedResubmitted { + event_block_hash: *event_block_hash, + m6id + }; + return Err(err); + } + (_, WithdrawalBundleStatus::Submitted) => { + let err = + error::ConnectWithdrawalBundleSubmitted::Resubmitted { + event_block_hash: *event_block_hash, + m6id, + submitted_block_height: bundle_status.latest().height, + }; + return Err(err); + } + ( + WithdrawalBundleInfo::Known(_), + WithdrawalBundleStatus::Dropped, + ) => { + tracing::warn!(%event_block_hash, %m6id, "dropped bundle submitted"); + } + ( + WithdrawalBundleInfo::Unknown + | WithdrawalBundleInfo::UnknownConfirmed { spend_utxos: _ }, + WithdrawalBundleStatus::Dropped, + ) => { + let err = + error::ConnectWithdrawalBundleSubmitted::UnknownDropped { + m6id, + dropped_block_height: bundle_status.latest().height, + }; + return Err(err); + } + ( + WithdrawalBundleInfo::Known(_), + WithdrawalBundleStatus::Pending, + ) => { + let err = + error::ConnectWithdrawalBundleSubmitted::DroppedPending( + m6id, + ); + return Err(err); + } + ( + WithdrawalBundleInfo::Unknown + | WithdrawalBundleInfo::UnknownConfirmed { spend_utxos: _ }, + WithdrawalBundleStatus::Pending, + ) => { + let err = + error::ConnectWithdrawalBundleSubmitted::UnknownPending { + m6id, + pending_block_height: bundle_status.latest().height, + }; + return Err(err); + } + ( + WithdrawalBundleInfo::Known(_) | WithdrawalBundleInfo::Unknown, + WithdrawalBundleStatus::Failed, + ) => { + tracing::warn!(%event_block_hash, %m6id, "failed bundle resubmitted"); + } + ( + WithdrawalBundleInfo::UnknownConfirmed { spend_utxos: _ }, + WithdrawalBundleStatus::Failed, + ) => { + let err = error::ConnectWithdrawalBundleSubmitted::UnknownConfirmedFailed { + m6id, + failed_block_height: bundle_status.latest().height, + }; + return Err(err); + } + } + bundle_status + .push(WithdrawalBundleStatus::Submitted, block_height) + .expect("push submitted status should be valid"); + state + .withdrawal_bundles + .put(rwtxn, &m6id, &(bundle, bundle_status))? } else { tracing::warn!( %event_block_hash, %m6id, "Unknown withdrawal bundle submitted" ); - state - .withdrawal_bundles - .put( - rwtxn, - &m6id, - &( - WithdrawalBundleInfo::Unknown, - RollBack::new( - WithdrawalBundleStatus::Submitted, - block_height, - ), - ), - ) - .map_err(DbError::from)?; + state.withdrawal_bundles.put( + rwtxn, + &m6id, + &( + WithdrawalBundleInfo::Unknown, + RollBack::new(WithdrawalBundleStatus::Submitted, block_height), + ), + )?; }; Ok(()) } @@ -344,7 +417,7 @@ fn connect_withdrawal_bundle_event( event: &WithdrawalBundleEvent, ) -> Result<(), Error> { match event.status { - WithdrawalBundleStatus::Submitted => { + WithdrawalBundleEventStatus::Submitted => { connect_withdrawal_bundle_submitted( state, rwtxn, @@ -353,8 +426,9 @@ fn connect_withdrawal_bundle_event( event_block_hash, event.m6id, ) + .map_err(Error::ConnectWithdrawalBundleSubmitted) } - WithdrawalBundleStatus::Confirmed => { + WithdrawalBundleEventStatus::Confirmed => { connect_withdrawal_bundle_confirmed( state, rwtxn, @@ -364,13 +438,15 @@ fn connect_withdrawal_bundle_event( event.m6id, ) } - WithdrawalBundleStatus::Failed => connect_withdrawal_bundle_failed( - state, - rwtxn, - block_height, - accumulator_diff, - event.m6id, - ), + WithdrawalBundleEventStatus::Failed => { + connect_withdrawal_bundle_failed( + state, + rwtxn, + block_height, + accumulator_diff, + event.m6id, + ) + } } } @@ -491,10 +567,22 @@ pub fn connect( collect_withdrawal_bundle(state, rwtxn, block_height)? { let m6id = bundle.compute_m6id(); - state - .pending_withdrawal_bundle - .put(rwtxn, &(), &(bundle, block_height)) - .map_err(DbError::from)?; + state.pending_withdrawal_bundle.put(rwtxn, &(), &m6id)?; + let bundle_status = if let Some((_bundle, mut bundle_status)) = + state.withdrawal_bundles.try_get(rwtxn, &m6id)? + { + bundle_status + .push(WithdrawalBundleStatus::Pending, block_height) + .expect("push pending status should be valid"); + bundle_status + } else { + RollBack::new(WithdrawalBundleStatus::Pending, block_height) + }; + state.withdrawal_bundles.put( + rwtxn, + &m6id, + &(WithdrawalBundleInfo::Known(bundle), bundle_status), + )?; tracing::trace!( %block_height, %m6id, @@ -516,16 +604,12 @@ fn disconnect_withdrawal_bundle_submitted( accumulator_diff: &mut AccumulatorDiff, m6id: M6id, ) -> Result<(), Error> { - let Some((bundle, bundle_status)) = state - .withdrawal_bundles - .try_get(rwtxn, &m6id) - .map_err(DbError::from)? + let Some((bundle, bundle_status)) = + state.withdrawal_bundles.try_get(rwtxn, &m6id)? else { - if let Some((bundle, _)) = state - .pending_withdrawal_bundle - .try_get(rwtxn, &()) - .map_err(DbError::from)? - && bundle.compute_m6id() == m6id + if let Some(pending_bundle_m6id) = + state.pending_withdrawal_bundle.try_get(rwtxn, &())? + && pending_bundle_m6id == m6id { // Already applied return Ok(()); @@ -533,43 +617,51 @@ fn disconnect_withdrawal_bundle_submitted( return Err(Error::UnknownWithdrawalBundle { m6id }); } }; - let bundle_status = bundle_status.latest(); - assert_eq!(bundle_status.value, WithdrawalBundleStatus::Submitted); - assert_eq!(bundle_status.height, block_height); - match bundle { + let (bundle_status, latest_bundle_status) = bundle_status.pop(); + assert_eq!( + latest_bundle_status.value, + WithdrawalBundleStatus::Submitted + ); + assert_eq!(latest_bundle_status.height, block_height); + match &bundle { WithdrawalBundleInfo::Unknown | WithdrawalBundleInfo::UnknownConfirmed { .. } => (), WithdrawalBundleInfo::Known(bundle) => { - for (outpoint, output) in bundle.spend_utxos().iter().rev() { - if !state - .stxos - .delete(rwtxn, &OutPointKey::from(outpoint)) - .map_err(DbError::from)? - { - return Err(Error::NoStxo { + if let Some(bundle_status) = &bundle_status + && bundle_status.latest().value + == WithdrawalBundleStatus::Pending + { + for (outpoint, output) in bundle.spend_utxos().iter().rev() { + if !state + .stxos + .delete(rwtxn, &OutPointKey::from(outpoint))? + { + return Err(Error::NoStxo { + outpoint: *outpoint, + }); + }; + state.utxos.put( + rwtxn, + &OutPointKey::from(outpoint), + output, + )?; + let utxo_hash = hash(&PointedOutput { outpoint: *outpoint, + output: output.clone(), }); - }; - state - .utxos - .put(rwtxn, &OutPointKey::from(outpoint), output) - .map_err(DbError::from)?; - let utxo_hash = hash(&PointedOutput { - outpoint: *outpoint, - output: output.clone(), - }); - accumulator_diff.insert(utxo_hash.into()); + accumulator_diff.insert(utxo_hash.into()); + } + state.pending_withdrawal_bundle.put(rwtxn, &(), &m6id)?; } - state - .pending_withdrawal_bundle - .put(rwtxn, &(), &(bundle, bundle_status.height - 1)) - .map_err(DbError::from)?; } } - state - .withdrawal_bundles - .delete(rwtxn, &m6id) - .map_err(DbError::from)?; + if let Some(bundle_status) = bundle_status { + state + .withdrawal_bundles + .put(rwtxn, &m6id, &(bundle, bundle_status))?; + } else { + state.withdrawal_bundles.delete(rwtxn, &m6id)?; + } Ok(()) } @@ -673,9 +765,10 @@ fn disconnect_withdrawal_bundle_failed( .delete(rwtxn, &OutPointKey::from(outpoint)) .map_err(DbError::from)? { - return Err(Error::NoUtxo { + return Err(error::NoUtxo { outpoint: *outpoint, - }); + } + .into()); }; let utxo_hash = hash(&PointedOutput { outpoint: *outpoint, @@ -719,7 +812,7 @@ fn disconnect_withdrawal_bundle_event( event: &WithdrawalBundleEvent, ) -> Result<(), Error> { match event.status { - WithdrawalBundleStatus::Submitted => { + WithdrawalBundleEventStatus::Submitted => { disconnect_withdrawal_bundle_submitted( state, rwtxn, @@ -728,7 +821,7 @@ fn disconnect_withdrawal_bundle_event( event.m6id, ) } - WithdrawalBundleStatus::Confirmed => { + WithdrawalBundleEventStatus::Confirmed => { disconnect_withdrawal_bundle_confirmed( state, rwtxn, @@ -737,13 +830,15 @@ fn disconnect_withdrawal_bundle_event( event.m6id, ) } - WithdrawalBundleStatus::Failed => disconnect_withdrawal_bundle_failed( - state, - rwtxn, - block_height, - accumulator_diff, - event.m6id, - ), + WithdrawalBundleEventStatus::Failed => { + disconnect_withdrawal_bundle_failed( + state, + rwtxn, + block_height, + accumulator_diff, + event.m6id, + ) + } } } @@ -767,7 +862,7 @@ fn disconnect_event( .delete(rwtxn, &OutPointKey::from(&outpoint)) .map_err(DbError::from)? { - return Err(Error::NoUtxo { outpoint }); + return Err(error::NoUtxo { outpoint }.into()); } let utxo_hash = hash(&PointedOutput { outpoint, output }); accumulator_diff.remove(utxo_hash.into()); @@ -832,8 +927,7 @@ pub fn disconnect( ), ) = state .withdrawal_bundle_event_blocks - .last(rwtxn) - .map_err(DbError::from)? + .last(rwtxn)? .ok_or(Error::NoWithdrawalBundleEventBlock)?; assert_eq!( latest_withdrawal_bundle_event_block_hash, @@ -842,29 +936,37 @@ pub fn disconnect( assert_eq!(block_height - 1, last_withdrawal_bundle_event_block_height); if !state .deposit_blocks - .delete(rwtxn, &last_withdrawal_bundle_event_block_seq_idx) - .map_err(DbError::from)? + .delete(rwtxn, &last_withdrawal_bundle_event_block_seq_idx)? { return Err(Error::NoWithdrawalBundleEventBlock); }; } let last_withdrawal_bundle_failure_height = state - .get_latest_failed_withdrawal_bundle(rwtxn) - .map_err(DbError::from)? + .get_latest_failed_withdrawal_bundle(rwtxn)? .map(|(height, _bundle)| height) .unwrap_or_default(); if block_height - last_withdrawal_bundle_failure_height > WITHDRAWAL_BUNDLE_FAILURE_GAP - && let Some((_bundle, bundle_height)) = state - .pending_withdrawal_bundle - .try_get(rwtxn, &()) - .map_err(DbError::from)? - && bundle_height == block_height - 1 + && let Some(bundle_m6id) = + state.pending_withdrawal_bundle.try_get(rwtxn, &())? + && let (bundle, bundle_status) = state + .withdrawal_bundles + .try_get(rwtxn, &bundle_m6id)? + .ok_or(error::PendingWithdrawalBundleUnknown(bundle_m6id))? + && bundle_status.latest().height == block_height - 1 { - state - .pending_withdrawal_bundle - .delete(rwtxn, &()) - .map_err(DbError::from)?; + state.pending_withdrawal_bundle.delete(rwtxn, &())?; + if let (Some(bundle_status), _latest_bundle_status) = + bundle_status.pop() + { + state.withdrawal_bundles.put( + rwtxn, + &bundle_m6id, + &(bundle, bundle_status), + )?; + } else { + state.withdrawal_bundles.delete(rwtxn, &bundle_m6id)?; + } } // Handle deposits. if let Some(latest_deposit_block_hash) = latest_deposit_block_hash { @@ -873,23 +975,18 @@ pub fn disconnect( (last_deposit_block_hash, last_deposit_block_height), ) = state .deposit_blocks - .last(rwtxn) - .map_err(DbError::from)? + .last(rwtxn)? .ok_or(Error::NoDepositBlock)?; assert_eq!(latest_deposit_block_hash, last_deposit_block_hash); assert_eq!(block_height - 1, last_deposit_block_height); if !state .deposit_blocks - .delete(rwtxn, &last_deposit_block_seq_idx) - .map_err(DbError::from)? + .delete(rwtxn, &last_deposit_block_seq_idx)? { return Err(Error::NoDepositBlock); }; } let () = accumulator.apply_diff(accumulator_diff)?; - state - .utreexo_accumulator - .put(rwtxn, &(), &accumulator) - .map_err(DbError::from)?; + state.utreexo_accumulator.put(rwtxn, &(), &accumulator)?; Ok(()) } diff --git a/lib/types/mod.rs b/lib/types/mod.rs index abd2624..8dac1eb 100644 --- a/lib/types/mod.rs +++ b/lib/types/mod.rs @@ -145,17 +145,27 @@ impl Header { } } +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum WithdrawalBundleEventStatus { + Confirmed, + Failed, + Submitted, +} + #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum WithdrawalBundleStatus { Confirmed, + /// Formerly pending bundle + Dropped, Failed, + Pending, Submitted, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct WithdrawalBundleEvent { pub m6id: M6id, - pub status: WithdrawalBundleStatus, + pub status: WithdrawalBundleEventStatus, } pub static OP_DRIVECHAIN_SCRIPT: LazyLock = @@ -851,7 +861,11 @@ pub struct Tip { } #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "clap", derive(clap::ValueEnum, strum::Display))] +#[cfg_attr( + feature = "clap", + derive(clap::ValueEnum, strum::Display), + strum(serialize_all = "lowercase") +)] pub enum Network { #[default] Signet, diff --git a/lib/types/proto.rs b/lib/types/proto.rs index 5fcc98f..156ce85 100644 --- a/lib/types/proto.rs +++ b/lib/types/proto.rs @@ -529,7 +529,7 @@ pub mod mainchain { } impl From - for crate::types::WithdrawalBundleStatus + for crate::types::WithdrawalBundleEventStatus { fn from( event: generated::withdrawal_bundle_event::event::Event, @@ -549,7 +549,7 @@ pub mod mainchain { } impl TryFrom - for crate::types::WithdrawalBundleStatus + for crate::types::WithdrawalBundleEventStatus { type Error = super::Error;