sweep: update getInputWitnessSizeUpperBound to be aware of nested p2sh

In this commit, we update the `getInputWitnessSizeUpperBound` and all
its callers to be aware of nested p2sh witness inputs. We do so by
adding another bool which is true if the output is a nested p2sh output.
If so, then in order to properly estimate the total weight, the caller
needs to factor in the non-witness data of the additional sigScript data
push.
This commit is contained in:
Olaoluwa Osuntokun 2018-12-20 20:28:03 -06:00
parent 40f0dbb5e6
commit 4ad175c16d
No known key found for this signature in database
GPG Key ID: CE58F7F8E20FD9A2
2 changed files with 42 additions and 17 deletions

@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
@ -376,7 +377,8 @@ func TestNegativeInput(t *testing.T) {
if !testTxIns(&sweepTx1, []*wire.OutPoint{ if !testTxIns(&sweepTx1, []*wire.OutPoint{
largeInput.OutPoint(), positiveInput.OutPoint(), largeInput.OutPoint(), positiveInput.OutPoint(),
}) { }) {
t.Fatal("Tx does not contain expected inputs") t.Fatalf("Tx does not contain expected inputs: %v",
spew.Sdump(sweepTx1))
} }
ctx.backend.mine() ctx.backend.mine()

@ -55,7 +55,7 @@ func generateInputPartitionings(sweepableInputs []Input,
// on the signature length, which is not known yet at this point. // on the signature length, which is not known yet at this point.
yields := make(map[wire.OutPoint]int64) yields := make(map[wire.OutPoint]int64)
for _, input := range sweepableInputs { for _, input := range sweepableInputs {
size, err := getInputWitnessSizeUpperBound(input) size, _, err := getInputWitnessSizeUpperBound(input)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"failed adding input weight: %v", err) "failed adding input weight: %v", err)
@ -125,10 +125,14 @@ func getPositiveYieldInputs(sweepableInputs []Input, maxInputs int,
for idx, input := range sweepableInputs { for idx, input := range sweepableInputs {
// Can ignore error, because it has already been checked when // Can ignore error, because it has already been checked when
// calculating the yields. // calculating the yields.
size, _ := getInputWitnessSizeUpperBound(input) size, isNestedP2SH, _ := getInputWitnessSizeUpperBound(input)
// Keep a running weight estimate of the input set. // Keep a running weight estimate of the input set.
if isNestedP2SH {
weightEstimate.AddNestedP2WSHInput(size)
} else {
weightEstimate.AddWitnessInput(size) weightEstimate.AddWitnessInput(size)
}
newTotal := total + btcutil.Amount(input.SignDesc().Output.Value) newTotal := total + btcutil.Amount(input.SignDesc().Output.Value)
@ -247,45 +251,54 @@ func createSweepTx(inputs []Input, outputPkScript []byte,
} }
// getInputWitnessSizeUpperBound returns the maximum length of the witness for // getInputWitnessSizeUpperBound returns the maximum length of the witness for
// the given input if it would be included in a tx. // the given input if it would be included in a tx. We also return if the
func getInputWitnessSizeUpperBound(input Input) (int, error) { // output itself is a nested p2sh output, if so then we need to take into
// account the extra sigScript data size.
func getInputWitnessSizeUpperBound(input Input) (int, bool, error) {
switch input.WitnessType() { switch input.WitnessType() {
// Outputs on a remote commitment transaction that pay directly // Outputs on a remote commitment transaction that pay directly to us.
// to us. case lnwallet.WitnessKeyHash:
fallthrough
case lnwallet.CommitmentNoDelay: case lnwallet.CommitmentNoDelay:
return lnwallet.P2WKHWitnessSize, nil return lnwallet.P2WKHWitnessSize, false, nil
// Outputs on a past commitment transaction that pay directly // Outputs on a past commitment transaction that pay directly
// to us. // to us.
case lnwallet.CommitmentTimeLock: case lnwallet.CommitmentTimeLock:
return lnwallet.ToLocalTimeoutWitnessSize, nil return lnwallet.ToLocalTimeoutWitnessSize, false, nil
// Outgoing second layer HTLC's that have confirmed within the // Outgoing second layer HTLC's that have confirmed within the
// chain, and the output they produced is now mature enough to // chain, and the output they produced is now mature enough to
// sweep. // sweep.
case lnwallet.HtlcOfferedTimeoutSecondLevel: case lnwallet.HtlcOfferedTimeoutSecondLevel:
return lnwallet.ToLocalTimeoutWitnessSize, nil return lnwallet.ToLocalTimeoutWitnessSize, false, nil
// Incoming second layer HTLC's that have confirmed within the // Incoming second layer HTLC's that have confirmed within the
// chain, and the output they produced is now mature enough to // chain, and the output they produced is now mature enough to
// sweep. // sweep.
case lnwallet.HtlcAcceptedSuccessSecondLevel: case lnwallet.HtlcAcceptedSuccessSecondLevel:
return lnwallet.ToLocalTimeoutWitnessSize, nil return lnwallet.ToLocalTimeoutWitnessSize, false, nil
// An HTLC on the commitment transaction of the remote party, // An HTLC on the commitment transaction of the remote party,
// that has had its absolute timelock expire. // that has had its absolute timelock expire.
case lnwallet.HtlcOfferedRemoteTimeout: case lnwallet.HtlcOfferedRemoteTimeout:
return lnwallet.AcceptedHtlcTimeoutWitnessSize, nil return lnwallet.AcceptedHtlcTimeoutWitnessSize, false, nil
// An HTLC on the commitment transaction of the remote party, // An HTLC on the commitment transaction of the remote party,
// that can be swept with the preimage. // that can be swept with the preimage.
case lnwallet.HtlcAcceptedRemoteSuccess: case lnwallet.HtlcAcceptedRemoteSuccess:
return lnwallet.OfferedHtlcSuccessWitnessSize, nil return lnwallet.OfferedHtlcSuccessWitnessSize, false, nil
// A nested P2SH input that has a p2wkh witness script. We'll mark this
// as nested P2SH so the caller can estimate the weight properly
// including the sigScript.
case lnwallet.NestedWitnessKeyHash:
return lnwallet.P2WKHWitnessSize, true, nil
} }
return 0, fmt.Errorf("unexpected witness type: %v", input.WitnessType()) return 0, false, fmt.Errorf("unexpected witness type: %v",
input.WitnessType())
} }
// getWeightEstimate returns a weight estimate for the given inputs. // getWeightEstimate returns a weight estimate for the given inputs.
@ -312,7 +325,10 @@ func getWeightEstimate(inputs []Input) ([]Input, int64, int, int) {
for i := range inputs { for i := range inputs {
input := inputs[i] input := inputs[i]
size, err := getInputWitnessSizeUpperBound(input) // For fee estimation purposes, we'll now attempt to obtain an
// upper bound on the weight this input will add when fully
// populated.
size, isNestedP2SH, err := getInputWitnessSizeUpperBound(input)
if err != nil { if err != nil {
log.Warn(err) log.Warn(err)
@ -320,7 +336,14 @@ func getWeightEstimate(inputs []Input) ([]Input, int64, int, int) {
// given. // given.
continue continue
} }
// If this is a nested P2SH input, then we'll need to factor in
// the additional data push within the sigScript.
if isNestedP2SH {
weightEstimate.AddNestedP2WSHInput(size)
} else {
weightEstimate.AddWitnessInput(size) weightEstimate.AddWitnessInput(size)
}
switch input.WitnessType() { switch input.WitnessType() {
case lnwallet.CommitmentTimeLock, case lnwallet.CommitmentTimeLock,