wallet: use constant input source for change calculation

To fix a bug where specifying multiple UTXOs that are by themselves
large enough to satisfy the output amount would lead to the rest of them
being added to fees, we need to provide the transaction author with a
constant list of UTXOs. If we didn't, the author would only consider one
input and calculate the change based on that alone. But since we'd add
all inputs to the PSBT, the rest of the amounts would go to fees.
This commit is contained in:
Oliver Gugger 2020-10-01 14:55:40 +02:00
parent 34bfc5efb9
commit 98e779a102
No known key found for this signature in database
GPG key ID: 8E4256593F177720
2 changed files with 50 additions and 8 deletions

View file

@ -134,7 +134,14 @@ func (w *Wallet) FundPsbt(packet *psbt.Packet, account uint32,
} }
// We can leverage the fee calculation of the txauthor package // We can leverage the fee calculation of the txauthor package
// if we provide the selected UTXOs as a coin source. // if we provide the selected UTXOs as a coin source. We just
// need to make sure we always return the full list of user-
// selected UTXOs rather than a subset, otherwise our change
// amount will be off (in case the user selected multiple UTXOs
// that are large enough on their own). That's why we use our
// own static input source creator instead of the more generic
// makeInputSource() that selects a subset that is "large
// enough".
credits := make([]wtxmgr.Credit, len(txIn)) credits := make([]wtxmgr.Credit, len(txIn))
for idx, in := range txIn { for idx, in := range txIn {
utxo := packet.Inputs[idx].WitnessUtxo utxo := packet.Inputs[idx].WitnessUtxo
@ -144,7 +151,7 @@ func (w *Wallet) FundPsbt(packet *psbt.Packet, account uint32,
PkScript: utxo.PkScript, PkScript: utxo.PkScript,
} }
} }
inputSource := makeInputSource(credits) inputSource := constantInputSource(credits)
// We also need a change source which needs to be able to insert // We also need a change source which needs to be able to insert
// a new change addresse into the database. // a new change addresse into the database.
@ -319,3 +326,30 @@ func (w *Wallet) FinalizePsbt(packet *psbt.Packet) error {
return nil return nil
} }
// constantInputSource creates an input source function that always returns the
// static set of user-selected UTXOs.
func constantInputSource(eligible []wtxmgr.Credit) txauthor.InputSource {
// Current inputs and their total value. These won't change over
// different invocations as we want our inputs to remain static since
// they're selected by the user.
currentTotal := btcutil.Amount(0)
currentInputs := make([]*wire.TxIn, 0, len(eligible))
currentScripts := make([][]byte, 0, len(eligible))
currentInputValues := make([]btcutil.Amount, 0, len(eligible))
for _, credit := range eligible {
nextInput := wire.NewTxIn(&credit.OutPoint, nil, nil)
currentTotal += credit.Amount
currentInputs = append(currentInputs, nextInput)
currentScripts = append(currentScripts, credit.PkScript)
currentInputValues = append(currentInputValues, credit.Amount)
}
return func(target btcutil.Amount) (btcutil.Amount, []*wire.TxIn,
[]btcutil.Amount, [][]byte, error) {
return currentTotal, currentInputs, currentInputValues,
currentScripts, nil
}
}

View file

@ -68,6 +68,8 @@ func TestFundPsbt(t *testing.T) {
feeRateSatPerKB btcutil.Amount feeRateSatPerKB btcutil.Amount
expectedErr string expectedErr string
validatePackage bool validatePackage bool
expectedFee int64
expectedChange int64
numExpectedInputs int numExpectedInputs int
}{{ }{{
name: "no outputs provided", name: "no outputs provided",
@ -106,6 +108,8 @@ func TestFundPsbt(t *testing.T) {
feeRateSatPerKB: 2000, // 2 sat/byte feeRateSatPerKB: 2000, // 2 sat/byte
expectedErr: "", expectedErr: "",
validatePackage: true, validatePackage: true,
expectedChange: 1000000 - 150000 - 368,
expectedFee: 368,
numExpectedInputs: 1, numExpectedInputs: 1,
}, { }, {
name: "two outputs, two inputs", name: "two outputs, two inputs",
@ -136,6 +140,8 @@ func TestFundPsbt(t *testing.T) {
feeRateSatPerKB: 2000, // 2 sat/byte feeRateSatPerKB: 2000, // 2 sat/byte
expectedErr: "", expectedErr: "",
validatePackage: true, validatePackage: true,
expectedFee: 506,
expectedChange: 2000000 - 150000 - 506,
numExpectedInputs: 2, numExpectedInputs: 2,
}} }}
@ -296,15 +302,17 @@ func TestFundPsbt(t *testing.T) {
txOuts[p2wshIndex].PkScript) txOuts[p2wshIndex].PkScript)
} }
// Finally, check the change output size and that it // Finally, check the change output size and fee.
// belongs to the wallet. fee := totalIn - totalOut
expectedFee := int64(368) if fee != tc.expectedFee {
expectedChange := 1000000 - 150000 - expectedFee t.Fatalf("unexpected fee, got %d wanted %d",
if txOuts[changeIndex].Value != expectedChange { fee, tc.expectedFee)
}
if txOuts[changeIndex].Value != tc.expectedChange {
t.Fatalf("unexpected change output size, got "+ t.Fatalf("unexpected change output size, got "+
"%d wanted %d", "%d wanted %d",
txOuts[changeIndex].Value, txOuts[changeIndex].Value,
expectedChange) tc.expectedChange)
} }
}) })
} }