lnwallet: Create type to estimate transaction weights.
The new TxWeightEstimator type can be used to replace the fee estimation code for funding transactions and transactions in breacharbiter.
This commit is contained in:
parent
d894917458
commit
fb32c3f73d
@ -2,9 +2,16 @@ package lnwallet
|
||||
|
||||
import (
|
||||
"github.com/roasbeef/btcd/blockchain"
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
)
|
||||
|
||||
const (
|
||||
// witnessScaleFactor determines the level of "discount" witness data
|
||||
// receives compared to "base" data. A scale factor of 4, denotes that
|
||||
// witness data is 1/4 as cheap as regular non-witness data. Value copied
|
||||
// here for convenience.
|
||||
witnessScaleFactor = blockchain.WitnessScaleFactor
|
||||
|
||||
// The weight(weight), which is different from the !size! (see BIP-141),
|
||||
// is calculated as:
|
||||
// Weight = 4 * BaseSize + WitnessSize (weight).
|
||||
@ -18,6 +25,12 @@ const (
|
||||
// - WitnessScriptSHA256: 32 bytes
|
||||
P2WSHSize = 1 + 1 + 32
|
||||
|
||||
// P2PKHOutputSize 34 bytes
|
||||
// - value: 8 bytes
|
||||
// - var_int: 1 byte (pkscript_length)
|
||||
// - pkscript (p2pkh): 25 bytes
|
||||
P2PKHOutputSize = 8 + 1 + 25
|
||||
|
||||
// P2WKHOutputSize 31 bytes
|
||||
// - value: 8 bytes
|
||||
// - var_int: 1 byte (pkscript_length)
|
||||
@ -36,12 +49,20 @@ const (
|
||||
// - PublicKeyHASH160: 20 bytes
|
||||
P2WPKHSize = 1 + 1 + 20
|
||||
|
||||
// P2WKHWitnessSize 108 bytes
|
||||
// P2PKHScriptSigSize 108 bytes
|
||||
// - OP_DATA: 1 byte (signature length)
|
||||
// - signature
|
||||
// - OP_DATA: 1 byte (pubkey length)
|
||||
// - pubkey
|
||||
P2WKHWitnessSize = 1 + 73 + 1 + 33
|
||||
P2PKHScriptSigSize = 1 + 73 + 1 + 33
|
||||
|
||||
// P2WKHWitnessSize 109 bytes
|
||||
// - number_of_witness_elements: 1 byte
|
||||
// - signature_length: 1 byte
|
||||
// - signature
|
||||
// - pubkey_length: 1 byte
|
||||
// - pubkey
|
||||
P2WKHWitnessSize = 1 + 1 + 73 + 1 + 33
|
||||
|
||||
// MultiSigSize 71 bytes
|
||||
// - OP_2: 1 byte
|
||||
@ -106,6 +127,11 @@ const (
|
||||
// - Marker: 1 byte
|
||||
WitnessHeaderSize = 1 + 1
|
||||
|
||||
// BaseTxSize 8 bytes
|
||||
// - Version: 4 bytes
|
||||
// - LockTime: 4 bytes
|
||||
BaseTxSize = 4 + 4
|
||||
|
||||
// BaseSweepTxSize 42 + 41 * num-swept-inputs bytes
|
||||
// - Version: 4 bytes
|
||||
// - WitnessHeader <---- part of the witness data
|
||||
@ -134,13 +160,13 @@ const (
|
||||
CommitmentDelayOutput + CommitmentKeyHashOutput + 4
|
||||
|
||||
// BaseCommitmentTxWeight 500 weight
|
||||
BaseCommitmentTxWeight = blockchain.WitnessScaleFactor * BaseCommitmentTxSize
|
||||
BaseCommitmentTxWeight = witnessScaleFactor * BaseCommitmentTxSize
|
||||
|
||||
// WitnessCommitmentTxWeight 224 weight
|
||||
WitnessCommitmentTxWeight = WitnessHeaderSize + WitnessSize
|
||||
|
||||
// HTLCWeight 172 weight
|
||||
HTLCWeight = blockchain.WitnessScaleFactor * HTLCSize
|
||||
HTLCWeight = witnessScaleFactor * HTLCSize
|
||||
|
||||
// HtlcTimeoutWeight is the weight of the HTLC timeout transaction
|
||||
// which will transition an outgoing HTLC to the delay-and-claim state.
|
||||
@ -301,3 +327,65 @@ func estimateCommitTxWeight(count int, prediction bool) int64 {
|
||||
|
||||
return htlcWeight + baseWeight + witnessWeight
|
||||
}
|
||||
|
||||
// TxWeightEstimator is able to calculate weight estimates for transactions
|
||||
// based on the input and output types. For purposes of estimation, all
|
||||
// signatures are assumed to be of the maximum possible size, 73 bytes.
|
||||
type TxWeightEstimator struct {
|
||||
hasWitness bool
|
||||
inputCount uint32
|
||||
outputCount uint32
|
||||
inputSize int
|
||||
inputWitnessSize int
|
||||
outputSize int
|
||||
}
|
||||
|
||||
// AddP2PKHInput updates the weight estimate to account for an additional input
|
||||
// spending a P2PKH output.
|
||||
func (twe *TxWeightEstimator) AddP2PKHInput() {
|
||||
twe.inputSize += InputSize + P2PKHScriptSigSize
|
||||
twe.inputWitnessSize++
|
||||
twe.inputCount++
|
||||
}
|
||||
|
||||
// AddP2WKHInput updates the weight estimate to account for an additional input
|
||||
// spending a P2PWKH output.
|
||||
func (twe *TxWeightEstimator) AddP2WKHInput() {
|
||||
twe.inputSize += InputSize
|
||||
twe.inputWitnessSize += P2WKHWitnessSize
|
||||
twe.inputCount++
|
||||
twe.hasWitness = true
|
||||
}
|
||||
|
||||
// AddP2PKHOutput updates the weight estimate to account for an additional P2PKH
|
||||
// output.
|
||||
func (twe *TxWeightEstimator) AddP2PKHOutput() {
|
||||
twe.outputSize += P2PKHOutputSize
|
||||
twe.outputCount++
|
||||
}
|
||||
|
||||
// AddP2WKHOutput updates the weight estimate to account for an additional P2WKH
|
||||
// output.
|
||||
func (twe *TxWeightEstimator) AddP2WKHOutput() {
|
||||
twe.outputSize += P2WKHOutputSize
|
||||
twe.outputCount++
|
||||
}
|
||||
|
||||
// AddP2WSHOutput updates the weight estimate to account for an additional P2WSH
|
||||
// output.
|
||||
func (twe *TxWeightEstimator) AddP2WSHOutput() {
|
||||
twe.outputSize += P2WSHOutputSize
|
||||
twe.outputCount++
|
||||
}
|
||||
|
||||
// Weight gets the estimated weight of the transaction.
|
||||
func (twe *TxWeightEstimator) Weight() int {
|
||||
txSizeStripped := BaseTxSize +
|
||||
wire.VarIntSerializeSize(uint64(twe.inputCount)) + twe.inputSize +
|
||||
wire.VarIntSerializeSize(uint64(twe.outputCount)) + twe.outputSize
|
||||
weight := txSizeStripped * witnessScaleFactor
|
||||
if twe.hasWitness {
|
||||
weight += WitnessHeaderSize + twe.inputWitnessSize
|
||||
}
|
||||
return weight
|
||||
}
|
||||
|
123
lnwallet/size_test.go
Normal file
123
lnwallet/size_test.go
Normal file
@ -0,0 +1,123 @@
|
||||
package lnwallet_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/roasbeef/btcd/blockchain"
|
||||
"github.com/roasbeef/btcd/chaincfg"
|
||||
"github.com/roasbeef/btcd/txscript"
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
"github.com/roasbeef/btcutil"
|
||||
)
|
||||
|
||||
// TestTxWeightEstimator tests that transaction weight estimates are calculated
|
||||
// correctly by comparing against an actual (though invalid) transaction
|
||||
// matching the template.
|
||||
func TestTxWeightEstimator(t *testing.T) {
|
||||
netParams := &chaincfg.MainNetParams
|
||||
|
||||
p2pkhAddr, err := btcutil.NewAddressPubKeyHash(
|
||||
make([]byte, 20), netParams)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate address: %v", err)
|
||||
}
|
||||
p2pkhScript, err := txscript.PayToAddrScript(p2pkhAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate scriptPubKey: %v", err)
|
||||
}
|
||||
|
||||
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(
|
||||
make([]byte, 20), netParams)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate address: %v", err)
|
||||
}
|
||||
p2wkhScript, err := txscript.PayToAddrScript(p2wkhAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate scriptPubKey: %v", err)
|
||||
}
|
||||
|
||||
p2wshAddr, err := btcutil.NewAddressWitnessScriptHash(
|
||||
make([]byte, 32), netParams)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate address: %v", err)
|
||||
}
|
||||
p2wshScript, err := txscript.PayToAddrScript(p2wshAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate scriptPubKey: %v", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
numP2PKHInputs int
|
||||
numP2WKHInputs int
|
||||
numP2PKHOutputs int
|
||||
numP2WKHOutputs int
|
||||
numP2WSHOutputs int
|
||||
}{
|
||||
{
|
||||
numP2PKHInputs: 1,
|
||||
numP2PKHOutputs: 2,
|
||||
},
|
||||
{
|
||||
numP2PKHInputs: 1,
|
||||
numP2WKHInputs: 1,
|
||||
numP2WKHOutputs: 1,
|
||||
numP2WSHOutputs: 1,
|
||||
},
|
||||
{
|
||||
numP2WKHInputs: 1,
|
||||
numP2WKHOutputs: 1,
|
||||
numP2WSHOutputs: 1,
|
||||
},
|
||||
{
|
||||
numP2WKHInputs: 2,
|
||||
numP2WKHOutputs: 1,
|
||||
numP2WSHOutputs: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range testCases {
|
||||
var weightEstimate lnwallet.TxWeightEstimator
|
||||
tx := wire.NewMsgTx(1)
|
||||
|
||||
for j := 0; j < test.numP2PKHInputs; j++ {
|
||||
weightEstimate.AddP2PKHInput()
|
||||
|
||||
signature := make([]byte, 73)
|
||||
compressedPubKey := make([]byte, 33)
|
||||
scriptSig, err := txscript.NewScriptBuilder().AddData(signature).
|
||||
AddData(compressedPubKey).Script()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate scriptSig: %v", err)
|
||||
}
|
||||
|
||||
tx.AddTxIn(&wire.TxIn{SignatureScript: scriptSig})
|
||||
}
|
||||
for j := 0; j < test.numP2WKHInputs; j++ {
|
||||
weightEstimate.AddP2WKHInput()
|
||||
|
||||
signature := make([]byte, 73)
|
||||
compressedPubKey := make([]byte, 33)
|
||||
witness := wire.TxWitness{signature, compressedPubKey}
|
||||
tx.AddTxIn(&wire.TxIn{Witness: witness})
|
||||
}
|
||||
for j := 0; j < test.numP2PKHOutputs; j++ {
|
||||
weightEstimate.AddP2PKHOutput()
|
||||
tx.AddTxOut(&wire.TxOut{PkScript: p2pkhScript})
|
||||
}
|
||||
for j := 0; j < test.numP2WKHOutputs; j++ {
|
||||
weightEstimate.AddP2WKHOutput()
|
||||
tx.AddTxOut(&wire.TxOut{PkScript: p2wkhScript})
|
||||
}
|
||||
for j := 0; j < test.numP2WSHOutputs; j++ {
|
||||
weightEstimate.AddP2WSHOutput()
|
||||
tx.AddTxOut(&wire.TxOut{PkScript: p2wshScript})
|
||||
}
|
||||
|
||||
expectedWeight := blockchain.GetTransactionWeight(btcutil.NewTx(tx))
|
||||
if weightEstimate.Weight() != int(expectedWeight) {
|
||||
t.Errorf("Case %d: Got wrong weight: expected %d, got %d",
|
||||
i, expectedWeight, weightEstimate.Weight())
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user