blockchain: add table driven unit tests for CalcSequenceLock
This commit is contained in:
parent
de709f28f6
commit
e855c0dd82
1 changed files with 368 additions and 0 deletions
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/btcsuite/btcd/blockchain"
|
"github.com/btcsuite/btcd/blockchain"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -111,3 +112,370 @@ func TestHaveBlock(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCalcSequenceLock tests the LockTimeToSequence function, and the
|
||||||
|
// CalcSequenceLock method of a Chain instance. The tests exercise several
|
||||||
|
// combinations of inputs to the CalcSequenceLock function in order to ensure
|
||||||
|
// the returned SequenceLocks are correct for each test instance.
|
||||||
|
func TestCalcSequenceLock(t *testing.T) {
|
||||||
|
fileName := "blk_0_to_4.dat.bz2"
|
||||||
|
blockTmp, err := loadBlocks(fileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error loading file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var blocks []*btcutil.Block
|
||||||
|
for _, block := range blockTmp {
|
||||||
|
blocks = append(blocks, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new database and chain instance to run tests against.
|
||||||
|
chain, teardownFunc, err := chainSetup("haveblock", &chaincfg.MainNetParams)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to setup chain instance: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer teardownFunc()
|
||||||
|
|
||||||
|
// Since we're not dealing with the real block chain, disable
|
||||||
|
// checkpoints and set the coinbase maturity to 1.
|
||||||
|
chain.DisableCheckpoints(true)
|
||||||
|
chain.TstSetCoinbaseMaturity(1)
|
||||||
|
|
||||||
|
// Load all the blocks into our test chain.
|
||||||
|
for i := 1; i < len(blocks); i++ {
|
||||||
|
_, isOrphan, err := chain.ProcessBlock(blocks[i], blockchain.BFNone)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ProcessBlock fail on block %v: %v\n", i, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isOrphan {
|
||||||
|
t.Errorf("ProcessBlock incorrectly returned block %v "+
|
||||||
|
"is an orphan\n", i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create with all the utxos within the create created above.
|
||||||
|
utxoView := blockchain.NewUtxoViewpoint()
|
||||||
|
for blockHeight, block := range blocks {
|
||||||
|
for _, tx := range block.Transactions() {
|
||||||
|
utxoView.AddTxOuts(tx, int32(blockHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
utxoView.SetBestHash(blocks[len(blocks)-1].Hash())
|
||||||
|
|
||||||
|
// The median past time from the point of view of the second to last
|
||||||
|
// block in the chain.
|
||||||
|
medianTime := blocks[2].MsgBlock().Header.Timestamp.Unix()
|
||||||
|
|
||||||
|
// The median past time of the *next* block will be the timestamp of
|
||||||
|
// the 2nd block due to the way MTP is calculated in order to be
|
||||||
|
// compatible with Bitcoin Core.
|
||||||
|
nextMedianTime := blocks[2].MsgBlock().Header.Timestamp.Unix()
|
||||||
|
|
||||||
|
// We'll refer to this utxo within each input in the transactions
|
||||||
|
// created below. This block that includes this UTXO has a height of 4.
|
||||||
|
targetTx := blocks[4].Transactions()[0]
|
||||||
|
utxo := wire.OutPoint{
|
||||||
|
Hash: *targetTx.Hash(),
|
||||||
|
Index: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an additional transaction which will serve as our unconfirmed
|
||||||
|
// output.
|
||||||
|
var fakeScript []byte
|
||||||
|
unConfTx := &wire.MsgTx{
|
||||||
|
TxOut: []*wire.TxOut{
|
||||||
|
&wire.TxOut{
|
||||||
|
PkScript: fakeScript,
|
||||||
|
Value: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
unConfUtxo := wire.OutPoint{
|
||||||
|
Hash: unConfTx.TxHash(),
|
||||||
|
Index: 0,
|
||||||
|
}
|
||||||
|
// Adding a utxo with a height of 0x7fffffff indicates that the output
|
||||||
|
// is currently unmined.
|
||||||
|
utxoView.AddTxOuts(btcutil.NewTx(unConfTx), 0x7fffffff)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
tx *btcutil.Tx
|
||||||
|
view *blockchain.UtxoViewpoint
|
||||||
|
|
||||||
|
want *blockchain.SequenceLock
|
||||||
|
|
||||||
|
mempool bool
|
||||||
|
}{
|
||||||
|
// A transaction of version one should disable sequence locks
|
||||||
|
// as the new sequence number semantics only apply to
|
||||||
|
// transactions version 2 or higher.
|
||||||
|
{
|
||||||
|
tx: btcutil.NewTx(&wire.MsgTx{
|
||||||
|
Version: 1,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(false, 3),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
view: utxoView,
|
||||||
|
want: &blockchain.SequenceLock{
|
||||||
|
Seconds: -1,
|
||||||
|
BlockHeight: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// A transaction with a single input, that a max int sequence
|
||||||
|
// number. This sequence number has the high bit set, so
|
||||||
|
// sequence locks should be disabled.
|
||||||
|
{
|
||||||
|
tx: btcutil.NewTx(&wire.MsgTx{
|
||||||
|
Version: 2,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: wire.MaxTxInSequenceNum,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
view: utxoView,
|
||||||
|
want: &blockchain.SequenceLock{
|
||||||
|
Seconds: -1,
|
||||||
|
BlockHeight: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// A transaction with a single input whose lock time is
|
||||||
|
// expressed in seconds. However, the specified lock time is
|
||||||
|
// below the required floor for time based lock times since
|
||||||
|
// they have time granularity of 512 seconds. As a result, the
|
||||||
|
// seconds lock-time should be just before the median time of
|
||||||
|
// the targeted block.
|
||||||
|
{
|
||||||
|
tx: btcutil.NewTx(&wire.MsgTx{
|
||||||
|
Version: 2,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(true, 2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
view: utxoView,
|
||||||
|
want: &blockchain.SequenceLock{
|
||||||
|
Seconds: medianTime - 1,
|
||||||
|
BlockHeight: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// A transaction with a single input whose lock time is
|
||||||
|
// expressed in seconds. The number of seconds should be 1023
|
||||||
|
// seconds after the median past time of the last block in the
|
||||||
|
// chain.
|
||||||
|
{
|
||||||
|
tx: btcutil.NewTx(&wire.MsgTx{
|
||||||
|
Version: 2,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(true, 1024),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
view: utxoView,
|
||||||
|
want: &blockchain.SequenceLock{
|
||||||
|
Seconds: medianTime + 1023,
|
||||||
|
BlockHeight: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// A transaction with multiple inputs. The first input has a
|
||||||
|
// sequence lock in blocks with a value of 4. The last input
|
||||||
|
// has a sequence number with a value of 5, but has the disable
|
||||||
|
// bit set. So the first lock should be selected as it's the
|
||||||
|
// target lock as its the furthest in the future lock that
|
||||||
|
// isn't disabled.
|
||||||
|
{
|
||||||
|
tx: btcutil.NewTx(&wire.MsgTx{
|
||||||
|
Version: 2,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(true, 2560),
|
||||||
|
},
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(false, 3) |
|
||||||
|
wire.SequenceLockTimeDisabled,
|
||||||
|
},
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(false, 3),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
view: utxoView,
|
||||||
|
want: &blockchain.SequenceLock{
|
||||||
|
Seconds: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1,
|
||||||
|
BlockHeight: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Transaction has a single input spending the genesis block
|
||||||
|
// transaction. The input's sequence number is encodes a
|
||||||
|
// relative lock-time in blocks (3 blocks). The sequence lock
|
||||||
|
// should have a value of -1 for seconds, but a block height of
|
||||||
|
// 6 meaning it can be included at height 7.
|
||||||
|
{
|
||||||
|
tx: btcutil.NewTx(&wire.MsgTx{
|
||||||
|
Version: 2,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(false, 3),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
view: utxoView,
|
||||||
|
want: &blockchain.SequenceLock{
|
||||||
|
Seconds: -1,
|
||||||
|
BlockHeight: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// A transaction with two inputs with lock times expressed in
|
||||||
|
// seconds. The selected sequence lock value for seconds should
|
||||||
|
// be the time further in the future.
|
||||||
|
{
|
||||||
|
tx: btcutil.NewTx(&wire.MsgTx{
|
||||||
|
Version: 2,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(true, 5120),
|
||||||
|
},
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(true, 2560),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
view: utxoView,
|
||||||
|
want: &blockchain.SequenceLock{
|
||||||
|
Seconds: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1,
|
||||||
|
BlockHeight: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// A transaction with two inputs with lock times expressed in
|
||||||
|
// seconds. The selected sequence lock value for blocks should
|
||||||
|
// be the height further in the future, so a height of 10
|
||||||
|
// indicating in can be included at height 7.
|
||||||
|
{
|
||||||
|
tx: btcutil.NewTx(&wire.MsgTx{
|
||||||
|
Version: 2,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(false, 1),
|
||||||
|
},
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(false, 7),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
view: utxoView,
|
||||||
|
want: &blockchain.SequenceLock{
|
||||||
|
Seconds: -1,
|
||||||
|
BlockHeight: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// A transaction with multiple inputs. Two inputs are time
|
||||||
|
// based, and the other two are input maturity based. The lock
|
||||||
|
// lying further into the future for both inputs should be
|
||||||
|
// chosen.
|
||||||
|
{
|
||||||
|
tx: btcutil.NewTx(&wire.MsgTx{
|
||||||
|
Version: 2,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(true, 2560),
|
||||||
|
},
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(true, 6656),
|
||||||
|
},
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(false, 3),
|
||||||
|
},
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: utxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(false, 9),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
view: utxoView,
|
||||||
|
want: &blockchain.SequenceLock{
|
||||||
|
Seconds: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1,
|
||||||
|
BlockHeight: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// A transaction with a single unconfirmed input. As the input
|
||||||
|
// is confirmed, the height of the input should be interpreted
|
||||||
|
// as the height of the *next* block. So the relative block
|
||||||
|
// lock should be based from a height of 5 rather than a height
|
||||||
|
// of 4.
|
||||||
|
{
|
||||||
|
tx: btcutil.NewTx(&wire.MsgTx{
|
||||||
|
Version: 2,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: unConfUtxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(false, 2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
view: utxoView,
|
||||||
|
want: &blockchain.SequenceLock{
|
||||||
|
Seconds: -1,
|
||||||
|
BlockHeight: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// A transaction with a single unconfirmed input. The input has
|
||||||
|
// a time based lock, so the lock time should be based off the
|
||||||
|
// MTP of the *next* block.
|
||||||
|
{
|
||||||
|
tx: btcutil.NewTx(&wire.MsgTx{
|
||||||
|
Version: 2,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
&wire.TxIn{
|
||||||
|
PreviousOutPoint: unConfUtxo,
|
||||||
|
Sequence: blockchain.LockTimeToSequence(true, 1024),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
view: utxoView,
|
||||||
|
want: &blockchain.SequenceLock{
|
||||||
|
Seconds: nextMedianTime + 1023,
|
||||||
|
BlockHeight: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %v SequenceLock tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
seqLock, err := chain.CalcSequenceLock(test.tx, test.view, test.mempool)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test #%d, unable to calc sequence lock: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if seqLock.Seconds != test.want.Seconds {
|
||||||
|
t.Fatalf("test #%d got %v seconds want %v seconds",
|
||||||
|
i, seqLock.Seconds, test.want.Seconds)
|
||||||
|
}
|
||||||
|
if seqLock.BlockHeight != test.want.BlockHeight {
|
||||||
|
t.Fatalf("test #%d got height of %v want height of %v ",
|
||||||
|
i, seqLock.BlockHeight, test.want.BlockHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue