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:
Jim Posen 2017-09-25 16:35:19 -07:00 committed by Olaoluwa Osuntokun
parent d894917458
commit fb32c3f73d
2 changed files with 215 additions and 4 deletions

@ -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

@ -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())
}
}
}