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;
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};
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};
use bitcoin::blockdata::script::{Builder, Script};
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;
use crate::io;
use crate::prelude::*;
use core::cell::RefCell;
+use core::ops::DerefMut;
use core::time::Duration;
use crate::sync::{Mutex, Arc};
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
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<Route, msgs::LightningError> {
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 locked_scorer = self.scorer.lock().unwrap();
- let scorer = ScorerAccountingForInFlightHtlcs::new(locked_scorer, inflight_htlcs);
+ let mut binding = self.scorer.lock().unwrap();
+ 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() {
return find_route_res;
}
let logger = TestLogger::new();
- let scorer = self.scorer.lock().unwrap();
find_route(
payer, params, &self.network_graph, first_hops, &logger,
- &ScorerAccountingForInFlightHtlcs::new(scorer, &inflight_htlcs), &(),
+ &ScorerAccountingForInFlightHtlcs::new(self.scorer.lock().unwrap().deref_mut(), &inflight_htlcs), &(),
&[42; 32]
)
}
}
}
+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<HashMap<OutPoint, VecDeque<JusticeTxData>>>,
+ /// 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<HashMap<OutPoint, HashMap<Txid, Transaction>>>,
+ 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<Transaction> {
+ 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<JusticeTxData> {
+ 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<Signer: sign::WriteableEcdsaChannelSigner> chainmonitor::Persist<Signer> for WatchtowerPersister {
+ fn persist_new_channel(&self, funding_txo: OutPoint,
+ data: &channelmonitor::ChannelMonitor<Signer>, 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<Signer>, 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.
expected_recv_msgs: Mutex<Option<Vec<wire::Message<()>>>>,
connected_peers: Mutex<HashSet<PublicKey>>,
pub message_fetch_counter: AtomicUsize,
+ genesis_hash: ChainHash,
}
impl TestChannelMessageHandler {
- pub fn new() -> Self {
+ pub fn new(genesis_hash: ChainHash) -> Self {
TestChannelMessageHandler {
pending_events: Mutex::new(Vec::new()),
expected_recv_msgs: Mutex::new(None),
connected_peers: Mutex::new(HashSet::new()),
message_fetch_counter: AtomicUsize::new(0),
+ genesis_hash,
}
}
channelmanager::provided_init_features(&UserConfig::default())
}
+ fn get_genesis_hashes(&self) -> Option<Vec<ChainHash>> {
+ Some(vec![self.genesis_hash])
+ }
+
fn handle_open_channel_v2(&self, _their_node_id: &PublicKey, msg: &msgs::OpenChannelV2) {
self.received_msg(wire::Message::OpenChannelV2(msg.clone()));
}
/// 1. belong to the specified module and
/// 2. match the given regex pattern.
/// Assert that the number of occurrences equals the given `count`
+ #[cfg(any(test, feature = "_test_utils"))]
pub fn assert_log_regex(&self, module: &str, pattern: regex::Regex, count: usize) {
let log_entries = self.lines.lock().unwrap();
let l: usize = log_entries.iter().filter(|&(&(ref m, ref l), _c)| {
unreachable!()
}
+ fn sign_bolt12_invoice_request(
+ &self, _invoice_request: &UnsignedInvoiceRequest
+ ) -> Result<schnorr::Signature, ()> {
+ unreachable!()
+ }
+
+ fn sign_bolt12_invoice(
+ &self, _invoice: &UnsignedBolt12Invoice,
+ ) -> Result<schnorr::Signature, ()> {
+ unreachable!()
+ }
+
fn sign_gossip_message(&self, _msg: msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
unreachable!()
}
self.backing.sign_invoice(hrp_bytes, invoice_data, recipient)
}
+ fn sign_bolt12_invoice_request(
+ &self, invoice_request: &UnsignedInvoiceRequest
+ ) -> Result<schnorr::Signature, ()> {
+ self.backing.sign_bolt12_invoice_request(invoice_request)
+ }
+
+ fn sign_bolt12_invoice(
+ &self, invoice: &UnsignedBolt12Invoice,
+ ) -> Result<schnorr::Signature, ()> {
+ self.backing.sign_bolt12_invoice(invoice)
+ }
+
fn sign_gossip_message(&self, msg: msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
self.backing.sign_gossip_message(msg)
}
}
}
}
+
+pub struct TestWalletSource {
+ secret_key: SecretKey,
+ utxos: RefCell<Vec<Utxo>>,
+ secp: Secp256k1<bitcoin::secp256k1::All>,
+}
+
+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<Vec<Utxo>, ()> {
+ Ok(self.utxos.borrow().clone())
+ }
+
+ fn get_change_script(&self) -> Result<Script, ()> {
+ 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<Transaction, ()> {
+ 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)
+ }
+}