Skip to content

(Fix and) Test that txn pay at least a minimum relay fee in functional tests #716

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 3 commits into from
Oct 5, 2020

Conversation

TheBlueMatt
Copy link
Collaborator

This fixes some issues in the close-tx logic as well tests aggressively that we always include a proper fee in transactions.

@codecov
Copy link

codecov bot commented Sep 18, 2020

Codecov Report

Merging #716 into main will increase coverage by 0.07%.
The diff coverage is 83.33%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #716      +/-   ##
==========================================
+ Coverage   91.92%   92.00%   +0.07%     
==========================================
  Files          37       36       -1     
  Lines       20098    20216     +118     
==========================================
+ Hits        18475    18599     +124     
+ Misses       1623     1617       -6     
Impacted Files Coverage Δ
lightning/src/ln/functional_tests.rs 97.09% <ø> (+0.23%) ⬆️
lightning/src/ln/channel.rs 86.32% <82.14%> (+0.08%) ⬆️
lightning/src/ln/functional_test_utils.rs 94.02% <100.00%> (-0.42%) ⬇️
lightning/src/ln/reorg_tests.rs 98.90% <0.00%> (-0.08%) ⬇️
lightning/src/ln/onion_route_tests.rs 97.78% <0.00%> (-0.05%) ⬇️
lightning/src/chain/transaction.rs 100.00% <0.00%> (ø)
lightning/src/ln/channelmanager.rs 85.64% <0.00%> (ø)
lightning/src/chain/chainmonitor.rs
... and 8 more

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 937d1d8...9d206a3. Read the comment docs.

@@ -3031,9 +3054,14 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
},
};

let closing_tx_max_weight = self.get_closing_transaction_weight(
if let Some(oup) = closing_tx.output.get(0) { Some(&oup.script_pubkey) } else { None },
Copy link
Contributor

Choose a reason for hiding this comment

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

Why would this ever not have 0/1 outputs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Close outputs can be dust, causing that channel party's balance to be burned to fees at close.

}
if (msg.fee_satoshis as u64) < min_feerate as u64 * closing_tx_max_weight / 1000 {
if let Some((last_feerate, _, _)) = self.last_sent_closing_fee {
if min_feerate >= last_feerate {
Copy link
Contributor

Choose a reason for hiding this comment

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

It should be >, not >=?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If its ==, that means the last time we proposed something to our counterparty it was already min_feerate and they came back with a counter-proposal. Proposing again would get us into a loop.

@naumenkogs
Copy link
Contributor

I'm not an expert in weight/fee calculation, but I think this works as intended. Just couple questions.

let proposed_total_fee_satoshis = proposed_feerate as u64 * tx_weight / 1000;

let (closing_tx, total_fee_satoshis) = self.build_closing_transaction(proposed_total_fee_satoshis, false);
let sig = self.holder_keys
.sign_closing_transaction(&closing_tx, &self.secp_ctx)
.ok();
assert!(closing_tx.get_weight() as u64 <= tx_weight);
Copy link

Choose a reason for hiding this comment

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

Do we assert somewhere we never sign 0-outputs closing transactions ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, I don't think so. I'm not sure what we could do in a world where both sides decide their closing transaction is dust...it feels...more than a bit awkward but you can't stop the closing process and there's not a lot of value in broadcasting it...

if let Some((_, last_fee, sig)) = self.last_sent_closing_fee {
if last_fee == msg.fee_satoshis {
self.build_signed_closing_transaction(&mut closing_tx, &msg.signature, &sig);
assert!(closing_tx.get_weight() as u64 <= closing_tx_max_weight);
debug_assert!(closing_tx.get_weight() as u64 >= closing_tx_max_weight - 2);
Copy link

Choose a reason for hiding this comment

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

What the reason of this 2 of allowed diff ? Further as the pre-computed weight is done with the knowledge of number of outputs shouldn't assert be strictly equal to effective transaction weight ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Signature sizes are variable, here we let it drop by 1 byte, though they could theoretically be even smaller.

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: maybe could've used a comment on this point

if min_feerate >= last_feerate {
return Err(ChannelError::Close(format!("Unable to come to consensus about closing feerate, remote wanted something lower ({}) than our Background feerate ({}).", last_feerate, min_feerate)));
}
min_feerate = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Background);
Copy link

Choose a reason for hiding this comment

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

Can we update min_feerate to Background feerate only if it's superior to min-relay-fee ?

Copy link
Collaborator Author

@TheBlueMatt TheBlueMatt Sep 22, 2020

Choose a reason for hiding this comment

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

The feerate API is very explicit that it must always return at least 253. We probably should check it at every callsite (via some wrapper), but this isn't any different for now.

for output in $tx.output.iter() {
total_value_out += output.value;
}
let min_fee = $tx.get_weight() as u64 / 4; // One sat per vbyte
Copy link

Choose a reason for hiding this comment

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

Or instead compute the effective fee based on min feerate per kw and check if value_in - value_out is equal or superior. Rather not introduce reference to vbyte.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

IIUC, bitcoin core requires 1 sat/vbyte, not 1/4 sat/weight, hence the 253 instead of 250. We could check it against 253 sat/weight, but the reality is the minimum is defined in vbytes.

Copy link

@ariard ariard left a comment

Choose a reason for hiding this comment

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

1 + // witness element count
4 + // 4 element lengths (multisig dummy, 2 sigs, and witness script)
self.get_funding_redeemscript().len() as u64 + // funding witness script
2*(1 + 71); // two signatures + sighash type flags
Copy link

Choose a reason for hiding this comment

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

IIRC aren't Bitcoin standard (BIP66) ECDSA sigs at most 72? I think we can trigger new asserts as they check real-transaction-weight <= not-bigger-expected-weight ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm...you may be right, but tests aren't failing. Is it because low-s means we never sign something with an S that has the high bit set (so never need a 0-byte prefix in S)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

After an extended conversation on IRC today, we noted that secp's verify function will fail for high-S, and low-S' requirement is that the S field is <0x7F...., which always has the top bit unset, implying 71 bytes for the DER signature, so I think this is fine. I went ahead and added a comment noting this.

@TheBlueMatt TheBlueMatt added this to the 0.0.12 milestone Sep 27, 2020
@ariard
Copy link

ariard commented Oct 5, 2020

Code Review ACK 22e20ec pending on fixing comment : #716 (comment)

@TheBlueMatt
Copy link
Collaborator Author

@ariard also pointed out on IRC that weight is always rounded up to calculate vbytes for feerates - I updated the PR to match.

Though hopefully we never see a fee of 2.1 million BTC, either...
This resolves a number of bugs around how we calculate feerates on
closing transactions:

 * We previously calculated the weight wrong both by always
   counting two outputs instead of counting the number of outputs
   that actually exist in the closing transaction and by not
   counting the witness redeemscript.
 * We use assertions to check the calculated weight matches what we
   actually build (with debug_assertions for variable-length sigs).
 * As an additional sanity check, we really should check that the
   transaction had at least min-relay-fee when we were the channel
   initator.
@TheBlueMatt
Copy link
Collaborator Author

Also rebased on upstream to pull CI fixes.

total_value_out += output.value;
}
let min_fee = ($tx.get_weight() as u64 + 3) / 4; // One sat per vbyte (ie per weight/4, rounded up)
// Input amount must be no larger than output amount + minimum relay fee
Copy link

Choose a reason for hiding this comment

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

"Input amount must be larger than output amount + minimum relay fee" as it's okay to have a far larger fee than minimum relay ?

This also pays a fee on the transactions we generate in response to
SpendableOutputDescriptors in tests.

This fixes the known issues in lightningdevkit#630, though we should test for
standardness in other ways as well.
@ariard
Copy link

ariard commented Oct 5, 2020

Code Review ACK 3219926

@TheBlueMatt TheBlueMatt merged commit 45a558b into lightningdevkit:main Oct 5, 2020
Copy link
Contributor

@valentinewallace valentinewallace left a comment

Choose a reason for hiding this comment

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

I have one question, but given that's resolved, LGTM 🌟 🌟

if let Some((_, last_fee, sig)) = self.last_sent_closing_fee {
if last_fee == msg.fee_satoshis {
self.build_signed_closing_transaction(&mut closing_tx, &msg.signature, &sig);
assert!(closing_tx.get_weight() as u64 <= closing_tx_max_weight);
debug_assert!(closing_tx.get_weight() as u64 >= closing_tx_max_weight - 2);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: maybe could've used a comment on this point

1 + // witness element count
4 + // 4 element lengths (2 sigs, multisig dummy, and witness script)
self.get_funding_redeemscript().len() as u64 + // funding witness script
2*(1 + 71); // two signatures + sighash type flags
Copy link
Contributor

Choose a reason for hiding this comment

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

are each of the 1's: one 1 for the multisig dummy, one 1 for the sighash flag? (if that makes sense)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Multisig dummy is counted a few lines up (see comment), both signatures have sighash flags, so hence the 2 here.

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