Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions lightning/src/chain/chainmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ChannelSigner: WriteableEcdsaChannelSigner> {
/// 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.
Expand Down
41 changes: 0 additions & 41 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8404,47 +8404,6 @@ where
}
}

impl Writeable for VecDeque<(Event, Option<EventCompletionAction>)> {
fn write<W: Writer>(&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<Event> =
MaybeReadable::read(&mut &event_encoded[..]).unwrap();
if action.is_some() { assert!(event_read.is_some()); }
}
}
Ok(())
}
}
impl Readable for VecDeque<(Event, Option<EventCompletionAction>)> {
fn read<R: Read>(reader: &mut R) -> Result<Self, DecodeError> {
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<EventCompletionAction>)>() 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) => {},
Expand Down
1 change: 1 addition & 0 deletions lightning/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
75 changes: 74 additions & 1 deletion lightning/src/util/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -785,6 +788,75 @@ where T: Readable + Eq + Hash
}
}

// VecDeques
impl Writeable for VecDeque<UnsignedJusticeData> {
#[inline]
fn write<W: Writer>(&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<UnsignedJusticeData> {
#[inline]
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
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::<UnsignedJusticeData>()));
for _ in 0..len.0 {
if let Some(val) = MaybeReadable::read(r)? {
ret.push_back(val);
}
}
Ok(ret)
}
}

impl Writeable for VecDeque<(Event, Option<EventCompletionAction>)> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO its a bit weird to move stuff that's only used in one file into ser.rs, I'd kinda rather keep it with the struct definition.

fn write<W: Writer>(&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<Event> =
MaybeReadable::read(&mut &event_encoded[..]).unwrap();
if action.is_some() { assert!(event_read.is_some()); }
}
}
Ok(())
}
}

impl Readable for VecDeque<(Event, Option<EventCompletionAction>)> {
fn read<R: Read>(reader: &mut R) -> Result<Self, DecodeError> {
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<EventCompletionAction>)>() 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)*) => {
Expand Down Expand Up @@ -848,6 +920,7 @@ impl Readable for Vec<u8> {
}
}

impl_for_vec!(u32);
impl_for_vec!(ecdsa::Signature);
impl_for_vec!(crate::chain::channelmonitor::ChannelMonitorUpdate);
impl_for_vec!(crate::ln::channelmanager::MonitorUpdateCompletionAction);
Expand Down
156 changes: 156 additions & 0 deletions lightning/src/util/watchtower.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be called amount_sat to dissociate well from amount_msat, used e.g in HTLCOutputInCommitment.

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<Self> {
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is unclear if this utility object aims to be hosted by a LDK user itself.

Note if you’re outsourcing your justice transaction broadcast to an external watchtower, if you have a range of RBF feerates (as build_to_local_justice_tx()) let you do so there is no guarantee the outsourced watchtower will not broadcast the highest-feerate transaction, which might be overpaying current mempool feerates. I don’t know if more documentation of this watchtower utility aims to be added on this PR or in the future.

/// 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<OutPoint, VecDeque<UnsignedJusticeData>>,
/// Sorted in ascending order.
feerates_per_kw: Vec<u32>,
destination_script: Script,
}

impl_writeable_tlv_based!(JusticeTxTracker, {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I know I suggested just implementing writeable here, but it really does make this whole thing kinda a pain to use. I wonder if we shouldn't store the latest commitment tx info in ChannelMonitor so that getting the revoked transaction given a ChannelMonitorUpdate isn't just one function call and requires no storage outside of the ChannelMonitor.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh yea, that does seem a lot easier. Will probably get around to trying this out sometime this weekend/early next week

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, no rush, just figured I'd mention.

(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<u32>, 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<Signer: sign::WriteableEcdsaChannelSigner>(
&mut self, funding_txo: OutPoint, monitor: &ChannelMonitor<Signer>
) -> Option<bool> {
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<Signer: sign::WriteableEcdsaChannelSigner>(
&mut self, funding_txo: OutPoint, monitor: &ChannelMonitor<Signer>,
update: &ChannelMonitorUpdate
) -> Vec<Transaction> {
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
}
}