TemporaryFailure,
/// Used to indicate no further channel monitor updates will be allowed (eg we've moved on to a
/// different watchtower and cannot update with all watchtowers that were previously informed
- /// of this channel). This will force-close the channel in question.
+ /// of this channel). This will force-close the channel in question (which will generate one
+ /// final ChannelMonitorUpdate which must be delivered to at least one ChannelMonitor copy).
///
- /// Should also be used to indicate a failure to update the local copy of the channel monitor.
+ /// Should also be used to indicate a failure to update the local persisted copy of the channel
+ /// monitor.
PermanentFailure,
}
/// events to it, while also taking any add/update_monitor events and passing them to some remote
/// server(s).
///
+/// In general, you must always have at least one local copy in memory, which must never fail to
+/// update (as it is responsible for broadcasting the latest state in case the channel is closed),
+/// and then persist it to various on-disk locations. If, for some reason, the in-memory copy fails
+/// to update (eg out-of-memory or some other condition), you must immediately shut down without
+/// taking any further action such as writing the current state to disk. This should likely be
+/// accomplished via panic!() or abort().
+///
/// Note that any updates to a channel's monitor *must* be applied to each instance of the
/// channel's monitor everywhere (including remote watchtowers) *before* this function returns. If
/// an update occurs and a remote watchtower is left with old state, it may broadcast transactions
match monitors.get_mut(&key) {
Some(orig_monitor) => {
log_trace!(self, "Updating Channel Monitor for channel {}", log_funding_info!(orig_monitor.key_storage));
- orig_monitor.update_monitor(update)
+ orig_monitor.update_monitor(update, &self.broadcaster)
},
None => Err(MonitorUpdateError("No such monitor registered"))
}
#[derive(Clone, PartialEq)]
pub(crate) enum InputMaterial {
Revoked {
- script: Script,
+ witness_script: Script,
pubkey: Option<PublicKey>,
key: SecretKey,
is_htlc: bool,
amount: u64,
},
RemoteHTLC {
- script: Script,
+ witness_script: Script,
key: SecretKey,
preimage: Option<PaymentPreimage>,
amount: u64,
locktime: u32,
},
LocalHTLC {
- script: Script,
+ witness_script: Script,
sigs: (Signature, Signature),
preimage: Option<PaymentPreimage>,
amount: u64,
impl Writeable for InputMaterial {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
match self {
- &InputMaterial::Revoked { ref script, ref pubkey, ref key, ref is_htlc, ref amount} => {
+ &InputMaterial::Revoked { ref witness_script, ref pubkey, ref key, ref is_htlc, ref amount} => {
writer.write_all(&[0; 1])?;
- script.write(writer)?;
+ witness_script.write(writer)?;
pubkey.write(writer)?;
writer.write_all(&key[..])?;
is_htlc.write(writer)?;
writer.write_all(&byte_utils::be64_to_array(*amount))?;
},
- &InputMaterial::RemoteHTLC { ref script, ref key, ref preimage, ref amount, ref locktime } => {
+ &InputMaterial::RemoteHTLC { ref witness_script, ref key, ref preimage, ref amount, ref locktime } => {
writer.write_all(&[1; 1])?;
- script.write(writer)?;
+ witness_script.write(writer)?;
key.write(writer)?;
preimage.write(writer)?;
writer.write_all(&byte_utils::be64_to_array(*amount))?;
writer.write_all(&byte_utils::be32_to_array(*locktime))?;
},
- &InputMaterial::LocalHTLC { ref script, ref sigs, ref preimage, ref amount } => {
+ &InputMaterial::LocalHTLC { ref witness_script, ref sigs, ref preimage, ref amount } => {
writer.write_all(&[2; 1])?;
- script.write(writer)?;
+ witness_script.write(writer)?;
sigs.0.write(writer)?;
sigs.1.write(writer)?;
preimage.write(writer)?;
fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let input_material = match <u8 as Readable>::read(reader)? {
0 => {
- let script = Readable::read(reader)?;
+ let witness_script = Readable::read(reader)?;
let pubkey = Readable::read(reader)?;
let key = Readable::read(reader)?;
let is_htlc = Readable::read(reader)?;
let amount = Readable::read(reader)?;
InputMaterial::Revoked {
- script,
+ witness_script,
pubkey,
key,
is_htlc,
}
},
1 => {
- let script = Readable::read(reader)?;
+ let witness_script = Readable::read(reader)?;
let key = Readable::read(reader)?;
let preimage = Readable::read(reader)?;
let amount = Readable::read(reader)?;
let locktime = Readable::read(reader)?;
InputMaterial::RemoteHTLC {
- script,
+ witness_script,
key,
preimage,
amount,
}
},
2 => {
- let script = Readable::read(reader)?;
+ let witness_script = Readable::read(reader)?;
let their_sig = Readable::read(reader)?;
let our_sig = Readable::read(reader)?;
let preimage = Readable::read(reader)?;
let amount = Readable::read(reader)?;
InputMaterial::LocalHTLC {
- script,
+ witness_script,
sigs: (their_sig, our_sig),
preimage,
amount
}
}
+/// ClaimRequest is a descriptor structure to communicate between detection
+/// and reaction module. They are generated by ChannelMonitor while parsing
+/// onchain txn leaked from a channel and handed over to OnchainTxHandler which
+/// is responsible for opportunistic aggregation, selecting and enforcing
+/// bumping logic, building and signing transactions.
+pub(crate) struct ClaimRequest {
+ // Block height before which claiming is exclusive to one party,
+ // after reaching it, claiming may be contentious.
+ pub(crate) absolute_timelock: u32,
+ // Timeout tx must have nLocktime set which means aggregating multiple
+ // ones must take the higher nLocktime among them to satisfy all of them.
+ // Sadly it has few pitfalls, a) it takes longuer to get fund back b) CLTV_DELTA
+ // of a sooner-HTLC could be swallowed by the highest nLocktime of the HTLC set.
+ // Do simplify we mark them as non-aggregable.
+ pub(crate) aggregable: bool,
+ // Basic bitcoin outpoint (txid, vout)
+ pub(crate) outpoint: BitcoinOutPoint,
+ // Following outpoint type, set of data needed to generate transaction digest
+ // and satisfy witness program.
+ pub(crate) witness_data: InputMaterial
+}
+
/// Upon discovering of some classes of onchain tx by ChannelMonitor, we may have to take actions on it
/// once they mature to enough confirmations (ANTI_REORG_DELAY)
#[derive(Clone, PartialEq)]
RescueRemoteCommitmentTXInfo {
their_current_per_commitment_point: PublicKey,
},
+ /// Used to indicate that the no future updates will occur, and likely that the latest local
+ /// commitment transaction(s) should be broadcast, as the channel has been force-closed.
+ ChannelForceClosed {
+ /// If set to false, we shouldn't broadcast the latest local commitment transaction as we
+ /// think we've fallen behind!
+ should_broadcast: bool,
+ },
}
impl Writeable for ChannelMonitorUpdateStep {
4u8.write(w)?;
their_current_per_commitment_point.write(w)?;
},
+ &ChannelMonitorUpdateStep::ChannelForceClosed { ref should_broadcast } => {
+ 5u8.write(w)?;
+ should_broadcast.write(w)?;
+ },
}
Ok(())
}
their_current_per_commitment_point: Readable::read(r)?,
})
},
+ 5u8 => {
+ Ok(ChannelMonitorUpdateStep::ChannelForceClosed {
+ should_broadcast: Readable::read(r)?
+ })
+ },
_ => Err(DecodeError::InvalidValue),
}
}
for ev in events.iter() {
match *ev {
OnchainEvent::HTLCUpdate { ref htlc_update } => {
- writer.write_all(&[1; 1])?;
+ 0u8.write(writer)?;
htlc_update.0.write(writer)?;
htlc_update.1.write(writer)?;
},
self.payment_preimages.insert(payment_hash.clone(), payment_preimage.clone());
}
+ pub(super) fn broadcast_latest_local_commitment_txn<B: Deref>(&mut self, broadcaster: &B)
+ where B::Target: BroadcasterInterface,
+ {
+ for tx in self.get_latest_local_commitment_txn().iter() {
+ broadcaster.broadcast_transaction(tx);
+ }
+ }
+
/// Used in Channel to cheat wrt the update_ids since it plays games, will be removed soon!
pub(super) fn update_monitor_ooo(&mut self, mut updates: ChannelMonitorUpdate) -> Result<(), MonitorUpdateError> {
for update in updates.updates.drain(..) {
self.provide_secret(idx, secret)?,
ChannelMonitorUpdateStep::RescueRemoteCommitmentTXInfo { their_current_per_commitment_point } =>
self.provide_rescue_remote_commitment_tx_info(their_current_per_commitment_point),
+ ChannelMonitorUpdateStep::ChannelForceClosed { .. } => {},
}
}
self.latest_update_id = updates.update_id;
/// itself.
///
/// panics if the given update is not the next update by update_id.
- pub fn update_monitor(&mut self, mut updates: ChannelMonitorUpdate) -> Result<(), MonitorUpdateError> {
+ pub fn update_monitor<B: Deref>(&mut self, mut updates: ChannelMonitorUpdate, broadcaster: &B) -> Result<(), MonitorUpdateError>
+ where B::Target: BroadcasterInterface,
+ {
if self.latest_update_id + 1 != updates.update_id {
panic!("Attempted to apply ChannelMonitorUpdates out of order, check the update_id before passing an update to update_monitor!");
}
self.provide_secret(idx, secret)?,
ChannelMonitorUpdateStep::RescueRemoteCommitmentTXInfo { their_current_per_commitment_point } =>
self.provide_rescue_remote_commitment_tx_info(their_current_per_commitment_point),
+ ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast } => {
+ if should_broadcast {
+ self.broadcast_latest_local_commitment_txn(broadcaster);
+ } else {
+ log_error!(self, "You have a toxic local commitment transaction avaible in channel monitor, read comment in ChannelMonitor::get_latest_local_commitment_txn to be informed of manual action to take");
+ }
+ }
}
}
self.latest_update_id = updates.update_id;
/// 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<(u32, bool, BitcoinOutPoint, InputMaterial)>, (Sha256dHash, Vec<TxOut>), Vec<SpendableOutputDescriptor>) {
+ fn check_spend_remote_transaction(&mut self, tx: &Transaction, height: u32) -> (Vec<ClaimRequest>, (Sha256dHash, Vec<TxOut>), Vec<SpendableOutputDescriptor>) {
// 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 claimable_outpoints = Vec::new();
// First, process non-htlc outputs (to_local & to_remote)
for (idx, outp) in tx.output.iter().enumerate() {
if outp.script_pubkey == revokeable_p2wsh {
- claimable_outpoints.push((height + self.our_to_self_delay as u32, true, BitcoinOutPoint { txid: commitment_txid, vout: idx as u32 }, InputMaterial::Revoked { script: revokeable_redeemscript.clone(), pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: false, amount: outp.value }));
+ let witness_data = InputMaterial::Revoked { witness_script: revokeable_redeemscript.clone(), pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: false, amount: outp.value };
+ claimable_outpoints.push(ClaimRequest { absolute_timelock: height + self.our_to_self_delay as u32, aggregable: true, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: idx as u32 }, witness_data});
} 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 },
tx.output[transaction_output_index as usize].script_pubkey != expected_script.to_v0_p2wsh() {
return (claimable_outpoints, (commitment_txid, watch_outputs), spendable_outputs); // Corrupted per_commitment_data, fuck this user
}
- claimable_outpoints.push((htlc.cltv_expiry, true, BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, InputMaterial::Revoked { script: expected_script, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: true, amount: tx.output[transaction_output_index as usize].value }));
+ let witness_data = InputMaterial::Revoked { witness_script: expected_script, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: true, amount: tx.output[transaction_output_index as usize].value };
+ claimable_outpoints.push(ClaimRequest { absolute_timelock: htlc.cltv_expiry, aggregable: true, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, witness_data });
}
}
}
let preimage = if htlc.offered { if let Some(p) = self.payment_preimages.get(&htlc.payment_hash) { Some(*p) } else { None } } else { None };
let aggregable = if !htlc.offered { false } else { true };
if preimage.is_some() || !htlc.offered {
- claimable_outpoints.push((htlc.cltv_expiry, aggregable, BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, InputMaterial::RemoteHTLC { script: expected_script, key: htlc_privkey, preimage, amount: htlc.amount_msat / 1000, locktime: htlc.cltv_expiry }));
+ let witness_data = InputMaterial::RemoteHTLC { witness_script: expected_script, key: htlc_privkey, preimage, amount: htlc.amount_msat / 1000, locktime: htlc.cltv_expiry };
+ claimable_outpoints.push(ClaimRequest { absolute_timelock: htlc.cltv_expiry, aggregable, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, witness_data });
}
}
}
}
/// Attempts to claim a remote HTLC-Success/HTLC-Timeout's outputs using the revocation key
- fn check_spend_remote_htlc(&mut self, tx: &Transaction, commitment_number: u64, height: u32) -> Vec<(u32, bool, BitcoinOutPoint, InputMaterial)> {
- //TODO: send back new outputs to guarantee pending_claim_request consistency
+ fn check_spend_remote_htlc(&mut self, tx: &Transaction, commitment_number: u64, height: u32) -> (Vec<ClaimRequest>, Option<(Sha256dHash, Vec<TxOut>)>) {
+ let htlc_txid = tx.txid();
if tx.input.len() != 1 || tx.output.len() != 1 || tx.input[0].witness.len() != 5 {
- return Vec::new()
+ return (Vec::new(), None)
}
macro_rules! ignore_error {
( $thing : expr ) => {
match $thing {
Ok(a) => a,
- Err(_) => return Vec::new()
+ Err(_) => return (Vec::new(), None)
}
};
}
- let secret = if let Some(secret) = self.get_secret(commitment_number) { secret } else { return Vec::new(); };
+ let secret = if let Some(secret) = self.get_secret(commitment_number) { secret } else { return (Vec::new(), None); };
let per_commitment_key = ignore_error!(SecretKey::from_slice(&secret));
let per_commitment_point = PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key);
let (revocation_pubkey, revocation_key) = match self.key_storage {
Storage::Watchtower { .. } => { unimplemented!() }
};
let delayed_key = match self.their_delayed_payment_base_key {
- None => return Vec::new(),
+ None => return (Vec::new(), None),
Some(their_delayed_payment_base_key) => ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, &per_commitment_point, &their_delayed_payment_base_key)),
};
let redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, self.our_to_self_delay, &delayed_key);
- let htlc_txid = tx.txid(); //TODO: This is gonna be a performance bottleneck for watchtowers!
log_trace!(self, "Remote HTLC broadcast {}:{}", htlc_txid, 0);
- let claimable_outpoints = vec!((height + self.our_to_self_delay as u32, true, BitcoinOutPoint { txid: htlc_txid, vout: 0}, InputMaterial::Revoked { script: redeemscript, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: false, amount: tx.output[0].value }));
- claimable_outpoints
+ let witness_data = InputMaterial::Revoked { witness_script: redeemscript, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: false, amount: tx.output[0].value };
+ let claimable_outpoints = vec!(ClaimRequest { absolute_timelock: height + self.our_to_self_delay as u32, aggregable: true, outpoint: BitcoinOutPoint { txid: htlc_txid, vout: 0}, witness_data });
+ (claimable_outpoints, Some((htlc_txid, tx.output.clone())))
}
fn broadcast_by_local_state(&self, local_tx: &LocalSignedTx, delayed_payment_base_key: &SecretKey) -> (Vec<Transaction>, Vec<SpendableOutputDescriptor>, Vec<TxOut>) {
add_dynamic_output!(htlc_timeout_tx, 0);
let mut per_input_material = HashMap::with_capacity(1);
- per_input_material.insert(htlc_timeout_tx.input[0].previous_output, InputMaterial::LocalHTLC { script: htlc_script, sigs: (*their_sig, our_sig), preimage: None, amount: htlc.amount_msat / 1000});
+ per_input_material.insert(htlc_timeout_tx.input[0].previous_output, InputMaterial::LocalHTLC { witness_script: htlc_script, sigs: (*their_sig, our_sig), preimage: None, amount: htlc.amount_msat / 1000});
//TODO: with option_simplified_commitment track outpoint too
log_trace!(self, "Outpoint {}:{} is being being claimed", htlc_timeout_tx.input[0].previous_output.vout, htlc_timeout_tx.input[0].previous_output.txid);
res.push(htlc_timeout_tx);
add_dynamic_output!(htlc_success_tx, 0);
let mut per_input_material = HashMap::with_capacity(1);
- per_input_material.insert(htlc_success_tx.input[0].previous_output, InputMaterial::LocalHTLC { script: htlc_script, sigs: (*their_sig, our_sig), preimage: Some(*payment_preimage), amount: htlc.amount_msat / 1000});
+ per_input_material.insert(htlc_success_tx.input[0].previous_output, InputMaterial::LocalHTLC { witness_script: htlc_script, sigs: (*their_sig, our_sig), preimage: Some(*payment_preimage), amount: htlc.amount_msat / 1000});
//TODO: with option_simplified_commitment track outpoint too
log_trace!(self, "Outpoint {}:{} is being being claimed", htlc_success_tx.input[0].previous_output.vout, htlc_success_tx.input[0].previous_output.txid);
res.push(htlc_success_tx);
/// 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(&mut self) -> Vec<Transaction> {
+ // TODO: We should likely move all of the logic in here into OnChainTxHandler and unify it
+ // to ensure add_local_sig is only ever called once no matter what. This likely includes
+ // tracking state and panic!()ing if we get an update after force-closure/local-tx signing.
log_trace!(self, "Getting signed latest local commitment transaction!");
if let &mut Some(ref mut local_tx) = &mut self.current_local_signed_commitment_tx {
match self.key_storage {
watch_outputs.push(new_outputs);
}
}
- claimable_outpoints.push(new_outpoints);
+ claimable_outpoints.append(&mut new_outpoints);
}
if !funding_txo.is_none() && claimable_outpoints.is_empty() {
if let Some(spendable_output) = self.check_spend_closing_transaction(&tx) {
}
} else {
if let Some(&(commitment_number, _)) = self.remote_commitment_txn_on_chain.get(&prevout.txid) {
- let mut new_outpoints = self.check_spend_remote_htlc(&tx, commitment_number, height);
- claimable_outpoints.push(new_outpoints);
+ let (mut new_outpoints, new_outputs_option) = self.check_spend_remote_htlc(&tx, commitment_number, height);
+ claimable_outpoints.append(&mut new_outpoints);
+ if let Some(new_outputs) = new_outputs_option {
+ watch_outputs.push(new_outputs);
+ }
}
}
}
}
}
}
- let mut spendable_output = self.onchain_tx_handler.block_connected(txn_matched, claimable_outpoints, height, broadcaster, &*fee_estimator);
+ let mut spendable_output = self.onchain_tx_handler.block_connected(txn_matched, claimable_outpoints, height, &*broadcaster, &*fee_estimator);
spendable_outputs.append(&mut spendable_output);
self.last_block_hash = block_hash.clone();
if let Some((source, payment_hash)) = payment_data {
let mut payment_preimage = PaymentPreimage([0; 32]);
if accepted_preimage_claim {
- payment_preimage.0.copy_from_slice(&input.witness[3]);
- self.pending_htlcs_updated.push(HTLCUpdate {
- source,
- payment_preimage: Some(payment_preimage),
- payment_hash
- });
+ if !self.pending_htlcs_updated.iter().any(|update| update.source == source) {
+ payment_preimage.0.copy_from_slice(&input.witness[3]);
+ self.pending_htlcs_updated.push(HTLCUpdate {
+ source,
+ payment_preimage: Some(payment_preimage),
+ payment_hash
+ });
+ }
} else if offered_preimage_claim {
- payment_preimage.0.copy_from_slice(&input.witness[1]);
- self.pending_htlcs_updated.push(HTLCUpdate {
- source,
- payment_preimage: Some(payment_preimage),
- payment_hash
- });
+ if !self.pending_htlcs_updated.iter().any(|update| update.source == source) {
+ payment_preimage.0.copy_from_slice(&input.witness[1]);
+ self.pending_htlcs_updated.push(HTLCUpdate {
+ source,
+ payment_preimage: Some(payment_preimage),
+ payment_hash
+ });
+ }
} else {
log_info!(self, "Failing HTLC with payment_hash {} timeout by a spend tx, waiting for confirmation (at height{})", log_bytes!(payment_hash.0), height + ANTI_REORG_DELAY - 1);
match self.onchain_events_waiting_threshold_conf.entry(height + ANTI_REORG_DELAY - 1) {