Skip to content

Commit 8a79877

Browse files
authored
Merge pull request #653 from ariard/2020-06-fix-outputs-tracking
Add outpoint index in watch_outputs to fix tracking
2 parents df778b6 + 27ee115 commit 8a79877

File tree

3 files changed

+104
-80
lines changed

3 files changed

+104
-80
lines changed

lightning/src/chain/chainmonitor.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ impl<ChanSigner: ChannelKeys, C: Deref, T: Deref, F: Deref, L: Deref> ChainMonit
9595

9696
if let Some(ref chain_source) = self.chain_source {
9797
for (txid, outputs) in txn_outputs.drain(..) {
98-
for (idx, output) in outputs.iter().enumerate() {
99-
chain_source.register_output(&OutPoint { txid, index: idx as u16 }, &output.script_pubkey);
98+
for (idx, output) in outputs.iter() {
99+
chain_source.register_output(&OutPoint { txid, index: *idx as u16 }, &output.script_pubkey);
100100
}
101101
}
102102
}
@@ -152,8 +152,8 @@ impl<ChanSigner: ChannelKeys, C: Deref, T: Deref, F: Deref, L: Deref> ChainMonit
152152
if let Some(ref chain_source) = self.chain_source {
153153
chain_source.register_tx(&funding_txo.0.txid, &funding_txo.1);
154154
for (txid, outputs) in monitor.get_outputs_to_watch().iter() {
155-
for (idx, script_pubkey) in outputs.iter().enumerate() {
156-
chain_source.register_output(&OutPoint { txid: *txid, index: idx as u16 }, &script_pubkey);
155+
for (idx, script_pubkey) in outputs.iter() {
156+
chain_source.register_output(&OutPoint { txid: *txid, index: *idx as u16 }, script_pubkey);
157157
}
158158
}
159159
}

lightning/src/chain/channelmonitor.rs

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ pub struct ChannelMonitor<ChanSigner: ChannelKeys> {
666666
// interface knows about the TXOs that we want to be notified of spends of. We could probably
667667
// be smart and derive them from the above storage fields, but its much simpler and more
668668
// Obviously Correct (tm) if we just keep track of them explicitly.
669-
outputs_to_watch: HashMap<Txid, Vec<Script>>,
669+
outputs_to_watch: HashMap<Txid, Vec<(u32, Script)>>,
670670

671671
#[cfg(test)]
672672
pub onchain_tx_handler: OnchainTxHandler<ChanSigner>,
@@ -914,10 +914,11 @@ impl<ChanSigner: ChannelKeys + Writeable> ChannelMonitor<ChanSigner> {
914914
}
915915

916916
(self.outputs_to_watch.len() as u64).write(writer)?;
917-
for (txid, output_scripts) in self.outputs_to_watch.iter() {
917+
for (txid, idx_scripts) in self.outputs_to_watch.iter() {
918918
txid.write(writer)?;
919-
(output_scripts.len() as u64).write(writer)?;
920-
for script in output_scripts.iter() {
919+
(idx_scripts.len() as u64).write(writer)?;
920+
for (idx, script) in idx_scripts.iter() {
921+
idx.write(writer)?;
921922
script.write(writer)?;
922923
}
923924
}
@@ -963,7 +964,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
963964
onchain_tx_handler.provide_latest_holder_tx(initial_holder_commitment_tx);
964965

965966
let mut outputs_to_watch = HashMap::new();
966-
outputs_to_watch.insert(funding_info.0.txid, vec![funding_info.1.clone()]);
967+
outputs_to_watch.insert(funding_info.0.txid, vec![(funding_info.0.index as u32, funding_info.1.clone())]);
967968

968969
ChannelMonitor {
969970
latest_update_id: 0,
@@ -1209,7 +1210,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
12091210
/// transaction), which we must learn about spends of via block_connected().
12101211
///
12111212
/// (C-not exported) because we have no HashMap bindings
1212-
pub fn get_outputs_to_watch(&self) -> &HashMap<Txid, Vec<Script>> {
1213+
pub fn get_outputs_to_watch(&self) -> &HashMap<Txid, Vec<(u32, Script)>> {
12131214
// If we've detected a counterparty commitment tx on chain, we must include it in the set
12141215
// of outputs to watch for spends of, otherwise we're likely to lose user funds. Because
12151216
// its trivial to do, double-check that here.
@@ -1264,7 +1265,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
12641265
/// HTLC-Success/HTLC-Timeout transactions.
12651266
/// Return updates for HTLC pending in the channel and failed automatically by the broadcast of
12661267
/// revoked counterparty commitment tx
1267-
fn check_spend_counterparty_transaction<L: Deref>(&mut self, tx: &Transaction, height: u32, logger: &L) -> (Vec<ClaimRequest>, (Txid, Vec<TxOut>)) where L::Target: Logger {
1268+
fn check_spend_counterparty_transaction<L: Deref>(&mut self, tx: &Transaction, height: u32, logger: &L) -> (Vec<ClaimRequest>, (Txid, Vec<(u32, TxOut)>)) where L::Target: Logger {
12681269
// Most secp and related errors trying to create keys means we have no hope of constructing
12691270
// a spend transaction...so we return no transactions to broadcast
12701271
let mut claimable_outpoints = Vec::new();
@@ -1319,7 +1320,9 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
13191320
if !claimable_outpoints.is_empty() || per_commitment_option.is_some() { // ie we're confident this is actually ours
13201321
// We're definitely a counterparty commitment transaction!
13211322
log_trace!(logger, "Got broadcast of revoked counterparty commitment transaction, going to generate general spend tx with {} inputs", claimable_outpoints.len());
1322-
watch_outputs.append(&mut tx.output.clone());
1323+
for (idx, outp) in tx.output.iter().enumerate() {
1324+
watch_outputs.push((idx as u32, outp.clone()));
1325+
}
13231326
self.counterparty_commitment_txn_on_chain.insert(commitment_txid, commitment_number);
13241327

13251328
macro_rules! check_htlc_fails {
@@ -1366,7 +1369,9 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
13661369
// already processed the block, resulting in the counterparty_commitment_txn_on_chain entry
13671370
// not being generated by the above conditional. Thus, to be safe, we go ahead and
13681371
// insert it here.
1369-
watch_outputs.append(&mut tx.output.clone());
1372+
for (idx, outp) in tx.output.iter().enumerate() {
1373+
watch_outputs.push((idx as u32, outp.clone()));
1374+
}
13701375
self.counterparty_commitment_txn_on_chain.insert(commitment_txid, commitment_number);
13711376

13721377
log_trace!(logger, "Got broadcast of non-revoked counterparty commitment transaction {}", commitment_txid);
@@ -1456,7 +1461,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
14561461
}
14571462

14581463
/// Attempts to claim a counterparty HTLC-Success/HTLC-Timeout's outputs using the revocation key
1459-
fn check_spend_counterparty_htlc<L: Deref>(&mut self, tx: &Transaction, commitment_number: u64, height: u32, logger: &L) -> (Vec<ClaimRequest>, Option<(Txid, Vec<TxOut>)>) where L::Target: Logger {
1464+
fn check_spend_counterparty_htlc<L: Deref>(&mut self, tx: &Transaction, commitment_number: u64, height: u32, logger: &L) -> (Vec<ClaimRequest>, Option<(Txid, Vec<(u32, TxOut)>)>) where L::Target: Logger {
14601465
let htlc_txid = tx.txid();
14611466
if tx.input.len() != 1 || tx.output.len() != 1 || tx.input[0].witness.len() != 5 {
14621467
return (Vec::new(), None)
@@ -1478,10 +1483,11 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
14781483
log_trace!(logger, "Counterparty HTLC broadcast {}:{}", htlc_txid, 0);
14791484
let witness_data = InputMaterial::Revoked { per_commitment_point, counterparty_delayed_payment_base_key: self.counterparty_tx_cache.counterparty_delayed_payment_base_key, counterparty_htlc_base_key: self.counterparty_tx_cache.counterparty_htlc_base_key, per_commitment_key, input_descriptor: InputDescriptors::RevokedOutput, amount: tx.output[0].value, htlc: None, on_counterparty_tx_csv: self.counterparty_tx_cache.on_counterparty_tx_csv };
14801485
let claimable_outpoints = vec!(ClaimRequest { absolute_timelock: height + self.counterparty_tx_cache.on_counterparty_tx_csv as u32, aggregable: true, outpoint: BitcoinOutPoint { txid: htlc_txid, vout: 0}, witness_data });
1481-
(claimable_outpoints, Some((htlc_txid, tx.output.clone())))
1486+
let outputs = vec![(0, tx.output[0].clone())];
1487+
(claimable_outpoints, Some((htlc_txid, outputs)))
14821488
}
14831489

1484-
fn broadcast_by_holder_state(&self, commitment_tx: &Transaction, holder_tx: &HolderSignedTx) -> (Vec<ClaimRequest>, Vec<TxOut>, Option<(Script, PublicKey, PublicKey)>) {
1490+
fn broadcast_by_holder_state(&self, commitment_tx: &Transaction, holder_tx: &HolderSignedTx) -> (Vec<ClaimRequest>, Vec<(u32, TxOut)>, Option<(Script, PublicKey, PublicKey)>) {
14851491
let mut claim_requests = Vec::with_capacity(holder_tx.htlc_outputs.len());
14861492
let mut watch_outputs = Vec::with_capacity(holder_tx.htlc_outputs.len());
14871493

@@ -1502,7 +1508,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
15021508
} else { None },
15031509
amount: htlc.amount_msat,
15041510
}});
1505-
watch_outputs.push(commitment_tx.output[transaction_output_index as usize].clone());
1511+
watch_outputs.push((transaction_output_index, commitment_tx.output[transaction_output_index as usize].clone()));
15061512
}
15071513
}
15081514

@@ -1512,7 +1518,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
15121518
/// Attempts to claim any claimable HTLCs in a commitment transaction which was not (yet)
15131519
/// revoked using data in holder_claimable_outpoints.
15141520
/// Should not be used if check_spend_revoked_transaction succeeds.
1515-
fn check_spend_holder_transaction<L: Deref>(&mut self, tx: &Transaction, height: u32, logger: &L) -> (Vec<ClaimRequest>, (Txid, Vec<TxOut>)) where L::Target: Logger {
1521+
fn check_spend_holder_transaction<L: Deref>(&mut self, tx: &Transaction, height: u32, logger: &L) -> (Vec<ClaimRequest>, (Txid, Vec<(u32, TxOut)>)) where L::Target: Logger {
15161522
let commitment_txid = tx.txid();
15171523
let mut claim_requests = Vec::new();
15181524
let mut watch_outputs = Vec::new();
@@ -1662,7 +1668,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
16621668
/// [`get_outputs_to_watch`].
16631669
///
16641670
/// [`get_outputs_to_watch`]: #method.get_outputs_to_watch
1665-
pub fn block_connected<B: Deref, F: Deref, L: Deref>(&mut self, header: &BlockHeader, txdata: &TransactionData, height: u32, broadcaster: B, fee_estimator: F, logger: L)-> Vec<(Txid, Vec<TxOut>)>
1671+
pub fn block_connected<B: Deref, F: Deref, L: Deref>(&mut self, header: &BlockHeader, txdata: &TransactionData, height: u32, broadcaster: B, fee_estimator: F, logger: L)-> Vec<(Txid, Vec<(u32, TxOut)>)>
16661672
where B::Target: BroadcasterInterface,
16671673
F::Target: FeeEstimator,
16681674
L::Target: Logger,
@@ -1763,9 +1769,23 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
17631769
// Determine new outputs to watch by comparing against previously known outputs to watch,
17641770
// updating the latter in the process.
17651771
watch_outputs.retain(|&(ref txid, ref txouts)| {
1766-
let output_scripts = txouts.iter().map(|o| o.script_pubkey.clone()).collect();
1767-
self.outputs_to_watch.insert(txid.clone(), output_scripts).is_none()
1772+
let idx_and_scripts = txouts.iter().map(|o| (o.0, o.1.script_pubkey.clone())).collect();
1773+
self.outputs_to_watch.insert(txid.clone(), idx_and_scripts).is_none()
17681774
});
1775+
#[cfg(test)]
1776+
{
1777+
// If we see a transaction for which we registered outputs previously,
1778+
// make sure the registered scriptpubkey at the expected index match
1779+
// the actual transaction output one. We failed this case before #653.
1780+
for tx in &txn_matched {
1781+
if let Some(outputs) = self.get_outputs_to_watch().get(&tx.txid()) {
1782+
for idx_and_script in outputs.iter() {
1783+
assert!((idx_and_script.0 as usize) < tx.output.len());
1784+
assert_eq!(tx.output[idx_and_script.0 as usize].script_pubkey, idx_and_script.1);
1785+
}
1786+
}
1787+
}
1788+
}
17691789
watch_outputs
17701790
}
17711791

@@ -1813,8 +1833,19 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
18131833
fn spends_watched_output(&self, tx: &Transaction) -> bool {
18141834
for input in tx.input.iter() {
18151835
if let Some(outputs) = self.get_outputs_to_watch().get(&input.previous_output.txid) {
1816-
for (idx, _script_pubkey) in outputs.iter().enumerate() {
1817-
if idx == input.previous_output.vout as usize {
1836+
for (idx, _script_pubkey) in outputs.iter() {
1837+
if *idx == input.previous_output.vout {
1838+
#[cfg(test)]
1839+
{
1840+
// If the expected script is a known type, check that the witness
1841+
// appears to be spending the correct type (ie that the match would
1842+
// actually succeed in BIP 158/159-style filters).
1843+
if _script_pubkey.is_v0_p2wsh() {
1844+
assert_eq!(&bitcoin::Address::p2wsh(&Script::from(input.witness.last().unwrap().clone()), bitcoin::Network::Bitcoin).script_pubkey(), _script_pubkey);
1845+
} else if _script_pubkey.is_v0_p2wpkh() {
1846+
assert_eq!(&bitcoin::Address::p2wpkh(&bitcoin::PublicKey::from_slice(&input.witness.last().unwrap()).unwrap(), bitcoin::Network::Bitcoin).unwrap().script_pubkey(), _script_pubkey);
1847+
} else { panic!(); }
1848+
}
18181849
return true;
18191850
}
18201851
}
@@ -2316,13 +2347,13 @@ impl<ChanSigner: ChannelKeys + Readable> Readable for (BlockHash, ChannelMonitor
23162347
}
23172348

23182349
let outputs_to_watch_len: u64 = Readable::read(reader)?;
2319-
let mut outputs_to_watch = HashMap::with_capacity(cmp::min(outputs_to_watch_len as usize, MAX_ALLOC_SIZE / (mem::size_of::<Txid>() + mem::size_of::<Vec<Script>>())));
2350+
let mut outputs_to_watch = HashMap::with_capacity(cmp::min(outputs_to_watch_len as usize, MAX_ALLOC_SIZE / (mem::size_of::<Txid>() + mem::size_of::<u32>() + mem::size_of::<Vec<Script>>())));
23202351
for _ in 0..outputs_to_watch_len {
23212352
let txid = Readable::read(reader)?;
23222353
let outputs_len: u64 = Readable::read(reader)?;
2323-
let mut outputs = Vec::with_capacity(cmp::min(outputs_len as usize, MAX_ALLOC_SIZE / mem::size_of::<Script>()));
2354+
let mut outputs = Vec::with_capacity(cmp::min(outputs_len as usize, MAX_ALLOC_SIZE / (mem::size_of::<u32>() + mem::size_of::<Script>())));
23242355
for _ in 0..outputs_len {
2325-
outputs.push(Readable::read(reader)?);
2356+
outputs.push((Readable::read(reader)?, Readable::read(reader)?));
23262357
}
23272358
if let Some(_) = outputs_to_watch.insert(txid, outputs) {
23282359
return Err(DecodeError::InvalidValue);

lightning/src/ln/functional_tests.rs

Lines changed: 47 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7157,60 +7157,6 @@ fn test_failure_delay_dust_htlc_local_commitment() {
71577157
do_test_failure_delay_dust_htlc_local_commitment(false);
71587158
}
71597159

7160-
#[test]
7161-
fn test_no_failure_dust_htlc_local_commitment() {
7162-
// Transaction filters for failing back dust htlc based on local commitment txn infos has been
7163-
// prone to error, we test here that a dummy transaction don't fail them.
7164-
7165-
let chanmon_cfgs = create_chanmon_cfgs(2);
7166-
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
7167-
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
7168-
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
7169-
let chan = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
7170-
7171-
// Rebalance a bit
7172-
send_payment(&nodes[0], &vec!(&nodes[1])[..], 8000000, 8_000_000);
7173-
7174-
let as_dust_limit = nodes[0].node.channel_state.lock().unwrap().by_id.get(&chan.2).unwrap().holder_dust_limit_satoshis;
7175-
let bs_dust_limit = nodes[1].node.channel_state.lock().unwrap().by_id.get(&chan.2).unwrap().holder_dust_limit_satoshis;
7176-
7177-
// We route 2 dust-HTLCs between A and B
7178-
let (preimage_1, _) = route_payment(&nodes[0], &[&nodes[1]], bs_dust_limit*1000);
7179-
let (preimage_2, _) = route_payment(&nodes[1], &[&nodes[0]], as_dust_limit*1000);
7180-
7181-
// Build a dummy invalid transaction trying to spend a commitment tx
7182-
let input = TxIn {
7183-
previous_output: BitcoinOutPoint { txid: chan.3.txid(), vout: 0 },
7184-
script_sig: Script::new(),
7185-
sequence: 0,
7186-
witness: Vec::new(),
7187-
};
7188-
7189-
let outp = TxOut {
7190-
script_pubkey: Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(),
7191-
value: 10000,
7192-
};
7193-
7194-
let dummy_tx = Transaction {
7195-
version: 2,
7196-
lock_time: 0,
7197-
input: vec![input],
7198-
output: vec![outp]
7199-
};
7200-
7201-
let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
7202-
nodes[0].chain_monitor.chain_monitor.block_connected(&header, &[(0, &dummy_tx)], 1);
7203-
assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0);
7204-
assert_eq!(nodes[0].node.get_and_clear_pending_msg_events().len(), 0);
7205-
// We broadcast a few more block to check everything is all right
7206-
connect_blocks(&nodes[0], 20, 1, true, header.block_hash());
7207-
assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0);
7208-
assert_eq!(nodes[0].node.get_and_clear_pending_msg_events().len(), 0);
7209-
7210-
claim_payment(&nodes[0], &vec!(&nodes[1])[..], preimage_1, bs_dust_limit*1000);
7211-
claim_payment(&nodes[1], &vec!(&nodes[0])[..], preimage_2, as_dust_limit*1000);
7212-
}
7213-
72147160
fn do_test_sweep_outbound_htlc_failure_update(revoked: bool, local: bool) {
72157161
// Outbound HTLC-failure updates must be cancelled if we get a reorg before we reach ANTI_REORG_DELAY.
72167162
// Broadcast of revoked remote commitment tx, trigger failure-update of dust/non-dust HTLCs
@@ -8497,3 +8443,50 @@ fn test_concurrent_monitor_claim() {
84978443
check_spends!(htlc_txn[1], bob_state_y);
84988444
}
84998445
}
8446+
8447+
#[test]
8448+
fn test_htlc_no_detection() {
8449+
// This test is a mutation to underscore the detection logic bug we had
8450+
// before #653. HTLC value routed is above the remaining balance, thus
8451+
// inverting HTLC and `to_remote` output. HTLC will come second and
8452+
// it wouldn't be seen by pre-#653 detection as we were enumerate()'ing
8453+
// on a watched outputs vector (Vec<TxOut>) thus implicitly relying on
8454+
// outputs order detection for correct spending children filtring.
8455+
8456+
let chanmon_cfgs = create_chanmon_cfgs(2);
8457+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
8458+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
8459+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
8460+
8461+
// Create some initial channels
8462+
let chan_1 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001, InitFeatures::known(), InitFeatures::known());
8463+
8464+
send_payment(&nodes[0], &vec!(&nodes[1])[..], 1_000_000, 1_000_000);
8465+
let (_, our_payment_hash) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 2_000_000);
8466+
let local_txn = get_local_commitment_txn!(nodes[0], chan_1.2);
8467+
assert_eq!(local_txn[0].input.len(), 1);
8468+
assert_eq!(local_txn[0].output.len(), 3);
8469+
check_spends!(local_txn[0], chan_1.3);
8470+
8471+
// Timeout HTLC on A's chain and so it can generate a HTLC-Timeout tx
8472+
let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
8473+
connect_block(&nodes[0], &Block { header, txdata: vec![local_txn[0].clone()] }, 200);
8474+
// We deliberately connect the local tx twice as this should provoke a failure calling
8475+
// this test before #653 fix.
8476+
connect_block(&nodes[0], &Block { header, txdata: vec![local_txn[0].clone()] }, 200);
8477+
check_closed_broadcast!(nodes[0], false);
8478+
check_added_monitors!(nodes[0], 1);
8479+
8480+
let htlc_timeout = {
8481+
let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
8482+
assert_eq!(node_txn[0].input.len(), 1);
8483+
assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
8484+
check_spends!(node_txn[0], local_txn[0]);
8485+
node_txn[0].clone()
8486+
};
8487+
8488+
let header_201 = BlockHeader { version: 0x20000000, prev_blockhash: header.block_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
8489+
connect_block(&nodes[0], &Block { header: header_201, txdata: vec![htlc_timeout.clone()] }, 201);
8490+
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1, 201, true, header_201.block_hash());
8491+
expect_payment_failed!(nodes[0], our_payment_hash, true);
8492+
}

0 commit comments

Comments
 (0)