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
// 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))
for idx, in := range txIn {
utxo := packet.Inputs[idx].WitnessUtxo
@ -144,7 +151,7 @@ func (w *Wallet) FundPsbt(packet *psbt.Packet, account uint32,
PkScript: utxo.PkScript,
}
}
inputSource := makeInputSource(credits)
inputSource := constantInputSource(credits)
// We also need a change source which needs to be able to insert
// a new change addresse into the database.
@ -319,3 +326,30 @@ func (w *Wallet) FinalizePsbt(packet *psbt.Packet) error {
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
expectedErr string
validatePackage bool
expectedFee int64
expectedChange int64
numExpectedInputs int
}{{
name: "no outputs provided",
@ -106,6 +108,8 @@ func TestFundPsbt(t *testing.T) {
feeRateSatPerKB: 2000, // 2 sat/byte
expectedErr: "",
validatePackage: true,
expectedChange: 1000000 - 150000 - 368,
expectedFee: 368,
numExpectedInputs: 1,
}, {
name: "two outputs, two inputs",
@ -136,6 +140,8 @@ func TestFundPsbt(t *testing.T) {
feeRateSatPerKB: 2000, // 2 sat/byte
expectedErr: "",
validatePackage: true,
expectedFee: 506,
expectedChange: 2000000 - 150000 - 506,
numExpectedInputs: 2,
}}
@ -296,15 +302,17 @@ func TestFundPsbt(t *testing.T) {
txOuts[p2wshIndex].PkScript)
}
// Finally, check the change output size and that it
// belongs to the wallet.
expectedFee := int64(368)
expectedChange := 1000000 - 150000 - expectedFee
if txOuts[changeIndex].Value != expectedChange {
// Finally, check the change output size and fee.
fee := totalIn - totalOut
if fee != tc.expectedFee {
t.Fatalf("unexpected fee, got %d wanted %d",
fee, tc.expectedFee)
}
if txOuts[changeIndex].Value != tc.expectedChange {
t.Fatalf("unexpected change output size, got "+
"%d wanted %d",
txOuts[changeIndex].Value,
expectedChange)
tc.expectedChange)
}
})
}