diff --git a/wallet/psbt.go b/wallet/psbt.go index e2d4fe8..1a68a71 100644 --- a/wallet/psbt.go +++ b/wallet/psbt.go @@ -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 + } +} diff --git a/wallet/psbt_test.go b/wallet/psbt_test.go index dfb67cd..b9747b2 100644 --- a/wallet/psbt_test.go +++ b/wallet/psbt_test.go @@ -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) } }) }