wallet: extend ChangeSource to support all key scopes

This commit is contained in:
Wilmer Paulino 2021-03-15 17:36:16 -07:00
parent ddbe5ecee4
commit b318e99f4f
No known key found for this signature in database
GPG key ID: 6DF57B9F9514972F
8 changed files with 131 additions and 54 deletions

View file

@ -22,6 +22,7 @@ import (
"github.com/btcsuite/btcwallet/netparams" "github.com/btcsuite/btcwallet/netparams"
"github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wallet/txrules" "github.com/btcsuite/btcwallet/wallet/txrules"
"github.com/btcsuite/btcwallet/wallet/txsizes"
"github.com/jessevdk/go-flags" "github.com/jessevdk/go-flags"
) )
@ -190,14 +191,22 @@ func makeInputSource(outputs []btcjson.ListUnspentResult) txauthor.InputSource {
// makeDestinationScriptSource creates a ChangeSource which is used to receive // makeDestinationScriptSource creates a ChangeSource which is used to receive
// all correlated previous input value. A non-change address is created by this // all correlated previous input value. A non-change address is created by this
// function. // function.
func makeDestinationScriptSource(rpcClient *rpcclient.Client, accountName string) txauthor.ChangeSource { func makeDestinationScriptSource(rpcClient *rpcclient.Client, accountName string) *txauthor.ChangeSource {
return func() ([]byte, error) {
// GetNewAddress always returns a P2PKH address since it assumes
// BIP-0044.
newChangeScript := func() ([]byte, error) {
destinationAddress, err := rpcClient.GetNewAddress(accountName) destinationAddress, err := rpcClient.GetNewAddress(accountName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return txscript.PayToAddrScript(destinationAddress) return txscript.PayToAddrScript(destinationAddress)
} }
return &txauthor.ChangeSource{
ScriptSize: txsizes.P2PKHPkScriptSize,
NewScript: newChangeScript,
}
} }
func main() { func main() {

1
go.mod
View file

@ -7,6 +7,7 @@ require (
github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 github.com/btcsuite/btcwallet/wallet/txrules v1.0.0
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0
github.com/btcsuite/btcwallet/walletdb v1.3.4 github.com/btcsuite/btcwallet/walletdb v1.3.4
github.com/btcsuite/btcwallet/wtxmgr v1.2.0 github.com/btcsuite/btcwallet/wtxmgr v1.2.0
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792

View file

@ -15,6 +15,7 @@ import (
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wallet/txsizes"
"github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr" "github.com/btcsuite/btcwallet/wtxmgr"
) )
@ -123,9 +124,12 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut, keyScope *waddrmgr.KeyScope,
} }
defer func() { _ = dbtx.Rollback() }() defer func() { _ = dbtx.Rollback() }()
addrmgrNs, changeSource := w.addrMgrWithChangeSource( addrmgrNs, changeSource, err := w.addrMgrWithChangeSource(
dbtx, keyScope, account, dbtx, keyScope, account,
) )
if err != nil {
return nil, err
}
// Get current block's height and hash. // Get current block's height and hash.
bs, err := chainClient.BlockStamp() bs, err := chainClient.BlockStamp()
@ -288,25 +292,54 @@ func (w *Wallet) findEligibleOutputs(dbtx walletdb.ReadTx,
} }
// addrMgrWithChangeSource returns the address manager bucket and a change // addrMgrWithChangeSource returns the address manager bucket and a change
// source function that returns change addresses from said address manager. The // source that returns change addresses from said address manager. The change
// change addresses will come from the specified key scope and account, unless // addresses will come from the specified key scope and account, unless a key
// a key scope is not specified. In that case, change addresses will always // scope is not specified. In that case, change addresses will always come from
// come from the P2WKH key scope. // the P2WKH key scope.
func (w *Wallet) addrMgrWithChangeSource(dbtx walletdb.ReadWriteTx, func (w *Wallet) addrMgrWithChangeSource(dbtx walletdb.ReadWriteTx,
changeKeyScope *waddrmgr.KeyScope, account uint32) (walletdb.ReadWriteBucket, changeKeyScope *waddrmgr.KeyScope, account uint32) (
txauthor.ChangeSource) { walletdb.ReadWriteBucket, *txauthor.ChangeSource, error) {
addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey) // Determine the address type for change addresses of the given account.
changeSource := func() ([]byte, error) {
// Derive the change output script. We'll use the default key
// scope responsible for P2WPKH addresses to do so. As a hack to
// allow spending from the imported account, change addresses
// are created from account 0.
var changeAddr btcutil.Address
var err error
if changeKeyScope == nil { if changeKeyScope == nil {
changeKeyScope = &waddrmgr.KeyScopeBIP0084 changeKeyScope = &waddrmgr.KeyScopeBIP0084
} }
addrType := waddrmgr.ScopeAddrMap[*changeKeyScope].InternalAddrType
// It's possible for the account to have an address schema override, so
// prefer that if it exists.
addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
scopeMgr, err := w.Manager.FetchScopedKeyManager(*changeKeyScope)
if err != nil {
return nil, nil, err
}
accountInfo, err := scopeMgr.AccountProperties(addrmgrNs, account)
if err != nil {
return nil, nil, err
}
if accountInfo.AddrSchema != nil {
addrType = accountInfo.AddrSchema.InternalAddrType
}
// Compute the expected size of the script for the change address type.
var scriptSize int
switch addrType {
case waddrmgr.PubKeyHash:
scriptSize = txsizes.P2PKHPkScriptSize
case waddrmgr.NestedWitnessPubKey:
scriptSize = txsizes.NestedP2WPKHPkScriptSize
case waddrmgr.WitnessPubKey:
scriptSize = txsizes.P2WPKHPkScriptSize
}
newChangeScript := func() ([]byte, error) {
// Derive the change output script. As a hack to allow spending
// from the imported account, change addresses are created from
// account 0.
var (
changeAddr btcutil.Address
err error
)
if account == waddrmgr.ImportedAddrAccount { if account == waddrmgr.ImportedAddrAccount {
changeAddr, err = w.newChangeAddress( changeAddr, err = w.newChangeAddress(
addrmgrNs, 0, *changeKeyScope, addrmgrNs, 0, *changeKeyScope,
@ -321,7 +354,11 @@ func (w *Wallet) addrMgrWithChangeSource(dbtx walletdb.ReadWriteTx,
} }
return txscript.PayToAddrScript(changeAddr) return txscript.PayToAddrScript(changeAddr)
} }
return addrmgrNs, changeSource
return addrmgrNs, &txauthor.ChangeSource{
ScriptSize: scriptSize,
NewScript: newChangeScript,
}, nil
} }
// validateMsgTx verifies transaction input scripts for tx. All previous output // validateMsgTx verifies transaction input scripts for tx. All previous output

View file

@ -173,9 +173,12 @@ func (w *Wallet) FundPsbt(packet *psbt.Packet, keyScope *waddrmgr.KeyScope,
if err != nil { if err != nil {
return 0, err return 0, err
} }
_, changeSource := w.addrMgrWithChangeSource( _, changeSource, err := w.addrMgrWithChangeSource(
dbtx, keyScope, account, dbtx, keyScope, account,
) )
if err != nil {
return 0, err
}
// Ask the txauthor to create a transaction with our selected // Ask the txauthor to create a transaction with our selected
// coins. This will perform fee estimation and add a change // coins. This will perform fee estimation and add a change

View file

@ -60,8 +60,15 @@ type AuthoredTx struct {
ChangeIndex int // negative if no change ChangeIndex int // negative if no change
} }
// ChangeSource provides P2PKH change output scripts for transaction creation. // ChangeSource provides change output scripts for transaction creation.
type ChangeSource func() ([]byte, error) type ChangeSource struct {
// NewScript is a closure that produces unique change output scripts per
// invocation.
NewScript func() ([]byte, error)
// ScriptSize is the size in bytes of scripts produced by `NewScript`.
ScriptSize int
}
// NewUnsignedTransaction creates an unsigned transaction paying to one or more // NewUnsignedTransaction creates an unsigned transaction paying to one or more
// non-change outputs. An appropriate transaction fee is included based on the // non-change outputs. An appropriate transaction fee is included based on the
@ -84,10 +91,12 @@ type ChangeSource func() ([]byte, error)
// //
// BUGS: Fee estimation may be off when redeeming non-compressed P2PKH outputs. // BUGS: Fee estimation may be off when redeeming non-compressed P2PKH outputs.
func NewUnsignedTransaction(outputs []*wire.TxOut, feeRatePerKb btcutil.Amount, func NewUnsignedTransaction(outputs []*wire.TxOut, feeRatePerKb btcutil.Amount,
fetchInputs InputSource, fetchChange ChangeSource) (*AuthoredTx, error) { fetchInputs InputSource, changeSource *ChangeSource) (*AuthoredTx, error) {
targetAmount := SumOutputValues(outputs) targetAmount := SumOutputValues(outputs)
estimatedSize := txsizes.EstimateVirtualSize(0, 1, 0, outputs, true) estimatedSize := txsizes.EstimateVirtualSize(
0, 1, 0, outputs, changeSource.ScriptSize,
)
targetFee := txrules.FeeForSerializeSize(feeRatePerKb, estimatedSize) targetFee := txrules.FeeForSerializeSize(feeRatePerKb, estimatedSize)
for { for {
@ -115,8 +124,9 @@ func NewUnsignedTransaction(outputs []*wire.TxOut, feeRatePerKb btcutil.Amount,
} }
} }
maxSignedSize := txsizes.EstimateVirtualSize(p2pkh, p2wpkh, maxSignedSize := txsizes.EstimateVirtualSize(
nested, outputs, true) p2pkh, p2wpkh, nested, outputs, changeSource.ScriptSize,
)
maxRequiredFee := txrules.FeeForSerializeSize(feeRatePerKb, maxSignedSize) maxRequiredFee := txrules.FeeForSerializeSize(feeRatePerKb, maxSignedSize)
remainingAmount := inputAmount - targetAmount remainingAmount := inputAmount - targetAmount
if remainingAmount < maxRequiredFee { if remainingAmount < maxRequiredFee {
@ -130,18 +140,16 @@ func NewUnsignedTransaction(outputs []*wire.TxOut, feeRatePerKb btcutil.Amount,
TxOut: outputs, TxOut: outputs,
LockTime: 0, LockTime: 0,
} }
changeIndex := -1 changeIndex := -1
changeAmount := inputAmount - targetAmount - maxRequiredFee changeAmount := inputAmount - targetAmount - maxRequiredFee
if changeAmount != 0 && !txrules.IsDustAmount(changeAmount, if changeAmount != 0 && !txrules.IsDustAmount(changeAmount,
txsizes.P2WPKHPkScriptSize, txrules.DefaultRelayFeePerKb) { changeSource.ScriptSize, txrules.DefaultRelayFeePerKb) {
changeScript, err := fetchChange()
changeScript, err := changeSource.NewScript()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(changeScript) > txsizes.P2WPKHPkScriptSize {
return nil, errors.New("fee estimation requires change " +
"scripts no larger than P2WPKH output scripts")
}
change := wire.NewTxOut(int64(changeAmount), changeScript) change := wire.NewTxOut(int64(changeAmount), changeScript)
l := len(outputs) l := len(outputs)
unsignedTransaction.TxOut = append(outputs[:l:l], change) unsignedTransaction.TxOut = append(outputs[:l:l], change)

View file

@ -61,7 +61,7 @@ func TestNewUnsignedTransaction(t *testing.T) {
Outputs: p2pkhOutputs(1e6), Outputs: p2pkhOutputs(1e6),
RelayFee: 1e3, RelayFee: 1e3,
ChangeAmount: 1e8 - 1e6 - txrules.FeeForSerializeSize(1e3, ChangeAmount: 1e8 - 1e6 - txrules.FeeForSerializeSize(1e3,
txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(1e6), true)), txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(1e6), txsizes.P2WPKHPkScriptSize)),
InputCount: 1, InputCount: 1,
}, },
2: { 2: {
@ -69,7 +69,7 @@ func TestNewUnsignedTransaction(t *testing.T) {
Outputs: p2pkhOutputs(1e6), Outputs: p2pkhOutputs(1e6),
RelayFee: 1e4, RelayFee: 1e4,
ChangeAmount: 1e8 - 1e6 - txrules.FeeForSerializeSize(1e4, ChangeAmount: 1e8 - 1e6 - txrules.FeeForSerializeSize(1e4,
txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(1e6), true)), txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(1e6), txsizes.P2WPKHPkScriptSize)),
InputCount: 1, InputCount: 1,
}, },
3: { 3: {
@ -77,7 +77,7 @@ func TestNewUnsignedTransaction(t *testing.T) {
Outputs: p2pkhOutputs(1e6, 1e6, 1e6), Outputs: p2pkhOutputs(1e6, 1e6, 1e6),
RelayFee: 1e4, RelayFee: 1e4,
ChangeAmount: 1e8 - 3e6 - txrules.FeeForSerializeSize(1e4, ChangeAmount: 1e8 - 3e6 - txrules.FeeForSerializeSize(1e4,
txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(1e6, 1e6, 1e6), true)), txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(1e6, 1e6, 1e6), txsizes.P2WPKHPkScriptSize)),
InputCount: 1, InputCount: 1,
}, },
4: { 4: {
@ -85,7 +85,7 @@ func TestNewUnsignedTransaction(t *testing.T) {
Outputs: p2pkhOutputs(1e6, 1e6, 1e6), Outputs: p2pkhOutputs(1e6, 1e6, 1e6),
RelayFee: 2.55e3, RelayFee: 2.55e3,
ChangeAmount: 1e8 - 3e6 - txrules.FeeForSerializeSize(2.55e3, ChangeAmount: 1e8 - 3e6 - txrules.FeeForSerializeSize(2.55e3,
txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(1e6, 1e6, 1e6), true)), txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(1e6, 1e6, 1e6), txsizes.P2WPKHPkScriptSize)),
InputCount: 1, InputCount: 1,
}, },
@ -93,7 +93,7 @@ func TestNewUnsignedTransaction(t *testing.T) {
5: { 5: {
UnspentOutputs: p2pkhOutputs(1e8), UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e8 - 545 - txrules.FeeForSerializeSize(1e3, Outputs: p2pkhOutputs(1e8 - 545 - txrules.FeeForSerializeSize(1e3,
txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(0), true))), txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(0), txsizes.P2WPKHPkScriptSize))),
RelayFee: 1e3, RelayFee: 1e3,
ChangeAmount: 545, ChangeAmount: 545,
InputCount: 1, InputCount: 1,
@ -101,7 +101,7 @@ func TestNewUnsignedTransaction(t *testing.T) {
6: { 6: {
UnspentOutputs: p2pkhOutputs(1e8), UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e8 - 546 - txrules.FeeForSerializeSize(1e3, Outputs: p2pkhOutputs(1e8 - 546 - txrules.FeeForSerializeSize(1e3,
txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(0), true))), txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(0), txsizes.P2WPKHPkScriptSize))),
RelayFee: 1e3, RelayFee: 1e3,
ChangeAmount: 546, ChangeAmount: 546,
InputCount: 1, InputCount: 1,
@ -111,7 +111,7 @@ func TestNewUnsignedTransaction(t *testing.T) {
7: { 7: {
UnspentOutputs: p2pkhOutputs(1e8), UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e8 - 1392 - txrules.FeeForSerializeSize(2.55e3, Outputs: p2pkhOutputs(1e8 - 1392 - txrules.FeeForSerializeSize(2.55e3,
txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(0), true))), txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(0), txsizes.P2WPKHPkScriptSize))),
RelayFee: 2.55e3, RelayFee: 2.55e3,
ChangeAmount: 1392, ChangeAmount: 1392,
InputCount: 1, InputCount: 1,
@ -119,7 +119,7 @@ func TestNewUnsignedTransaction(t *testing.T) {
8: { 8: {
UnspentOutputs: p2pkhOutputs(1e8), UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e8 - 1393 - txrules.FeeForSerializeSize(2.55e3, Outputs: p2pkhOutputs(1e8 - 1393 - txrules.FeeForSerializeSize(2.55e3,
txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(0), true))), txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(0), txsizes.P2WPKHPkScriptSize))),
RelayFee: 2.55e3, RelayFee: 2.55e3,
ChangeAmount: 1393, ChangeAmount: 1393,
InputCount: 1, InputCount: 1,
@ -131,7 +131,7 @@ func TestNewUnsignedTransaction(t *testing.T) {
9: { 9: {
UnspentOutputs: p2pkhOutputs(1e8, 1e8), UnspentOutputs: p2pkhOutputs(1e8, 1e8),
Outputs: p2pkhOutputs(1e8 - 546 - txrules.FeeForSerializeSize(1e3, Outputs: p2pkhOutputs(1e8 - 546 - txrules.FeeForSerializeSize(1e3,
txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(0), true))), txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(0), txsizes.P2WPKHPkScriptSize))),
RelayFee: 1e3, RelayFee: 1e3,
ChangeAmount: 546, ChangeAmount: 546,
InputCount: 1, InputCount: 1,
@ -145,7 +145,7 @@ func TestNewUnsignedTransaction(t *testing.T) {
10: { 10: {
UnspentOutputs: p2pkhOutputs(1e8, 1e8), UnspentOutputs: p2pkhOutputs(1e8, 1e8),
Outputs: p2pkhOutputs(1e8 - 545 - txrules.FeeForSerializeSize(1e3, Outputs: p2pkhOutputs(1e8 - 545 - txrules.FeeForSerializeSize(1e3,
txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(0), true))), txsizes.EstimateVirtualSize(1, 0, 0, p2pkhOutputs(0), txsizes.P2WPKHPkScriptSize))),
RelayFee: 1e3, RelayFee: 1e3,
ChangeAmount: 545, ChangeAmount: 545,
InputCount: 1, InputCount: 1,
@ -157,7 +157,7 @@ func TestNewUnsignedTransaction(t *testing.T) {
Outputs: p2pkhOutputs(1e8), Outputs: p2pkhOutputs(1e8),
RelayFee: 1e3, RelayFee: 1e3,
ChangeAmount: 1e8 - txrules.FeeForSerializeSize(1e3, ChangeAmount: 1e8 - txrules.FeeForSerializeSize(1e3,
txsizes.EstimateVirtualSize(2, 0, 0, p2pkhOutputs(1e8), true)), txsizes.EstimateVirtualSize(2, 0, 0, p2pkhOutputs(1e8), txsizes.P2WPKHPkScriptSize)),
InputCount: 2, InputCount: 2,
}, },
@ -172,9 +172,12 @@ func TestNewUnsignedTransaction(t *testing.T) {
}, },
} }
changeSource := func() ([]byte, error) { changeSource := &ChangeSource{
NewScript: func() ([]byte, error) {
// Only length matters for these tests. // Only length matters for these tests.
return make([]byte, txsizes.P2WPKHPkScriptSize), nil return make([]byte, txsizes.P2WPKHPkScriptSize), nil
},
ScriptSize: txsizes.P2WPKHPkScriptSize,
} }
for i, test := range tests { for i, test := range tests {

View file

@ -82,6 +82,16 @@ const (
// - 4 bytes sequence // - 4 bytes sequence
RedeemP2WPKHInputSize = 32 + 4 + 1 + RedeemP2WPKHScriptSize + 4 RedeemP2WPKHInputSize = 32 + 4 + 1 + RedeemP2WPKHScriptSize + 4
// NestedP2WPKHPkScriptSize is the size of a transaction output script
// that pays to a pay-to-witness-key hash nested in P2SH (P2SH-P2WPKH).
// It is calculated as:
//
// - OP_HASH160
// - OP_DATA_20
// - 20 bytes script hash
// - OP_EQUAL
NestedP2WPKHPkScriptSize = 1 + 1 + 20 + 1
// RedeemNestedP2WPKHScriptSize is the worst case size of a transaction // RedeemNestedP2WPKHScriptSize is the worst case size of a transaction
// input script that redeems a pay-to-witness-key hash nested in P2SH // input script that redeems a pay-to-witness-key hash nested in P2SH
// (P2SH-P2WPKH). It is calculated as: // (P2SH-P2WPKH). It is calculated as:
@ -150,12 +160,14 @@ func EstimateSerializeSize(inputCount int, txOuts []*wire.TxOut, addChangeOutput
// from txOuts. The estimate is incremented for an additional P2PKH // from txOuts. The estimate is incremented for an additional P2PKH
// change output if addChangeOutput is true. // change output if addChangeOutput is true.
func EstimateVirtualSize(numP2PKHIns, numP2WPKHIns, numNestedP2WPKHIns int, func EstimateVirtualSize(numP2PKHIns, numP2WPKHIns, numNestedP2WPKHIns int,
txOuts []*wire.TxOut, addChangeOutput bool) int { txOuts []*wire.TxOut, changeScriptSize int) int {
changeSize := 0
outputCount := len(txOuts) outputCount := len(txOuts)
if addChangeOutput {
// We are always using P2WPKH as change output. changeOutputSize := 0
changeSize = P2WPKHOutputSize if changeScriptSize > 0 {
changeOutputSize = 8 +
wire.VarIntSerializeSize(uint64(changeScriptSize)) +
changeScriptSize
outputCount++ outputCount++
} }
@ -170,7 +182,7 @@ func EstimateVirtualSize(numP2PKHIns, numP2WPKHIns, numNestedP2WPKHIns int,
numP2WPKHIns*RedeemP2WPKHInputSize + numP2WPKHIns*RedeemP2WPKHInputSize +
numNestedP2WPKHIns*RedeemNestedP2WPKHInputSize + numNestedP2WPKHIns*RedeemNestedP2WPKHInputSize +
SumOutputSerializeSizes(txOuts) + SumOutputSerializeSizes(txOuts) +
changeSize changeOutputSize
// If this transaction has any witness inputs, we must count the // If this transaction has any witness inputs, we must count the
// witness data. // witness data.

View file

@ -163,8 +163,12 @@ func TestEstimateVirtualSize(t *testing.T) {
t.Fatalf("unable to get test tx: %v", err) t.Fatalf("unable to get test tx: %v", err)
} }
changeScriptSize := 0
if test.change {
changeScriptSize = P2WPKHPkScriptSize
}
est := EstimateVirtualSize(test.p2pkhIns, test.p2wpkhIns, est := EstimateVirtualSize(test.p2pkhIns, test.p2wpkhIns,
test.nestedp2wpkhIns, tx.TxOut, test.change) test.nestedp2wpkhIns, tx.TxOut, changeScriptSize)
if est != test.result { if est != test.result {
t.Fatalf("expected estimated vsize to be %d, "+ t.Fatalf("expected estimated vsize to be %d, "+