mirror of
https://github.com/LBRYFoundation/lbcwallet.git
synced 2025-08-23 17:47:29 +00:00
wallet: extend ChangeSource to support all key scopes
This commit is contained in:
parent
ddbe5ecee4
commit
b318e99f4f
8 changed files with 131 additions and 54 deletions
|
@ -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
1
go.mod
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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, "+
|
||||||
|
|
Loading…
Add table
Reference in a new issue