X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Futil%2Ftest_utils.rs;h=bcd460ee1b11e1343add61ccee1bfb9c713726a1;hb=f13073913816cf957a62c8bfa80541d9c1fa9ba6;hp=212f1f4b60a392f6ad605fe599f200b3c543b778;hpb=0d3adb8fa08474b6d893a373b78e0195d061cfe6;p=rust-lightning diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 212f1f4b..bcd460ee 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -11,6 +11,7 @@ use crate::chain; use crate::chain::WatchedOutput; use crate::chain::chaininterface; use crate::chain::chaininterface::ConfirmationTarget; +use crate::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW; use crate::chain::chainmonitor; use crate::chain::chainmonitor::MonitorUpdateId; use crate::chain::channelmonitor; @@ -18,11 +19,15 @@ use crate::chain::channelmonitor::MonitorEvent; use crate::chain::transaction::OutPoint; use crate::sign; use crate::events; +use crate::events::bump_transaction::{WalletSource, Utxo}; use crate::ln::channelmanager; +use crate::ln::chan_utils::CommitmentTransaction; use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use crate::ln::{msgs, wire}; use crate::ln::msgs::LightningError; use crate::ln::script::ShutdownScript; +use crate::offers::invoice::UnsignedBolt12Invoice; +use crate::offers::invoice_request::UnsignedInvoiceRequest; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId}; use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult}; use crate::routing::router::{find_route, InFlightHtlcs, Path, Route, RouteParameters, Router, ScorerAccountingForInFlightHtlcs}; @@ -32,6 +37,7 @@ use crate::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState}; use crate::util::logger::{Logger, Level, Record}; use crate::util::ser::{Readable, ReadableArgs, Writer, Writeable}; +use bitcoin::EcdsaSighashType; use bitcoin::blockdata::constants::ChainHash; use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::transaction::{Transaction, TxOut}; @@ -40,10 +46,12 @@ use bitcoin::blockdata::opcodes; use bitcoin::blockdata::block::Block; use bitcoin::network::constants::Network; use bitcoin::hash_types::{BlockHash, Txid}; +use bitcoin::util::sighash::SighashCache; -use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1, ecdsa::Signature, Scalar}; +use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey}; use bitcoin::secp256k1::ecdh::SharedSecret; -use bitcoin::secp256k1::ecdsa::RecoverableSignature; +use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; +use bitcoin::secp256k1::schnorr; #[cfg(any(test, feature = "_test_utils"))] use regex; @@ -109,13 +117,13 @@ impl<'a> TestRouter<'a> { impl<'a> Router for TestRouter<'a> { fn find_route( &self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&channelmanager::ChannelDetails]>, - inflight_htlcs: &InFlightHtlcs + inflight_htlcs: InFlightHtlcs ) -> Result { if let Some((find_route_query, find_route_res)) = self.next_routes.lock().unwrap().pop_front() { assert_eq!(find_route_query, *params); if let Ok(ref route) = find_route_res { let mut binding = self.scorer.lock().unwrap(); - let scorer = ScorerAccountingForInFlightHtlcs::new(binding.deref_mut(), inflight_htlcs); + let scorer = ScorerAccountingForInFlightHtlcs::new(binding.deref_mut(), &inflight_htlcs); for path in &route.paths { let mut aggregate_msat = 0u64; for (idx, hop) in path.hops.iter().rev().enumerate() { @@ -266,6 +274,108 @@ impl<'a> chain::Watch for TestChainMonitor<'a> { } } +struct JusticeTxData { + justice_tx: Transaction, + value: u64, + commitment_number: u64, +} + +pub(crate) struct WatchtowerPersister { + persister: TestPersister, + /// Upon a new commitment_signed, we'll get a + /// ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTxInfo. We'll store the justice tx + /// amount, and commitment number so we can build the justice tx after our counterparty + /// revokes it. + unsigned_justice_tx_data: Mutex>>, + /// After receiving a revoke_and_ack for a commitment number, we'll form and store the justice + /// tx which would be used to provide a watchtower with the data it needs. + watchtower_state: Mutex>>, + destination_script: Script, +} + +impl WatchtowerPersister { + pub(crate) fn new(destination_script: Script) -> Self { + WatchtowerPersister { + persister: TestPersister::new(), + unsigned_justice_tx_data: Mutex::new(HashMap::new()), + watchtower_state: Mutex::new(HashMap::new()), + destination_script, + } + } + + pub(crate) fn justice_tx(&self, funding_txo: OutPoint, commitment_txid: &Txid) + -> Option { + self.watchtower_state.lock().unwrap().get(&funding_txo).unwrap().get(commitment_txid).cloned() + } + + fn form_justice_data_from_commitment(&self, counterparty_commitment_tx: &CommitmentTransaction) + -> Option { + let trusted_tx = counterparty_commitment_tx.trust(); + let output_idx = trusted_tx.revokeable_output_index()?; + let built_tx = trusted_tx.built_transaction(); + let value = built_tx.transaction.output[output_idx as usize].value; + let justice_tx = trusted_tx.build_to_local_justice_tx( + FEERATE_FLOOR_SATS_PER_KW as u64, self.destination_script.clone()).ok()?; + let commitment_number = counterparty_commitment_tx.commitment_number(); + Some(JusticeTxData { justice_tx, value, commitment_number }) + } +} + +impl chainmonitor::Persist for WatchtowerPersister { + fn persist_new_channel(&self, funding_txo: OutPoint, + data: &channelmonitor::ChannelMonitor, id: MonitorUpdateId + ) -> chain::ChannelMonitorUpdateStatus { + let res = self.persister.persist_new_channel(funding_txo, data, id); + + assert!(self.unsigned_justice_tx_data.lock().unwrap() + .insert(funding_txo, VecDeque::new()).is_none()); + assert!(self.watchtower_state.lock().unwrap() + .insert(funding_txo, HashMap::new()).is_none()); + + let initial_counterparty_commitment_tx = data.initial_counterparty_commitment_tx() + .expect("First and only call expects Some"); + if let Some(justice_data) + = self.form_justice_data_from_commitment(&initial_counterparty_commitment_tx) { + self.unsigned_justice_tx_data.lock().unwrap() + .get_mut(&funding_txo).unwrap() + .push_back(justice_data); + } + res + } + + fn update_persisted_channel( + &self, funding_txo: OutPoint, update: Option<&channelmonitor::ChannelMonitorUpdate>, + data: &channelmonitor::ChannelMonitor, update_id: MonitorUpdateId + ) -> chain::ChannelMonitorUpdateStatus { + let res = self.persister.update_persisted_channel(funding_txo, update, data, update_id); + + if let Some(update) = update { + let commitment_txs = data.counterparty_commitment_txs_from_update(update); + let justice_datas = commitment_txs.into_iter() + .filter_map(|commitment_tx| self.form_justice_data_from_commitment(&commitment_tx)); + let mut channels_justice_txs = self.unsigned_justice_tx_data.lock().unwrap(); + let channel_state = channels_justice_txs.get_mut(&funding_txo).unwrap(); + channel_state.extend(justice_datas); + + while let Some(JusticeTxData { justice_tx, value, commitment_number }) = channel_state.front() { + let input_idx = 0; + let commitment_txid = justice_tx.input[input_idx].previous_output.txid; + match data.sign_to_local_justice_tx(justice_tx.clone(), input_idx, *value, *commitment_number) { + Ok(signed_justice_tx) => { + let dup = self.watchtower_state.lock().unwrap() + .get_mut(&funding_txo).unwrap() + .insert(commitment_txid, signed_justice_tx); + assert!(dup.is_none()); + channel_state.pop_front(); + }, + Err(_) => break, + } + } + } + res + } +} + pub struct TestPersister { /// The queue of update statuses we'll return. If none are queued, ::Completed will always be /// returned. @@ -797,6 +907,18 @@ impl NodeSigner for TestNodeSigner { unreachable!() } + fn sign_bolt12_invoice_request( + &self, _invoice_request: &UnsignedInvoiceRequest + ) -> Result { + unreachable!() + } + + fn sign_bolt12_invoice( + &self, _invoice: &UnsignedBolt12Invoice, + ) -> Result { + unreachable!() + } + fn sign_gossip_message(&self, _msg: msgs::UnsignedGossipMessage) -> Result { unreachable!() } @@ -837,6 +959,18 @@ impl NodeSigner for TestKeysInterface { self.backing.sign_invoice(hrp_bytes, invoice_data, recipient) } + fn sign_bolt12_invoice_request( + &self, invoice_request: &UnsignedInvoiceRequest + ) -> Result { + self.backing.sign_bolt12_invoice_request(invoice_request) + } + + fn sign_bolt12_invoice( + &self, invoice: &UnsignedBolt12Invoice, + ) -> Result { + self.backing.sign_bolt12_invoice(invoice) + } + fn sign_gossip_message(&self, msg: msgs::UnsignedGossipMessage) -> Result { self.backing.sign_gossip_message(msg) } @@ -1067,3 +1201,65 @@ impl Drop for TestScorer { } } } + +pub struct TestWalletSource { + secret_key: SecretKey, + utxos: RefCell>, + secp: Secp256k1, +} + +impl TestWalletSource { + pub fn new(secret_key: SecretKey) -> Self { + Self { + secret_key, + utxos: RefCell::new(Vec::new()), + secp: Secp256k1::new(), + } + } + + pub fn add_utxo(&self, outpoint: bitcoin::OutPoint, value: u64) -> TxOut { + let public_key = bitcoin::PublicKey::new(self.secret_key.public_key(&self.secp)); + let utxo = Utxo::new_p2pkh(outpoint, value, &public_key.pubkey_hash()); + self.utxos.borrow_mut().push(utxo.clone()); + utxo.output + } + + pub fn add_custom_utxo(&self, utxo: Utxo) -> TxOut { + let output = utxo.output.clone(); + self.utxos.borrow_mut().push(utxo); + output + } + + pub fn remove_utxo(&self, outpoint: bitcoin::OutPoint) { + self.utxos.borrow_mut().retain(|utxo| utxo.outpoint != outpoint); + } +} + +impl WalletSource for TestWalletSource { + fn list_confirmed_utxos(&self) -> Result, ()> { + Ok(self.utxos.borrow().clone()) + } + + fn get_change_script(&self) -> Result { + let public_key = bitcoin::PublicKey::new(self.secret_key.public_key(&self.secp)); + Ok(Script::new_p2pkh(&public_key.pubkey_hash())) + } + + fn sign_tx(&self, mut tx: Transaction) -> Result { + let utxos = self.utxos.borrow(); + for i in 0..tx.input.len() { + if let Some(utxo) = utxos.iter().find(|utxo| utxo.outpoint == tx.input[i].previous_output) { + let sighash = SighashCache::new(&tx) + .legacy_signature_hash(i, &utxo.output.script_pubkey, EcdsaSighashType::All as u32) + .map_err(|_| ())?; + let sig = self.secp.sign_ecdsa(&sighash.as_hash().into(), &self.secret_key); + let bitcoin_sig = bitcoin::EcdsaSig { sig, hash_ty: EcdsaSighashType::All }.to_vec(); + tx.input[i].script_sig = Builder::new() + .push_slice(&bitcoin_sig) + .push_slice(&self.secret_key.public_key(&self.secp).serialize()) + .into_script(); + } + } + Ok(tx) + } +}