Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement claiming of revoked HTLC transactions by ChannelMonitor #163

Merged
merged 3 commits into from
Sep 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ impl Channel {
&PublicKey::from_secret_key(&secp_ctx, &chan_keys.delayed_payment_base_key),
&chan_keys.htlc_base_key,
BREAKDOWN_TIMEOUT, our_channel_monitor_claim_script);
channel_monitor.set_their_htlc_base_key(&msg.htlc_basepoint);
channel_monitor.set_their_base_keys(&msg.htlc_basepoint, &msg.delayed_payment_basepoint);
channel_monitor.set_their_to_self_delay(msg.to_self_delay);

let mut chan = Channel {
Expand Down Expand Up @@ -1236,7 +1236,7 @@ impl Channel {
// max_accepted_htlcs too small
// dust_limit_satoshis too small

self.channel_monitor.set_their_htlc_base_key(&msg.htlc_basepoint);
self.channel_monitor.set_their_base_keys(&msg.htlc_basepoint, &msg.delayed_payment_basepoint);

self.their_dust_limit_satoshis = msg.dust_limit_satoshis;
self.their_max_htlc_value_in_flight_msat = cmp::min(msg.max_htlc_value_in_flight_msat, self.channel_value_satoshis * 1000);
Expand Down
48 changes: 32 additions & 16 deletions src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3089,9 +3089,11 @@ mod tests {

#[derive(PartialEq)]
enum HTLCType { NONE, TIMEOUT, SUCCESS }
fn test_txn_broadcast(node: &Node, chan: &(msgs::ChannelUpdate, msgs::ChannelUpdate, [u8; 32], Transaction), commitment_tx: Option<Transaction>, has_htlc_tx: HTLCType) -> Vec<Transaction> {
#[derive(PartialEq)]
enum PenaltyType { NONE, HTLC }
fn test_txn_broadcast(node: &Node, chan: &(msgs::ChannelUpdate, msgs::ChannelUpdate, [u8; 32], Transaction), commitment_tx: Option<Transaction>, revoked_tx: Option<Transaction>, has_htlc_tx: HTLCType, has_penalty_tx: PenaltyType) -> Vec<Transaction> {
let mut node_txn = node.tx_broadcaster.txn_broadcasted.lock().unwrap();
assert!(node_txn.len() >= if commitment_tx.is_some() { 0 } else { 1 } + if has_htlc_tx == HTLCType::NONE { 0 } else { 1 });
assert!(node_txn.len() >= if has_htlc_tx == HTLCType::NONE { 0 } else { 1 } + if has_penalty_tx == PenaltyType::NONE { 0 } else { 1 });

let mut res = Vec::with_capacity(2);

Expand All @@ -3107,7 +3109,9 @@ mod tests {
}
}
}
assert_eq!(res.len(), 1);
if !revoked_tx.is_some() && !(has_penalty_tx == PenaltyType::HTLC) {
assert_eq!(res.len(), 1);
}

if has_htlc_tx != HTLCType::NONE {
for tx in node_txn.iter() {
Expand All @@ -3126,6 +3130,20 @@ mod tests {
}
assert_eq!(res.len(), 2);
}

if has_penalty_tx == PenaltyType::HTLC {
let revoked_tx = revoked_tx.unwrap();
for tx in node_txn.iter() {
if tx.input.len() == 1 && tx.input[0].previous_output.txid == revoked_tx.txid() {
let mut funding_tx_map = HashMap::new();
funding_tx_map.insert(revoked_tx.txid(), revoked_tx.clone());
tx.verify(&funding_tx_map).unwrap();
res.push(tx.clone());
break;
}
}
assert_eq!(res.len(), 1);
}
node_txn.clear();
res
}
Expand Down Expand Up @@ -3203,10 +3221,10 @@ mod tests {
// Simple case with no pending HTLCs:
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id(), true);
{
let mut node_txn = test_txn_broadcast(&nodes[1], &chan_1, None, HTLCType::NONE);
let mut node_txn = test_txn_broadcast(&nodes[1], &chan_1, None, None, HTLCType::NONE, PenaltyType::NONE);
let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
nodes[0].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![node_txn.drain(..).next().unwrap()] }, 1);
test_txn_broadcast(&nodes[0], &chan_1, None, HTLCType::NONE);
test_txn_broadcast(&nodes[0], &chan_1, None, None, HTLCType::NONE, PenaltyType::NONE);
}
get_announce_close_broadcast_events(&nodes, 0, 1);
assert_eq!(nodes[0].node.list_channels().len(), 0);
Expand All @@ -3218,10 +3236,10 @@ mod tests {
// Simple case of one pending HTLC to HTLC-Timeout
nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id(), true);
{
let mut node_txn = test_txn_broadcast(&nodes[1], &chan_2, None, HTLCType::TIMEOUT);
let mut node_txn = test_txn_broadcast(&nodes[1], &chan_2, None, None, HTLCType::TIMEOUT, PenaltyType::NONE);
let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
nodes[2].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![node_txn.drain(..).next().unwrap()] }, 1);
test_txn_broadcast(&nodes[2], &chan_2, None, HTLCType::NONE);
test_txn_broadcast(&nodes[2], &chan_2, None, None, HTLCType::NONE, PenaltyType::NONE);
}
get_announce_close_broadcast_events(&nodes, 1, 2);
assert_eq!(nodes[1].node.list_channels().len(), 0);
Expand Down Expand Up @@ -3255,7 +3273,7 @@ mod tests {
// HTLC-Timeout and a nodes[3] claim against it (+ its own announces)
nodes[2].node.peer_disconnected(&nodes[3].node.get_our_node_id(), true);
{
let node_txn = test_txn_broadcast(&nodes[2], &chan_3, None, HTLCType::TIMEOUT);
let node_txn = test_txn_broadcast(&nodes[2], &chan_3, None, None, HTLCType::TIMEOUT, PenaltyType::NONE);

// Claim the payment on nodes[3], giving it knowledge of the preimage
claim_funds!(nodes[3], nodes[2], payment_preimage_1);
Expand All @@ -3280,9 +3298,9 @@ mod tests {
nodes[3].chain_monitor.block_connected_checked(&header, i, &Vec::new()[..], &[0; 0]);
}

let node_txn = test_txn_broadcast(&nodes[3], &chan_4, None, HTLCType::TIMEOUT);
let node_txn = test_txn_broadcast(&nodes[3], &chan_4, None, None, HTLCType::TIMEOUT, PenaltyType::NONE);

// Claim the payment on nodes[3], giving it knowledge of the preimage
// Claim the payment on nodes[4], giving it knowledge of the preimage
claim_funds!(nodes[4], nodes[3], payment_preimage_2);

header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
Expand All @@ -3292,7 +3310,7 @@ mod tests {
nodes[4].chain_monitor.block_connected_checked(&header, i, &Vec::new()[..], &[0; 0]);
}

test_txn_broadcast(&nodes[4], &chan_4, None, HTLCType::SUCCESS);
test_txn_broadcast(&nodes[4], &chan_4, None, None, HTLCType::SUCCESS, PenaltyType::NONE);

header = BlockHeader { version: 0x20000000, prev_blockhash: header.bitcoin_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
nodes[4].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![node_txn[0].clone()] }, TEST_FINAL_CLTV - 5);
Expand Down Expand Up @@ -3327,15 +3345,13 @@ mod tests {
node_txn[0].verify(&funding_tx_map).unwrap();
node_txn.swap_remove(0);
}
test_txn_broadcast(&nodes[1], &chan_5, None, HTLCType::NONE);
test_txn_broadcast(&nodes[1], &chan_5, None, None, HTLCType::NONE, PenaltyType::NONE);

nodes[0].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![revoked_local_txn[0].clone()] }, 1);
let node_txn = test_txn_broadcast(&nodes[0], &chan_5, Some(revoked_local_txn[0].clone()), HTLCType::TIMEOUT);
let node_txn = test_txn_broadcast(&nodes[0], &chan_5, Some(revoked_local_txn[0].clone()), None, HTLCType::TIMEOUT, PenaltyType::NONE);
header = BlockHeader { version: 0x20000000, prev_blockhash: header.bitcoin_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
nodes[1].chain_monitor.block_connected_with_filtering(&Block { header, txdata: vec![node_txn[1].clone()] }, 1);

//TODO: At this point nodes[1] should claim the revoked HTLC-Timeout output, but that's
//not yet implemented in ChannelMonitor
test_txn_broadcast(&nodes[1], &chan_5, None, Some(node_txn[1].clone()), HTLCType::NONE, PenaltyType::HTLC);
}
get_announce_close_broadcast_events(&nodes, 0, 1);
assert_eq!(nodes[0].node.list_channels().len(), 0);
Expand Down
115 changes: 107 additions & 8 deletions src/ln/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ pub struct ChannelMonitor {
key_storage: KeyStorage,
delayed_payment_base_key: PublicKey,
their_htlc_base_key: Option<PublicKey>,
their_delayed_payment_base_key: Option<PublicKey>,
// first is the idx of the first of the two revocation points
their_cur_revocation_points: Option<(u64, PublicKey, Option<PublicKey>)>,

Expand Down Expand Up @@ -207,6 +208,7 @@ impl Clone for ChannelMonitor {
key_storage: self.key_storage.clone(),
delayed_payment_base_key: self.delayed_payment_base_key.clone(),
their_htlc_base_key: self.their_htlc_base_key.clone(),
their_delayed_payment_base_key: self.their_delayed_payment_base_key.clone(),
their_cur_revocation_points: self.their_cur_revocation_points.clone(),

our_to_self_delay: self.our_to_self_delay,
Expand Down Expand Up @@ -238,6 +240,7 @@ impl PartialEq for ChannelMonitor {
self.key_storage != other.key_storage ||
self.delayed_payment_base_key != other.delayed_payment_base_key ||
self.their_htlc_base_key != other.their_htlc_base_key ||
self.their_delayed_payment_base_key != other.their_delayed_payment_base_key ||
self.their_cur_revocation_points != other.their_cur_revocation_points ||
self.our_to_self_delay != other.our_to_self_delay ||
self.their_to_self_delay != other.their_to_self_delay ||
Expand Down Expand Up @@ -274,6 +277,7 @@ impl ChannelMonitor {
},
delayed_payment_base_key: delayed_payment_base_key.clone(),
their_htlc_base_key: None,
their_delayed_payment_base_key: None,
their_cur_revocation_points: None,

our_to_self_delay: our_to_self_delay,
Expand Down Expand Up @@ -478,8 +482,10 @@ impl ChannelMonitor {
self.funding_txo = Some(funding_info);
}

pub(super) fn set_their_htlc_base_key(&mut self, their_htlc_base_key: &PublicKey) {
/// We log these base keys at channel opening to being able to rebuild redeemscript in case of leaked revoked commit tx
pub(super) fn set_their_base_keys(&mut self, their_htlc_base_key: &PublicKey, their_delayed_payment_base_key: &PublicKey) {
self.their_htlc_base_key = Some(their_htlc_base_key.clone());
self.their_delayed_payment_base_key = Some(their_delayed_payment_base_key.clone());
}

pub(super) fn set_their_to_self_delay(&mut self, their_to_self_delay: u16) {
Expand Down Expand Up @@ -531,6 +537,7 @@ impl ChannelMonitor {

res.extend_from_slice(&self.delayed_payment_base_key.serialize());
res.extend_from_slice(&self.their_htlc_base_key.as_ref().unwrap().serialize());
res.extend_from_slice(&self.their_delayed_payment_base_key.as_ref().unwrap().serialize());

match self.their_cur_revocation_points {
Some((idx, pubkey, second_option)) => {
Expand Down Expand Up @@ -705,6 +712,7 @@ impl ChannelMonitor {

let delayed_payment_base_key = unwrap_obj!(PublicKey::from_slice(&secp_ctx, read_bytes!(33)));
let their_htlc_base_key = Some(unwrap_obj!(PublicKey::from_slice(&secp_ctx, read_bytes!(33))));
let their_delayed_payment_base_key = Some(unwrap_obj!(PublicKey::from_slice(&secp_ctx, read_bytes!(33))));

let their_cur_revocation_points = {
let first_idx = byte_utils::slice_to_be48(read_bytes!(6));
Expand Down Expand Up @@ -867,6 +875,7 @@ impl ChannelMonitor {
key_storage,
delayed_payment_base_key,
their_htlc_base_key,
their_delayed_payment_base_key,
their_cur_revocation_points,

our_to_self_delay,
Expand Down Expand Up @@ -915,8 +924,7 @@ impl ChannelMonitor {
/// Attempts to claim a remote commitment transaction's outputs using the revocation key and
/// data in remote_claimable_outpoints. Will directly claim any HTLC outputs which expire at a
/// height > height + CLTV_SHARED_CLAIM_BUFFER. In any case, will install monitoring for
/// HTLC-Success/HTLC-Timeout transactions, and claim them using the revocation key (if
/// applicable) as well.
/// HTLC-Success/HTLC-Timeout transactions.
fn check_spend_remote_transaction(&self, tx: &Transaction, height: u32) -> (Vec<Transaction>, (Sha256dHash, Vec<TxOut>)) {
// Most secp and related errors trying to create keys means we have no hope of constructing
// a spend transaction...so we return no transactions to broadcast
Expand Down Expand Up @@ -1196,13 +1204,97 @@ impl ChannelMonitor {
txn_to_broadcast.push(spend_tx);
}
}
} else {
//TODO: For each input check if its in our remote_commitment_txn_on_chain map!
}

(txn_to_broadcast, (commitment_txid, watch_outputs))
}

/// Attempst to claim a remote HTLC-Success/HTLC-Timeout s outputs using the revocation key
fn check_spend_remote_htlc(&self, tx: &Transaction, commitment_number: u64) -> Vec<Transaction> {
Copy link
Author

Choose a reason for hiding this comment

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

Was refactored mainly to avoid double-locking of remote_commitment_tx_on_chain as block_connected has only them to match HTLC transactions, so a part of old commit was moved there.

let mut txn_to_broadcast = Vec::new();

let htlc_txid = tx.txid(); //TODO: This is gonna be a performance bottleneck for watchtowers!

macro_rules! ignore_error {
( $thing : expr ) => {
match $thing {
Ok(a) => a,
Err(_) => return txn_to_broadcast
}
};
}

let secret = self.get_secret(commitment_number).unwrap();
let per_commitment_key = ignore_error!(SecretKey::from_slice(&self.secp_ctx, &secret));
let revocation_pubkey = match self.key_storage {
KeyStorage::PrivMode { ref revocation_base_key, .. } => {
let per_commitment_point = PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key);
ignore_error!(chan_utils::derive_public_revocation_key(&self.secp_ctx, &per_commitment_point, &PublicKey::from_secret_key(&self.secp_ctx, &revocation_base_key)))
},
KeyStorage::SigsMode { ref revocation_base_key, .. } => {
let per_commitment_point = PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key);
ignore_error!(chan_utils::derive_public_revocation_key(&self.secp_ctx, &per_commitment_point, &revocation_base_key))
},
};
let delayed_key = match self.their_delayed_payment_base_key {
None => return txn_to_broadcast,
Some(their_delayed_payment_base_key) => ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, &PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key), &their_delayed_payment_base_key)),
};
let redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, self.their_to_self_delay.unwrap(), &delayed_key);
let revokeable_p2wsh = redeemscript.to_v0_p2wsh();

let mut inputs = Vec::new();
let mut amount = 0;

if tx.output[0].script_pubkey == revokeable_p2wsh { //HTLC transactions have one txin, one txout
inputs.push(TxIn {
previous_output: BitcoinOutPoint {
txid: htlc_txid,
vout: 0,
},
script_sig: Script::new(),
sequence: 0xfffffffd,
witness: Vec::new(),
});
amount = tx.output[0].value;
}

if !inputs.is_empty() {
let outputs = vec!(TxOut {
script_pubkey: self.destination_script.clone(),
value: amount, //TODO: - fee
});

let mut spend_tx = Transaction {
version: 2,
lock_time: 0,
input: inputs,
output: outputs,
};


let sighash_parts = bip143::SighashComponents::new(&spend_tx);

let sig = match self.key_storage {
KeyStorage::PrivMode { ref revocation_base_key, .. } => {
let sighash = ignore_error!(Message::from_slice(&sighash_parts.sighash_all(&spend_tx.input[0], &redeemscript, amount)[..]));
let revocation_key = ignore_error!(chan_utils::derive_private_revocation_key(&self.secp_ctx, &per_commitment_key, &revocation_base_key));
self.secp_ctx.sign(&sighash, &revocation_key)
}
KeyStorage::SigsMode { .. } => {
unimplemented!();
}
};
spend_tx.input[0].witness.push(sig.serialize_der(&self.secp_ctx).to_vec());
spend_tx.input[0].witness[0].push(SigHashType::All as u8);
spend_tx.input[0].witness.push(vec!(1));
spend_tx.input[0].witness.push(redeemscript.into_bytes());

txn_to_broadcast.push(spend_tx);
}
txn_to_broadcast
}

fn broadcast_by_local_state(&self, local_tx: &LocalSignedTx) -> Vec<Transaction> {
let mut res = Vec::with_capacity(local_tx.htlc_outputs.len());

Expand Down Expand Up @@ -1264,19 +1356,26 @@ impl ChannelMonitor {
fn block_connected(&self, txn_matched: &[&Transaction], height: u32, broadcaster: &BroadcasterInterface)-> Vec<(Sha256dHash, Vec<TxOut>)> {
let mut watch_outputs = Vec::new();
for tx in txn_matched {
let mut txn: Vec<Transaction> = Vec::new();
for txin in tx.input.iter() {
if self.funding_txo.is_none() || (txin.previous_output.txid == self.funding_txo.as_ref().unwrap().0.txid && txin.previous_output.vout == self.funding_txo.as_ref().unwrap().0.index as u32) {
let (mut txn, new_outputs) = self.check_spend_remote_transaction(tx, height);
let (remote_txn, new_outputs) = self.check_spend_remote_transaction(tx, height);
txn = remote_txn;
if !new_outputs.1.is_empty() {
watch_outputs.push(new_outputs);
}
if txn.is_empty() {
txn = self.check_spend_local_transaction(tx, height);
}
for tx in txn.iter() {
broadcaster.broadcast_transaction(tx);
} else {
let remote_commitment_txn_on_chain = self.remote_commitment_txn_on_chain.lock().unwrap();
for commitment_number in remote_commitment_txn_on_chain.get(&txin.previous_output.txid) {
txn = self.check_spend_remote_htlc(tx, *commitment_number);
}
}
for tx in txn.iter() {
broadcaster.broadcast_transaction(tx);
}
}
}
if let Some(ref cur_local_tx) = self.current_local_signed_commitment_tx {
Expand Down