Skip to content

Introduce OnchainTxHandler, move bumping and tracking logic #462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 4, 2020

Conversation

ariard
Copy link

@ariard ariard commented Jan 24, 2020

Build on top of #461, unify transaction generation code in its own nodule.

Simplify parsing code (easier to implement option_simplified_commitment) and first step towards removal of in-memory private keys from ChannelMonitor.

Note: there is one intermediary failure on e2a3bd6, which is related to #459, need to rebase also on top of it when it's merged.

@ariard ariard force-pushed the 2020-01-refactor-chan branch from f23fcb4 to 8603afb Compare January 24, 2020 22:49
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not for this PR, but I wonder if it will be more robust to create a sample tx and serialize it to get these numbers

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure further dry-up of this code with some tx template what somehow in my mind

log_trace!(self, "Can't new-estimation bump new claiming tx, amount {} is too small", $amount);
return None;
}
// ...else just increase the previous feerate by 25% (because that's a nice number)
Copy link
Member

@devrandom devrandom Jan 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I know this is not changed in this PR)

Might want to consider escalating the fee much faster in the exponential increase case, in case the fee estimator is wrong. Even switching to 100% increase/try after some time. The game theory is that we might lose the entire channel value, so even if we end up paying 10% in fees it may be worth it.

Also, if the fee estimator increases moderately for a long time, the second branch (exponential fee increase) will never be taken, and again if the fee estimator is wrong we can lose funds.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree picking up a %25 bump was just magic and more aggressive heuristics could be implemented. So far IIRC we are the only implem bumping fees would like more feedback from other LN devs.

The game theory remark is a very good one.

Copy link
Member

@devrandom devrandom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial incomplete pass.

@@ -1395,12 +1220,13 @@ impl ChannelMonitor {
/// 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, fee_estimator: &FeeEstimator) -> (Vec<Transaction>, (Sha256dHash, Vec<TxOut>), Vec<SpendableOutputDescriptor>) {
fn check_spend_remote_transaction(&mut self, tx: &Transaction, height: u32) -> (HashMap<Sha256dHash, Vec<(u32, bool, BitcoinOutPoint, InputMaterial)>>, (Sha256dHash, Vec<TxOut>), Option<SpendableOutputDescriptor>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The four-tuple should probably be a crate visible struct.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, added a ClaimRequest struct with more doc about field purpose.

}

fn get_height_timer(current_height: u32, timelock_expiration: u32) -> u32 {
if timelock_expiration <= current_height || timelock_expiration - current_height <= 3 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second clause subsumes the first.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, corrected in its own commit as it was already there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reserved this one because got test failures due to unsigned int overflow..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timelock_expiration <= current_height + 3 should work.

inputs_info.push((payment_preimage, tx.output[transaction_output_index as usize].value, htlc.cltv_expiry));
total_value += tx.output[transaction_output_index as usize].value;
} else {
let mut single_htlc_tx = Transaction {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm probably missing something, but I don't see where this code moved.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's L1321 as of 8603afb, aggregation now happens in OnchainTxHandler so there is no more a isolated vs multiple htlcs branch anymore.

@TheBlueMatt
Copy link
Collaborator

Was kinda hoping #461 went in first, but waiting to take a look at this for concept acks before that was unfair. Sorry about that.

At a high-level design-wise, I like the direction, though I'm not sure exactly what the end-goal is. I presume the eventual goal is to just have ChannelMonitor watch the chain with no way to sign anything, and then just call OnchainTxHandler to get the signed transactions. Thus, the monitoring would be disconnected from the signer, and OnchainTxHandler could contain only pre-signed transactions. However, that leaves me confused why the RBF bumping would be in OnchainTxHandler.

Alternatively, OnchainTxHandler could be responsible for monitoring any outputs which are "to-be-claimed", with ChannelMonitor solely responsible for detecting such outputs and notifying ChannelManager appropriately after OnchainTxHandler has done its job, though that would leave OnchainTxHandler with a further substruct which could either be presigned txn or with a ChannelKeys/Signer.

@ariard
Copy link
Author

ariard commented Feb 10, 2020

Alternatively, OnchainTxHandler could be responsible for monitoring any outputs which are "to-be-claimed", with ChannelMonitor solely responsible for detecting such outputs and notifying ChannelManager appropriately after OnchainTxHandler has done its job, though that would leave OnchainTxHandler with a further substruct which could either be presigned txn or with a ChannelKeys/Signer.

End-goals are unifying transaction generation code, encapsulate in-flight and bumping logic complexity in its own module and simplify tx parsing code (a.k.a the detection). Like you said, next step is obviously to add Signer in OnchainTxHandler. Signer implementation would determine if it's in watchtower mode or local mode (sign_justice_tx would return pre-signed txn for watchtower).

ChannelMonitor would be left with only channel tx detection and passing preimage/timeout to offchain, which are already complex tasks (specially if we have different commitment tx format like first option_simplified_commitment).

Long-term, OnchainTxHandler could be reused for dual-funding tx/splicing, where we may have to bump onchain txn too (I don't want to write another bumping logic).

However, that leaves me confused why the RBF bumping would be in OnchainTxHandler.

I still need to think about this but IMO what you can sign determine how you can bump (no key access for watchtower means CPFP for justice tx instead of RBF).

@TheBlueMatt
Copy link
Collaborator

Right, detection-vs-response is a good distinction, I just wanted to make sure that was the goal, and note that its not the only thing we need.

@devrandom
Copy link
Member

My comments were addressed. (note that for some reason there are no "Resolve" buttons visible to me)

@ariard ariard force-pushed the 2020-01-refactor-chan branch 2 times, most recently from 9d24658 to 5cb0880 Compare February 12, 2020 21:25
@ariard
Copy link
Author

ariard commented Feb 12, 2020

Rebased 0de8d5d, ready for new review.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't get to do a bunch more than a skim-through but the top-level API looks great. Noted a few nits that I came across but a full review may have to wait until next week.

Revoked {
/// Witness script
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the documentation-required checks don't apply to pub(crate) things. Two word comments probably indicate you should just change the variable name and move on :)

// block connection we scan all inputs and if any of them is among a set of a claiming request we test for set
// equality between spending transaction and claim request. If true, it means transaction was one our claiming one
// after a security delay of 6 blocks we remove pending claim request. If false, it means transaction wasn't and
// we need to regenerate new claim request with reduced set of still-claimable outpoints.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're gonna fix a spelling mistake in a mostly-move, can you do it in a separate commit so that --color-moved doesn't barf?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember to do a spelling fix there ? What git options did you use there?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git show --color-moved highlights this line as a different color.

// We simply modify last_block_hash in Channel's block_connected so that serialization is
// consistent but hopefully the users' copy handles block_connected in a consistent way.
// (we do *not*, however, update them in insert_combine to ensure any local user copies keep
// their last_block_hash from its state and not based on updated copies that didn't run through
// the full block_connected).
pub(crate) last_block_hash: Sha256dHash,
secp_ctx: Secp256k1<secp256k1::All>, //TODO: dedup this a bit...
secp_ctx: Secp256k1<secp256k1::All>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? We should still think about deduping the secp contexts especially in a ManyChannelMonitor.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooops, I tried at first to throw it entirely in OnchainTxHandler IIIRC. That's said, after moving local commitment txn construction there too, secp state should be the matter of the Signer

assert_eq!(node_txn[0].clone().input[0].witness.last().unwrap().len(), 71);
assert_eq!(node_txn[1].clone().input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);

timeout_tx = node_txn[2].clone();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: on some occasions it's tx, others it's txn. We should probably use the same abbreviated form. I'd personally opt for tx.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, bookmarked point, I prefer to defer this until the Great Functional Test cleanup

}],
};

macro_rules! RBF_bump {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the reason for this being a macro?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC I though at first having different code paths for different transaction cases (revoked, valid-remote broadcast, ...) but in fact was able to subsume all them on one. I think it can be functionify when we implement CPFP as a bumping mechanism or implement a better bumping strategy (cf devrandom point above)

match per_outp_material {
&InputMaterial::Revoked { ref script, ref is_htlc, ref amount, .. } => {
inputs_witnesses_weight += Self::get_witnesses_weight(if !is_htlc { &[InputDescriptors::RevokedOutput] } else if HTLCType::scriptlen_to_htlctype(script.len()) == Some(HTLCType::OfferedHTLC) { &[InputDescriptors::RevokedOfferedHTLC] } else if HTLCType::scriptlen_to_htlctype(script.len()) == Some(HTLCType::AcceptedHTLC) { &[InputDescriptors::RevokedReceivedHTLC] } else { &[] });
amt += *amount;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would strongly recommend using different names from these variables. Maybe revoked_amount and total_amount?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or current_revoked_amount

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, updated name with suggestions

}
}

let new_timer = Self::get_height_timer(height, cached_claim_datas.soonest_timelock);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some explanation of what's happening here and what the variables will be used for could bee heelpful

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes add comment on assumptions there and also on top of get_height_timer, tell me if it's clear enough :)

@TheBlueMatt TheBlueMatt added this to the 0.0.10 milestone Feb 17, 2020
@ariard ariard force-pushed the 2020-01-refactor-chan branch from 5cb0880 to e9dff10 Compare February 18, 2020 18:14
@ariard
Copy link
Author

ariard commented Feb 18, 2020

Thanks @TheBlueMatt and @arik-so for reviewing, addressed your comment at e9dff10.

@TheBlueMatt TheBlueMatt modified the milestones: 0.0.10, 0.0.11 Feb 26, 2020
@ariard ariard force-pushed the 2020-01-refactor-chan branch from e9dff10 to 5aa3816 Compare February 26, 2020 22:01
@ariard
Copy link
Author

ariard commented Feb 26, 2020

Rebased on top #489 at 5aa3816

@TheBlueMatt
Copy link
Collaborator

Travis failure is

   --> lightning/src/ln/channelmonitor.rs:811:22

    |

811 |  secp_ctx: Secp256k1<secp256k1::All>, //TODO: dedup this a bit...

    |                      ^^^^^^^^^ Use of undeclared type or module `secp256k1`

error[E0433]: failed to resolve. Use of undeclared type or module `secp256k1`

   --> lightning/src/ln/onchaintx.rs:174:22

    |

174 |  secp_ctx: Secp256k1<secp256k1::All>,

    |                      ^^^^^^^^^ Use of undeclared type or module `secp256k1`

Can you rebase on master now that 489 landed?

@ariard ariard force-pushed the 2020-01-refactor-chan branch 2 times, most recently from 4167f24 to bce1508 Compare February 28, 2020 17:00
@ariard
Copy link
Author

ariard commented Feb 28, 2020

Rebased bce1508, with Travis 1.22 failure fixed.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only looked at the first two commits so far - can you squash them, as there appears to be a few awkward issues introduced in between that I dont feel like reviewing carefully :).

}
}
}

if !inputs.is_empty() || !txn_to_broadcast.is_empty() || per_commitment_option.is_some() { // ie we're confident this is actually ours
// Last, track onchain revoked commitment transaction and fail backward outgoing HTLCs as payment path is broken
if !claimable_outpoints.is_empty() || per_commitment_option.is_some() { // ie we're confident this is actually ours
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think its possible for us to have inserted into claimable_outpoints by this point, only outpoints. I guess there are no tests which hit this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm is this check accurate even using outpoints, given per_commitment_option.is_some (and we shouldn't care about tracking revoked remote commitment tx on chain if there is no htlc outputs, which may explain why we don't hit with a test)

@ariard ariard force-pushed the 2020-01-refactor-chan branch 2 times, most recently from a063ba1 to 9888ff3 Compare March 3, 2020 00:38
Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, a few complaints but all relatively minor in the scheme of things. LoC diff stats are so nice, tho.

Some((new_timer, new_feerate, bumped_tx))
}

pub(super) fn block_connected<B: Deref, F: Deref>(&mut self, txn_matched: &[&Transaction], claim_requests: HashMap<Sha256dHash, Vec<ClaimRequest>>, height: u32, broadcaster: B, fee_estimator: F) -> Vec<SpendableOutputDescriptor>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claim_requests seems pretty redundant as-is - its a map from previous-txid to a vec of a struct which contains the previous-txid in the outpoint. Can we drop the duplication to make it a bit easier to see correctness?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, my initial worry was someone inadvertently introducing cross-block aggregation, segmenting by txid was the reason.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but why not just have it be a map from previous-txid to something that contains the vout, instead of a full OutPoint?

new_claimable_outpoints.insert(k.clone(), (txid, height));
}
log_trace!(self, "Broadcast onchain {}", log_tx!(tx));
spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't generate this until the tx is confirmed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but that's already current behavior, pre-refactoring. Spendable output generation after ANTI_REORG_SAFE_DELAY is a todo in another PR

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grr that sucks. Alright, we should improve that, then.

self.claimable_outpoints.insert(k, v);
}
for (k, v) in new_pending_claim_requests.drain() {
self.pending_claim_requests.insert(k, v);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do this in the loop instead of having another map to track them before adding them later?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought borrow-checker would complain for some ownership already taken on self, but it works well, generate_claim_tx isn't mutable (it used to be in a previous verison, or at least I tried with one...)

pubkey: Option<PublicKey>,
key: SecretKey,
is_htlc: bool,
amount: u64,
revoked_amount: u64,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really dont understand the need for this commit - amount, revoked_amount, local_amount and remote_amount are all the amount as it appears in the transaction output. I think the new names may be even more confusing as it'll make me do a double-take to make sure we don't need to calculate them from some Channel data instead of from on-chain outputs.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay was in reaction to #462 (comment), I'm ~0 on this, dropped the commit

Encapsulates tracking and bumping of in-flight transactions in
its own component. This component may be latter abstracted
to reuse tracking and RBF for new features (e.g dual-funding,
splicing)

Build all transactions generation in one place. Also as fees
and signatures are closely tied, what keys do you have determine
what bumping mode you can use.
Antoine Riard added 4 commits March 4, 2020 16:06
Height timer as an important component of a more-secure, fee-sensitive
claiming of time-constrained LN outputs, therefore document assumptions.
@ariard ariard force-pushed the 2020-01-refactor-chan branch from 9888ff3 to a4a5e01 Compare March 4, 2020 21:08
let mut aggregated_soonest = ::std::u32::MAX;
let mut spendable_outputs = Vec::new();

// Try to aggregate outputs if they're 1) belong to same parent tx, 2) their
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is wrong now - we may aggregate an HTLC claim which spent a commitment tx with a spend of the commitment tx. This is, of course, fine, as any observer doesn't learn anything from that behavior, but the comment should be fixed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm why comment is wrong ? Spending a HTLC output and to_local/to_remote output that still spending same commitment tx, sorry your comment is unclear to me.

Some((new_timer, new_feerate, bumped_tx))
}

pub(super) fn block_connected<B: Deref, F: Deref>(&mut self, txn_matched: &[&Transaction], claimable_outpoints: Vec<Vec<ClaimRequest>>, height: u32, broadcaster: B, fee_estimator: F) -> Vec<SpendableOutputDescriptor>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please drop the Vec<Vec<>>. That seems needlessly inefficient.

@codecov
Copy link

codecov bot commented Mar 4, 2020

Codecov Report

Merging #462 into master will decrease coverage by 0.24%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #462      +/-   ##
==========================================
- Coverage   89.97%   89.72%   -0.25%     
==========================================
  Files          33       34       +1     
  Lines       19150    18997     -153     
==========================================
- Hits        17231    17046     -185     
- Misses       1919     1951      +32     
Impacted Files Coverage Δ
lightning/src/ln/channelmonitor.rs 90.30% <0.00%> (-2.76%) ⬇️
lightning/src/ln/functional_tests.rs 96.17% <0.00%> (-0.08%) ⬇️
lightning/src/ln/onchaintx.rs 92.46% <0.00%> (ø)
lightning/src/ln/onion_utils.rs 93.88% <0.00%> (+0.01%) ⬆️
lightning/src/ln/reorg_tests.rs 98.94% <0.00%> (+0.02%) ⬆️
lightning/src/ln/chanmon_update_fail_tests.rs 97.31% <0.00%> (+0.02%) ⬆️
lightning/src/ln/peer_handler.rs 50.08% <0.00%> (+0.16%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e946357...a4a5e01. Read the comment docs.

@TheBlueMatt
Copy link
Collaborator

I'm gonna have a few follow-ups anyway that are only partially-related, so I'll just take the above comments with those.

@TheBlueMatt TheBlueMatt merged commit 48549de into lightningdevkit:master Mar 4, 2020
TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this pull request Mar 4, 2020
This comment was stale and referred to a previous implementation
of lightningdevkit#462, which changed before it was merged.
TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this pull request Mar 5, 2020
This comment was stale and referred to a previous implementation
of lightningdevkit#462, which changed before it was merged.
TheBlueMatt added a commit that referenced this pull request Mar 5, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants