diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 6051f00b90a..5754f72c3bb 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -98,14 +98,16 @@ impl MonitorUpdateId { /// /// Third-party watchtowers may be built as a part of an implementation of this trait, with the /// advantage that you can control whether to resume channel operation depending on if an update -/// has been persisted to a watchtower. For this, you may find the following methods useful: -/// [`ChannelMonitor::initial_counterparty_commitment_tx`], +/// has been persisted to a watchtower. A utility for tracking and building signed justice +/// transactions is provided in the [`util::watchtower`] module. Otherwise, you may find the +/// following methods useful: [`ChannelMonitor::initial_counterparty_commitment_tx`], /// [`ChannelMonitor::counterparty_commitment_txs_from_update`], /// [`ChannelMonitor::sign_to_local_justice_tx`], [`TrustedCommitmentTransaction::revokeable_output_index`], /// [`TrustedCommitmentTransaction::build_to_local_justice_tx`]. /// /// [`TrustedCommitmentTransaction::revokeable_output_index`]: crate::ln::chan_utils::TrustedCommitmentTransaction::revokeable_output_index /// [`TrustedCommitmentTransaction::build_to_local_justice_tx`]: crate::ln::chan_utils::TrustedCommitmentTransaction::build_to_local_justice_tx +/// [`util::watchtower`]: crate::util::watchtower pub trait Persist { /// Persist a new channel's data in response to a [`chain::Watch::watch_channel`] call. This is /// called by [`ChannelManager`] for new channels, or may be called directly, e.g. on startup. diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 213a2882fbc..53fff152e8d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -8404,47 +8404,6 @@ where } } -impl Writeable for VecDeque<(Event, Option)> { - fn write(&self, w: &mut W) -> Result<(), io::Error> { - (self.len() as u64).write(w)?; - for (event, action) in self.iter() { - event.write(w)?; - action.write(w)?; - #[cfg(debug_assertions)] { - // Events are MaybeReadable, in some cases indicating that they shouldn't actually - // be persisted and are regenerated on restart. However, if such an event has a - // post-event-handling action we'll write nothing for the event and would have to - // either forget the action or fail on deserialization (which we do below). Thus, - // check that the event is sane here. - let event_encoded = event.encode(); - let event_read: Option = - MaybeReadable::read(&mut &event_encoded[..]).unwrap(); - if action.is_some() { assert!(event_read.is_some()); } - } - } - Ok(()) - } -} -impl Readable for VecDeque<(Event, Option)> { - fn read(reader: &mut R) -> Result { - let len: u64 = Readable::read(reader)?; - const MAX_ALLOC_SIZE: u64 = 1024 * 16; - let mut events: Self = VecDeque::with_capacity(cmp::min( - MAX_ALLOC_SIZE/mem::size_of::<(events::Event, Option)>() as u64, - len) as usize); - for _ in 0..len { - let ev_opt = MaybeReadable::read(reader)?; - let action = Readable::read(reader)?; - if let Some(ev) = ev_opt { - events.push_back((ev, action)); - } else if action.is_some() { - return Err(DecodeError::InvalidValue); - } - } - Ok(events) - } -} - impl_writeable_tlv_based_enum!(ChannelShutdownState, (0, NotShuttingDown) => {}, (2, ShutdownInitiated) => {}, diff --git a/lightning/src/util/mod.rs b/lightning/src/util/mod.rs index cc1b5f581af..3abd5f9af2a 100644 --- a/lightning/src/util/mod.rs +++ b/lightning/src/util/mod.rs @@ -22,6 +22,7 @@ pub mod invoice; pub mod persist; pub mod string; pub mod wakers; +pub mod watchtower; pub(crate) mod atomic_counter; pub(crate) mod byte_utils; diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 1eb5e7424c8..c6f9533574c 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -13,12 +13,14 @@ //! [`ChannelManager`]: crate::ln::channelmanager::ChannelManager //! [`ChannelMonitor`]: crate::chain::channelmonitor::ChannelMonitor +use crate::events::Event; +use crate::ln::channelmanager::EventCompletionAction; use crate::prelude::*; use crate::io::{self, Read, Seek, Write}; use crate::io_extras::{copy, sink}; use core::hash::Hash; use crate::sync::Mutex; -use core::cmp; +use core::{cmp, mem}; use core::convert::TryFrom; use core::ops::Deref; @@ -45,6 +47,7 @@ use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret}; use crate::util::byte_utils::{be48_to_array, slice_to_be48}; use crate::util::string::UntrustedString; +use crate::util::watchtower::UnsignedJusticeData; /// serialization buffer size pub const MAX_BUF_SIZE: usize = 64 * 1024; @@ -785,6 +788,75 @@ where T: Readable + Eq + Hash } } +// VecDeques +impl Writeable for VecDeque { + #[inline] + fn write(&self, w: &mut W) -> Result<(), io::Error> { + CollectionLength(self.len() as u64).write(w)?; + for elem in self.iter() { + elem.write(w)?; + } + Ok(()) + } +} + +impl Readable for VecDeque { + #[inline] + fn read(r: &mut R) -> Result { + let len: CollectionLength = Readable::read(r)?; + let mut ret = VecDeque::with_capacity(cmp::min( + len.0 as usize, MAX_BUF_SIZE / core::mem::size_of::())); + for _ in 0..len.0 { + if let Some(val) = MaybeReadable::read(r)? { + ret.push_back(val); + } + } + Ok(ret) + } +} + +impl Writeable for VecDeque<(Event, Option)> { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + (self.len() as u64).write(w)?; + for (event, action) in self.iter() { + event.write(w)?; + action.write(w)?; + #[cfg(debug_assertions)] { + // Events are MaybeReadable, in some cases indicating that they shouldn't actually + // be persisted and are regenerated on restart. However, if such an event has a + // post-event-handling action we'll write nothing for the event and would have to + // either forget the action or fail on deserialization (which we do below). Thus, + // check that the event is sane here. + let event_encoded = event.encode(); + let event_read: Option = + MaybeReadable::read(&mut &event_encoded[..]).unwrap(); + if action.is_some() { assert!(event_read.is_some()); } + } + } + Ok(()) + } +} + +impl Readable for VecDeque<(Event, Option)> { + fn read(reader: &mut R) -> Result { + let len: u64 = Readable::read(reader)?; + const MAX_ALLOC_SIZE: u64 = 1024 * 16; + let mut events: Self = VecDeque::with_capacity(cmp::min( + MAX_ALLOC_SIZE/mem::size_of::<(Event, Option)>() as u64, + len) as usize); + for _ in 0..len { + let ev_opt = MaybeReadable::read(reader)?; + let action = Readable::read(reader)?; + if let Some(ev) = ev_opt { + events.push_back((ev, action)); + } else if action.is_some() { + return Err(DecodeError::InvalidValue); + } + } + Ok(events) + } +} + // Vectors macro_rules! impl_writeable_for_vec { ($ty: ty $(, $name: ident)*) => { @@ -848,6 +920,7 @@ impl Readable for Vec { } } +impl_for_vec!(u32); impl_for_vec!(ecdsa::Signature); impl_for_vec!(crate::chain::channelmonitor::ChannelMonitorUpdate); impl_for_vec!(crate::ln::channelmanager::MonitorUpdateCompletionAction); diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 7a9ce06910b..1afa553e31b 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -22,7 +22,6 @@ use crate::events; use crate::events::bump_transaction::{WalletSource, Utxo}; use crate::ln::ChannelId; use crate::ln::channelmanager; -use crate::ln::chan_utils::CommitmentTransaction; use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use crate::ln::{msgs, wire}; use crate::ln::msgs::LightningError; @@ -38,6 +37,7 @@ use crate::util::config::UserConfig; use crate::util::test_channel_signer::{TestChannelSigner, EnforcementState}; use crate::util::logger::{Logger, Level, Record}; use crate::util::ser::{Readable, ReadableArgs, Writer, Writeable}; +use crate::util::watchtower::JusticeTxTracker; use bitcoin::EcdsaSighashType; use bitcoin::blockdata::constants::ChainHash; @@ -61,7 +61,6 @@ use regex; use crate::io; use crate::prelude::*; use core::cell::RefCell; -use core::ops::Deref; use core::time::Duration; use crate::sync::{Mutex, Arc}; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; @@ -276,50 +275,30 @@ impl<'a> chain::Watch for TestChainMonitor<'a> { } } -struct JusticeTxData { - justice_tx: Transaction, - value: u64, - commitment_number: u64, -} - pub(crate) struct WatchtowerPersister { persister: TestPersister, /// Upon a new commitment_signed, we'll get a - /// ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTxInfo. We'll store the justice tx - /// amount, and commitment number so we can build the justice tx after our counterparty - /// revokes it. - unsigned_justice_tx_data: Mutex>>, + /// `ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTxInfo`. We'll use our utility + /// object `JusticeTxTracker` to build and sign the justice tx. + justice_tx_tracker: RefCell, /// After receiving a revoke_and_ack for a commitment number, we'll form and store the justice /// tx which would be used to provide a watchtower with the data it needs. - watchtower_state: Mutex>>, - destination_script: Script, + signed_justice_txs: RefCell>>, } impl WatchtowerPersister { pub(crate) fn new(destination_script: Script) -> Self { WatchtowerPersister { persister: TestPersister::new(), - unsigned_justice_tx_data: Mutex::new(HashMap::new()), - watchtower_state: Mutex::new(HashMap::new()), - destination_script, + justice_tx_tracker: RefCell::new(JusticeTxTracker::new(vec![FEERATE_FLOOR_SATS_PER_KW], + destination_script)), + signed_justice_txs: RefCell::new(HashMap::new()), } } pub(crate) fn justice_tx(&self, funding_txo: OutPoint, commitment_txid: &Txid) -> Option { - self.watchtower_state.lock().unwrap().get(&funding_txo).unwrap().get(commitment_txid).cloned() - } - - fn form_justice_data_from_commitment(&self, counterparty_commitment_tx: &CommitmentTransaction) - -> Option { - let trusted_tx = counterparty_commitment_tx.trust(); - let output_idx = trusted_tx.revokeable_output_index()?; - let built_tx = trusted_tx.built_transaction(); - let value = built_tx.transaction.output[output_idx as usize].value; - let justice_tx = trusted_tx.build_to_local_justice_tx( - FEERATE_FLOOR_SATS_PER_KW as u64, self.destination_script.clone()).ok()?; - let commitment_number = counterparty_commitment_tx.commitment_number(); - Some(JusticeTxData { justice_tx, value, commitment_number }) + self.signed_justice_txs.borrow().get(&funding_txo).unwrap().get(commitment_txid).cloned() } } @@ -328,20 +307,8 @@ impl chainmonitor::Persist fo data: &channelmonitor::ChannelMonitor, id: MonitorUpdateId ) -> chain::ChannelMonitorUpdateStatus { let res = self.persister.persist_new_channel(funding_txo, data, id); - - assert!(self.unsigned_justice_tx_data.lock().unwrap() - .insert(funding_txo, VecDeque::new()).is_none()); - assert!(self.watchtower_state.lock().unwrap() - .insert(funding_txo, HashMap::new()).is_none()); - - let initial_counterparty_commitment_tx = data.initial_counterparty_commitment_tx() - .expect("First and only call expects Some"); - if let Some(justice_data) - = self.form_justice_data_from_commitment(&initial_counterparty_commitment_tx) { - self.unsigned_justice_tx_data.lock().unwrap() - .get_mut(&funding_txo).unwrap() - .push_back(justice_data); - } + self.justice_tx_tracker.borrow_mut().add_new_channel(funding_txo, data); + assert!(self.signed_justice_txs.borrow_mut().insert(funding_txo, HashMap::new()).is_none()); res } @@ -350,29 +317,15 @@ impl chainmonitor::Persist fo data: &channelmonitor::ChannelMonitor, update_id: MonitorUpdateId ) -> chain::ChannelMonitorUpdateStatus { let res = self.persister.update_persisted_channel(funding_txo, update, data, update_id); - - if let Some(update) = update { - let commitment_txs = data.counterparty_commitment_txs_from_update(update); - let justice_datas = commitment_txs.into_iter() - .filter_map(|commitment_tx| self.form_justice_data_from_commitment(&commitment_tx)); - let mut channels_justice_txs = self.unsigned_justice_tx_data.lock().unwrap(); - let channel_state = channels_justice_txs.get_mut(&funding_txo).unwrap(); - channel_state.extend(justice_datas); - - while let Some(JusticeTxData { justice_tx, value, commitment_number }) = channel_state.front() { - let input_idx = 0; - let commitment_txid = justice_tx.input[input_idx].previous_output.txid; - match data.sign_to_local_justice_tx(justice_tx.clone(), input_idx, *value, *commitment_number) { - Ok(signed_justice_tx) => { - let dup = self.watchtower_state.lock().unwrap() - .get_mut(&funding_txo).unwrap() - .insert(commitment_txid, signed_justice_tx); - assert!(dup.is_none()); - channel_state.pop_front(); - }, - Err(_) => break, - } - } + let signed_justice_txs = match update { + Some(update) => + self.justice_tx_tracker.borrow_mut().process_update(funding_txo, data, update), + None => vec![], + }; + for tx in signed_justice_txs { + let commitment_txid = tx.input[0].previous_output.txid; + self.signed_justice_txs.borrow_mut() + .get_mut(&funding_txo).unwrap().insert(commitment_txid, tx); } res } diff --git a/lightning/src/util/watchtower.rs b/lightning/src/util/watchtower.rs new file mode 100644 index 00000000000..2fac6e30fc5 --- /dev/null +++ b/lightning/src/util/watchtower.rs @@ -0,0 +1,156 @@ +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! This module contains a simple utility object [`JusticeTxTracker`] that can be used to track +//! the state required to build and sign a justice transaction claiming a +//! to-broadcaster output if a counterparty broadcasts a revoked commitment transaction. +//! This is intended to be used in an implementation of the [`Persist`] trait (see for +//! more info). +//! +//! [`Persist`]: crate::chain::chainmonitor::Persist + +use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate}; +use crate::chain::transaction::OutPoint; +use crate::ln::chan_utils::CommitmentTransaction; +use crate::sign; +use crate::prelude::*; + +use bitcoin::blockdata::transaction::Transaction; +use bitcoin::blockdata::script::Script; + +pub(crate) struct UnsignedJusticeData { + justice_tx: Transaction, + value: u64, + commitment_number: u64, +} + +impl_writeable_tlv_based!(UnsignedJusticeData, { + (0, justice_tx, required), + (2, value, required), + (4, commitment_number, required), +}); + +impl UnsignedJusticeData { + /// Returns `None` if the justice transaction cannot be built with the given feerate, + /// or the commitment transaction lacks a to-broadcaster output. + fn new_from_commitment_tx( + counterparty_commitment_tx: &CommitmentTransaction, destination_script: Script, + feerate_per_kw: u32 + ) -> Option { + let commitment_number = counterparty_commitment_tx.commitment_number(); + let trusted_tx = counterparty_commitment_tx.trust(); + let value = trusted_tx.to_broadcaster_value_sat(); + let justice_tx = trusted_tx.build_to_local_justice_tx( + feerate_per_kw as u64, destination_script).ok()?; + Some(Self { justice_tx, value, commitment_number }) + } +} + +/// A simple utility object that can be used to track the state required to build and sign a +/// justice transaction claiming a to-broadcaster output if a counterparty broadcasts a revoked +/// commitment transaction. +/// This is intended to be used in an implementation of the [`Persist`] trait (see for +/// more info). +/// +/// Note: this should be persisted and read on startup, otherwise you may end up missing justice +/// transactions for certain commitments. +/// +/// [`Persist`]: crate::chain::chainmonitor::Persist +pub struct JusticeTxTracker { + unsigned_justice_data: HashMap>, + /// Sorted in ascending order. + feerates_per_kw: Vec, + destination_script: Script, +} + +impl_writeable_tlv_based!(JusticeTxTracker, { + (0, unsigned_justice_data, required), + (2, feerates_per_kw, required), + (4, destination_script, required), +}); + +impl JusticeTxTracker { + /// Creates a new tracker that will build justice transactions for each provided feerate + /// claiming outputs to the given destination script. + pub fn new(mut feerates_per_kw: Vec, destination_script: Script) -> Self { + feerates_per_kw.sort_unstable(); + Self { + unsigned_justice_data: HashMap::new(), + feerates_per_kw, + destination_script, + } + } + + /// Processes the commitment transaction and stores the justice data, returning whether the + /// commitment transaction had a to-broadcaster output. + fn process_commitment_transaction( + &mut self, funding_txo: OutPoint, commitment_tx: &CommitmentTransaction, + ) -> bool { + for feerate_per_kw in self.feerates_per_kw.iter() { + let justice_data = match UnsignedJusticeData::new_from_commitment_tx( + commitment_tx, self.destination_script.clone(), *feerate_per_kw + ) { + Some(justice_data) => justice_data, + None => return false, + }; + self.unsigned_justice_data + .entry(funding_txo).or_insert(VecDeque::new()) + .push_back(justice_data); + } + true + } + + /// Processes the initial commitment transaction for when the channel monitor is first + /// persisted, expected to be used upon [`Persist::persist_new_channel`]. + /// + /// Returns `None` if the monitor doesn't track the initial commitment tx, otherwise returns + /// `Some`, with a boolean representing whether the commitment tx had a to-broadcaster output. + /// + /// [`Persist::persist_new_channel`]: crate::chain::chainmonitor::Persist::persist_new_channel + pub fn add_new_channel( + &mut self, funding_txo: OutPoint, monitor: &ChannelMonitor + ) -> Option { + self.unsigned_justice_data.insert(funding_txo, VecDeque::new()); + let initial_counterparty_commitment_tx = monitor.initial_counterparty_commitment_tx()?; + Some(self.process_commitment_transaction(funding_txo, &initial_counterparty_commitment_tx)) + } + + /// Processes any new counterparty commitment transactions present in the provided `update`, + /// and returns a list of newly signed justice transactions ready to be broadcast. + /// + /// This is expected to be used within and implementation of + /// [`Persist::update_persisted_channel`]. + /// + /// [`Persist::update_persisted_channel`]: crate::chain::chainmonitor::Persist::update_persisted_channel + pub fn process_update( + &mut self, funding_txo: OutPoint, monitor: &ChannelMonitor, + update: &ChannelMonitorUpdate + ) -> Vec { + let commitment_txs = monitor.counterparty_commitment_txs_from_update(update); + for commitment_tx in commitment_txs { + self.process_commitment_transaction(funding_txo, &commitment_tx); + } + + let mut signed_justice_txs = Vec::new(); + let channel_queue = self.unsigned_justice_data + .entry(funding_txo).or_insert(VecDeque::new()); + + while let Some(UnsignedJusticeData { + justice_tx, value, commitment_number + }) = channel_queue.front() { + match monitor.sign_to_local_justice_tx( + justice_tx.clone(), 0, *value, *commitment_number + ) { + Ok(signed_justice_tx) => { + signed_justice_txs.push(signed_justice_tx); + channel_queue.pop_front(); + }, + Err(_) => break, + } + } + signed_justice_txs + } +}