Skip to content

Commit d22650c

Browse files
authored
Merge pull request #319 from TheBlueMatt/2019-03-htlc-sorting
Fix HTLC-output-in-commitment sorting for duplicate-HTLCs
2 parents 8b4cb9f + a085488 commit d22650c

File tree

2 files changed

+26
-95
lines changed

2 files changed

+26
-95
lines changed

src/ln/channel.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -935,7 +935,19 @@ impl Channel {
935935
}, None));
936936
}
937937

938-
transaction_utils::sort_outputs(&mut txouts);
938+
transaction_utils::sort_outputs(&mut txouts, |a, b| {
939+
if let &Some(ref a_htlc) = a {
940+
if let &Some(ref b_htlc) = b {
941+
a_htlc.0.cltv_expiry.cmp(&b_htlc.0.cltv_expiry)
942+
// Note that due to hash collisions, we have to have a fallback comparison
943+
// here for fuzztarget mode (otherwise at least chanmon_fail_consistency
944+
// may fail)!
945+
.then(a_htlc.0.payment_hash.0.cmp(&b_htlc.0.payment_hash.0))
946+
// For non-HTLC outputs, if they're copying our SPK we don't really care if we
947+
// close the channel due to mismatches - they're doing something dumb:
948+
} else { cmp::Ordering::Equal }
949+
} else { cmp::Ordering::Equal }
950+
});
939951

940952
let mut outputs: Vec<TxOut> = Vec::with_capacity(txouts.len());
941953
let mut htlcs_included: Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)> = Vec::with_capacity(txouts.len() + included_dust_htlcs.len());
@@ -1011,7 +1023,7 @@ impl Channel {
10111023
}, ()));
10121024
}
10131025

1014-
transaction_utils::sort_outputs(&mut txouts);
1026+
transaction_utils::sort_outputs(&mut txouts, |_, _| { cmp::Ordering::Equal }); // Ordering doesnt matter if they used our pubkey...
10151027

10161028
let mut outputs: Vec<TxOut> = Vec::new();
10171029
for out in txouts.drain(..) {

src/util/transaction_utils.rs

Lines changed: 12 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,14 @@
1-
use bitcoin::blockdata::transaction::{TxIn, TxOut};
2-
use bitcoin_hashes::sha256d::Hash as Sha256dHash;
1+
use bitcoin::blockdata::transaction::TxOut;
32

43
use std::cmp::Ordering;
54

6-
pub fn sort_outputs<T>(outputs: &mut Vec<(TxOut, T)>) {
5+
pub fn sort_outputs<T, C : Fn(&T, &T) -> Ordering>(outputs: &mut Vec<(TxOut, T)>, tie_breaker: C) {
76
outputs.sort_unstable_by(|a, b| {
8-
a.0.value.cmp(&b.0.value).then(
9-
a.0.script_pubkey[..].cmp(&b.0.script_pubkey[..])
10-
)
11-
});
12-
}
13-
14-
fn cmp(a: &Sha256dHash, b: &Sha256dHash) -> Ordering {
15-
use bitcoin_hashes::Hash;
16-
17-
let av = a.into_inner();
18-
let bv = b.into_inner();
19-
for i in (0..32).rev() {
20-
let cmp = av[i].cmp(&bv[i]);
21-
if cmp != Ordering::Equal {
22-
return cmp;
23-
}
24-
}
25-
Ordering::Equal
26-
}
27-
28-
pub fn sort_inputs<T>(inputs: &mut Vec<(TxIn, T)>) {
29-
inputs.sort_unstable_by(|a, b| {
30-
cmp( &a.0.previous_output.txid, &b.0.previous_output.txid).then(
31-
a.0.previous_output.vout.cmp(&b.0.previous_output.vout))
7+
a.0.value.cmp(&b.0.value).then_with(|| {
8+
a.0.script_pubkey[..].cmp(&b.0.script_pubkey[..]).then_with(|| {
9+
tie_breaker(&a.1, &b.1)
10+
})
11+
})
3212
});
3313
}
3414

@@ -37,9 +17,7 @@ mod tests {
3717
use super::*;
3818

3919
use bitcoin::blockdata::script::{Script, Builder};
40-
use bitcoin::blockdata::transaction::{TxOut, OutPoint};
41-
use bitcoin_hashes::sha256d::Hash as Sha256dHash;
42-
use bitcoin_hashes::hex::FromHex;
20+
use bitcoin::blockdata::transaction::TxOut;
4321

4422
use hex::decode;
4523

@@ -58,7 +36,7 @@ mod tests {
5836
let txout2_ = txout2.clone();
5937

6038
let mut outputs = vec![(txout1, "ignore"), (txout2, "ignore")];
61-
sort_outputs(&mut outputs);
39+
sort_outputs(&mut outputs, |_, _| { unreachable!(); });
6240

6341
assert_eq!(
6442
&outputs,
@@ -81,7 +59,7 @@ mod tests {
8159
let txout2_ = txout2.clone();
8260

8361
let mut outputs = vec![(txout1, "ignore"), (txout2, "ignore")];
84-
sort_outputs(&mut outputs);
62+
sort_outputs(&mut outputs, |_, _| { unreachable!(); });
8563

8664
assert_eq!(
8765
&outputs,
@@ -105,7 +83,7 @@ mod tests {
10583
let txout2_ = txout2.clone();
10684

10785
let mut outputs = vec![(txout1, "ignore"), (txout2, "ignore")];
108-
sort_outputs(&mut outputs);
86+
sort_outputs(&mut outputs, |_, _| { unreachable!(); });
10987

11088
assert_eq!(&outputs, &vec![(txout1_, "ignore"), (txout2_, "ignore")]);
11189
}
@@ -131,7 +109,7 @@ mod tests {
131109
outputs.reverse(); // prep it
132110

133111
// actually do the work!
134-
sort_outputs(&mut outputs);
112+
sort_outputs(&mut outputs, |_, _| { unreachable!(); });
135113

136114
assert_eq!(outputs, expected);
137115
}
@@ -151,63 +129,4 @@ mod tests {
151129
bip69_txout_test_1: TXOUT1.to_vec(),
152130
bip69_txout_test_2: TXOUT2.to_vec(),
153131
}
154-
155-
macro_rules! bip_txin_tests {
156-
($($name:ident: $value:expr,)*) => {
157-
$(
158-
#[test]
159-
fn $name() {
160-
let expected_raw: Vec<(&str, u32)> = $value;
161-
let expected: Vec<(TxIn, &str)> = expected_raw.iter().map(
162-
|txin_raw| TxIn {
163-
previous_output: OutPoint {
164-
txid: Sha256dHash::from_hex(txin_raw.0).unwrap(),
165-
vout: txin_raw.1,
166-
},
167-
script_sig: Script::new(),
168-
sequence: 0,
169-
witness: vec![]
170-
}
171-
).map(|txin| (txin, "ignore")).collect();
172-
173-
let mut inputs = expected.clone();
174-
inputs.reverse();
175-
176-
sort_inputs(&mut inputs);
177-
178-
assert_eq!(expected, inputs);
179-
}
180-
)*
181-
}
182-
}
183-
184-
const TXIN1_BIP69: [(&str, u32); 17] = [
185-
("0e53ec5dfb2cb8a71fec32dc9a634a35b7e24799295ddd5278217822e0b31f57", 0),
186-
("26aa6e6d8b9e49bb0630aac301db6757c02e3619feb4ee0eea81eb1672947024", 1),
187-
("28e0fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2", 0),
188-
("381de9b9ae1a94d9c17f6a08ef9d341a5ce29e2e60c36a52d333ff6203e58d5d", 1),
189-
("3b8b2f8efceb60ba78ca8bba206a137f14cb5ea4035e761ee204302d46b98de2", 0),
190-
("402b2c02411720bf409eff60d05adad684f135838962823f3614cc657dd7bc0a", 1),
191-
("54ffff182965ed0957dba1239c27164ace5a73c9b62a660c74b7b7f15ff61e7a", 1),
192-
("643e5f4e66373a57251fb173151e838ccd27d279aca882997e005016bb53d5aa", 0),
193-
("6c1d56f31b2de4bfc6aaea28396b333102b1f600da9c6d6149e96ca43f1102b1", 1),
194-
("7a1de137cbafb5c70405455c49c5104ca3057a1f1243e6563bb9245c9c88c191", 0),
195-
("7d037ceb2ee0dc03e82f17be7935d238b35d1deabf953a892a4507bfbeeb3ba4", 1),
196-
("a5e899dddb28776ea9ddac0a502316d53a4a3fca607c72f66c470e0412e34086", 0),
197-
("b4112b8f900a7ca0c8b0e7c4dfad35c6be5f6be46b3458974988e1cdb2fa61b8", 0),
198-
("bafd65e3c7f3f9fdfdc1ddb026131b278c3be1af90a4a6ffa78c4658f9ec0c85", 0),
199-
("de0411a1e97484a2804ff1dbde260ac19de841bebad1880c782941aca883b4e9", 1),
200-
("f0a130a84912d03c1d284974f563c5949ac13f8342b8112edff52971599e6a45", 0),
201-
("f320832a9d2e2452af63154bc687493484a0e7745ebd3aaf9ca19eb80834ad60", 0),
202-
];
203-
204-
205-
const TXIN2_BIP69: [(&str, u32); 2] = [
206-
("35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", 0),
207-
("35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", 1),
208-
];
209-
bip_txin_tests! {
210-
bip69_txin_test_1: TXIN1_BIP69.to_vec(),
211-
bip69_txin_test_2: TXIN2_BIP69.to_vec(),
212-
}
213132
}

0 commit comments

Comments
 (0)