Skip to content

Commit 0129ebf

Browse files
committed
tapfreighter+tapscript: replace weight estimator
In this commit, we remove the amount of tap-specific code handling fee estimation by replacing EstimateWeight() with EstimateFee(). This is a thin wrapper around txsizes.EstimateVirttualSize(), and is very similar to the implementation in txauthor/NewUnsignedTransaction.
1 parent d733b81 commit 0129ebf

File tree

3 files changed

+64
-54
lines changed

3 files changed

+64
-54
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ require (
1010
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2
1111
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
1212
github.com/btcsuite/btcwallet v0.16.10-0.20231017144732-e3ff37491e9c
13+
github.com/btcsuite/btcwallet/wallet/txrules v1.2.0
14+
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3
1315
github.com/caddyserver/certmagic v0.17.2
1416
github.com/davecgh/go-spew v1.1.1
1517
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
@@ -60,8 +62,6 @@ require (
6062
github.com/andybalholm/brotli v1.0.3 // indirect
6163
github.com/beorn7/perks v1.0.1 // indirect
6264
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 // indirect
63-
github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 // indirect
64-
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 // indirect
6565
github.com/btcsuite/btcwallet/walletdb v1.4.0 // indirect
6666
github.com/btcsuite/btcwallet/wtxmgr v1.5.0 // indirect
6767
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect

tapfreighter/wallet.go

Lines changed: 21 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/btcsuite/btcd/chaincfg/chainhash"
1919
"github.com/btcsuite/btcd/txscript"
2020
"github.com/btcsuite/btcd/wire"
21+
"github.com/btcsuite/btcwallet/wallet/txrules"
2122
"github.com/davecgh/go-spew/spew"
2223
"github.com/lightninglabs/taproot-assets/address"
2324
"github.com/lightninglabs/taproot-assets/asset"
@@ -28,7 +29,6 @@ import (
2829
"github.com/lightninglabs/taproot-assets/tapgarden"
2930
"github.com/lightninglabs/taproot-assets/tappsbt"
3031
"github.com/lightninglabs/taproot-assets/tapscript"
31-
"github.com/lightningnetwork/lnd/input"
3232
"github.com/lightningnetwork/lnd/keychain"
3333
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
3434
)
@@ -1654,61 +1654,30 @@ func addAnchorPsbtInputs(btcPkt *psbt.Packet, vPkt *tappsbt.VPacket,
16541654
}
16551655

16561656
// Now that we've added an extra input, we'll want to re-calculate the
1657-
// total weight of the transaction, so we can ensure we're paying
1658-
// enough in fees.
1659-
var (
1660-
weightEstimator input.TxWeightEstimator
1661-
inputAmt, outputAmt int64
1662-
)
1663-
for _, pIn := range btcPkt.Inputs {
1664-
inputAmt += pIn.WitnessUtxo.Value
1665-
1666-
inputPkScript := pIn.WitnessUtxo.PkScript
1667-
switch {
1668-
case txscript.IsPayToWitnessPubKeyHash(inputPkScript):
1669-
weightEstimator.AddP2WKHInput()
1670-
1671-
case txscript.IsPayToScriptHash(inputPkScript):
1672-
weightEstimator.AddNestedP2WKHInput()
1673-
1674-
case txscript.IsPayToTaproot(inputPkScript):
1675-
weightEstimator.AddTaprootKeySpendInput(
1676-
txscript.SigHashDefault,
1677-
)
1678-
default:
1679-
return fmt.Errorf("unknown pkScript: %x",
1680-
inputPkScript)
1657+
// total vsize of the transaction and the necessary fee at the desired
1658+
// fee rate.
1659+
inputScripts := make([][]byte, 0, len(btcPkt.Inputs))
1660+
for inputIdx, input := range btcPkt.Inputs {
1661+
if input.WitnessUtxo == nil {
1662+
return fmt.Errorf("PSBT input %d doesn't specify "+
1663+
"witness UTXO, which is not supported",
1664+
inputIdx)
16811665
}
1682-
}
1683-
for _, txOut := range btcPkt.UnsignedTx.TxOut {
1684-
outputAmt += txOut.Value
16851666

1686-
addrType, _, _, err := txscript.ExtractPkScriptAddrs(
1687-
txOut.PkScript, params,
1688-
)
1689-
if err != nil {
1690-
return err
1667+
if len(input.WitnessUtxo.PkScript) == 0 {
1668+
return fmt.Errorf("input %d on psbt missing "+
1669+
"pkscript", inputIdx)
16911670
}
16921671

1693-
switch addrType {
1694-
case txscript.WitnessV0PubKeyHashTy:
1695-
weightEstimator.AddP2WKHOutput()
1696-
1697-
case txscript.WitnessV0ScriptHashTy:
1698-
weightEstimator.AddP2WSHOutput()
1699-
1700-
case txscript.WitnessV1TaprootTy:
1701-
weightEstimator.AddP2TROutput()
1702-
default:
1703-
return fmt.Errorf("unknown pkscript: %x",
1704-
txOut.PkScript)
1705-
}
1672+
inputScripts = append(inputScripts, input.WitnessUtxo.PkScript)
17061673
}
17071674

1708-
// With this, we can now calculate the total fee we need to pay. We'll
1709-
// also make sure to round up the required fee to the floor.
1710-
totalWeight := int64(weightEstimator.Weight())
1711-
requiredFee := feeRate.FeeForWeight(totalWeight)
1675+
estimatedSize, requiredFee := tapscript.EstimateFee(
1676+
inputScripts, btcPkt.UnsignedTx.TxOut, feeRate,
1677+
)
1678+
log.Infof("Estimated TX vsize: %d", estimatedSize)
1679+
log.Infof("TX required fee before change adjustment: %d at feerate "+
1680+
"%d sat/vB", requiredFee, feeRate.FeePerKVByte()/1000)
17121681

17131682
// Given the current fee (which doesn't account for our input) and the
17141683
// total fee we want to pay, we'll adjust the wallet's change output
@@ -1729,8 +1698,8 @@ func addAnchorPsbtInputs(btcPkt *psbt.Packet, vPkt *tappsbt.VPacket,
17291698
// The fee may exceed the total value of the change output, which means
17301699
// this spend is impossible with the given inputs and fee rate.
17311700
if changeValue-feeDelta < 0 {
1732-
return fmt.Errorf("fee of %d sats exceeds change amount of %d"+
1733-
"sats", requiredFee, changeValue)
1701+
return fmt.Errorf("fee exceeds change amount: (fee=%d, "+
1702+
"change=%d) ", requiredFee, changeValue)
17341703
}
17351704

17361705
btcPkt.UnsignedTx.TxOut[lastIdx].Value -= feeDelta

tapscript/util.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ package tapscript
33
import (
44
"github.com/btcsuite/btcd/btcec/v2"
55
"github.com/btcsuite/btcd/btcec/v2/schnorr"
6+
"github.com/btcsuite/btcd/btcutil"
67
"github.com/btcsuite/btcd/chaincfg/chainhash"
78
"github.com/btcsuite/btcd/txscript"
9+
"github.com/btcsuite/btcd/wire"
10+
"github.com/btcsuite/btcwallet/wallet/txsizes"
811
"github.com/lightninglabs/taproot-assets/commitment"
12+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
913
)
1014

1115
// PayToAddrScript constructs a P2TR script that embeds a Taproot Asset
@@ -31,3 +35,40 @@ func PayToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) {
3135
AddData(schnorr.SerializePubKey(taprootKey)).
3236
Script()
3337
}
38+
39+
// EstimateFee provides a worst-case fee and vsize estimate for a transaction
40+
// built from the given inputs and outputs. This mirrors the fee estimation
41+
// implemented in btcwallet/wallet/txauthor/author.go:NewUnsignedTransaction()
42+
// EstimateFee assumes that a change output (or a dummy output for change) is
43+
// included in the set of given outputs.
44+
func EstimateFee(inputScripts [][]byte, outputs []*wire.TxOut,
45+
feeRate chainfee.SatPerKWeight) (int, btcutil.Amount) {
46+
47+
// Count the types of input scripts.
48+
var nested, p2wpkh, p2tr, p2pkh int
49+
for _, pkScript := range inputScripts {
50+
switch {
51+
// If this is a p2sh output, we assume this is a
52+
// nested P2WKH.
53+
case txscript.IsPayToScriptHash(pkScript):
54+
nested++
55+
case txscript.IsPayToWitnessPubKeyHash(pkScript):
56+
p2wpkh++
57+
case txscript.IsPayToTaproot(pkScript):
58+
p2tr++
59+
default:
60+
p2pkh++
61+
}
62+
}
63+
64+
// The change output is already in the set of given outputs, so we don't
65+
// need to account for an additional output.
66+
maxSignedSize := txsizes.EstimateVirtualSize(
67+
p2pkh, p2tr, p2wpkh, nested, outputs, 0,
68+
)
69+
maxRequiredFee := feeRate.FeePerKVByte().FeeForVSize(
70+
int64(maxSignedSize),
71+
)
72+
73+
return maxSignedSize, maxRequiredFee
74+
}

0 commit comments

Comments
 (0)