use ln::chan_utils::HTLCOutputInCommitment;
use ln::channelmanager::{HTLCSource, PaymentPreimage, PaymentHash};
use ln::channel::{ACCEPTED_HTLC_SCRIPT_WEIGHT, OFFERED_HTLC_SCRIPT_WEIGHT};
- use chain::chaininterface::{ChainListener, ChainWatchInterface, BroadcasterInterface};
+ use chain::chaininterface::{ChainListener, ChainWatchInterface, BroadcasterInterface, FeeEstimator, ConfirmationTarget};
use chain::transaction::OutPoint;
use chain::keysinterface::SpendableOutputDescriptor;
use util::logger::Logger;
pending_events: Mutex<Vec<events::Event>>,
pending_htlc_updated: Mutex<HashMap<PaymentHash, Vec<(HTLCSource, Option<PaymentPreimage>)>>>,
logger: Arc<Logger>,
+ fee_estimator: Arc<FeeEstimator>
}
impl<Key : Send + cmp::Eq + hash::Hash> ChainListener for SimpleManyChannelMonitor<Key> {
{
let mut monitors = self.monitors.lock().unwrap();
for monitor in monitors.values_mut() {
- let (txn_outputs, spendable_outputs, mut htlc_updated) = monitor.block_connected(txn_matched, height, &block_hash, &*self.broadcaster);
+ let (txn_outputs, spendable_outputs, mut htlc_updated) = monitor.block_connected(txn_matched, height, &block_hash, &*self.broadcaster, &*self.fee_estimator);
if spendable_outputs.len() > 0 {
new_events.push(events::Event::SpendableOutputs {
outputs: spendable_outputs,
impl<Key : Send + cmp::Eq + hash::Hash + 'static> SimpleManyChannelMonitor<Key> {
/// Creates a new object which can be used to monitor several channels given the chain
/// interface with which to register to receive notifications.
- pub fn new(chain_monitor: Arc<ChainWatchInterface>, broadcaster: Arc<BroadcasterInterface>, logger: Arc<Logger>) -> Arc<SimpleManyChannelMonitor<Key>> {
+ pub fn new(chain_monitor: Arc<ChainWatchInterface>, broadcaster: Arc<BroadcasterInterface>, logger: Arc<Logger>, feeest: Arc<FeeEstimator>) -> Arc<SimpleManyChannelMonitor<Key>> {
let res = Arc::new(SimpleManyChannelMonitor {
monitors: Mutex::new(HashMap::new()),
chain_monitor,
pending_events: Mutex::new(Vec::new()),
pending_htlc_updated: Mutex::new(HashMap::new()),
logger,
+ fee_estimator: feeest,
});
let weak_res = Arc::downgrade(&res);
res.chain_monitor.register_listener(weak_res);
htlc_outputs: Vec<(HTLCOutputInCommitment, Option<(Signature, Signature)>, Option<HTLCSource>)>,
}
+ #[derive(PartialEq)]
+ enum InputDescriptors {
+ RevokedOfferedHTLC,
+ RevokedReceivedHTLC,
+ OfferedHTLC,
+ ReceivedHTLC,
+ RevokedOutput, // either a revoked to_local output on commitment tx, a revoked HTLC-Timeout output or a revoked HTLC-Success output
+ }
+
const SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 1;
}
}
+ fn get_witnesses_weight(inputs: &[InputDescriptors]) -> u64 {
+ let mut tx_weight = 2; // count segwit flags
+ for inp in inputs {
+ // We use expected weight (and not actual) as signatures and time lock delays may vary
+ tx_weight += match inp {
+ // number_of_witness_elements + sig_length + revocation_sig + pubkey_length + revocationpubkey + witness_script_length + witness_script
+ &InputDescriptors::RevokedOfferedHTLC => {
+ 1 + 1 + 73 + 1 + 33 + 1 + 133
+ },
+ // number_of_witness_elements + sig_length + revocation_sig + pubkey_length + revocationpubkey + witness_script_length + witness_script
+ &InputDescriptors::RevokedReceivedHTLC => {
+ 1 + 1 + 73 + 1 + 33 + 1 + 139
+ },
+ // number_of_witness_elements + sig_length + remotehtlc_sig + preimage_length + preimage + witness_script_length + witness_script
+ &InputDescriptors::OfferedHTLC => {
+ 1 + 1 + 73 + 1 + 32 + 1 + 133
+ },
+ // number_of_witness_elements + sig_length + revocation_sig + pubkey_length + revocationpubkey + witness_script_length + witness_script
+ &InputDescriptors::ReceivedHTLC => {
+ 1 + 1 + 73 + 1 + 1 + 1 + 139
+ },
+ // number_of_witness_elements + sig_length + revocation_sig + true_length + op_true + witness_script_length + witness_script
+ &InputDescriptors::RevokedOutput => {
+ 1 + 1 + 73 + 1 + 1 + 1 + 77
+ },
+ };
+ }
+ tx_weight
+ }
+
#[inline]
fn place_secret(idx: u64) -> u8 {
for i in 0..48 {
writer.write_all(&delayed_payment_base_key[..])?;
writer.write_all(&payment_base_key[..])?;
writer.write_all(&shutdown_pubkey.serialize())?;
- if let Some(ref prev_latest_per_commitment_point) = *prev_latest_per_commitment_point {
- writer.write_all(&[1; 1])?;
- writer.write_all(&prev_latest_per_commitment_point.serialize())?;
- } else {
- writer.write_all(&[0; 1])?;
- }
- if let Some(ref latest_per_commitment_point) = *latest_per_commitment_point {
- writer.write_all(&[1; 1])?;
- writer.write_all(&latest_per_commitment_point.serialize())?;
- } else {
- writer.write_all(&[0; 1])?;
- }
+ prev_latest_per_commitment_point.write(writer)?;
+ latest_per_commitment_point.write(writer)?;
match funding_info {
&Some((ref outpoint, ref script)) => {
writer.write_all(&outpoint.txid[..])?;
debug_assert!(false, "Try to serialize a useless Local monitor !");
},
}
- write_option!(current_remote_commitment_txid);
- write_option!(prev_remote_commitment_txid);
+ current_remote_commitment_txid.write(writer)?;
+ prev_remote_commitment_txid.write(writer)?;
},
Storage::Watchtower { .. } => unimplemented!(),
}
writer.write_all(&byte_utils::be64_to_array($htlc_output.amount_msat))?;
writer.write_all(&byte_utils::be32_to_array($htlc_output.cltv_expiry))?;
writer.write_all(&$htlc_output.payment_hash.0[..])?;
- write_option!(&$htlc_output.transaction_output_index);
+ $htlc_output.transaction_output_index.write(writer)?;
}
}
/// HTLC-Success/HTLC-Timeout transactions.
/// Return updates for HTLC pending in the channel and failed automatically by the broadcast of
/// revoked remote commitment tx
- fn check_spend_remote_transaction(&mut self, tx: &Transaction, height: u32) -> (Vec<Transaction>, (Sha256dHash, Vec<TxOut>), Vec<SpendableOutputDescriptor>, Vec<(HTLCSource, Option<PaymentPreimage>, PaymentHash)>) {
+ fn check_spend_remote_transaction(&mut self, tx: &Transaction, height: u32, fee_estimator: &FeeEstimator) -> (Vec<Transaction>, (Sha256dHash, Vec<TxOut>), Vec<SpendableOutputDescriptor>, Vec<(HTLCSource, Option<PaymentPreimage>, PaymentHash)>) {
// Most secp and related errors trying to create keys means we have no hope of constructing
// a spend transaction...so we return no transactions to broadcast
let mut txn_to_broadcast = Vec::new();
let mut values = Vec::new();
let mut inputs = Vec::new();
let mut htlc_idxs = Vec::new();
+ let mut input_descriptors = Vec::new();
for (idx, outp) in tx.output.iter().enumerate() {
if outp.script_pubkey == revokeable_p2wsh {
htlc_idxs.push(None);
values.push(outp.value);
total_value += outp.value;
+ input_descriptors.push(InputDescriptors::RevokedOutput);
} else if Some(&outp.script_pubkey) == local_payment_p2wpkh.as_ref() {
spendable_outputs.push(SpendableOutputDescriptor::DynamicOutputP2WPKH {
outpoint: BitcoinOutPoint { txid: commitment_txid, vout: idx as u32 },
htlc_idxs.push(Some(idx));
values.push(tx.output[transaction_output_index as usize].value);
total_value += htlc.amount_msat / 1000;
+ input_descriptors.push(if htlc.offered { InputDescriptors::RevokedOfferedHTLC } else { InputDescriptors::RevokedReceivedHTLC });
} else {
let mut single_htlc_tx = Transaction {
version: 2,
input: vec![input],
output: vec!(TxOut {
script_pubkey: self.destination_script.clone(),
- value: htlc.amount_msat / 1000, //TODO: - fee
+ value: htlc.amount_msat / 1000,
}),
};
+ let predicted_weight = single_htlc_tx.get_weight() + Self::get_witnesses_weight(&[if htlc.offered { InputDescriptors::RevokedOfferedHTLC } else { InputDescriptors::RevokedReceivedHTLC }]);
+ single_htlc_tx.output[0].value -= fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) * predicted_weight / 1000;
let sighash_parts = bip143::SighashComponents::new(&single_htlc_tx);
sign_input!(sighash_parts, single_htlc_tx.input[0], Some(idx), htlc.amount_msat / 1000);
+ assert!(predicted_weight >= single_htlc_tx.get_weight());
txn_to_broadcast.push(single_htlc_tx);
}
}
let outputs = vec!(TxOut {
script_pubkey: self.destination_script.clone(),
- value: total_value, //TODO: - fee
+ value: total_value,
});
let mut spend_tx = Transaction {
version: 2,
input: inputs,
output: outputs,
};
+ let predicted_weight = spend_tx.get_weight() + Self::get_witnesses_weight(&input_descriptors[..]);
+ spend_tx.output[0].value -= fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) * predicted_weight / 1000;
let mut values_drain = values.drain(..);
let sighash_parts = bip143::SighashComponents::new(&spend_tx);
let value = values_drain.next().unwrap();
sign_input!(sighash_parts, input, htlc_idx, value);
}
+ assert!(predicted_weight >= spend_tx.get_weight());
spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
outpoint: BitcoinOutPoint { txid: spend_tx.txid(), vout: 0 },
let mut total_value = 0;
let mut values = Vec::new();
let mut inputs = Vec::new();
+ let mut input_descriptors = Vec::new();
macro_rules! sign_input {
($sighash_parts: expr, $input: expr, $amount: expr, $preimage: expr) => {
inputs.push(input);
values.push((tx.output[transaction_output_index as usize].value, payment_preimage));
total_value += htlc.amount_msat / 1000;
+ input_descriptors.push(if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC });
} else {
let mut single_htlc_tx = Transaction {
version: 2,
input: vec![input],
output: vec!(TxOut {
script_pubkey: self.destination_script.clone(),
- value: htlc.amount_msat / 1000, //TODO: - fee
+ value: htlc.amount_msat / 1000,
}),
};
+ let predicted_weight = single_htlc_tx.get_weight() + Self::get_witnesses_weight(&[if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC }]);
+ single_htlc_tx.output[0].value -= fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) * predicted_weight / 1000;
let sighash_parts = bip143::SighashComponents::new(&single_htlc_tx);
sign_input!(sighash_parts, single_htlc_tx.input[0], htlc.amount_msat / 1000, payment_preimage.0.to_vec());
+ assert!(predicted_weight >= single_htlc_tx.get_weight());
spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
outpoint: BitcoinOutPoint { txid: single_htlc_tx.txid(), vout: 0 },
output: single_htlc_tx.output[0].clone(),
let outputs = vec!(TxOut {
script_pubkey: self.destination_script.clone(),
- value: total_value, //TODO: - fee
+ value: total_value
});
let mut spend_tx = Transaction {
version: 2,
input: inputs,
output: outputs,
};
+ let predicted_weight = spend_tx.get_weight() + Self::get_witnesses_weight(&input_descriptors[..]);
+ spend_tx.output[0].value -= fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) * predicted_weight / 1000;
let mut values_drain = values.drain(..);
let sighash_parts = bip143::SighashComponents::new(&spend_tx);
sign_input!(sighash_parts, input, value.0, (value.1).0.to_vec());
}
+ assert!(predicted_weight >= spend_tx.get_weight());
spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
outpoint: BitcoinOutPoint { txid: spend_tx.txid(), vout: 0 },
output: spend_tx.output[0].clone(),
}
/// Attempts to claim a remote HTLC-Success/HTLC-Timeout's outputs using the revocation key
- fn check_spend_remote_htlc(&self, tx: &Transaction, commitment_number: u64) -> (Option<Transaction>, Option<SpendableOutputDescriptor>) {
+ fn check_spend_remote_htlc(&self, tx: &Transaction, commitment_number: u64, fee_estimator: &FeeEstimator) -> (Option<Transaction>, Option<SpendableOutputDescriptor>) {
if tx.input.len() != 1 || tx.output.len() != 1 {
return (None, None)
}
if !inputs.is_empty() {
let outputs = vec!(TxOut {
script_pubkey: self.destination_script.clone(),
- value: amount, //TODO: - fee
+ value: amount
});
let mut spend_tx = Transaction {
input: inputs,
output: outputs,
};
+ let predicted_weight = spend_tx.get_weight() + Self::get_witnesses_weight(&[InputDescriptors::RevokedOutput]);
+ spend_tx.output[0].value -= fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) * predicted_weight / 1000;
let sighash_parts = bip143::SighashComponents::new(&spend_tx);
spend_tx.input[0].witness.push(vec!(1));
spend_tx.input[0].witness.push(redeemscript.into_bytes());
+ assert!(predicted_weight >= spend_tx.get_weight());
let outpoint = BitcoinOutPoint { txid: spend_tx.txid(), vout: 0 };
let output = spend_tx.output[0].clone();
(Some(spend_tx), Some(SpendableOutputDescriptor::StaticOutput { outpoint, output }))
}
}
- fn block_connected(&mut self, txn_matched: &[&Transaction], height: u32, block_hash: &Sha256dHash, broadcaster: &BroadcasterInterface)-> (Vec<(Sha256dHash, Vec<TxOut>)>, Vec<SpendableOutputDescriptor>, Vec<(HTLCSource, Option<PaymentPreimage>, PaymentHash)>) {
+ fn block_connected(&mut self, txn_matched: &[&Transaction], height: u32, block_hash: &Sha256dHash, broadcaster: &BroadcasterInterface, fee_estimator: &FeeEstimator)-> (Vec<(Sha256dHash, Vec<TxOut>)>, Vec<SpendableOutputDescriptor>, Vec<(HTLCSource, Option<PaymentPreimage>, PaymentHash)>) {
let mut watch_outputs = Vec::new();
let mut spendable_outputs = Vec::new();
let mut htlc_updated = Vec::new();
}
};
if funding_txo.is_none() || (prevout.txid == funding_txo.as_ref().unwrap().0.txid && prevout.vout == funding_txo.as_ref().unwrap().0.index as u32) {
- let (remote_txn, new_outputs, mut spendable_output, mut updated) = self.check_spend_remote_transaction(tx, height);
+ let (remote_txn, new_outputs, mut spendable_output, mut updated) = self.check_spend_remote_transaction(tx, height, fee_estimator);
txn = remote_txn;
spendable_outputs.append(&mut spendable_output);
if !new_outputs.1.is_empty() {
}
} else {
if let Some(&(commitment_number, _)) = self.remote_commitment_txn_on_chain.get(&prevout.txid) {
- let (tx, spendable_output) = self.check_spend_remote_htlc(tx, commitment_number);
+ let (tx, spendable_output) = self.check_spend_remote_htlc(tx, commitment_number, fee_estimator);
if let Some(tx) = tx {
txn.push(tx);
}
#[cfg(test)]
mod tests {
- use bitcoin::blockdata::script::Script;
- use bitcoin::blockdata::transaction::Transaction;
+ use bitcoin::blockdata::script::{Script, Builder};
+ use bitcoin::blockdata::opcodes;
+ use bitcoin::blockdata::transaction::{Transaction, TxIn, TxOut, SigHashType};
+ use bitcoin::blockdata::transaction::OutPoint as BitcoinOutPoint;
+ use bitcoin::util::bip143;
use bitcoin_hashes::Hash;
use bitcoin_hashes::sha256::Hash as Sha256;
+ use bitcoin_hashes::sha256d::Hash as Sha256dHash;
+ use bitcoin_hashes::hex::FromHex;
use hex;
use ln::channelmanager::{PaymentPreimage, PaymentHash};
- use ln::channelmonitor::ChannelMonitor;
+ use ln::channelmonitor::{ChannelMonitor, InputDescriptors};
+ use ln::chan_utils;
use ln::chan_utils::{HTLCOutputInCommitment, TxCreationKeys};
use util::test_utils::TestLogger;
use secp256k1::key::{SecretKey,PublicKey};
test_preimages_exist!(&preimages[0..5], monitor);
}
+ #[test]
+ fn test_claim_txn_weight_computation() {
+ // We test Claim txn weight, knowing that we want expected weigth and
+ // not actual case to avoid sigs and time-lock delays hell variances.
+
+ let secp_ctx = Secp256k1::new();
+ let privkey = SecretKey::from_slice(&hex::decode("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap();
+ let pubkey = PublicKey::from_secret_key(&secp_ctx, &privkey);
+ let mut sum_actual_sigs: u64 = 0;
+
+ macro_rules! sign_input {
+ ($sighash_parts: expr, $input: expr, $idx: expr, $amount: expr, $input_type: expr, $sum_actual_sigs: expr) => {
+ let htlc = HTLCOutputInCommitment {
+ offered: if *$input_type == InputDescriptors::RevokedOfferedHTLC || *$input_type == InputDescriptors::OfferedHTLC { true } else { false },
+ amount_msat: 0,
+ cltv_expiry: 2 << 16,
+ payment_hash: PaymentHash([1; 32]),
+ transaction_output_index: Some($idx),
+ };
+ let redeem_script = if *$input_type == InputDescriptors::RevokedOutput { chan_utils::get_revokeable_redeemscript(&pubkey, 256, &pubkey) } else { chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, &pubkey, &pubkey, &pubkey) };
+ let sighash = hash_to_message!(&$sighash_parts.sighash_all(&$input, &redeem_script, $amount)[..]);
+ let sig = secp_ctx.sign(&sighash, &privkey);
+ $input.witness.push(sig.serialize_der().to_vec());
+ $input.witness[0].push(SigHashType::All as u8);
+ sum_actual_sigs += $input.witness[0].len() as u64;
+ if *$input_type == InputDescriptors::RevokedOutput {
+ $input.witness.push(vec!(1));
+ } else if *$input_type == InputDescriptors::RevokedOfferedHTLC || *$input_type == InputDescriptors::RevokedReceivedHTLC {
+ $input.witness.push(pubkey.clone().serialize().to_vec());
+ } else if *$input_type == InputDescriptors::ReceivedHTLC {
+ $input.witness.push(vec![0]);
+ } else {
+ $input.witness.push(PaymentPreimage([1; 32]).0.to_vec());
+ }
+ $input.witness.push(redeem_script.into_bytes());
+ println!("witness[0] {}", $input.witness[0].len());
+ println!("witness[1] {}", $input.witness[1].len());
+ println!("witness[2] {}", $input.witness[2].len());
+ }
+ }
+
+ let script_pubkey = Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script();
+ let txid = Sha256dHash::from_hex("56944c5d3f98413ef45cf54545538103cc9f298e0575820ad3591376e2e0f65d").unwrap();
+
+ // Justice tx with 1 to_local, 2 revoked offered HTLCs, 1 revoked received HTLCs
+ let mut claim_tx = Transaction { version: 0, lock_time: 0, input: Vec::new(), output: Vec::new() };
+ for i in 0..4 {
+ claim_tx.input.push(TxIn {
+ previous_output: BitcoinOutPoint {
+ txid,
+ vout: i,
+ },
+ script_sig: Script::new(),
+ sequence: 0xfffffffd,
+ witness: Vec::new(),
+ });
+ }
+ claim_tx.output.push(TxOut {
+ script_pubkey: script_pubkey.clone(),
+ value: 0,
+ });
+ let base_weight = claim_tx.get_weight();
+ let sighash_parts = bip143::SighashComponents::new(&claim_tx);
+ let inputs_des = vec![InputDescriptors::RevokedOutput, InputDescriptors::RevokedOfferedHTLC, InputDescriptors::RevokedOfferedHTLC, InputDescriptors::RevokedReceivedHTLC];
+ for (idx, inp) in claim_tx.input.iter_mut().zip(inputs_des.iter()).enumerate() {
+ sign_input!(sighash_parts, inp.0, idx as u32, 0, inp.1, sum_actual_sigs);
+ }
+ assert_eq!(base_weight + ChannelMonitor::get_witnesses_weight(&inputs_des[..]), claim_tx.get_weight() + /* max_length_sig */ (73 * inputs_des.len() as u64 - sum_actual_sigs));
+
+ // Claim tx with 1 offered HTLCs, 3 received HTLCs
+ claim_tx.input.clear();
+ sum_actual_sigs = 0;
+ for i in 0..4 {
+ claim_tx.input.push(TxIn {
+ previous_output: BitcoinOutPoint {
+ txid,
+ vout: i,
+ },
+ script_sig: Script::new(),
+ sequence: 0xfffffffd,
+ witness: Vec::new(),
+ });
+ }
+ let base_weight = claim_tx.get_weight();
+ let sighash_parts = bip143::SighashComponents::new(&claim_tx);
+ let inputs_des = vec![InputDescriptors::OfferedHTLC, InputDescriptors::ReceivedHTLC, InputDescriptors::ReceivedHTLC, InputDescriptors::ReceivedHTLC];
+ for (idx, inp) in claim_tx.input.iter_mut().zip(inputs_des.iter()).enumerate() {
+ sign_input!(sighash_parts, inp.0, idx as u32, 0, inp.1, sum_actual_sigs);
+ }
+ assert_eq!(base_weight + ChannelMonitor::get_witnesses_weight(&inputs_des[..]), claim_tx.get_weight() + /* max_length_sig */ (73 * inputs_des.len() as u64 - sum_actual_sigs));
+
+ // Justice tx with 1 revoked HTLC-Success tx output
+ claim_tx.input.clear();
+ sum_actual_sigs = 0;
+ claim_tx.input.push(TxIn {
+ previous_output: BitcoinOutPoint {
+ txid,
+ vout: 0,
+ },
+ script_sig: Script::new(),
+ sequence: 0xfffffffd,
+ witness: Vec::new(),
+ });
+ let base_weight = claim_tx.get_weight();
+ let sighash_parts = bip143::SighashComponents::new(&claim_tx);
+ let inputs_des = vec![InputDescriptors::RevokedOutput];
+ for (idx, inp) in claim_tx.input.iter_mut().zip(inputs_des.iter()).enumerate() {
+ sign_input!(sighash_parts, inp.0, idx as u32, 0, inp.1, sum_actual_sigs);
+ }
+ assert_eq!(base_weight + ChannelMonitor::get_witnesses_weight(&inputs_des[..]), claim_tx.get_weight() + /* max_length_isg */ (73 * inputs_des.len() as u64 - sum_actual_sigs));
+ }
+
// Further testing is done in the ChannelManager integration tests.
}