github.com/decred/dcrd/blockchain@v1.2.1/sequencelock_test.go (about)

     1  // Copyright (c) 2017-2018 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package blockchain
     6  
     7  import (
     8  	"fmt"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/decred/dcrd/chaincfg"
    13  	"github.com/decred/dcrd/dcrutil"
    14  	"github.com/decred/dcrd/wire"
    15  )
    16  
    17  // mustLockTimeToSeq converts the passed relative lock time to a sequence number
    18  // by using LockTimeToSequence.  It only differs in that it will panic if there
    19  // is an error so errors in the source code can be detected.  It will only (and
    20  // must only)  be called with hard-coded, and therefore known good, values.
    21  func mustLockTimeToSeq(isSeconds bool, lockTime uint32) uint32 {
    22  	sequence, err := LockTimeToSequence(isSeconds, lockTime)
    23  	if err != nil {
    24  		panic(fmt.Sprintf("invalid lock time in source file: "+
    25  			"isSeconds: %v, lockTime: %d", isSeconds, lockTime))
    26  	}
    27  	return sequence
    28  }
    29  
    30  // TestCalcSequenceLock exercises several combinations of inputs to the
    31  // CalcSequenceLock function in order to ensure the returned sequence locks are
    32  // as expected.
    33  func TestCalcSequenceLock(t *testing.T) {
    34  	// Generate a synthetic simnet chain with enough nodes to properly test
    35  	// the sequence lock functionality.
    36  	numBlocks := uint32(20)
    37  	params := &chaincfg.RegNetParams
    38  	bc := newFakeChain(params)
    39  	node := bc.bestChain.Tip()
    40  	blockTime := time.Unix(node.timestamp, 0)
    41  	for i := uint32(0); i < numBlocks; i++ {
    42  		blockTime = blockTime.Add(time.Second)
    43  		node = newFakeNode(node, 1, 1, 0, blockTime)
    44  		bc.index.AddNode(node)
    45  		bc.bestChain.SetTip(node)
    46  	}
    47  
    48  	// Create a utxo view with a fake utxo for the inputs used in the
    49  	// transactions created below.  This utxo is added such that it has an
    50  	// age of 4 blocks.
    51  	targetTx := dcrutil.NewTx(&wire.MsgTx{
    52  		TxOut: []*wire.TxOut{{
    53  			Value:    10,
    54  			Version:  0,
    55  			PkScript: nil,
    56  		}},
    57  	})
    58  	view := NewUtxoViewpoint()
    59  	view.AddTxOuts(targetTx, int64(numBlocks)-4, 0)
    60  	view.SetBestHash(&node.hash)
    61  
    62  	// Create a utxo that spends the fake utxo created above for use in the
    63  	// transactions created in the tests.  It has an age of 4 blocks.  Note
    64  	// that the sequence lock heights are always calculated from the same
    65  	// point of view that they were originally calculated from for a given
    66  	// utxo.  That is to say, the height prior to it.
    67  	utxo := wire.OutPoint{
    68  		Hash:  *targetTx.Hash(),
    69  		Index: 0,
    70  		Tree:  wire.TxTreeRegular,
    71  	}
    72  	prevUtxoHeight := int64(numBlocks) - 4
    73  
    74  	// Obtain the median time past from the PoV of the input created above.
    75  	// The median time for the input is the median time from the PoV of the
    76  	// block *prior* to the one that included it.
    77  	medianTime := node.RelativeAncestor(5).CalcPastMedianTime().Unix()
    78  
    79  	// The median time calculated from the PoV of the best block in the
    80  	// test chain.  For unconfirmed inputs, this value will be used since
    81  	// the median time will be calculated from the PoV of the
    82  	// yet-to-be-mined block.
    83  	nextMedianTime := node.CalcPastMedianTime().Unix()
    84  	nextBlockHeight := int64(numBlocks) + 1
    85  
    86  	// Add an additional transaction which will serve as our unconfirmed
    87  	// output.
    88  	unConfTx := &wire.MsgTx{
    89  		TxOut: []*wire.TxOut{{
    90  			Value:    5,
    91  			Version:  0,
    92  			PkScript: nil,
    93  		}},
    94  	}
    95  	unConfUtxo := wire.OutPoint{
    96  		Hash:  unConfTx.TxHash(),
    97  		Index: 0,
    98  		Tree:  wire.TxTreeRegular,
    99  	}
   100  
   101  	// Adding a utxo with a height of 0x7fffffff indicates that the output
   102  	// is currently unmined.
   103  	view.AddTxOuts(dcrutil.NewTx(unConfTx), 0x7fffffff, wire.NullBlockIndex)
   104  
   105  	tests := []struct {
   106  		name      string
   107  		txVersion uint16
   108  		inputs    []*wire.TxIn
   109  		isActive  bool
   110  		want      SequenceLock
   111  	}{
   112  		{
   113  			// A transaction of version one should disable sequence
   114  			// locks as the new sequence number semantics only apply
   115  			// to transactions version 2 or higher.
   116  			name:      "v1 transaction",
   117  			txVersion: 1,
   118  			inputs: []*wire.TxIn{{
   119  				PreviousOutPoint: utxo,
   120  				Sequence:         mustLockTimeToSeq(false, 3),
   121  			}},
   122  			isActive: true,
   123  			want: SequenceLock{
   124  				MinHeight: -1,
   125  				MinTime:   -1,
   126  			},
   127  		},
   128  		{
   129  			// A transaction with a single input with max sequence
   130  			// number.  This sequence number has the high bit set,
   131  			// so sequence locks should be disabled.
   132  			name:      "max sequence number",
   133  			txVersion: 2,
   134  			inputs: []*wire.TxIn{{
   135  				PreviousOutPoint: utxo,
   136  				Sequence:         wire.MaxTxInSequenceNum,
   137  			}},
   138  			isActive: true,
   139  			want: SequenceLock{
   140  				MinHeight: -1,
   141  				MinTime:   -1,
   142  			},
   143  		},
   144  		{
   145  			// A transaction that would result in a specific
   146  			// sequence lock except set the agenda is not being
   147  			// active yet, so sequence locks should be disabled.
   148  			name:      "agenda not yet active",
   149  			txVersion: 2,
   150  			inputs: []*wire.TxIn{{
   151  				PreviousOutPoint: utxo,
   152  				Sequence:         mustLockTimeToSeq(true, 2),
   153  			}},
   154  			isActive: false,
   155  			want: SequenceLock{
   156  				MinHeight: -1,
   157  				MinTime:   -1,
   158  			},
   159  		},
   160  		{
   161  			// A transaction with a single input whose locktime is
   162  			// expressed in seconds.  However, the specified lock
   163  			// time is below the required floor for time based lock
   164  			// times since they have time granularity of 512
   165  			// seconds.  As a result, the seconds locktime should be
   166  			// just before the median time of the targeted block.
   167  			name:      "seconds below granularity",
   168  			txVersion: 2,
   169  			inputs: []*wire.TxIn{{
   170  				PreviousOutPoint: utxo,
   171  				Sequence:         mustLockTimeToSeq(true, 2),
   172  			}},
   173  			isActive: true,
   174  			want: SequenceLock{
   175  				MinHeight: -1,
   176  				MinTime:   medianTime - 1,
   177  			},
   178  		},
   179  		{
   180  			// A transaction with a single input whose locktime is
   181  			// expressed in seconds.  The number of seconds should
   182  			// be 1023 seconds after the median past time of the
   183  			// input.
   184  			name:      "1024 seconds",
   185  			txVersion: 2,
   186  			inputs: []*wire.TxIn{{
   187  				PreviousOutPoint: utxo,
   188  				Sequence:         mustLockTimeToSeq(true, 1024),
   189  			}},
   190  			isActive: true,
   191  			want: SequenceLock{
   192  				MinHeight: -1,
   193  				MinTime:   medianTime + 1023,
   194  			},
   195  		},
   196  		{
   197  			// A transaction with multiple inputs.  The first input
   198  			// has a locktime expressed in seconds.  The second
   199  			// input has a sequence lock in blocks with a value of
   200  			// 4.  The last input has a sequence number with a value
   201  			// of 5, but has the disable bit set.  So the first lock
   202  			// should be selected as it's the latest lock that isn't
   203  			// disabled.
   204  			name:      "multiple inputs, 1 disabled",
   205  			txVersion: 2,
   206  			inputs: []*wire.TxIn{{
   207  				PreviousOutPoint: utxo,
   208  				Sequence:         mustLockTimeToSeq(true, 2560),
   209  			}, {
   210  				PreviousOutPoint: utxo,
   211  				Sequence:         mustLockTimeToSeq(false, 4),
   212  			}, {
   213  				PreviousOutPoint: utxo,
   214  				Sequence: mustLockTimeToSeq(false, 5) |
   215  					wire.SequenceLockTimeDisabled,
   216  			}},
   217  			isActive: true,
   218  			want: SequenceLock{
   219  				MinHeight: prevUtxoHeight + 3,
   220  				MinTime:   medianTime + (5 << wire.SequenceLockTimeGranularity) - 1,
   221  			},
   222  		},
   223  		{
   224  			// A transaction with a single input.  The input's
   225  			// sequence number encodes a relative locktime in blocks
   226  			// (3 blocks).  The sequence lock should  have a value
   227  			// of -1 for seconds, but a height of 2 meaning it can
   228  			// be included at height 3.
   229  			name:      "3 blocks",
   230  			txVersion: 2,
   231  			inputs: []*wire.TxIn{{
   232  				PreviousOutPoint: utxo,
   233  				Sequence:         mustLockTimeToSeq(false, 3),
   234  			}},
   235  			isActive: true,
   236  			want: SequenceLock{
   237  				MinHeight: prevUtxoHeight + 2,
   238  				MinTime:   -1,
   239  			},
   240  		},
   241  		{
   242  			// A transaction with two inputs with locktimes
   243  			// expressed in seconds.  The selected sequence lock
   244  			// value for seconds should be the time further in the
   245  			// future.
   246  			name:      "2 inputs both in seconds",
   247  			txVersion: 2,
   248  			inputs: []*wire.TxIn{{
   249  				PreviousOutPoint: utxo,
   250  				Sequence:         mustLockTimeToSeq(true, 5120),
   251  			}, {
   252  				PreviousOutPoint: utxo,
   253  				Sequence:         mustLockTimeToSeq(true, 2560),
   254  			}},
   255  			isActive: true,
   256  			want: SequenceLock{
   257  				MinHeight: -1,
   258  				MinTime:   medianTime + (10 << wire.SequenceLockTimeGranularity) - 1,
   259  			},
   260  		},
   261  		{
   262  			// A transaction with two inputs with locktimes
   263  			// expressed in blocks.  The selected sequence lock
   264  			// value for blocks should be the height further in the
   265  			// future, so a height of 10 indicating it can be
   266  			// included at height 11.
   267  			name:      "2 inputs both in blocks",
   268  			txVersion: 2,
   269  			inputs: []*wire.TxIn{{
   270  				PreviousOutPoint: utxo,
   271  				Sequence:         mustLockTimeToSeq(false, 1),
   272  			}, {
   273  				PreviousOutPoint: utxo,
   274  				Sequence:         mustLockTimeToSeq(false, 11),
   275  			}},
   276  			isActive: true,
   277  			want: SequenceLock{
   278  				MinHeight: prevUtxoHeight + 10,
   279  				MinTime:   -1,
   280  			},
   281  		},
   282  		{
   283  			// A transaction with multiple inputs.  Two inputs are
   284  			// seconds and the other two are blocks. The lock
   285  			// further into the future for both inputs should be
   286  			// chosen.
   287  			name:      "4 inputs, 2 in seconds, 2 in blocks",
   288  			txVersion: 2,
   289  			inputs: []*wire.TxIn{{
   290  				PreviousOutPoint: utxo,
   291  				Sequence:         mustLockTimeToSeq(true, 2560),
   292  			}, {
   293  				PreviousOutPoint: utxo,
   294  				Sequence:         mustLockTimeToSeq(true, 6656),
   295  			}, {
   296  				PreviousOutPoint: utxo,
   297  				Sequence:         mustLockTimeToSeq(false, 3),
   298  			}, {
   299  				PreviousOutPoint: utxo,
   300  				Sequence:         mustLockTimeToSeq(false, 9),
   301  			}},
   302  			isActive: true,
   303  			want: SequenceLock{
   304  				MinHeight: prevUtxoHeight + 8,
   305  				MinTime:   medianTime + (13 << wire.SequenceLockTimeGranularity) - 1,
   306  			},
   307  		},
   308  		{
   309  			// A transaction with a single unconfirmed input.  Since
   310  			// the input is unconfirmed, the height of the input
   311  			// should be interpreted as the height of the *next*
   312  			// block.  So, a 2 block relative lock means the
   313  			// sequence lock should be for 1 block after the *next*
   314  			// block height, indicating it can be included 2 blocks
   315  			// after that.
   316  			name:      "unconfirmed input in blocks",
   317  			txVersion: 2,
   318  			inputs: []*wire.TxIn{{
   319  				PreviousOutPoint: unConfUtxo,
   320  				Sequence:         mustLockTimeToSeq(false, 2),
   321  			}},
   322  			isActive: true,
   323  			want: SequenceLock{
   324  				MinHeight: nextBlockHeight + 1,
   325  				MinTime:   -1,
   326  			},
   327  		},
   328  		{
   329  			// A transaction with a single unconfirmed input.  The
   330  			// input has locktime in seconds, so the locktime should
   331  			// be based off the median time of the *next* block.
   332  			name:      "unconfirmed input in seconds",
   333  			txVersion: 2,
   334  			inputs: []*wire.TxIn{{
   335  				PreviousOutPoint: unConfUtxo,
   336  				Sequence:         mustLockTimeToSeq(true, 1024),
   337  			}},
   338  			isActive: true,
   339  			want: SequenceLock{
   340  				MinHeight: -1,
   341  				MinTime:   nextMedianTime + 1023,
   342  			},
   343  		},
   344  	}
   345  
   346  	for i, test := range tests {
   347  		// Create fake spending transaction per the test input data.
   348  		tx := wire.MsgTx{
   349  			SerType:  wire.TxSerializeFull,
   350  			Version:  test.txVersion,
   351  			LockTime: 0,
   352  			Expiry:   0,
   353  			TxOut:    nil,
   354  		}
   355  		for _, txIn := range test.inputs {
   356  			tx.AddTxIn(txIn)
   357  		}
   358  		utilTx := dcrutil.NewTx(&tx)
   359  
   360  		// Calculate the sequence lock for the test input data.  Since
   361  		// the exported function always has the agenda active, use the
   362  		// unexported function when simulating the agenda not being
   363  		// active, and alternate between them to ensure both are
   364  		// exercised.
   365  		var seqLock *SequenceLock
   366  		var err error
   367  		if test.isActive && i%2 == 0 {
   368  			seqLock, err = bc.CalcSequenceLock(utilTx, view)
   369  		} else {
   370  			bc.chainLock.Lock()
   371  			seqLock, err = bc.calcSequenceLock(node, utilTx, view,
   372  				test.isActive)
   373  			bc.chainLock.Unlock()
   374  		}
   375  		if err != nil {
   376  			t.Errorf("%s: unable to calc sequence lock: %v",
   377  				test.name, err)
   378  			continue
   379  		}
   380  
   381  		// Ensure both the returned sequence lock seconds and block
   382  		// height match the expected values.
   383  		if seqLock.MinTime != test.want.MinTime {
   384  			t.Errorf("%s: mistmached seconds - got %v, want %v",
   385  				test.name, seqLock.MinTime, test.want.MinTime)
   386  			continue
   387  		}
   388  		if seqLock.MinHeight != test.want.MinHeight {
   389  			t.Errorf("%s: mismatched height - got %v, want %v",
   390  				test.name, seqLock.MinHeight,
   391  				test.want.MinHeight)
   392  		}
   393  	}
   394  }
   395  
   396  // TestLockTimeToSequence ensure the convenience function to convert relative
   397  // lock times to a sequence number works as expected.
   398  func TestLockTimeToSequence(t *testing.T) {
   399  	const (
   400  		// The following constants are used over the package-level
   401  		// definitions to ensure tests correctly detect any changes to
   402  		// them.
   403  		secondsGranularityBits = 9
   404  		secondsBit             = 1 << 22
   405  		maxValue               = 1<<16 - 1
   406  		maxBlockHeight         = maxValue
   407  		maxSeconds             = maxValue << secondsGranularityBits
   408  	)
   409  
   410  	tests := []struct {
   411  		name      string
   412  		locktime  uint32
   413  		isSeconds bool
   414  		expected  uint32
   415  		invalid   bool
   416  	}{
   417  		{
   418  			name:      "relative block height 0",
   419  			locktime:  0,
   420  			isSeconds: false,
   421  			expected:  0,
   422  		},
   423  		{
   424  			name:      "max relative block height",
   425  			locktime:  maxBlockHeight,
   426  			isSeconds: false,
   427  			expected:  maxBlockHeight,
   428  		},
   429  		{
   430  			name:      "max relative block height +1",
   431  			locktime:  maxBlockHeight + 1,
   432  			isSeconds: false,
   433  			expected:  0,
   434  			invalid:   true,
   435  		},
   436  		{
   437  			name:      "relative seconds 0",
   438  			locktime:  0,
   439  			isSeconds: true,
   440  			expected:  secondsBit,
   441  		},
   442  		{
   443  			name:      "relative seconds granularity - 1",
   444  			locktime:  (1 << secondsGranularityBits) - 1,
   445  			isSeconds: true,
   446  			expected:  secondsBit,
   447  		},
   448  		{
   449  			name:      "relative seconds exact granularity",
   450  			locktime:  1 << secondsGranularityBits,
   451  			isSeconds: true,
   452  			expected:  secondsBit + 1,
   453  		},
   454  		{
   455  			name:      "relative seconds granularity + 1",
   456  			locktime:  (1 << secondsGranularityBits) + 1,
   457  			isSeconds: true,
   458  			expected:  secondsBit + 1,
   459  		},
   460  		{
   461  			name:      "relative seconds max - 1",
   462  			locktime:  maxSeconds - 1,
   463  			isSeconds: true,
   464  			expected:  secondsBit + maxValue - 1,
   465  		},
   466  		{
   467  			name:      "relative seconds max",
   468  			locktime:  maxSeconds,
   469  			isSeconds: true,
   470  			expected:  secondsBit + maxValue,
   471  		},
   472  		{
   473  			name:      "relative seconds max +1",
   474  			locktime:  maxSeconds + 1,
   475  			isSeconds: true,
   476  			expected:  0,
   477  			invalid:   true,
   478  		},
   479  	}
   480  
   481  	for _, test := range tests {
   482  		gotSequence, err := LockTimeToSequence(test.isSeconds,
   483  			test.locktime)
   484  		if err != nil && !test.invalid {
   485  			t.Errorf("%s: unexpected error: %v", test.name, err)
   486  			continue
   487  
   488  		}
   489  		if err == nil && test.invalid {
   490  			t.Errorf("%s: did not receive expected error", test.name)
   491  			continue
   492  		}
   493  
   494  		if gotSequence != test.expected {
   495  			t.Errorf("%s: mismatched sequence - got %d, want %d",
   496  				test.name, gotSequence, test.expected)
   497  			continue
   498  		}
   499  
   500  	}
   501  }