lbcwallet/wallet/txsizes/size_test.go

175 lines
6 KiB
Go
Raw Normal View History

package txsizes
Refactor wallet transaction creation code. This began as a change to improve the fee calculation code and evolved into a much larger refactor which improves the readability and modularity of all of the transaction creation code. Transaction fee calculations have been switched from full increments of the relay fee to a proportion based on the transaction size. This means that for a relay fee of 1e3 satoshis/kB, a 500 byte transaction is only required to pay a 5e2 satoshi fee and a 1500 byte transaction only need pay a 1.5e3 fee. The previous code would end up estimating these fees to be 1e3 and 2e3 respectively. Because the previous code would add more fee than needed in almost every case, the transaction size estimations were optimistic (best/smallest case) and signing was done in a loop where the fee was incremented by the relay fee again each time the actual size of the signed transaction rendered the fee too low. This has switched to using worst case transaction size estimates rather than best case, and signing is only performed once. Transaction input signature creation has switched from using txscript.SignatureScript to txscript.SignTxOutput. The new API is able to redeem outputs other than just P2PKH, so the previous restrictions about P2SH outputs being unspendable (except through the signrawtransaction RPC) no longer hold. Several new public packages have been added: wallet/txauthor - transaction authoring and signing wallet/txfees - fee estimations and change output inclusion wallet/txrules - simple consensus and mempool policy rule checks Along with some internal packages: wallet/internal/txsizes - transaction size estimation internal/helpers - context free convenience functions The txsizes package is internal as the estimations it provides are specific for the algorithms used by these new packages.
2016-02-28 05:30:56 +01:00
import (
"bytes"
"encoding/hex"
Refactor wallet transaction creation code. This began as a change to improve the fee calculation code and evolved into a much larger refactor which improves the readability and modularity of all of the transaction creation code. Transaction fee calculations have been switched from full increments of the relay fee to a proportion based on the transaction size. This means that for a relay fee of 1e3 satoshis/kB, a 500 byte transaction is only required to pay a 5e2 satoshi fee and a 1500 byte transaction only need pay a 1.5e3 fee. The previous code would end up estimating these fees to be 1e3 and 2e3 respectively. Because the previous code would add more fee than needed in almost every case, the transaction size estimations were optimistic (best/smallest case) and signing was done in a loop where the fee was incremented by the relay fee again each time the actual size of the signed transaction rendered the fee too low. This has switched to using worst case transaction size estimates rather than best case, and signing is only performed once. Transaction input signature creation has switched from using txscript.SignatureScript to txscript.SignTxOutput. The new API is able to redeem outputs other than just P2PKH, so the previous restrictions about P2SH outputs being unspendable (except through the signrawtransaction RPC) no longer hold. Several new public packages have been added: wallet/txauthor - transaction authoring and signing wallet/txfees - fee estimations and change output inclusion wallet/txrules - simple consensus and mempool policy rule checks Along with some internal packages: wallet/internal/txsizes - transaction size estimation internal/helpers - context free convenience functions The txsizes package is internal as the estimations it provides are specific for the algorithms used by these new packages.
2016-02-28 05:30:56 +01:00
"testing"
"github.com/btcsuite/btcd/wire"
Refactor wallet transaction creation code. This began as a change to improve the fee calculation code and evolved into a much larger refactor which improves the readability and modularity of all of the transaction creation code. Transaction fee calculations have been switched from full increments of the relay fee to a proportion based on the transaction size. This means that for a relay fee of 1e3 satoshis/kB, a 500 byte transaction is only required to pay a 5e2 satoshi fee and a 1500 byte transaction only need pay a 1.5e3 fee. The previous code would end up estimating these fees to be 1e3 and 2e3 respectively. Because the previous code would add more fee than needed in almost every case, the transaction size estimations were optimistic (best/smallest case) and signing was done in a loop where the fee was incremented by the relay fee again each time the actual size of the signed transaction rendered the fee too low. This has switched to using worst case transaction size estimates rather than best case, and signing is only performed once. Transaction input signature creation has switched from using txscript.SignatureScript to txscript.SignTxOutput. The new API is able to redeem outputs other than just P2PKH, so the previous restrictions about P2SH outputs being unspendable (except through the signrawtransaction RPC) no longer hold. Several new public packages have been added: wallet/txauthor - transaction authoring and signing wallet/txfees - fee estimations and change output inclusion wallet/txrules - simple consensus and mempool policy rule checks Along with some internal packages: wallet/internal/txsizes - transaction size estimation internal/helpers - context free convenience functions The txsizes package is internal as the estimations it provides are specific for the algorithms used by these new packages.
2016-02-28 05:30:56 +01:00
)
const (
p2pkhScriptSize = P2PKHPkScriptSize
p2shScriptSize = 23
)
func makeInts(value int, n int) []int {
v := make([]int, n)
for i := range v {
v[i] = value
}
return v
}
func TestEstimateSerializeSize(t *testing.T) {
tests := []struct {
InputCount int
OutputScriptLengths []int
AddChangeOutput bool
ExpectedSizeEstimate int
}{
0: {1, []int{}, false, 159},
1: {1, []int{p2pkhScriptSize}, false, 193},
2: {1, []int{}, true, 193},
3: {1, []int{p2pkhScriptSize}, true, 227},
4: {1, []int{p2shScriptSize}, false, 191},
5: {1, []int{p2shScriptSize}, true, 225},
6: {2, []int{}, false, 308},
7: {2, []int{p2pkhScriptSize}, false, 342},
8: {2, []int{}, true, 342},
9: {2, []int{p2pkhScriptSize}, true, 376},
10: {2, []int{p2shScriptSize}, false, 340},
11: {2, []int{p2shScriptSize}, true, 374},
// 0xfd is discriminant for 16-bit compact ints, compact int
// total size increases from 1 byte to 3.
12: {1, makeInts(p2pkhScriptSize, 0xfc), false, 8727},
13: {1, makeInts(p2pkhScriptSize, 0xfd), false, 8727 + P2PKHOutputSize + 2},
14: {1, makeInts(p2pkhScriptSize, 0xfc), true, 8727 + P2PKHOutputSize + 2},
15: {0xfc, []int{}, false, 37558},
16: {0xfd, []int{}, false, 37558 + RedeemP2PKHInputSize + 2},
}
for i, test := range tests {
outputs := make([]*wire.TxOut, 0, len(test.OutputScriptLengths))
for _, l := range test.OutputScriptLengths {
outputs = append(outputs, &wire.TxOut{PkScript: make([]byte, l)})
}
actualEstimate := EstimateSerializeSize(test.InputCount, outputs, test.AddChangeOutput)
if actualEstimate != test.ExpectedSizeEstimate {
t.Errorf("Test %d: Got %v: Expected %v", i, actualEstimate, test.ExpectedSizeEstimate)
}
}
}
func TestEstimateVirtualSize(t *testing.T) {
type estimateVSizeTest struct {
tx func() (*wire.MsgTx, error)
p2wpkhIns int
nestedp2wpkhIns int
p2pkhIns int
change bool
result int
}
// TODO(halseth): add tests for more combination out inputs/outputs.
tests := []estimateVSizeTest{
// Spending P2WPKH to two outputs. Example adapted from example in BIP-143.
{
tx: func() (*wire.MsgTx, error) {
txHex := "01000000000101ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac0247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000"
b, err := hex.DecodeString(txHex)
if err != nil {
return nil, err
}
tx := &wire.MsgTx{}
err = tx.Deserialize(bytes.NewReader(b))
if err != nil {
return nil, err
}
return tx, nil
},
p2wpkhIns: 1,
result: 147,
},
{
// Spending P2SH-P2WPKH to two outputs. Example adapted from example in BIP-143.
tx: func() (*wire.MsgTx, error) {
txHex := "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000"
b, err := hex.DecodeString(txHex)
if err != nil {
return nil, err
}
tx := &wire.MsgTx{}
err = tx.Deserialize(bytes.NewReader(b))
if err != nil {
return nil, err
}
return tx, nil
},
nestedp2wpkhIns: 1,
result: 170,
},
{
// Spendin P2WPKH to on output, adding one change output. We reuse
// the transaction spending to two outputs, removing one of them.
tx: func() (*wire.MsgTx, error) {
txHex := "01000000000101ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac0247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000"
b, err := hex.DecodeString(txHex)
if err != nil {
return nil, err
}
tx := &wire.MsgTx{}
err = tx.Deserialize(bytes.NewReader(b))
if err != nil {
return nil, err
}
// Only keep the first output.
tx.TxOut = []*wire.TxOut{tx.TxOut[0]}
return tx, nil
},
p2wpkhIns: 1,
change: true,
result: 144,
},
{
// Spending one P2PKH to two P2PKH outputs (no witness data).
tx: func() (*wire.MsgTx, error) {
txHex := "0100000001a4c91c9720157a5ee582a7966471d9c70d0a860fa7757b4c42a535a12054a4c9000000006c493046022100d49c452a00e5b1213ac84d92269510a05a584a4d0949bd7d0ad4e3408ac8e80a022100bf98707ffaf1eb9dff146f7da54e68651c0a27e3653ec3882b7a95202328579c01210332d98672a4246fe917b9c724c339e757d46b1ffde3fb27fdc680b4bb29b6ad59ffffffff02a0860100000000001976a9144fb55ee0524076acd4c14e7773561e4c298c8e2788ac20688a0b000000001976a914cb7f6bb8e95a2cd06423932cfbbce73d16a18df088ac00000000"
b, err := hex.DecodeString(txHex)
if err != nil {
return nil, err
}
tx := &wire.MsgTx{}
err = tx.Deserialize(bytes.NewReader(b))
if err != nil {
return nil, err
}
return tx, nil
},
p2pkhIns: 1,
result: 227,
},
}
for _, test := range tests {
tx, err := test.tx()
if err != nil {
t.Fatalf("unable to get test tx: %v", err)
}
est := EstimateVirtualSize(test.p2pkhIns, test.p2wpkhIns,
test.nestedp2wpkhIns, tx.TxOut, test.change)
if est != test.result {
t.Fatalf("expected estimated vsize to be %d, "+
"instead got %d", test.result, est)
}
}
}