refactor(alice): concretize enc sig publication requirement (#664)

* refactor(alice): concretize enc sig publication requirement

* add changelog entry
This commit is contained in:
Mohan 2025-11-01 00:44:58 +01:00 committed by GitHub
parent d9438c5913
commit 0fec5d556d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 26 additions and 19 deletions

View file

@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
- ASB: Fix an issue where we would not redeem the Bitcoin and force a refund even though it was still possible to do so.
## [3.2.7] - 2025-10-28 ## [3.2.7] - 2025-10-28
## [3.2.6] - 2025-10-27 ## [3.2.6] - 2025-10-27

View file

@ -11,9 +11,9 @@ tor-hsservice = { workspace = true }
tor-rtcompat = { workspace = true } tor-rtcompat = { workspace = true }
# Async # Async
backoff = { workspace = true, features = ["tokio"] }
futures = { workspace = true } futures = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "time", "macros", "sync", "process", "fs", "net", "io-util"] } tokio = { workspace = true, features = ["rt-multi-thread", "time", "macros", "sync", "process", "fs", "net", "io-util"] }
backoff = { workspace = true, features = ["tokio"] }
# Libp2p # Libp2p
libp2p = { workspace = true, features = ["rendezvous", "tcp", "yamux", "dns", "noise", "ping", "websocket", "tokio", "macros"] } libp2p = { workspace = true, features = ["rendezvous", "tcp", "yamux", "dns", "noise", "ping", "websocket", "tokio", "macros"] }
@ -28,6 +28,6 @@ tracing-subscriber = { workspace = true, default-features = false, features = ["
anyhow = "1" anyhow = "1"
# Other # Other
swap-env = { path = "../swap-env" }
atty = "0.2" atty = "0.2"
structopt = { version = "0.3", default-features = false } structopt = { version = "0.3", default-features = false }
swap-env = { path = "../swap-env" }

View file

@ -45,10 +45,6 @@ impl CancelTimelock {
pub const fn new(number_of_blocks: u32) -> Self { pub const fn new(number_of_blocks: u32) -> Self {
Self(number_of_blocks) Self(number_of_blocks)
} }
pub fn half(&self) -> CancelTimelock {
Self(self.0 / 2)
}
} }
impl Add<CancelTimelock> for BlockHeight { impl Add<CancelTimelock> for BlockHeight {

View file

@ -9,6 +9,9 @@ pub struct Config {
pub bitcoin_lock_mempool_timeout: Duration, pub bitcoin_lock_mempool_timeout: Duration,
pub bitcoin_lock_confirmed_timeout: Duration, pub bitcoin_lock_confirmed_timeout: Duration,
pub bitcoin_finality_confirmations: u32, pub bitcoin_finality_confirmations: u32,
/// The upper bound for the number of blocks that will be mined before our
/// Bitcoin transaction is included in a block
pub bitcoin_blocks_till_confirmed_upper_bound_assumption: u32,
pub bitcoin_avg_block_time: Duration, pub bitcoin_avg_block_time: Duration,
pub bitcoin_cancel_timelock: u32, pub bitcoin_cancel_timelock: u32,
pub bitcoin_punish_timelock: u32, pub bitcoin_punish_timelock: u32,
@ -52,6 +55,9 @@ impl GetConfig for Mainnet {
bitcoin_lock_mempool_timeout: 10.std_minutes(), bitcoin_lock_mempool_timeout: 10.std_minutes(),
bitcoin_lock_confirmed_timeout: 2.std_hours(), bitcoin_lock_confirmed_timeout: 2.std_hours(),
bitcoin_finality_confirmations: 1, bitcoin_finality_confirmations: 1,
// We assume that a transaction that was constructed to be confirmed within one block
// will be confirmed within at most 6 blocks
bitcoin_blocks_till_confirmed_upper_bound_assumption: 6,
bitcoin_avg_block_time: 10.std_minutes(), bitcoin_avg_block_time: 10.std_minutes(),
bitcoin_cancel_timelock: 72, bitcoin_cancel_timelock: 72,
bitcoin_punish_timelock: 144, bitcoin_punish_timelock: 144,
@ -73,6 +79,7 @@ impl GetConfig for Testnet {
bitcoin_lock_mempool_timeout: 10.std_minutes(), bitcoin_lock_mempool_timeout: 10.std_minutes(),
bitcoin_lock_confirmed_timeout: 1.std_hours(), bitcoin_lock_confirmed_timeout: 1.std_hours(),
bitcoin_finality_confirmations: 1, bitcoin_finality_confirmations: 1,
bitcoin_blocks_till_confirmed_upper_bound_assumption: 6,
bitcoin_avg_block_time: 10.std_minutes(), bitcoin_avg_block_time: 10.std_minutes(),
bitcoin_cancel_timelock: 12 * 3, bitcoin_cancel_timelock: 12 * 3,
bitcoin_punish_timelock: 24 * 3, bitcoin_punish_timelock: 24 * 3,
@ -92,6 +99,7 @@ impl GetConfig for Regtest {
bitcoin_lock_mempool_timeout: 30.std_seconds(), bitcoin_lock_mempool_timeout: 30.std_seconds(),
bitcoin_lock_confirmed_timeout: 5.std_minutes(), bitcoin_lock_confirmed_timeout: 5.std_minutes(),
bitcoin_finality_confirmations: 1, bitcoin_finality_confirmations: 1,
bitcoin_blocks_till_confirmed_upper_bound_assumption: 6,
bitcoin_avg_block_time: 5.std_seconds(), bitcoin_avg_block_time: 5.std_seconds(),
bitcoin_cancel_timelock: 100, bitcoin_cancel_timelock: 100,
bitcoin_punish_timelock: 50, bitcoin_punish_timelock: 50,

View file

@ -451,26 +451,25 @@ where
.build(); .build();
match backoff::future::retry_notify(backoff.clone(), || async { match backoff::future::retry_notify(backoff.clone(), || async {
// We can only redeem the Bitcoin if we are sure that our Bitcoin redeem transaction
// will be confirmed before the timelock expires
//
// We only publish the transaction if we have more than half of the timelock left.
let tx_lock_status = bitcoin_wallet let tx_lock_status = bitcoin_wallet
.status_of_script(&state3.tx_lock.clone()) .status_of_script(&state3.tx_lock.clone())
.await?; .await?;
if tx_lock_status.is_confirmed_with(state3.cancel_timelock.half()) { // If the cancel timelock is expired, it it not safe to publish the Bitcoin redeem transaction anymore
//
// TODO: In practice this should be redundant because the logic above will trigger for a superset of the cases where this is true
if tx_lock_status.is_confirmed_with(state3.cancel_timelock) {
return Ok(None); return Ok(None);
} }
// If the cancel timelock is expired, it it not safe to publish the Bitcoin redeem transaction anymore // We can only redeem the Bitcoin if we are fairly sure that our Bitcoin redeem transaction
// will be confirmed before the cancel timelock expires
// //
// TODO: This should never be true if the logic above is not true but we do it anyway to be safe // We make an assumption that it will take at most `env_config.bitcoin_blocks_till_confirmed_upper_bound_assumption` blocks
// TODO: Remove this // until our transaction is included in a block. If this assumption is not satisfied, we will not publish the transaction.
if !matches!( //
state3.expired_timelocks(&*bitcoin_wallet).await?, // We will instead wait for the cancel timelock to expire and then refund.
ExpiredTimelocks::None { .. } if tx_lock_status.blocks_left_until(state3.cancel_timelock) < env_config.bitcoin_blocks_till_confirmed_upper_bound_assumption {
) {
return Ok(None); return Ok(None);
} }
@ -494,6 +493,7 @@ where
// We wait until we see the transaction in the mempool before transitioning to the next state // We wait until we see the transaction in the mempool before transitioning to the next state
Some((txid, subscription)) => match subscription.wait_until_seen().await { Some((txid, subscription)) => match subscription.wait_until_seen().await {
Ok(_) => AliceState::BtcRedeemTransactionPublished { state3, transfer_proof }, Ok(_) => AliceState::BtcRedeemTransactionPublished { state3, transfer_proof },
// TODO: No need to bail here, we should just retry?
Err(e) => { Err(e) => {
// We extract the txid and the hex representation of the transaction // We extract the txid and the hex representation of the transaction
// this'll allow the user to manually re-publish the transaction // this'll allow the user to manually re-publish the transaction
@ -503,7 +503,8 @@ where
} }
}, },
// More than half of the cancel timelock expired before we could publish the redeem transaction // It is not safe to publish the Bitcoin redeem transaction anymore
// We wait for the cancel timelock to expire and then refund
None => { None => {
tracing::error!("We were unable to publish the Bitcoin redeem transaction before the timelock expired."); tracing::error!("We were unable to publish the Bitcoin redeem transaction before the timelock expired.");