use util::ser::{ReadableArgs, Readable, Writer, Writeable, WriterWriteAdaptor, U48};
use util::{byte_utils, events};
-use std::collections::{HashMap, hash_map};
+use std::collections::{HashMap, hash_map, HashSet};
use std::sync::{Arc,Mutex};
use std::{hash,cmp, mem};
/// Higher-level cache structure needed to re-generate bumped claim txn if needed
#[derive(Clone, PartialEq)]
-struct ClaimTxBumpMaterial {
+pub struct ClaimTxBumpMaterial {
// At every block tick, used to check if pending claiming tx is taking too
// much time for confirmation and we need to bump it.
height_timer: u32,
key_storage: Storage,
their_htlc_base_key: Option<PublicKey>,
their_delayed_payment_base_key: Option<PublicKey>,
+ funding_redeemscript: Option<Script>,
+ channel_value_satoshis: Option<u64>,
// first is the idx of the first of the two revocation points
their_cur_revocation_points: Option<(u64, PublicKey, Option<PublicKey>)>,
// Key is identifier of the pending claim request, i.e the txid of the initial claiming transaction generated by
// us and is immutable until all outpoint of the claimable set are post-anti-reorg-delay solved.
// Entry is cache of elements need to generate a bumped claiming transaction (see ClaimTxBumpMaterial)
+ #[cfg(test)] // Used in functional_test to verify sanitization
+ pub pending_claim_requests: HashMap<Sha256dHash, ClaimTxBumpMaterial>,
+ #[cfg(not(test))]
pending_claim_requests: HashMap<Sha256dHash, ClaimTxBumpMaterial>,
// Used to link outpoints claimed in a connected block to a pending claim request.
// is txid of the initial claiming transaction and is immutable until outpoint is
// post-anti-reorg-delay solved, confirmaiton_block is used to erase entry if
// block with output gets disconnected.
+ #[cfg(test)] // Used in functional_test to verify sanitization
+ pub claimable_outpoints: HashMap<BitcoinOutPoint, (Sha256dHash, u32)>,
+ #[cfg(not(test))]
claimable_outpoints: HashMap<BitcoinOutPoint, (Sha256dHash, u32)>,
// Used to track onchain events, i.e transactions parts of channels confirmed on chain, on which
self.key_storage != other.key_storage ||
self.their_htlc_base_key != other.their_htlc_base_key ||
self.their_delayed_payment_base_key != other.their_delayed_payment_base_key ||
+ self.funding_redeemscript != other.funding_redeemscript ||
+ self.channel_value_satoshis != other.channel_value_satoshis ||
self.their_cur_revocation_points != other.their_cur_revocation_points ||
self.our_to_self_delay != other.our_to_self_delay ||
self.their_to_self_delay != other.their_to_self_delay ||
},
their_htlc_base_key: None,
their_delayed_payment_base_key: None,
+ funding_redeemscript: None,
+ channel_value_satoshis: None,
their_cur_revocation_points: None,
our_to_self_delay: our_to_self_delay,
Ok(())
}
- /// Panics if commitment_transaction_number_obscure_factor doesn't fit in 48 bits
- pub(super) fn set_commitment_obscure_factor(&mut self, commitment_transaction_number_obscure_factor: u64) {
- assert!(commitment_transaction_number_obscure_factor < (1 << 48));
- self.commitment_transaction_number_obscure_factor = commitment_transaction_number_obscure_factor;
- }
-
/// Allows this monitor to scan only for transactions which are applicable. Note that this is
/// optional, without it this monitor cannot be used in an SPV client, but you may wish to
/// avoid this (or call unset_funding_info) on a monitor you wish to send to a watchtower as it
}
/// We log these base keys at channel opening to being able to rebuild redeemscript in case of leaked revoked commit tx
- pub(super) fn set_their_base_keys(&mut self, their_htlc_base_key: &PublicKey, their_delayed_payment_base_key: &PublicKey) {
+ /// Panics if commitment_transaction_number_obscure_factor doesn't fit in 48 bits
+ pub(super) fn set_basic_channel_info(&mut self, their_htlc_base_key: &PublicKey, their_delayed_payment_base_key: &PublicKey, their_to_self_delay: u16, funding_redeemscript: Script, channel_value_satoshis: u64, commitment_transaction_number_obscure_factor: u64) {
self.their_htlc_base_key = Some(their_htlc_base_key.clone());
self.their_delayed_payment_base_key = Some(their_delayed_payment_base_key.clone());
- }
-
- pub(super) fn set_their_to_self_delay(&mut self, their_to_self_delay: u16) {
self.their_to_self_delay = Some(their_to_self_delay);
+ self.funding_redeemscript = Some(funding_redeemscript);
+ self.channel_value_satoshis = Some(channel_value_satoshis);
+ assert!(commitment_transaction_number_obscure_factor < (1 << 48));
+ self.commitment_transaction_number_obscure_factor = commitment_transaction_number_obscure_factor;
}
pub(super) fn unset_funding_info(&mut self) {
writer.write_all(&self.their_htlc_base_key.as_ref().unwrap().serialize())?;
writer.write_all(&self.their_delayed_payment_base_key.as_ref().unwrap().serialize())?;
+ self.funding_redeemscript.as_ref().unwrap().write(writer)?;
+ self.channel_value_satoshis.unwrap().write(writer)?;
match self.their_cur_revocation_points {
Some((idx, pubkey, second_option)) => {
/// out-of-band the other node operator to coordinate with him if option is available to you.
/// In any-case, choice is up to the user.
pub fn get_latest_local_commitment_txn(&self) -> Vec<Transaction> {
+ log_trace!(self, "Getting signed latest local commitment transaction!");
if let &Some(ref local_tx) = &self.current_local_signed_commitment_tx {
let mut res = vec![local_tx.tx.clone()];
match self.key_storage {
let mut watch_outputs = Vec::new();
let mut spendable_outputs = Vec::new();
let mut htlc_updated = Vec::new();
- let mut bump_candidates = Vec::new();
+ let mut bump_candidates = HashSet::new();
for tx in txn_matched {
if tx.input.len() == 1 {
// Assuming our keys were not leaked (in which case we're screwed no matter what),
// Scan all input to verify is one of the outpoint spent is of interest for us
let mut claimed_outputs_material = Vec::new();
for inp in &tx.input {
- if let Some(ancestor_claimable_txid) = self.claimable_outpoints.get(&inp.previous_output) {
+ if let Some(first_claim_txid_height) = self.claimable_outpoints.get(&inp.previous_output) {
// If outpoint has claim request pending on it...
- if let Some(claim_material) = self.pending_claim_requests.get_mut(&ancestor_claimable_txid.0) {
+ if let Some(claim_material) = self.pending_claim_requests.get_mut(&first_claim_txid_height.0) {
//... we need to verify equality between transaction outpoints and claim request
// outpoints to know if transaction is the original claim or a bumped one issued
// by us.
}
}
- // If this is our transaction (or our counterparty spent all the outputs
- // before we could anyway), wait for ANTI_REORG_DELAY and clean the RBF
- // tracking map.
- if set_equality {
- let new_event = OnchainEvent::Claim { claim_request: ancestor_claimable_txid.0.clone() };
- match self.onchain_events_waiting_threshold_conf.entry(height + ANTI_REORG_DELAY - 1) {
- hash_map::Entry::Occupied(mut entry) => {
- if !entry.get().contains(&new_event) {
- entry.get_mut().push(new_event);
+ macro_rules! clean_claim_request_after_safety_delay {
+ () => {
+ let new_event = OnchainEvent::Claim { claim_request: first_claim_txid_height.0.clone() };
+ match self.onchain_events_waiting_threshold_conf.entry(height + ANTI_REORG_DELAY - 1) {
+ hash_map::Entry::Occupied(mut entry) => {
+ if !entry.get().contains(&new_event) {
+ entry.get_mut().push(new_event);
+ }
+ },
+ hash_map::Entry::Vacant(entry) => {
+ entry.insert(vec![new_event]);
}
- },
- hash_map::Entry::Vacant(entry) => {
- entry.insert(vec![new_event]);
}
}
+ }
+
+ // If this is our transaction (or our counterparty spent all the outputs
+ // before we could anyway with same inputs order than us), wait for
+ // ANTI_REORG_DELAY and clean the RBF tracking map.
+ if set_equality {
+ clean_claim_request_after_safety_delay!();
} else { // If false, generate new claim request with update outpoint set
for input in tx.input.iter() {
if let Some(input_material) = claim_material.per_input_material.remove(&input.previous_output) {
claimed_outputs_material.push((input.previous_output, input_material));
}
+ // If there are no outpoints left to claim in this request, drop it entirely after ANTI_REORG_DELAY.
+ if claim_material.per_input_material.is_empty() {
+ clean_claim_request_after_safety_delay!();
+ }
}
//TODO: recompute soonest_timelock to avoid wasting a bit on fees
- bump_candidates.push((ancestor_claimable_txid.0.clone(), claim_material.clone()));
+ bump_candidates.insert(first_claim_txid_height.0.clone());
}
break; //No need to iterate further, either tx is our or their
} else {
}
}
}
- for (ancestor_claim_txid, ref mut cached_claim_datas) in self.pending_claim_requests.iter_mut() {
+ for (first_claim_txid, ref mut cached_claim_datas) in self.pending_claim_requests.iter_mut() {
if cached_claim_datas.height_timer == height {
- bump_candidates.push((ancestor_claim_txid.clone(), cached_claim_datas.clone()));
+ bump_candidates.insert(first_claim_txid.clone());
}
}
- for &mut (_, ref mut cached_claim_datas) in bump_candidates.iter_mut() {
- if let Some((new_timer, new_feerate, bump_tx)) = self.bump_claim_tx(height, &cached_claim_datas, fee_estimator) {
- cached_claim_datas.height_timer = new_timer;
- cached_claim_datas.feerate_previous = new_feerate;
- broadcaster.broadcast_transaction(&bump_tx);
+ for first_claim_txid in bump_candidates.iter() {
+ if let Some((new_timer, new_feerate)) = {
+ if let Some(claim_material) = self.pending_claim_requests.get(first_claim_txid) {
+ if let Some((new_timer, new_feerate, bump_tx)) = self.bump_claim_tx(height, &claim_material, fee_estimator) {
+ broadcaster.broadcast_transaction(&bump_tx);
+ Some((new_timer, new_feerate))
+ } else { None }
+ } else { unreachable!(); }
+ } {
+ if let Some(claim_material) = self.pending_claim_requests.get_mut(first_claim_txid) {
+ claim_material.height_timer = new_timer;
+ claim_material.feerate_previous = new_feerate;
+ } else { unreachable!(); }
}
}
- for (ancestor_claim_txid, cached_claim_datas) in bump_candidates.drain(..) {
- self.pending_claim_requests.insert(ancestor_claim_txid, cached_claim_datas);
- }
self.last_block_hash = block_hash.clone();
(watch_outputs, spendable_outputs, htlc_updated)
}
let their_htlc_base_key = Some(Readable::read(reader)?);
let their_delayed_payment_base_key = Some(Readable::read(reader)?);
+ let funding_redeemscript = Some(Readable::read(reader)?);
+ let channel_value_satoshis = Some(Readable::read(reader)?);
let their_cur_revocation_points = {
let first_idx = <U48 as Readable<R>>::read(reader)?.0;
key_storage,
their_htlc_base_key,
their_delayed_payment_base_key,
+ funding_redeemscript,
+ channel_value_satoshis,
their_cur_revocation_points,
our_to_self_delay,
// Prune with one old state and a local commitment tx holding a few overlaps with the
// old state.
let mut monitor = ChannelMonitor::new(&SecretKey::from_slice(&[42; 32]).unwrap(), &SecretKey::from_slice(&[43; 32]).unwrap(), &SecretKey::from_slice(&[44; 32]).unwrap(), &SecretKey::from_slice(&[44; 32]).unwrap(), &PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[45; 32]).unwrap()), 0, Script::new(), logger.clone());
- monitor.set_their_to_self_delay(10);
+ monitor.their_to_self_delay = Some(10);
monitor.provide_latest_local_commitment_tx_info(dummy_tx.clone(), dummy_keys!(), 0, preimages_to_local_htlcs!(preimages[0..10]));
monitor.provide_latest_remote_commitment_tx_info(&dummy_tx, preimages_slice_to_htlc_outputs!(preimages[5..15]), 281474976710655, dummy_key);