-
Notifications
You must be signed in to change notification settings - Fork 409
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
Introduce OnchainTxHandler, move bumping and tracking logic #462
Conversation
f23fcb4
to
8603afb
Compare
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initial incomplete pass.
lightning/src/ln/channelmonitor.rs
Outdated
@@ -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>) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
lightning/src/ln/onchaintx.rs
Outdated
} | ||
|
||
fn get_height_timer(current_height: u32, timelock_expiration: u32) -> u32 { | ||
if timelock_expiration <= current_height || timelock_expiration - current_height <= 3 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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..
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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. |
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).
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). |
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. |
My comments were addressed. (note that for some reason there are no "Resolve" buttons visible to me) |
9d24658
to
5cb0880
Compare
Rebased 0de8d5d, ready for new review. |
There was a problem hiding this 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.
lightning/src/ln/channelmonitor.rs
Outdated
Revoked { | ||
/// Witness script |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
lightning/src/ln/channelmonitor.rs
Outdated
// 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>, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or current_revoked_amount
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 :)
5cb0880
to
e9dff10
Compare
Thanks @TheBlueMatt and @arik-so for reviewing, addressed your comment at e9dff10. |
e9dff10
to
5aa3816
Compare
Travis failure is
Can you rebase on master now that 489 landed? |
4167f24
to
bce1508
Compare
Rebased bce1508, with Travis 1.22 failure fixed. |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
a063ba1
to
9888ff3
Compare
There was a problem hiding this 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.
lightning/src/ln/onchaintx.rs
Outdated
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> |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
lightning/src/ln/onchaintx.rs
Outdated
self.claimable_outpoints.insert(k, v); | ||
} | ||
for (k, v) in new_pending_claim_requests.drain() { | ||
self.pending_claim_requests.insert(k, v); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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...)
lightning/src/ln/channelmonitor.rs
Outdated
pubkey: Option<PublicKey>, | ||
key: SecretKey, | ||
is_htlc: bool, | ||
amount: u64, | ||
revoked_amount: u64, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Height timer as an important component of a more-secure, fee-sensitive claiming of time-constrained LN outputs, therefore document assumptions.
9888ff3
to
a4a5e01
Compare
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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 Report
@@ 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
Continue to review full report at Codecov.
|
I'm gonna have a few follow-ups anyway that are only partially-related, so I'll just take the above comments with those. |
This comment was stale and referred to a previous implementation of lightningdevkit#462, which changed before it was merged.
This comment was stale and referred to a previous implementation of lightningdevkit#462, which changed before it was merged.
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.