use bitcoin_hashes::sha256d::Hash as Sha256dHash;
-use secp256k1::Secp256k1;
+use secp256k1::{Secp256k1, Signature};
use secp256k1;
use ln::msgs::DecodeError;
}
}
+impl Readable for Option<Vec<Option<(usize, Signature)>>> {
+ fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ match Readable::read(reader)? {
+ 0u8 => Ok(None),
+ 1u8 => {
+ let vlen: u64 = Readable::read(reader)?;
+ let mut ret = Vec::with_capacity(cmp::min(vlen as usize, MAX_ALLOC_SIZE / ::std::mem::size_of::<Option<(usize, Signature)>>()));
+ for _ in 0..vlen {
+ ret.push(match Readable::read(reader)? {
+ 0u8 => None,
+ 1u8 => Some((<u64 as Readable>::read(reader)? as usize, Readable::read(reader)?)),
+ _ => return Err(DecodeError::InvalidValue)
+ });
+ }
+ Ok(Some(ret))
+ },
+ _ => Err(DecodeError::InvalidValue),
+ }
+ }
+}
+
+impl Writeable for Option<Vec<Option<(usize, Signature)>>> {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
+ match self {
+ &Some(ref vec) => {
+ 1u8.write(writer)?;
+ (vec.len() as u64).write(writer)?;
+ for opt in vec.iter() {
+ match opt {
+ &Some((ref idx, ref sig)) => {
+ 1u8.write(writer)?;
+ (*idx as u64).write(writer)?;
+ sig.write(writer)?;
+ },
+ &None => 0u8.write(writer)?,
+ }
+ }
+ },
+ &None => 0u8.write(writer)?,
+ }
+ Ok(())
+ }
+}
+
/// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and
/// do RBF bumping if possible.
pub struct OnchainTxHandler<ChanSigner: ChannelKeys> {
destination_script: Script,
- funding_redeemscript: Script,
local_commitment: Option<LocalCommitmentTransaction>,
+ // local_htlc_sigs and prev_local_htlc_sigs are in the order as they appear in the commitment
+ // transaction outputs (hence the Option<>s inside the Vec). The first usize is the index in
+ // the set of HTLCs in the LocalCommitmentTransaction (including those which do not appear in
+ // the commitment transaction).
+ local_htlc_sigs: Option<Vec<Option<(usize, Signature)>>>,
prev_local_commitment: Option<LocalCommitmentTransaction>,
+ prev_local_htlc_sigs: Option<Vec<Option<(usize, Signature)>>>,
local_csv: u16,
key_storage: ChanSigner,
impl<ChanSigner: ChannelKeys + Writeable> OnchainTxHandler<ChanSigner> {
pub(crate) fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
self.destination_script.write(writer)?;
- self.funding_redeemscript.write(writer)?;
self.local_commitment.write(writer)?;
+ self.local_htlc_sigs.write(writer)?;
self.prev_local_commitment.write(writer)?;
+ self.prev_local_htlc_sigs.write(writer)?;
self.local_csv.write(writer)?;
impl<ChanSigner: ChannelKeys + Readable> ReadableArgs<Arc<Logger>> for OnchainTxHandler<ChanSigner> {
fn read<R: ::std::io::Read>(reader: &mut R, logger: Arc<Logger>) -> Result<Self, DecodeError> {
let destination_script = Readable::read(reader)?;
- let funding_redeemscript = Readable::read(reader)?;
let local_commitment = Readable::read(reader)?;
+ let local_htlc_sigs = Readable::read(reader)?;
let prev_local_commitment = Readable::read(reader)?;
+ let prev_local_htlc_sigs = Readable::read(reader)?;
let local_csv = Readable::read(reader)?;
Ok(OnchainTxHandler {
destination_script,
- funding_redeemscript,
local_commitment,
+ local_htlc_sigs,
prev_local_commitment,
+ prev_local_htlc_sigs,
local_csv,
key_storage,
claimable_outpoints,
}
impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
- pub(super) fn new(destination_script: Script, keys: ChanSigner, funding_redeemscript: Script, local_csv: u16, logger: Arc<Logger>) -> Self {
+ pub(super) fn new(destination_script: Script, keys: ChanSigner, local_csv: u16, logger: Arc<Logger>) -> Self {
let key_storage = keys;
OnchainTxHandler {
destination_script,
- funding_redeemscript,
local_commitment: None,
+ local_htlc_sigs: None,
prev_local_commitment: None,
+ prev_local_htlc_sigs: None,
local_csv,
key_storage,
pending_claim_requests: HashMap::new(),
for (_, (outp, per_outp_material)) in cached_claim_datas.per_input_material.iter().enumerate() {
match per_outp_material {
&InputMaterial::LocalHTLC { ref preimage, ref amount } => {
- let mut htlc_tx = None;
- if let Some(ref mut local_commitment) = self.local_commitment {
- if local_commitment.txid() == outp.txid {
- self.key_storage.sign_htlc_transaction(local_commitment, outp.vout, *preimage, self.local_csv, &self.secp_ctx);
- htlc_tx = local_commitment.htlc_with_valid_witness(outp.vout).clone();
- }
- }
- if let Some(ref mut prev_local_commitment) = self.prev_local_commitment {
- if prev_local_commitment.txid() == outp.txid {
- self.key_storage.sign_htlc_transaction(prev_local_commitment, outp.vout, *preimage, self.local_csv, &self.secp_ctx);
- htlc_tx = prev_local_commitment.htlc_with_valid_witness(outp.vout).clone();
- }
- }
+ let htlc_tx = self.get_fully_signed_htlc_tx(outp, preimage);
if let Some(htlc_tx) = htlc_tx {
let feerate = (amount - htlc_tx.output[0].value) * 1000 / htlc_tx.get_weight() as u64;
// Timer set to $NEVER given we can't bump tx without anchor outputs
}
return None;
},
- &InputMaterial::Funding { ref channel_value } => {
- if let Some(ref mut local_commitment) = self.local_commitment {
- self.key_storage.sign_local_commitment(local_commitment, &self.funding_redeemscript, *channel_value, &self.secp_ctx);
- let signed_tx = local_commitment.with_valid_witness().clone();
- let mut amt_outputs = 0;
- for outp in signed_tx.output.iter() {
- amt_outputs += outp.value;
- }
- let feerate = (channel_value - amt_outputs) * 1000 / signed_tx.get_weight() as u64;
- // Timer set to $NEVER given we can't bump tx without anchor outputs
- log_trace!(self, "Going to broadcast Local Transaction {} claiming funding output {} from {}...", signed_tx.txid(), outp.vout, outp.txid);
- return Some((None, feerate, signed_tx));
- }
+ &InputMaterial::Funding { ref funding_redeemscript } => {
+ let signed_tx = self.get_fully_signed_local_tx(funding_redeemscript).unwrap();
+ // Timer set to $NEVER given we can't bump tx without anchor outputs
+ log_trace!(self, "Going to broadcast Local Transaction {} claiming funding output {} from {}...", signed_tx.txid(), outp.vout, outp.txid);
+ return Some((None, self.local_commitment.as_ref().unwrap().feerate_per_kw, signed_tx));
}
_ => unreachable!()
}
// Generate claim transactions and track them to bump if necessary at
// height timer expiration (i.e in how many blocks we're going to take action).
- for claim in new_claims {
- let mut claim_material = ClaimTxBumpMaterial { height_timer: None, feerate_previous: 0, soonest_timelock: claim.0, per_input_material: claim.1.clone() };
+ for (soonest_timelock, claim) in new_claims.drain(..) {
+ let mut claim_material = ClaimTxBumpMaterial { height_timer: None, feerate_previous: 0, soonest_timelock, per_input_material: claim };
if let Some((new_timer, new_feerate, tx)) = self.generate_claim_tx(height, &claim_material, &*fee_estimator) {
claim_material.height_timer = new_timer;
claim_material.feerate_previous = new_feerate;
let txid = tx.txid();
- self.pending_claim_requests.insert(txid, claim_material);
- for k in claim.1.keys() {
+ for k in claim_material.per_input_material.keys() {
log_trace!(self, "Registering claiming request for {}:{}", k.txid, k.vout);
self.claimable_outpoints.insert(k.clone(), (txid, height));
}
+ self.pending_claim_requests.insert(txid, claim_material);
log_trace!(self, "Broadcast onchain {}", log_tx!(tx));
broadcaster.broadcast_transaction(&tx);
}
// Build, bump and rebroadcast tx accordingly
log_trace!(self, "Bumping {} candidates", bump_candidates.len());
- let mut pending_claim_updates = Vec::with_capacity(bump_candidates.len());
for (first_claim_txid, claim_material) in bump_candidates.iter() {
if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(height, &claim_material, &*fee_estimator) {
log_trace!(self, "Broadcast onchain {}", log_tx!(bump_tx));
broadcaster.broadcast_transaction(&bump_tx);
- pending_claim_updates.push((*first_claim_txid, new_timer, new_feerate));
- }
- }
- for updates in pending_claim_updates {
- if let Some(claim_material) = self.pending_claim_requests.get_mut(&updates.0) {
- claim_material.height_timer = updates.1;
- claim_material.feerate_previous = updates.2;
+ 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;
+ }
}
}
}
// HTLC-timeout or channel force-closure), don't allow any further update of local
// commitment transaction view to avoid delivery of revocation secret to counterparty
// for the aformentionned signed transaction.
- if let Some(ref local_commitment) = self.local_commitment {
- if local_commitment.has_local_sig() { return Err(()) }
+ if self.local_htlc_sigs.is_some() || self.prev_local_htlc_sigs.is_some() {
+ return Err(());
}
self.prev_local_commitment = self.local_commitment.take();
self.local_commitment = Some(tx);
Ok(())
}
- pub(super) fn get_fully_signed_local_tx(&mut self, channel_value_satoshis: u64) -> Option<Transaction> {
+ fn sign_latest_local_htlcs(&mut self) {
+ if let Some(ref local_commitment) = self.local_commitment {
+ if let Ok(sigs) = self.key_storage.sign_local_commitment_htlc_transactions(local_commitment, self.local_csv, &self.secp_ctx) {
+ self.local_htlc_sigs = Some(Vec::new());
+ let ret = self.local_htlc_sigs.as_mut().unwrap();
+ for (htlc_idx, (local_sig, &(ref htlc, _))) in sigs.iter().zip(local_commitment.per_htlc.iter()).enumerate() {
+ if let Some(tx_idx) = htlc.transaction_output_index {
+ if ret.len() <= tx_idx as usize { ret.resize(tx_idx as usize + 1, None); }
+ ret[tx_idx as usize] = Some((htlc_idx, local_sig.expect("Did not receive a signature for a non-dust HTLC")));
+ } else {
+ assert!(local_sig.is_none(), "Received a signature for a dust HTLC");
+ }
+ }
+ }
+ }
+ }
+ fn sign_prev_local_htlcs(&mut self) {
+ if let Some(ref local_commitment) = self.prev_local_commitment {
+ if let Ok(sigs) = self.key_storage.sign_local_commitment_htlc_transactions(local_commitment, self.local_csv, &self.secp_ctx) {
+ self.prev_local_htlc_sigs = Some(Vec::new());
+ let ret = self.prev_local_htlc_sigs.as_mut().unwrap();
+ for (htlc_idx, (local_sig, &(ref htlc, _))) in sigs.iter().zip(local_commitment.per_htlc.iter()).enumerate() {
+ if let Some(tx_idx) = htlc.transaction_output_index {
+ if ret.len() <= tx_idx as usize { ret.resize(tx_idx as usize + 1, None); }
+ ret[tx_idx as usize] = Some((htlc_idx, local_sig.expect("Did not receive a signature for a non-dust HTLC")));
+ } else {
+ assert!(local_sig.is_none(), "Received a signature for a dust HTLC");
+ }
+ }
+ }
+ }
+ }
+
+ //TODO: getting lastest local transactions should be infaillible and result in us "force-closing the channel", but we may
+ // have empty local commitment transaction if a ChannelMonitor is asked to force-close just after Channel::get_outbound_funding_created,
+ // before providing a initial commitment transaction. For outbound channel, init ChannelMonitor at Channel::funding_signed, there is nothing
+ // to monitor before.
+ pub(super) fn get_fully_signed_local_tx(&mut self, funding_redeemscript: &Script) -> Option<Transaction> {
if let Some(ref mut local_commitment) = self.local_commitment {
- self.key_storage.sign_local_commitment(local_commitment, &self.funding_redeemscript, channel_value_satoshis, &self.secp_ctx);
- return Some(local_commitment.with_valid_witness().clone());
+ match self.key_storage.sign_local_commitment(local_commitment, &self.secp_ctx) {
+ Ok(sig) => Some(local_commitment.add_local_sig(funding_redeemscript, sig)),
+ Err(_) => return None,
+ }
+ } else {
+ None
}
- None
}
#[cfg(test)]
- pub(super) fn get_fully_signed_copy_local_tx(&mut self, channel_value_satoshis: u64) -> Option<Transaction> {
+ pub(super) fn get_fully_signed_copy_local_tx(&mut self, funding_redeemscript: &Script) -> Option<Transaction> {
if let Some(ref mut local_commitment) = self.local_commitment {
- let mut local_commitment = local_commitment.clone();
- self.key_storage.unsafe_sign_local_commitment(&mut local_commitment, &self.funding_redeemscript, channel_value_satoshis, &self.secp_ctx);
- return Some(local_commitment.with_valid_witness().clone());
+ let local_commitment = local_commitment.clone();
+ match self.key_storage.sign_local_commitment(&local_commitment, &self.secp_ctx) {
+ Ok(sig) => Some(local_commitment.add_local_sig(funding_redeemscript, sig)),
+ Err(_) => return None,
+ }
+ } else {
+ None
}
- None
}
- pub(super) fn get_fully_signed_htlc_tx(&mut self, txid: Sha256dHash, htlc_index: u32, preimage: Option<PaymentPreimage>) -> Option<Transaction> {
- //TODO: store preimage in OnchainTxHandler
- if let Some(ref mut local_commitment) = self.local_commitment {
- if local_commitment.txid() == txid {
- self.key_storage.sign_htlc_transaction(local_commitment, htlc_index, preimage, self.local_csv, &self.secp_ctx);
- return local_commitment.htlc_with_valid_witness(htlc_index).clone();
+ pub(super) fn get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>) -> Option<Transaction> {
+ let mut htlc_tx = None;
+ if self.local_commitment.is_some() {
+ let commitment_txid = self.local_commitment.as_ref().unwrap().txid();
+ if commitment_txid == outp.txid {
+ self.sign_latest_local_htlcs();
+ if let &Some(ref htlc_sigs) = &self.local_htlc_sigs {
+ let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap();
+ htlc_tx = Some(self.local_commitment.as_ref().unwrap()
+ .get_signed_htlc_tx(*htlc_idx, htlc_sig, preimage, self.local_csv));
+ }
}
}
- None
+ if self.prev_local_commitment.is_some() {
+ let commitment_txid = self.prev_local_commitment.as_ref().unwrap().txid();
+ if commitment_txid == outp.txid {
+ self.sign_prev_local_htlcs();
+ if let &Some(ref htlc_sigs) = &self.prev_local_htlc_sigs {
+ let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap();
+ htlc_tx = Some(self.prev_local_commitment.as_ref().unwrap()
+ .get_signed_htlc_tx(*htlc_idx, htlc_sig, preimage, self.local_csv));
+ }
+ }
+ }
+ htlc_tx
+ }
+
+ #[cfg(test)]
+ pub(super) fn unsafe_get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>) -> Option<Transaction> {
+ let latest_had_sigs = self.local_htlc_sigs.is_some();
+ let prev_had_sigs = self.prev_local_htlc_sigs.is_some();
+ let ret = self.get_fully_signed_htlc_tx(outp, preimage);
+ if !latest_had_sigs {
+ self.local_htlc_sigs = None;
+ }
+ if !prev_had_sigs {
+ self.prev_local_htlc_sigs = None;
+ }
+ ret
}
}