diff --git a/Cargo.lock b/Cargo.lock index 9e7f937..8c1a79f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3297,6 +3297,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nonempty" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "303e8749c804ccd6ca3b428de7fe0d86cb86bc7606bc15291f100fd487960bb8" +dependencies = [ + "serde", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -5029,6 +5038,7 @@ dependencies = [ "heed", "hex", "jsonrpsee", + "nonempty", "parking_lot", "prost", "prost-build", diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 015c3b6..35960e1 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -26,6 +26,7 @@ hashlink = { version = "0.9.1", features = ["serde_impl"] } heed = "0.20.1" hex = { version = "0.4.3", features = ["serde"] } jsonrpsee = { version = "0.23.2" } +nonempty = { version = "0.10.0", features = ["serialize"] } parking_lot = "0.12.1" prost = "0.13.3" prost-types = "0.13.3" diff --git a/lib/node/mod.rs b/lib/node/mod.rs index dd88ea6..313b272 100644 --- a/lib/node/mod.rs +++ b/lib/node/mod.rs @@ -575,12 +575,14 @@ where let rotxn = self.env.read_txn()?; let bundle = self.state.get_pending_withdrawal_bundle(&rotxn)?; if let Some((bundle, _)) = bundle { + let m6id = bundle.compute_m6id(); let mut cusf_mainchain_wallet_lock = cusf_mainchain_wallet.lock().await; let () = cusf_mainchain_wallet_lock .broadcast_withdrawal_bundle(bundle.tx()) .await?; drop(cusf_mainchain_wallet_lock); + tracing::trace!(%m6id, "Broadcast withdrawal bundle"); } Ok(true) } diff --git a/lib/node/net_task.rs b/lib/node/net_task.rs index 77f06d6..0f59d74 100644 --- a/lib/node/net_task.rs +++ b/lib/node/net_task.rs @@ -1,6 +1,7 @@ //! Task to manage peers and their responses use std::{ + cmp::Ordering, collections::{HashMap, HashSet}, net::SocketAddr, sync::Arc, @@ -79,8 +80,39 @@ where Transport: proto::Transport, { let last_deposit_block_hash = state.get_last_deposit_block_hash(rwtxn)?; + let last_withdrawal_bundle_event_block_hash = + state.get_last_withdrawal_bundle_event_block_hash(rwtxn)?; + let last_2wpd_block = match ( + last_deposit_block_hash, + last_withdrawal_bundle_event_block_hash, + ) { + (None, None) => None, + (Some(last_deposit_block_hash), None) => Some(last_deposit_block_hash), + (None, Some(last_withdrawal_bundle_event_block_hash)) => { + Some(last_withdrawal_bundle_event_block_hash) + } + ( + Some(last_deposit_block_hash), + Some(last_withdrawal_bundle_event_block_hash), + ) => { + if archive.is_main_descendant( + rwtxn, + last_deposit_block_hash, + last_withdrawal_bundle_event_block_hash, + )? { + Some(last_withdrawal_bundle_event_block_hash) + } else { + assert!(archive.is_main_descendant( + rwtxn, + last_withdrawal_bundle_event_block_hash, + last_deposit_block_hash + )?); + Some(last_deposit_block_hash) + } + } + }; let two_way_peg_data = cusf_mainchain - .get_two_way_peg_data(last_deposit_block_hash, header.prev_main_hash) + .get_two_way_peg_data(last_2wpd_block, header.prev_main_hash) .await?; let block_hash = header.hash(); let _fees: bitcoin::Amount = state.validate_block(rwtxn, header, body)?; @@ -120,17 +152,67 @@ where let tip_body = archive.get_body(rwtxn, tip_block_hash)?; let height = state.get_height(rwtxn)?; let two_way_peg_data = { - let start_block_hash = state + let last_applied_deposit_block = state .deposit_blocks .rev_iter(rwtxn)? .transpose_into_fallible() .find_map(|(_, (block_hash, applied_height))| { if applied_height < height - 1 { - Ok(Some(block_hash)) + Ok(Some((block_hash, applied_height))) + } else { + Ok(None) + } + })?; + let last_applied_withdrawal_bundle_event_block = state + .withdrawal_bundle_event_blocks + .rev_iter(rwtxn)? + .transpose_into_fallible() + .find_map(|(_, (block_hash, applied_height))| { + if applied_height < height - 1 { + Ok(Some((block_hash, applied_height))) } else { Ok(None) } })?; + let start_block_hash = match ( + last_applied_deposit_block, + last_applied_withdrawal_bundle_event_block, + ) { + (None, None) => None, + (Some((block_hash, _)), None) | (None, Some((block_hash, _))) => { + Some(block_hash) + } + ( + Some((deposit_block, deposit_block_applied_height)), + Some(( + withdrawal_event_block, + withdrawal_event_block_applied_height, + )), + ) => { + match deposit_block_applied_height + .cmp(&withdrawal_event_block_applied_height) + { + Ordering::Less => Some(withdrawal_event_block), + Ordering::Greater => Some(deposit_block), + Ordering::Equal => { + if archive.is_main_descendant( + rwtxn, + withdrawal_event_block, + deposit_block, + )? { + Some(withdrawal_event_block) + } else { + assert!(archive.is_main_descendant( + rwtxn, + deposit_block, + withdrawal_event_block + )?); + Some(deposit_block) + } + } + } + } + }; cusf_mainchain .get_two_way_peg_data(start_block_hash, tip_header.prev_main_hash) .await? diff --git a/lib/state.rs b/lib/state.rs index b1c8263..cdb57ef 100644 --- a/lib/state.rs +++ b/lib/state.rs @@ -2,7 +2,9 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use futures::Stream; use heed::{types::SerdeBincode, Database, RoTxn, RwTxn}; +use nonempty::{nonempty, NonEmpty}; use rustreexo::accumulator::{node_hash::NodeHash, proof::Proof}; +use serde::{Deserialize, Serialize}; use crate::{ authorization::Authorization, @@ -10,8 +12,8 @@ use crate::{ hash, proto::mainchain::TwoWayPegData, Accumulator, Address, AggregatedWithdrawal, AmountOverflowError, AmountUnderflowError, AuthorizedTransaction, BlockHash, Body, FilledTransaction, GetAddress, - GetValue, Header, InPoint, MerkleRoot, OutPoint, Output, OutputContent, - PointedOutput, SpentOutput, Transaction, Txid, Verify, + GetValue, Header, InPoint, M6id, MerkleRoot, OutPoint, Output, + OutputContent, PointedOutput, SpentOutput, Transaction, Txid, Verify, WithdrawalBundle, WithdrawalBundleError, WithdrawalBundleStatus, }, util::{EnvExt, UnitKey, Watchable, WatchableDb}, @@ -31,6 +33,56 @@ pub enum InvalidHeaderError { }, } +/// Data of type `T` paired with block height at which it was last updated +#[derive(Clone, Debug, Deserialize, Serialize)] +struct HeightStamped { + value: T, + height: u32, +} + +/// Wrapper struct for fields that support rollbacks +#[derive(Clone, Debug, Deserialize, Serialize)] +#[repr(transparent)] +#[serde(transparent)] +struct RollBack(NonEmpty>); + +impl RollBack { + fn new(value: T, height: u32) -> Self { + let txid_stamped = HeightStamped { value, height }; + Self(nonempty![txid_stamped]) + } + + /// Pop the most recent value + fn pop(mut self) -> (Option, HeightStamped) { + if let Some(value) = self.0.pop() { + (Some(self), value) + } else { + (None, self.0.head) + } + } + + /// Attempt to push a value as the new most recent. + /// Returns the value if the operation fails. + fn push(&mut self, value: T, height: u32) -> Result<(), T> { + if self.0.last().height >= height { + return Err(value); + } + let height_stamped = HeightStamped { value, height }; + self.0.push(height_stamped); + Ok(()) + } + + /// Returns the earliest value + fn earliest(&self) -> &HeightStamped { + self.0.first() + } + + /// Returns the most recent value + fn latest(&self) -> &HeightStamped { + self.0.last() + } +} + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("failed to verify authorization")] @@ -64,6 +116,8 @@ pub enum Error { NotEnoughValueIn, #[error("utxo {outpoint} doesn't exist")] NoUtxo { outpoint: OutPoint }, + #[error("Withdrawal bundle event block doesn't exist")] + NoWithdrawalBundleEventBlock, #[error("utreexo error: {0}")] Utreexo(String), #[error("Utreexo proof verification failed for tx {txid}")] @@ -74,6 +128,8 @@ pub enum Error { UtxoDoubleSpent, #[error("too many sigops")] TooManySigops, + #[error("Unknown withdrawal bundle: {m6id}")] + UnknownWithdrawalBundle { m6id: M6id }, #[error("wrong public key for address")] WrongPubKeyForAddress, #[error(transparent)] @@ -91,20 +147,24 @@ pub struct State { /// Pending withdrawal bundle and block height pub pending_withdrawal_bundle: Database, SerdeBincode<(WithdrawalBundle, u32)>>, - /// Mapping from block height to withdrawal bundle and status - pub withdrawal_bundles: Database< - SerdeBincode, - SerdeBincode<(WithdrawalBundle, WithdrawalBundleStatus)>, + latest_failed_withdrawal_bundle: + Database, SerdeBincode>>, + withdrawal_bundles: Database< + SerdeBincode, + SerdeBincode<(WithdrawalBundle, RollBack)>, >, /// deposit blocks and the height at which they were applied, keyed sequentially pub deposit_blocks: Database, SerdeBincode<(bitcoin::BlockHash, u32)>>, + /// withdrawal bundle event blocks and the height at which they were applied, keyed sequentially + pub withdrawal_bundle_event_blocks: + Database, SerdeBincode<(bitcoin::BlockHash, u32)>>, pub utreexo_accumulator: Database, SerdeBincode>, } impl State { - pub const NUM_DBS: u32 = 8; + pub const NUM_DBS: u32 = 10; pub const WITHDRAWAL_BUNDLE_FAILURE_GAP: u32 = 4; pub fn new(env: &heed::Env) -> Result { @@ -115,10 +175,18 @@ impl State { let stxos = env.create_database(&mut rwtxn, Some("stxos"))?; let pending_withdrawal_bundle = env.create_database(&mut rwtxn, Some("pending_withdrawal_bundle"))?; + let latest_failed_withdrawal_bundle = env.create_database( + &mut rwtxn, + Some("latest_failed_withdrawal_bundle"), + )?; let withdrawal_bundles = env.create_database(&mut rwtxn, Some("withdrawal_bundles"))?; let deposit_blocks = env.create_database(&mut rwtxn, Some("deposit_blocks"))?; + let withdrawal_bundle_event_blocks = env.create_database( + &mut rwtxn, + Some("withdrawal_bundle_event_blocks"), + )?; let utreexo_accumulator = env.create_database(&mut rwtxn, Some("utreexo_accumulator"))?; rwtxn.commit()?; @@ -128,8 +196,10 @@ impl State { utxos, stxos, pending_withdrawal_bundle, + latest_failed_withdrawal_bundle, withdrawal_bundles, deposit_blocks, + withdrawal_bundle_event_blocks, utreexo_accumulator, }) } @@ -176,13 +246,17 @@ impl State { &self, rotxn: &RoTxn, ) -> Result, Error> { - for item in self.withdrawal_bundles.rev_iter(rotxn)? { - if let (height, (bundle, WithdrawalBundleStatus::Failed)) = item? { - let res = Some((height, bundle)); - return Ok(res); - } - } - Ok(None) + let Some(latest_failed_m6id) = + self.latest_failed_withdrawal_bundle.get(rotxn, &UnitKey)? + else { + return Ok(None); + }; + let latest_failed_m6id = latest_failed_m6id.latest().value; + let (bundle, bundle_status) = self.withdrawal_bundles.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, bundle))) } /// Get the current Utreexo accumulator @@ -558,12 +632,24 @@ impl State { Ok(block_hash) } + pub fn get_last_withdrawal_bundle_event_block_hash( + &self, + rotxn: &RoTxn, + ) -> Result, Error> { + let block_hash = self + .withdrawal_bundle_event_blocks + .last(rotxn)? + .map(|(_, (block_hash, _))| block_hash); + Ok(block_hash) + } + pub fn connect_two_way_peg_data( &self, rwtxn: &mut RwTxn, two_way_peg_data: &TwoWayPegData, ) -> Result<(), Error> { let block_height = self.get_height(rwtxn)?; + tracing::trace!(%block_height, "Connecting 2WPD..."); let mut accumulator = self .utreexo_accumulator .get(rwtxn, &UnitKey)? @@ -573,7 +659,8 @@ impl State { // Accumulator leaves to delete let mut accumulator_del = HashSet::::new(); // Handle deposits. - if let Some(deposit_block_hash) = two_way_peg_data.deposit_block_hash() + if let Some(latest_deposit_block_hash) = + two_way_peg_data.latest_deposit_block_hash() { let deposit_block_seq_idx = self .deposit_blocks @@ -582,7 +669,7 @@ impl State { self.deposit_blocks.put( rwtxn, &deposit_block_seq_idx, - &(deposit_block_hash, block_height - 1), + &(latest_deposit_block_hash, block_height - 1), )?; } for deposit in two_way_peg_data @@ -596,7 +683,20 @@ impl State { accumulator_add.push(utxo_hash.into()) } - // Handle withdrawals. + // Handle withdrawals + if let Some(latest_withdrawal_bundle_event_block_hash) = + two_way_peg_data.latest_withdrawal_bundle_event_block_hash() + { + let withdrawal_bundle_event_block_seq_idx = self + .withdrawal_bundle_event_blocks + .last(rwtxn)? + .map_or(0, |(seq_idx, _)| seq_idx + 1); + self.withdrawal_bundle_event_blocks.put( + rwtxn, + &withdrawal_bundle_event_block_seq_idx, + &(*latest_withdrawal_bundle_event_block_hash, block_height - 1), + )?; + } let last_withdrawal_bundle_failure_height = self .get_latest_failed_withdrawal_bundle(rwtxn)? .map(|(height, _bundle)| height) @@ -625,28 +725,112 @@ impl State { }; self.stxos.put(rwtxn, outpoint, &spent_output)?; } + let m6id = bundle.compute_m6id(); self.pending_withdrawal_bundle.put( rwtxn, &UnitKey, &(bundle, block_height), )?; + tracing::trace!( + %block_height, + %m6id, + "Stored pending withdrawal bundle" + ); } } for (_, m6id, event) in two_way_peg_data.withdrawal_bundle_events() { - if let Some((bundle, bundle_block_height)) = - self.pending_withdrawal_bundle.get(rwtxn, &UnitKey)? - { - if bundle.compute_m6id() != *m6id { - continue; + match event.status { + WithdrawalBundleStatus::Submitted => { + let Some((bundle, bundle_block_height)) = + self.pending_withdrawal_bundle.get(rwtxn, &UnitKey)? + else { + if let Some((_bundle, bundle_status)) = + self.withdrawal_bundles.get(rwtxn, m6id)? + { + // Already applied + assert_eq!( + bundle_status.earliest().value, + WithdrawalBundleStatus::Submitted + ); + continue; + } + return Err(Error::UnknownWithdrawalBundle { + m6id: *m6id, + }); + }; + assert_eq!(bundle_block_height, block_height - 2); + if bundle.compute_m6id() != *m6id { + return Err(Error::UnknownWithdrawalBundle { + m6id: *m6id, + }); + } + tracing::debug!( + %m6id, + "Withdrawal bundle successfully submitted" + ); + self.withdrawal_bundles.put( + rwtxn, + m6id, + &( + bundle, + RollBack::new( + WithdrawalBundleStatus::Submitted, + block_height, + ), + ), + )?; + self.pending_withdrawal_bundle.delete(rwtxn, &UnitKey)?; } - assert_eq!(bundle_block_height, block_height); - self.withdrawal_bundles.put( - rwtxn, - &block_height, - &(bundle.clone(), event.status), - )?; - self.pending_withdrawal_bundle.delete(rwtxn, &UnitKey)?; - if let WithdrawalBundleStatus::Failed = event.status { + WithdrawalBundleStatus::Confirmed => { + let Some((bundle, mut bundle_status)) = + self.withdrawal_bundles.get(rwtxn, m6id)? + else { + return Err(Error::UnknownWithdrawalBundle { + m6id: *m6id, + }); + }; + if bundle_status.latest().value + == WithdrawalBundleStatus::Confirmed + { + // Already applied + continue; + } else { + assert_eq!( + bundle_status.latest().value, + WithdrawalBundleStatus::Submitted + ); + } + bundle_status + .push(WithdrawalBundleStatus::Confirmed, block_height) + .expect("Push confirmed status should be valid"); + self.withdrawal_bundles.put( + rwtxn, + m6id, + &(bundle, bundle_status), + )?; + } + WithdrawalBundleStatus::Failed => { + let Some((bundle, mut bundle_status)) = + self.withdrawal_bundles.get(rwtxn, m6id)? + else { + return Err(Error::UnknownWithdrawalBundle { + m6id: *m6id, + }); + }; + if bundle_status.latest().value + == WithdrawalBundleStatus::Failed + { + // Already applied + continue; + } else { + assert_eq!( + bundle_status.latest().value, + WithdrawalBundleStatus::Submitted + ); + } + bundle_status + .push(WithdrawalBundleStatus::Failed, block_height) + .expect("Push failed status should be valid"); for (outpoint, output) in bundle.spend_utxos() { self.stxos.delete(rwtxn, outpoint)?; self.utxos.put(rwtxn, outpoint, output)?; @@ -656,6 +840,30 @@ impl State { }); accumulator_del.remove(&utxo_hash.into()); } + let latest_failed_m6id = + if let Some(mut latest_failed_m6id) = self + .latest_failed_withdrawal_bundle + .get(rwtxn, &UnitKey)? + { + latest_failed_m6id + .push(*m6id, block_height) + .expect( + "Push latest failed m6id should be valid", + ); + latest_failed_m6id + } else { + RollBack::new(*m6id, block_height) + }; + self.latest_failed_withdrawal_bundle.put( + rwtxn, + &UnitKey, + &latest_failed_m6id, + )?; + self.withdrawal_bundles.put( + rwtxn, + m6id, + &(bundle, bundle_status), + )?; } } } @@ -688,26 +896,99 @@ impl State { for (_, m6id, event) in two_way_peg_data.withdrawal_bundle_events().rev() { - if let Some(( - latest_bundle_height, - (latest_bundle, latest_bundle_status), - )) = self.withdrawal_bundles.last(rwtxn)? - { - if latest_bundle.compute_m6id() != *m6id { - continue; + match event.status { + WithdrawalBundleStatus::Submitted => { + let Some((bundle, bundle_status)) = + self.withdrawal_bundles.get(rwtxn, m6id)? + else { + if let Some((bundle, _)) = self + .pending_withdrawal_bundle + .get(rwtxn, &UnitKey)? + && bundle.compute_m6id() == *m6id + { + // Already applied + continue; + } + return Err(Error::UnknownWithdrawalBundle { + m6id: *m6id, + }); + }; + let bundle_status = bundle_status.latest(); + assert_eq!( + bundle_status.value, + WithdrawalBundleStatus::Submitted + ); + assert_eq!(bundle_status.height, block_height); + self.pending_withdrawal_bundle.put( + rwtxn, + &UnitKey, + &(bundle, bundle_status.height - 2), + )?; + self.withdrawal_bundles.delete(rwtxn, m6id)?; } - assert_eq!(event.status, latest_bundle_status); - assert_eq!(latest_bundle_height, block_height); - self.withdrawal_bundles - .delete(rwtxn, &latest_bundle_height)?; - self.pending_withdrawal_bundle.put( - rwtxn, - &UnitKey, - &(latest_bundle.clone(), latest_bundle_height), - )?; - if event.status == WithdrawalBundleStatus::Failed { - for (outpoint, output) in - latest_bundle.spend_utxos().iter().rev() + WithdrawalBundleStatus::Confirmed => { + let Some((bundle, bundle_status)) = + self.withdrawal_bundles.get(rwtxn, m6id)? + else { + return Err(Error::UnknownWithdrawalBundle { + m6id: *m6id, + }); + }; + let (prev_bundle_status, latest_bundle_status) = + bundle_status.pop(); + if latest_bundle_status.value + == WithdrawalBundleStatus::Submitted + { + // Already applied + continue; + } else { + assert_eq!( + latest_bundle_status.value, + WithdrawalBundleStatus::Confirmed + ); + } + assert_eq!(latest_bundle_status.height, block_height); + let prev_bundle_status = prev_bundle_status + .expect("Pop confirmed bundle status should be valid"); + assert_eq!( + prev_bundle_status.latest().value, + WithdrawalBundleStatus::Submitted + ); + self.withdrawal_bundles.put( + rwtxn, + m6id, + &(bundle, prev_bundle_status), + )?; + } + WithdrawalBundleStatus::Failed => { + let Some((bundle, bundle_status)) = + self.withdrawal_bundles.get(rwtxn, m6id)? + else { + return Err(Error::UnknownWithdrawalBundle { + m6id: *m6id, + }); + }; + let (prev_bundle_status, latest_bundle_status) = + bundle_status.pop(); + if latest_bundle_status.value + == WithdrawalBundleStatus::Submitted + { + // Already applied + continue; + } else { + assert_eq!( + latest_bundle_status.value, + WithdrawalBundleStatus::Failed + ); + } + assert_eq!(latest_bundle_status.height, block_height); + let prev_bundle_status = prev_bundle_status + .expect("Pop failed bundle status should be valid"); + assert_eq!( + prev_bundle_status.latest().value, + WithdrawalBundleStatus::Submitted + ); + for (outpoint, output) in bundle.spend_utxos().iter().rev() { let spent_output = SpentOutput { output: output.clone(), @@ -725,10 +1006,62 @@ impl State { }); accumulator_add.push(utxo_hash.into()); } + self.withdrawal_bundles.put( + rwtxn, + m6id, + &(bundle, prev_bundle_status), + )?; + let (prev_latest_failed_m6id, latest_failed_m6id) = self + .latest_failed_withdrawal_bundle + .get(rwtxn, &UnitKey)? + .expect("latest failed withdrawal bundle should exist") + .pop(); + assert_eq!(latest_failed_m6id.value, *m6id); + assert_eq!(latest_failed_m6id.height, block_height); + if let Some(prev_latest_failed_m6id) = + prev_latest_failed_m6id + { + self.latest_failed_withdrawal_bundle.put( + rwtxn, + &UnitKey, + &prev_latest_failed_m6id, + )?; + } else { + self.latest_failed_withdrawal_bundle + .delete(rwtxn, &UnitKey)?; + } } } } - // Handle withdrawals. + // Handle withdrawals + if let Some(latest_withdrawal_bundle_event_block_hash) = + two_way_peg_data.latest_withdrawal_bundle_event_block_hash() + { + let ( + last_withdrawal_bundle_event_block_seq_idx, + ( + last_withdrawal_bundle_event_block_hash, + last_withdrawal_bundle_event_block_height, + ), + ) = self + .withdrawal_bundle_event_blocks + .last(rwtxn)? + .ok_or(Error::NoWithdrawalBundleEventBlock)?; + assert_eq!( + *latest_withdrawal_bundle_event_block_hash, + last_withdrawal_bundle_event_block_hash + ); + assert_eq!( + block_height - 1, + last_withdrawal_bundle_event_block_height + ); + if !self + .deposit_blocks + .delete(rwtxn, &last_withdrawal_bundle_event_block_seq_idx)? + { + return Err(Error::NoWithdrawalBundleEventBlock); + }; + } let last_withdrawal_bundle_failure_height = self .get_latest_failed_withdrawal_bundle(rwtxn)? .map(|(height, _bundle)| height) @@ -737,7 +1070,7 @@ impl State { > Self::WITHDRAWAL_BUNDLE_FAILURE_GAP && let Some((bundle, bundle_height)) = self.pending_withdrawal_bundle.get(rwtxn, &UnitKey)? - && bundle_height == block_height + && bundle_height == block_height - 2 { self.pending_withdrawal_bundle.delete(rwtxn, &UnitKey)?; for (outpoint, output) in bundle.spend_utxos().iter().rev() { @@ -755,7 +1088,8 @@ impl State { } } // Handle deposits. - if let Some(deposit_block_hash) = two_way_peg_data.deposit_block_hash() + if let Some(latest_deposit_block_hash) = + two_way_peg_data.latest_deposit_block_hash() { let ( last_deposit_block_seq_idx, @@ -764,7 +1098,7 @@ impl State { .deposit_blocks .last(rwtxn)? .ok_or(Error::NoDepositBlock)?; - assert_eq!(deposit_block_hash, last_deposit_block_hash); + assert_eq!(latest_deposit_block_hash, last_deposit_block_hash); assert_eq!(block_height - 1, last_deposit_block_height); if !self .deposit_blocks diff --git a/lib/types/proto.rs b/lib/types/proto.rs index ee24c1c..3ad5de1 100644 --- a/lib/types/proto.rs +++ b/lib/types/proto.rs @@ -578,12 +578,21 @@ pub mod mainchain { }) } - /// Last deposit block hash - pub fn deposit_block_hash(&self) -> Option { + /// Latest deposit block hash + pub fn latest_deposit_block_hash(&self) -> Option { self.deposits() .next_back() .map(|(block_hash, _)| block_hash) } + + /// Latest withdrawal bundle event block hash + pub fn latest_withdrawal_bundle_event_block_hash( + &self, + ) -> Option<&BlockHash> { + self.withdrawal_bundle_events() + .next_back() + .map(|(block_hash, _, _)| block_hash) + } } impl TryFrom for TwoWayPegData {