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

     1  // Copyright (c) 2017-2019 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  	"testing"
     9  	"time"
    10  
    11  	"github.com/decred/dcrd/blockchain/chaingen"
    12  	"github.com/decred/dcrd/blockchain/stake"
    13  	"github.com/decred/dcrd/chaincfg"
    14  	"github.com/decred/dcrd/dcrutil"
    15  	"github.com/decred/dcrd/txscript"
    16  	"github.com/decred/dcrd/wire"
    17  )
    18  
    19  // testLNFeaturesDeployment ensures the deployment of the LN features agenda
    20  // activates the expected changes for the provided network parameters.
    21  func testLNFeaturesDeployment(t *testing.T, params *chaincfg.Params) {
    22  	// baseConsensusScriptVerifyFlags are the expected script flags when the
    23  	// agenda is not active.
    24  	const baseConsensusScriptVerifyFlags = txscript.ScriptVerifyCleanStack |
    25  		txscript.ScriptVerifyCheckLockTimeVerify
    26  
    27  	// Clone the parameters so they can be mutated, find the correct deployment
    28  	// for the LN features agenda as well as the yes vote choice within it, and,
    29  	// finally, ensure it is always available to vote by removing the time
    30  	// constraints to prevent test failures when the real expiration time
    31  	// passes.
    32  	params = cloneParams(params)
    33  	deploymentVer, deployment, err := findDeployment(params,
    34  		chaincfg.VoteIDLNFeatures)
    35  	if err != nil {
    36  		t.Fatal(err)
    37  	}
    38  	yesChoice, err := findDeploymentChoice(deployment, "yes")
    39  	if err != nil {
    40  		t.Fatal(err)
    41  	}
    42  	removeDeploymentTimeConstraints(deployment)
    43  
    44  	// Shorter versions of params for convenience.
    45  	stakeValidationHeight := uint32(params.StakeValidationHeight)
    46  	ruleChangeActivationInterval := params.RuleChangeActivationInterval
    47  
    48  	tests := []struct {
    49  		name          string
    50  		numNodes      uint32 // num fake nodes to create
    51  		curActive     bool   // whether agenda active for current block
    52  		nextActive    bool   // whether agenda active for NEXT block
    53  		expectedFlags txscript.ScriptFlags
    54  	}{
    55  		{
    56  			name:          "stake validation height",
    57  			numNodes:      stakeValidationHeight,
    58  			curActive:     false,
    59  			nextActive:    false,
    60  			expectedFlags: baseConsensusScriptVerifyFlags,
    61  		},
    62  		{
    63  			name:          "started",
    64  			numNodes:      ruleChangeActivationInterval,
    65  			curActive:     false,
    66  			nextActive:    false,
    67  			expectedFlags: baseConsensusScriptVerifyFlags,
    68  		},
    69  		{
    70  			name:          "lockedin",
    71  			numNodes:      ruleChangeActivationInterval,
    72  			curActive:     false,
    73  			nextActive:    false,
    74  			expectedFlags: baseConsensusScriptVerifyFlags,
    75  		},
    76  		{
    77  			name:          "one before active",
    78  			numNodes:      ruleChangeActivationInterval - 1,
    79  			curActive:     false,
    80  			nextActive:    true,
    81  			expectedFlags: baseConsensusScriptVerifyFlags,
    82  		},
    83  		{
    84  			name:       "exactly active",
    85  			numNodes:   1,
    86  			curActive:  true,
    87  			nextActive: true,
    88  			expectedFlags: baseConsensusScriptVerifyFlags |
    89  				txscript.ScriptVerifyCheckSequenceVerify |
    90  				txscript.ScriptVerifySHA256,
    91  		},
    92  		{
    93  			name:       "one after active",
    94  			numNodes:   1,
    95  			curActive:  true,
    96  			nextActive: true,
    97  			expectedFlags: baseConsensusScriptVerifyFlags |
    98  				txscript.ScriptVerifyCheckSequenceVerify |
    99  				txscript.ScriptVerifySHA256,
   100  		},
   101  	}
   102  
   103  	curTimestamp := time.Now()
   104  	bc := newFakeChain(params)
   105  	node := bc.bestChain.Tip()
   106  	for _, test := range tests {
   107  		for i := uint32(0); i < test.numNodes; i++ {
   108  			node = newFakeNode(node, int32(deploymentVer),
   109  				deploymentVer, 0, curTimestamp)
   110  
   111  			// Create fake votes that vote yes on the agenda to
   112  			// ensure it is activated.
   113  			for j := uint16(0); j < params.TicketsPerBlock; j++ {
   114  				node.votes = append(node.votes, stake.VoteVersionTuple{
   115  					Version: deploymentVer,
   116  					Bits:    yesChoice.Bits | 0x01,
   117  				})
   118  			}
   119  			bc.bestChain.SetTip(node)
   120  			curTimestamp = curTimestamp.Add(time.Second)
   121  		}
   122  
   123  		// Ensure the agenda reports the expected activation status for
   124  		// the current block.
   125  		gotActive, err := bc.isLNFeaturesAgendaActive(node.parent)
   126  		if err != nil {
   127  			t.Errorf("%s: unexpected err: %v", test.name, err)
   128  			continue
   129  		}
   130  		if gotActive != test.curActive {
   131  			t.Errorf("%s: mismatched current active status - got: "+
   132  				"%v, want: %v", test.name, gotActive,
   133  				test.curActive)
   134  			continue
   135  		}
   136  
   137  		// Ensure the agenda reports the expected activation status for
   138  		// the NEXT block
   139  		gotActive, err = bc.IsLNFeaturesAgendaActive()
   140  		if err != nil {
   141  			t.Errorf("%s: unexpected err: %v", test.name, err)
   142  			continue
   143  		}
   144  		if gotActive != test.nextActive {
   145  			t.Errorf("%s: mismatched next active status - got: %v, "+
   146  				"want: %v", test.name, gotActive,
   147  				test.nextActive)
   148  			continue
   149  		}
   150  
   151  		// Ensure the consensus script verify flags are as expected.
   152  		gotFlags, err := bc.consensusScriptVerifyFlags(node)
   153  		if err != nil {
   154  			t.Errorf("%s: unexpected err: %v", test.name, err)
   155  			continue
   156  		}
   157  		if gotFlags != test.expectedFlags {
   158  			t.Errorf("%s: mismatched flags - got %v, want %v",
   159  				test.name, gotFlags, test.expectedFlags)
   160  			continue
   161  		}
   162  	}
   163  }
   164  
   165  // TestLNFeaturesDeployment ensures the deployment of the LN features agenda
   166  // activate the expected changes.
   167  func TestLNFeaturesDeployment(t *testing.T) {
   168  	testLNFeaturesDeployment(t, &chaincfg.MainNetParams)
   169  	testLNFeaturesDeployment(t, &chaincfg.RegNetParams)
   170  }
   171  
   172  // testFixSeqLocksDeployment ensures the deployment of the fix sequence locks
   173  // agenda activates for the provided network parameters.
   174  func testFixSeqLocksDeployment(t *testing.T, params *chaincfg.Params) {
   175  	// Clone the parameters so they can be mutated, find the correct deployment
   176  	// for the fix sequence locks agenda as well as the yes vote choice within
   177  	// it, and, finally, ensure it is always available to vote by removing the
   178  	// time constraints to prevent test failures when the real expiration time
   179  	// passes.
   180  	params = cloneParams(params)
   181  	deploymentVer, deployment, err := findDeployment(params,
   182  		chaincfg.VoteIDFixLNSeqLocks)
   183  	if err != nil {
   184  		t.Fatal(err)
   185  	}
   186  	yesChoice, err := findDeploymentChoice(deployment, "yes")
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  	removeDeploymentTimeConstraints(deployment)
   191  
   192  	// Shorter versions of params for convenience.
   193  	stakeValidationHeight := uint32(params.StakeValidationHeight)
   194  	ruleChangeActivationInterval := params.RuleChangeActivationInterval
   195  
   196  	tests := []struct {
   197  		name       string
   198  		numNodes   uint32 // num fake nodes to create
   199  		curActive  bool   // whether agenda active for current block
   200  		nextActive bool   // whether agenda active for NEXT block
   201  	}{
   202  		{
   203  			name:       "stake validation height",
   204  			numNodes:   stakeValidationHeight,
   205  			curActive:  false,
   206  			nextActive: false,
   207  		},
   208  		{
   209  			name:       "started",
   210  			numNodes:   ruleChangeActivationInterval,
   211  			curActive:  false,
   212  			nextActive: false,
   213  		},
   214  		{
   215  			name:       "lockedin",
   216  			numNodes:   ruleChangeActivationInterval,
   217  			curActive:  false,
   218  			nextActive: false,
   219  		},
   220  		{
   221  			name:       "one before active",
   222  			numNodes:   ruleChangeActivationInterval - 1,
   223  			curActive:  false,
   224  			nextActive: true,
   225  		},
   226  		{
   227  			name:       "exactly active",
   228  			numNodes:   1,
   229  			curActive:  true,
   230  			nextActive: true,
   231  		},
   232  		{
   233  			name:       "one after active",
   234  			numNodes:   1,
   235  			curActive:  true,
   236  			nextActive: true,
   237  		},
   238  	}
   239  
   240  	curTimestamp := time.Now()
   241  	bc := newFakeChain(params)
   242  	node := bc.bestChain.Tip()
   243  	for _, test := range tests {
   244  		for i := uint32(0); i < test.numNodes; i++ {
   245  			node = newFakeNode(node, int32(deploymentVer), deploymentVer, 0,
   246  				curTimestamp)
   247  
   248  			// Create fake votes that vote yes on the agenda to ensure it is
   249  			// activated.
   250  			for j := uint16(0); j < params.TicketsPerBlock; j++ {
   251  				node.votes = append(node.votes, stake.VoteVersionTuple{
   252  					Version: deploymentVer,
   253  					Bits:    yesChoice.Bits | 0x01,
   254  				})
   255  			}
   256  			bc.bestChain.SetTip(node)
   257  			curTimestamp = curTimestamp.Add(time.Second)
   258  		}
   259  
   260  		// Ensure the agenda reports the expected activation status for the
   261  		// current block.
   262  		gotActive, err := bc.isFixSeqLocksAgendaActive(node.parent)
   263  		if err != nil {
   264  			t.Errorf("%s: unexpected err: %v", test.name, err)
   265  			continue
   266  		}
   267  		if gotActive != test.curActive {
   268  			t.Errorf("%s: mismatched current active status - got: %v, want: %v",
   269  				test.name, gotActive, test.curActive)
   270  			continue
   271  		}
   272  
   273  		// Ensure the agenda reports the expected activation status for the NEXT
   274  		// block
   275  		gotActive, err = bc.IsFixSeqLocksAgendaActive()
   276  		if err != nil {
   277  			t.Errorf("%s: unexpected err: %v", test.name, err)
   278  			continue
   279  		}
   280  		if gotActive != test.nextActive {
   281  			t.Errorf("%s: mismatched next active status - got: %v, want: %v",
   282  				test.name, gotActive, test.nextActive)
   283  			continue
   284  		}
   285  	}
   286  }
   287  
   288  // TestFixSeqLocksDeployment ensures the deployment of the fix sequence locks
   289  // agenda activates as expected.
   290  func TestFixSeqLocksDeployment(t *testing.T) {
   291  	testFixSeqLocksDeployment(t, &chaincfg.MainNetParams)
   292  	testFixSeqLocksDeployment(t, &chaincfg.RegNetParams)
   293  }
   294  
   295  // TestFixedSequenceLocks ensures that sequence locks within blocks behave as
   296  // expected once the fix sequence locks agenda is active.
   297  func TestFixedSequenceLocks(t *testing.T) {
   298  	// Use a set of test chain parameters which allow for quicker vote
   299  	// activation as compared to various existing network params.
   300  	params := quickVoteActivationParams()
   301  
   302  	// Clone the parameters so they can be mutated, find the correct deployment
   303  	// for the fix sequence locks agenda, and, finally, ensure it is always
   304  	// available to vote by removing the time constraints to prevent test
   305  	// failures when the real expiration time passes.
   306  	const fslVoteID = chaincfg.VoteIDFixLNSeqLocks
   307  	params = cloneParams(params)
   308  	fslVersion, deployment, err := findDeployment(params, fslVoteID)
   309  	if err != nil {
   310  		t.Fatal(err)
   311  	}
   312  	removeDeploymentTimeConstraints(deployment)
   313  
   314  	// Create a test harness initialized with the genesis block as the tip.
   315  	g, teardownFunc := newChaingenHarness(t, params, "fixseqlockstest")
   316  	defer teardownFunc()
   317  
   318  	// replaceFixSeqLocksVersions is a munge function which modifies the
   319  	// provided block by replacing the block, stake, and vote versions with the
   320  	// fix sequence locks deployment version.
   321  	replaceFixSeqLocksVersions := func(b *wire.MsgBlock) {
   322  		chaingen.ReplaceBlockVersion(int32(fslVersion))(b)
   323  		chaingen.ReplaceStakeVersion(fslVersion)(b)
   324  		chaingen.ReplaceVoteVersions(fslVersion)(b)
   325  	}
   326  
   327  	// ---------------------------------------------------------------------
   328  	// Generate and accept enough blocks with the appropriate vote bits set
   329  	// to reach one block prior to the fix sequence locks agenda becoming
   330  	// active.
   331  	// ---------------------------------------------------------------------
   332  
   333  	g.AdvanceToStakeValidationHeight()
   334  	g.AdvanceFromSVHToActiveAgenda(fslVoteID)
   335  
   336  	// ---------------------------------------------------------------------
   337  	// Perform a series of sequence lock tests now that fix sequence locks
   338  	// enforcement is active.
   339  	// ---------------------------------------------------------------------
   340  
   341  	// enableSeqLocks modifies the passed transaction to enable sequence locks
   342  	// for the provided input.
   343  	enableSeqLocks := func(tx *wire.MsgTx, txInIdx int) {
   344  		tx.Version = 2
   345  		tx.TxIn[txInIdx].Sequence = 0
   346  	}
   347  
   348  	// ---------------------------------------------------------------------
   349  	// Create block that has a transaction with an input shared with a
   350  	// transaction in the stake tree and has several outputs used in
   351  	// subsequent blocks.  Also, enable sequence locks for the first of
   352  	// those outputs.
   353  	//
   354  	//   ... -> b0
   355  	// ---------------------------------------------------------------------
   356  
   357  	outs := g.OldestCoinbaseOuts()
   358  	b0 := g.NextBlock("b0", &outs[0], outs[1:], replaceFixSeqLocksVersions,
   359  		func(b *wire.MsgBlock) {
   360  			// Save the current outputs of the spend tx and clear them.
   361  			tx := b.Transactions[1]
   362  			origOut := tx.TxOut[0]
   363  			origOpReturnOut := tx.TxOut[1]
   364  			tx.TxOut = tx.TxOut[:0]
   365  
   366  			// Evenly split the original output amount over multiple outputs.
   367  			const numOutputs = 6
   368  			amount := origOut.Value / numOutputs
   369  			for i := 0; i < numOutputs; i++ {
   370  				if i == numOutputs-1 {
   371  					amount = origOut.Value - amount*(numOutputs-1)
   372  				}
   373  				tx.AddTxOut(wire.NewTxOut(amount, origOut.PkScript))
   374  			}
   375  
   376  			// Add the original op return back to the outputs and enable
   377  			// sequence locks for the first output.
   378  			tx.AddTxOut(origOpReturnOut)
   379  			enableSeqLocks(tx, 0)
   380  		})
   381  	g.SaveTipCoinbaseOuts()
   382  	g.AcceptTipBlock()
   383  
   384  	// ---------------------------------------------------------------------
   385  	// Create block that spends from an output created in the previous
   386  	// block.
   387  	//
   388  	//   ... -> b0 -> b1a
   389  	// ---------------------------------------------------------------------
   390  
   391  	outs = g.OldestCoinbaseOuts()
   392  	g.NextBlock("b1a", nil, outs[1:], replaceFixSeqLocksVersions,
   393  		func(b *wire.MsgBlock) {
   394  			spend := chaingen.MakeSpendableOut(b0, 1, 0)
   395  			tx := g.CreateSpendTx(&spend, dcrutil.Amount(1))
   396  			enableSeqLocks(tx, 0)
   397  			b.AddTransaction(tx)
   398  		})
   399  	g.AcceptTipBlock()
   400  
   401  	// ---------------------------------------------------------------------
   402  	// Create block that involves reorganize to a sequence lock spending
   403  	// from an output created in a block prior to the parent also spent on
   404  	// on the side chain.
   405  	//
   406  	//   ... -> b0 -> b1  -> b2
   407  	//            \-> b1a
   408  	// ---------------------------------------------------------------------
   409  	g.SetTip("b0")
   410  	g.NextBlock("b1", nil, outs[1:], replaceFixSeqLocksVersions)
   411  	g.SaveTipCoinbaseOuts()
   412  	g.AcceptedToSideChainWithExpectedTip("b1a")
   413  
   414  	outs = g.OldestCoinbaseOuts()
   415  	g.NextBlock("b2", nil, outs[1:], replaceFixSeqLocksVersions,
   416  		func(b *wire.MsgBlock) {
   417  			spend := chaingen.MakeSpendableOut(b0, 1, 0)
   418  			tx := g.CreateSpendTx(&spend, dcrutil.Amount(1))
   419  			enableSeqLocks(tx, 0)
   420  			b.AddTransaction(tx)
   421  		})
   422  	g.SaveTipCoinbaseOuts()
   423  	g.AcceptTipBlock()
   424  	g.ExpectTip("b2")
   425  
   426  	// ---------------------------------------------------------------------
   427  	// Create block that involves a sequence lock on a vote.
   428  	//
   429  	//   ... -> b2 -> b3
   430  	// ---------------------------------------------------------------------
   431  
   432  	outs = g.OldestCoinbaseOuts()
   433  	g.NextBlock("b3", nil, outs[1:], replaceFixSeqLocksVersions,
   434  		func(b *wire.MsgBlock) {
   435  			enableSeqLocks(b.STransactions[0], 0)
   436  		})
   437  	g.SaveTipCoinbaseOuts()
   438  	g.AcceptTipBlock()
   439  
   440  	// ---------------------------------------------------------------------
   441  	// Create block that involves a sequence lock on a ticket.
   442  	//
   443  	//   ... -> b3 -> b4
   444  	// ---------------------------------------------------------------------
   445  
   446  	outs = g.OldestCoinbaseOuts()
   447  	g.NextBlock("b4", nil, outs[1:], replaceFixSeqLocksVersions,
   448  		func(b *wire.MsgBlock) {
   449  			enableSeqLocks(b.STransactions[5], 0)
   450  		})
   451  	g.SaveTipCoinbaseOuts()
   452  	g.AcceptTipBlock()
   453  
   454  	// ---------------------------------------------------------------------
   455  	// Create two blocks such that the tip block involves a sequence lock
   456  	// spending from a different output of a transaction the parent block
   457  	// also spends from.
   458  	//
   459  	//   ... -> b4 -> b5 -> b6
   460  	// ---------------------------------------------------------------------
   461  
   462  	outs = g.OldestCoinbaseOuts()
   463  	g.NextBlock("b5", nil, outs[1:], replaceFixSeqLocksVersions,
   464  		func(b *wire.MsgBlock) {
   465  			spend := chaingen.MakeSpendableOut(b0, 1, 1)
   466  			tx := g.CreateSpendTx(&spend, dcrutil.Amount(1))
   467  			b.AddTransaction(tx)
   468  		})
   469  	g.SaveTipCoinbaseOuts()
   470  	g.AcceptTipBlock()
   471  
   472  	outs = g.OldestCoinbaseOuts()
   473  	g.NextBlock("b6", nil, outs[1:], replaceFixSeqLocksVersions,
   474  		func(b *wire.MsgBlock) {
   475  			spend := chaingen.MakeSpendableOut(b0, 1, 2)
   476  			tx := g.CreateSpendTx(&spend, dcrutil.Amount(1))
   477  			enableSeqLocks(tx, 0)
   478  			b.AddTransaction(tx)
   479  		})
   480  	g.SaveTipCoinbaseOuts()
   481  	g.AcceptTipBlock()
   482  
   483  	// ---------------------------------------------------------------------
   484  	// Create block that involves a sequence lock spending from a regular
   485  	// tree transaction earlier in the block.  This used to be rejected
   486  	// due to a consensus bug, however the fix sequence locks agenda allows
   487  	// it to be accepted as desired.
   488  	//
   489  	//   ... -> b6 -> b7
   490  	// ---------------------------------------------------------------------
   491  
   492  	outs = g.OldestCoinbaseOuts()
   493  	g.NextBlock("b7", &outs[0], outs[1:], replaceFixSeqLocksVersions,
   494  		func(b *wire.MsgBlock) {
   495  			spend := chaingen.MakeSpendableOut(b, 1, 0)
   496  			tx := g.CreateSpendTx(&spend, dcrutil.Amount(1))
   497  			enableSeqLocks(tx, 0)
   498  			b.AddTransaction(tx)
   499  		})
   500  	g.SaveTipCoinbaseOuts()
   501  	g.AcceptTipBlock()
   502  
   503  	// ---------------------------------------------------------------------
   504  	// Create block that involves a sequence lock spending from a block
   505  	// prior to the parent.  This used to be rejected due to a consensus
   506  	// bug, however the fix sequence locks agenda allows it to be accepted
   507  	// as desired.
   508  	//
   509  	//   ... -> b6 -> b8 -> b9
   510  	// ---------------------------------------------------------------------
   511  
   512  	outs = g.OldestCoinbaseOuts()
   513  	g.NextBlock("b8", nil, outs[1:], replaceFixSeqLocksVersions)
   514  	g.SaveTipCoinbaseOuts()
   515  	g.AcceptTipBlock()
   516  
   517  	outs = g.OldestCoinbaseOuts()
   518  	g.NextBlock("b9", nil, outs[1:], replaceFixSeqLocksVersions,
   519  		func(b *wire.MsgBlock) {
   520  			spend := chaingen.MakeSpendableOut(b0, 1, 3)
   521  			tx := g.CreateSpendTx(&spend, dcrutil.Amount(1))
   522  			enableSeqLocks(tx, 0)
   523  			b.AddTransaction(tx)
   524  		})
   525  	g.SaveTipCoinbaseOuts()
   526  	g.AcceptTipBlock()
   527  
   528  	// ---------------------------------------------------------------------
   529  	// Create two blocks such that the tip block involves a sequence lock
   530  	// spending from a different output of a transaction the parent block
   531  	// also spends from when the parent block has been disapproved.  This
   532  	// used to be rejected due to a consensus bug, however the fix sequence
   533  	// locks agenda allows it to be accepted as desired.
   534  	//
   535  	//   ... -> b8 -> b10 -> b11
   536  	// ---------------------------------------------------------------------
   537  
   538  	const (
   539  		// vbDisapprovePrev and vbApprovePrev represent no and yes votes,
   540  		// respectively, on whether or not to approve the previous block.
   541  		vbDisapprovePrev = 0x0000
   542  		vbApprovePrev    = 0x0001
   543  	)
   544  
   545  	outs = g.OldestCoinbaseOuts()
   546  	g.NextBlock("b10", nil, outs[1:], replaceFixSeqLocksVersions,
   547  		func(b *wire.MsgBlock) {
   548  			spend := chaingen.MakeSpendableOut(b0, 1, 4)
   549  			tx := g.CreateSpendTx(&spend, dcrutil.Amount(1))
   550  			b.AddTransaction(tx)
   551  		})
   552  	g.SaveTipCoinbaseOuts()
   553  	g.AcceptTipBlock()
   554  
   555  	outs = g.OldestCoinbaseOuts()
   556  	g.NextBlock("b11", nil, outs[1:], replaceFixSeqLocksVersions,
   557  		chaingen.ReplaceVotes(vbDisapprovePrev, fslVersion),
   558  		func(b *wire.MsgBlock) {
   559  			b.Header.VoteBits &^= vbApprovePrev
   560  			spend := chaingen.MakeSpendableOut(b0, 1, 5)
   561  			tx := g.CreateSpendTx(&spend, dcrutil.Amount(1))
   562  			enableSeqLocks(tx, 0)
   563  			b.AddTransaction(tx)
   564  		})
   565  	g.SaveTipCoinbaseOuts()
   566  	g.AcceptTipBlock()
   567  }