Skip to content

Commit a132fdc

Browse files
committed
Implement WatchtowerPersister to mock forming justice tx from monitor updates
1 parent f018fdc commit a132fdc

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

lightning/src/util/test_utils.rs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use crate::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
3232
use crate::util::logger::{Logger, Level, Record};
3333
use crate::util::ser::{Readable, ReadableArgs, Writer, Writeable};
3434

35+
use bitcoin::{TxIn, Witness, EcdsaSighashType};
3536
use bitcoin::blockdata::constants::genesis_block;
3637
use bitcoin::blockdata::transaction::{Transaction, TxOut};
3738
use bitcoin::blockdata::script::{Builder, Script};
@@ -264,6 +265,157 @@ impl<'a> chain::Watch<EnforcingSigner> for TestChainMonitor<'a> {
264265
}
265266
}
266267

268+
pub enum WatchtowerState {
269+
/// Upon a new commitment signed, we'll get a
270+
/// ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTxInfo. We'll store the commitment txid and
271+
/// revokeable output index to use to form the justice tx once we get a revoke_and_ack with the
272+
/// commitment secret.
273+
CounterpartyCommitmentTxSeen {
274+
commitment_txid: Txid,
275+
output_idx: u32,
276+
value: u64,
277+
},
278+
/// After receiving a revoke_and_ack for a commitment number, we'll form and store the justice
279+
/// tx which would be used to provide the watchtower with the data it needs.
280+
JusticeTxFormed(Transaction),
281+
}
282+
283+
pub struct WatchtowerPersister {
284+
pub persister: TestPersister,
285+
pub destination_script: Script,
286+
pub channel_watchtower_state: Mutex<HashMap<OutPoint, HashMap<u64, WatchtowerState>>>,
287+
}
288+
289+
impl WatchtowerPersister {
290+
pub(crate) fn new(destination_script: Script) -> Self {
291+
WatchtowerPersister {
292+
persister: TestPersister::new(),
293+
destination_script,
294+
channel_watchtower_state: Mutex::new(HashMap::new()),
295+
}
296+
}
297+
298+
fn build_justice_tx(&self, commitment_txid: Txid, output_idx: u32, value: u64) -> Transaction {
299+
let mut justice_tx = Transaction {
300+
version: 2,
301+
lock_time: bitcoin::PackedLockTime::ZERO,
302+
input: vec![TxIn {
303+
previous_output: bitcoin::OutPoint {
304+
txid: commitment_txid,
305+
vout: output_idx,
306+
},
307+
script_sig: Script::new(),
308+
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
309+
witness: Witness::new(),
310+
}],
311+
output: vec![TxOut {
312+
script_pubkey: self.destination_script.clone(),
313+
value,
314+
}],
315+
};
316+
// Naive fee calculation to pass tests
317+
let min_fee = (justice_tx.weight() as u64 + 3) / 4;
318+
justice_tx.output[0].value -= min_fee * 2;
319+
justice_tx
320+
}
321+
322+
fn sign_justice_tx<Signer: sign::WriteableEcdsaChannelSigner>(&self, mut justice_tx: Transaction, secret: &[u8; 32], value: u64, data: &channelmonitor::ChannelMonitor<Signer>) -> Result<Transaction, ()> {
323+
let secp_ctx = Secp256k1::new();
324+
let per_commitment_key = SecretKey::from_slice(secret).unwrap();
325+
let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_key);
326+
let revokeable_redeemscript = data.get_revokeable_redeemscript(per_commitment_point);
327+
328+
let input_idx = 0;
329+
let sig = match data.sign_justice_revoked_output(&justice_tx, input_idx, value, &per_commitment_key) {
330+
Ok(sig) => sig,
331+
Err(_) => return Err(()),
332+
};
333+
334+
let mut ser_sig = sig.serialize_der().to_vec();
335+
ser_sig.push(EcdsaSighashType::All as u8);
336+
justice_tx.input[0].witness.push(ser_sig);
337+
justice_tx.input[0].witness.push(vec!(1));
338+
justice_tx.input[0].witness.push(revokeable_redeemscript.clone().into_bytes());
339+
340+
Ok(justice_tx)
341+
}
342+
}
343+
344+
impl<Signer: sign::WriteableEcdsaChannelSigner> chainmonitor::Persist<Signer> for WatchtowerPersister {
345+
fn persist_new_channel(&self, funding_txo: OutPoint, data: &channelmonitor::ChannelMonitor<Signer>, id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
346+
assert!(self.channel_watchtower_state.lock().unwrap()
347+
.insert(funding_txo, HashMap::new()).is_none());
348+
349+
// No ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTxInfo is persisted on channel creation
350+
// so we get the same info and update the watchtower
351+
let commitment_number = (1u64 << 48) - 1;
352+
let output_idx = 0;
353+
let commitment_txid = data.get_current_counterparty_commitment_txid().expect("Should have at least one counterparty commitment tx");
354+
let value = data.initial_to_counterparty_value();
355+
if value != 0 {
356+
assert!(self.channel_watchtower_state.lock().unwrap().get_mut(&funding_txo).unwrap()
357+
.insert(commitment_number, WatchtowerState::CounterpartyCommitmentTxSeen {
358+
commitment_txid,
359+
output_idx,
360+
value,
361+
}).is_none());
362+
}
363+
364+
self.persister.persist_new_channel(funding_txo, data, id)
365+
}
366+
367+
fn update_persisted_channel(&self, funding_txo: OutPoint, update: Option<&channelmonitor::ChannelMonitorUpdate>, data: &channelmonitor::ChannelMonitor<Signer>, update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
368+
if let Some(up) = update {
369+
for step in up.updates.iter() {
370+
match step {
371+
channelmonitor::ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { commitment_txid, htlc_outputs: _, commitment_number, their_per_commitment_point, non_htlc_outputs } => {
372+
// Find the revokeable output and store the data needed to build the
373+
// justice tx later
374+
let revokeable_redeemscript = data.get_revokeable_redeemscript(*their_per_commitment_point);
375+
let revokeable_p2wsh = revokeable_redeemscript.to_v0_p2wsh();
376+
let (output_idx, output) = match non_htlc_outputs.iter().enumerate()
377+
.find(|(_, output)| output.script_pubkey == revokeable_p2wsh) {
378+
Some((idx, output)) => (idx, output),
379+
None => {
380+
println!("No revokeable output found, revokeable output must be below dust limit.");
381+
continue;
382+
},
383+
};
384+
385+
assert!(self.channel_watchtower_state.lock().unwrap().get_mut(&funding_txo).unwrap()
386+
.insert(*commitment_number, WatchtowerState::CounterpartyCommitmentTxSeen {
387+
commitment_txid: *commitment_txid,
388+
output_idx: output_idx as u32,
389+
value: output.value,
390+
}).is_none());
391+
},
392+
channelmonitor::ChannelMonitorUpdateStep::CommitmentSecret { idx, secret } => {
393+
// Build the justice tx and store it
394+
let mut channel_watchtower_state = self.channel_watchtower_state.lock().unwrap();
395+
let justice_tx = match channel_watchtower_state.get(&funding_txo).unwrap().get(idx) {
396+
Some(WatchtowerState::CounterpartyCommitmentTxSeen { commitment_txid, output_idx, value }) => {
397+
let justice_tx = self.build_justice_tx(*commitment_txid, *output_idx, *value);
398+
self.sign_justice_tx(justice_tx, secret, *value, data).expect("Should be able to sign justice tx")
399+
},
400+
_ => {
401+
println!("No CounterpartyCommitmentTxSeen found, revokeable output must have been below dust limit.");
402+
continue;
403+
},
404+
};
405+
406+
channel_watchtower_state.get_mut(&funding_txo).unwrap()
407+
.insert(*idx, WatchtowerState::JusticeTxFormed(justice_tx))
408+
.expect("Should only happen after previous update");
409+
},
410+
_ => {},
411+
}
412+
}
413+
}
414+
415+
self.persister.update_persisted_channel(funding_txo, update, data, update_id)
416+
}
417+
}
418+
267419
pub struct TestPersister {
268420
/// The queue of update statuses we'll return. If none are queued, ::Completed will always be
269421
/// returned.

0 commit comments

Comments
 (0)