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

     1  // Copyright (c) 2016-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  	"fmt"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/decred/dcrd/chaincfg"
    13  	"github.com/decred/dcrd/chaincfg/chainhash"
    14  )
    15  
    16  // isVoterMajorityVersion determines if minVer requirement is met based on
    17  // prevNode.  The function always uses the voter versions of the prior window.
    18  // For example, if StakeVersionInterval = 11 and StakeValidationHeight = 13 the
    19  // windows start at 13 + 11 -1 = 24 and are as follows: 24-34, 35-45, 46-56 ...
    20  // If height comes in at 35 we use the 24-34 window, up to height 45.
    21  // If height comes in at 46 we use the 35-45 window, up to height 56 etc.
    22  //
    23  // This function MUST be called with the chain state lock held (for writes).
    24  func (b *BlockChain) isVoterMajorityVersion(minVer uint32, prevNode *blockNode) bool {
    25  	// Walk blockchain backwards to calculate version.
    26  	node := b.findStakeVersionPriorNode(prevNode)
    27  	if node == nil {
    28  		return 0 >= minVer
    29  	}
    30  
    31  	// Generate map key and look up cached result.
    32  	key := stakeMajorityCacheVersionKey(minVer, &node.hash)
    33  	if result, ok := b.isVoterMajorityVersionCache[key]; ok {
    34  		return result
    35  	}
    36  
    37  	// Tally both the total number of votes in the previous stake version validation
    38  	// interval and how many of those votes are at least the requested minimum
    39  	// version.
    40  	totalVotesFound := int32(0)
    41  	versionCount := int32(0)
    42  	iterNode := node
    43  	for i := int64(0); i < b.chainParams.StakeVersionInterval && iterNode != nil; i++ {
    44  		totalVotesFound += int32(len(iterNode.votes))
    45  		for _, v := range iterNode.votes {
    46  			if v.Version >= minVer {
    47  				versionCount++
    48  			}
    49  		}
    50  
    51  		iterNode = iterNode.parent
    52  	}
    53  
    54  	// Determine the required amount of votes to reach supermajority.
    55  	numRequired := totalVotesFound * b.chainParams.StakeMajorityMultiplier /
    56  		b.chainParams.StakeMajorityDivisor
    57  
    58  	// Cache value.
    59  	result := versionCount >= numRequired
    60  	b.isVoterMajorityVersionCache[key] = result
    61  
    62  	return result
    63  }
    64  
    65  func TestCalcWantHeight(t *testing.T) {
    66  	// For example, if StakeVersionInterval = 11 and StakeValidationHeight = 13 the
    67  	// windows start at 13 + (11 * 2) 25 and are as follows: 24-34, 35-45, 46-56 ...
    68  	// If height comes in at 35 we use the 24-34 window, up to height 45.
    69  	// If height comes in at 46 we use the 35-45 window, up to height 56 etc.
    70  	tests := []struct {
    71  		name       string
    72  		skip       int64
    73  		interval   int64
    74  		multiplier int64
    75  		negative   int64
    76  	}{
    77  		{
    78  			name:       "13 11 10000",
    79  			skip:       13,
    80  			interval:   11,
    81  			multiplier: 10000,
    82  		},
    83  		{
    84  			name:       "27 33 10000",
    85  			skip:       27,
    86  			interval:   33,
    87  			multiplier: 10000,
    88  		},
    89  		{
    90  			name:       "mainnet params",
    91  			skip:       chaincfg.MainNetParams.StakeValidationHeight,
    92  			interval:   chaincfg.MainNetParams.StakeVersionInterval,
    93  			multiplier: 5000,
    94  		},
    95  		{
    96  			name:       "testnet3 params",
    97  			skip:       chaincfg.TestNet3Params.StakeValidationHeight,
    98  			interval:   chaincfg.TestNet3Params.StakeVersionInterval,
    99  			multiplier: 1000,
   100  		},
   101  		{
   102  			name:       "simnet params",
   103  			skip:       chaincfg.SimNetParams.StakeValidationHeight,
   104  			interval:   chaincfg.SimNetParams.StakeVersionInterval,
   105  			multiplier: 10000,
   106  		},
   107  		{
   108  			name:       "regnet params",
   109  			skip:       chaincfg.RegNetParams.StakeValidationHeight,
   110  			interval:   chaincfg.RegNetParams.StakeVersionInterval,
   111  			multiplier: 10000,
   112  		},
   113  		{
   114  			name:       "negative mainnet params",
   115  			skip:       chaincfg.MainNetParams.StakeValidationHeight,
   116  			interval:   chaincfg.MainNetParams.StakeVersionInterval,
   117  			multiplier: 1000,
   118  			negative:   1,
   119  		},
   120  	}
   121  
   122  	for _, test := range tests {
   123  		t.Logf("running: %v skip: %v interval: %v",
   124  			test.name, test.skip, test.interval)
   125  
   126  		start := test.skip + test.interval*2
   127  		expectedHeight := start - 1 // zero based
   128  		x := int64(0) + test.negative
   129  		for i := start; i < test.multiplier*test.interval; i++ {
   130  			if x%test.interval == 0 && i != start {
   131  				expectedHeight += test.interval
   132  			}
   133  			wantHeight := calcWantHeight(test.skip, test.interval, i)
   134  
   135  			if wantHeight != expectedHeight {
   136  				if test.negative == 0 {
   137  					t.Fatalf("%v: i %v x %v -> wantHeight %v expectedHeight %v\n",
   138  						test.name, i, x, wantHeight, expectedHeight)
   139  				}
   140  			}
   141  
   142  			x++
   143  		}
   144  	}
   145  }
   146  
   147  // TestCalcStakeVersionCorners ensures that stake version calculation works as
   148  // intended under various corner cases such as attempting to go back backwards.
   149  func TestCalcStakeVersionCorners(t *testing.T) {
   150  	params := &chaincfg.SimNetParams
   151  	svh := params.StakeValidationHeight
   152  	svi := params.StakeVersionInterval
   153  
   154  	// Generate enough nodes to reach stake validation height with stake
   155  	// versions set to 0.
   156  	bc := newFakeChain(params)
   157  	node := bc.bestChain.Tip()
   158  	for i := int64(1); i <= svh; i++ {
   159  		node = newFakeNode(node, 0, 0, 0, time.Now())
   160  		bc.bestChain.SetTip(node)
   161  	}
   162  	if node.height != svh {
   163  		t.Fatalf("invalid height got %v expected %v", node.height, svh)
   164  	}
   165  
   166  	// Generate 3 intervals with v2 votes and calculated stake version.
   167  	for i := int64(0); i < svi*3; i++ {
   168  		sv := bc.calcStakeVersion(node)
   169  
   170  		// Set vote and stake versions.
   171  		node = newFakeNode(node, 3, sv, 0, time.Now())
   172  		appendFakeVotes(node, params.TicketsPerBlock, 2, 0)
   173  		bc.bestChain.SetTip(node)
   174  	}
   175  
   176  	// Versions 0 and 2 should now be considered the majority version, but
   177  	// v4 should not yet be considered majority.
   178  	if !bc.isStakeMajorityVersion(0, node) {
   179  		t.Fatalf("invalid StakeVersion expected 0 -> true")
   180  	}
   181  	if !bc.isStakeMajorityVersion(2, node) {
   182  		t.Fatalf("invalid StakeVersion expected 2 -> true")
   183  	}
   184  	if bc.isStakeMajorityVersion(4, node) {
   185  		t.Fatalf("invalid StakeVersion expected 4 -> false")
   186  	}
   187  
   188  	// Generate 3 intervals with v4 votes and calculated stake version.
   189  	for i := int64(0); i < svi*3; i++ {
   190  		sv := bc.calcStakeVersion(node)
   191  
   192  		// Set vote and stake versions.
   193  		node = newFakeNode(node, 3, sv, 0, time.Now())
   194  		appendFakeVotes(node, params.TicketsPerBlock, 4, 0)
   195  		bc.bestChain.SetTip(node)
   196  	}
   197  
   198  	// Versions up to and including v4 should now be considered the majority
   199  	// version, but v5 should not yet be considered majority.
   200  	for _, version := range []uint32{0, 2, 4} {
   201  		if !bc.isStakeMajorityVersion(version, node) {
   202  			t.Fatalf("invalid StakeVersion expected %d -> true",
   203  				version)
   204  		}
   205  
   206  	}
   207  	if bc.isStakeMajorityVersion(5, node) {
   208  		t.Fatalf("invalid StakeVersion expected 5 -> false")
   209  	}
   210  
   211  	// Generate 3 intervals with v2 votes and calculated stake version.
   212  	for i := int64(0); i < svi*3; i++ {
   213  		sv := bc.calcStakeVersion(node)
   214  
   215  		// Set vote and stake versions.
   216  		node = newFakeNode(node, 3, sv, 0, time.Now())
   217  		appendFakeVotes(node, params.TicketsPerBlock, 2, 0)
   218  		bc.bestChain.SetTip(node)
   219  	}
   220  
   221  	// Versions up to and including v4 should still be considered the
   222  	// majority version since even though there were multiple intervals with
   223  	// a majority v2 votes, the stake version is not allowed to go
   224  	// backwards.  Version 5 should still not be consider majority.
   225  	for _, version := range []uint32{0, 2, 4} {
   226  		if !bc.isStakeMajorityVersion(version, node) {
   227  			t.Fatalf("invalid StakeVersion expected %d -> true",
   228  				version)
   229  		}
   230  
   231  	}
   232  	if bc.isStakeMajorityVersion(5, node) {
   233  		t.Fatalf("invalid StakeVersion expected 5 -> false")
   234  	}
   235  
   236  	// Generate 2 intervals with v5 votes and calculated stake version.
   237  	for i := int64(0); i < svi*2; i++ {
   238  		sv := bc.calcStakeVersion(node)
   239  
   240  		// Set vote and stake versions.
   241  		node = newFakeNode(node, 3, sv, 0, time.Now())
   242  		appendFakeVotes(node, params.TicketsPerBlock, 5, 0)
   243  		bc.bestChain.SetTip(node)
   244  	}
   245  
   246  	// Versions up to and including v5 should now be considered the majority
   247  	// version, but v6 should not yet be.
   248  	for _, version := range []uint32{0, 2, 4, 5} {
   249  		if !bc.isStakeMajorityVersion(version, node) {
   250  			t.Fatalf("invalid StakeVersion expected %d -> true",
   251  				version)
   252  		}
   253  
   254  	}
   255  	if bc.isStakeMajorityVersion(6, node) {
   256  		t.Fatalf("invalid StakeVersion expected 6 -> false")
   257  	}
   258  
   259  	// Generate 1 interval with v4 votes to test the edge condition.
   260  	for i := int64(0); i < svi; i++ {
   261  		sv := bc.calcStakeVersion(node)
   262  
   263  		// Set vote and stake versions.
   264  		node = newFakeNode(node, 3, sv, 0, time.Now())
   265  		appendFakeVotes(node, params.TicketsPerBlock, 4, 0)
   266  		bc.bestChain.SetTip(node)
   267  
   268  	}
   269  
   270  	// Versions up to and including v5 should still be considered the
   271  	// majority version since even though there was an interval with a
   272  	// majority v4 votes, the stake version is not allowed to go backwards.
   273  	// Version 6 should still not be consider majority.
   274  	for _, version := range []uint32{0, 2, 4, 5} {
   275  		if !bc.isStakeMajorityVersion(version, node) {
   276  			t.Fatalf("invalid StakeVersion expected %d -> true",
   277  				version)
   278  		}
   279  
   280  	}
   281  	if bc.isStakeMajorityVersion(6, node) {
   282  		t.Fatalf("invalid StakeVersion expected 6 -> false")
   283  	}
   284  
   285  	// Generate another interval with v4 votes.
   286  	for i := int64(0); i < svi; i++ {
   287  		sv := bc.calcStakeVersion(node)
   288  
   289  		// Set stake versions.
   290  		node = newFakeNode(node, 3, sv, 0, time.Now())
   291  		appendFakeVotes(node, params.TicketsPerBlock, 4, 0)
   292  		bc.bestChain.SetTip(node)
   293  
   294  	}
   295  
   296  	// Versions up to and including v5 should still be considered the
   297  	// majority version since even though there was another interval with a
   298  	// majority v4 votes, the stake version is not allowed to go backwards.
   299  	// Version 6 should still not be consider majority.
   300  	for _, version := range []uint32{0, 2, 4, 5} {
   301  		if !bc.isStakeMajorityVersion(version, node) {
   302  			t.Fatalf("invalid StakeVersion expected %d -> true",
   303  				version)
   304  		}
   305  
   306  	}
   307  	if bc.isStakeMajorityVersion(6, node) {
   308  		t.Fatalf("invalid StakeVersion expected 6 -> false")
   309  	}
   310  }
   311  
   312  // TestCalcStakeVersion ensures that stake version calculation works as
   313  // intended when
   314  func TestCalcStakeVersion(t *testing.T) {
   315  	params := &chaincfg.SimNetParams
   316  	svh := params.StakeValidationHeight
   317  	svi := params.StakeVersionInterval
   318  	tpb := params.TicketsPerBlock
   319  
   320  	tests := []struct {
   321  		name          string
   322  		numNodes      int64
   323  		expectVersion uint32
   324  		set           func(*blockNode)
   325  	}{
   326  		{
   327  			name:          "headerStake 2 votes 3",
   328  			numNodes:      svh + svi*3,
   329  			expectVersion: 3,
   330  			set: func(node *blockNode) {
   331  				if node.height > svh {
   332  					appendFakeVotes(node, tpb, 3, 0)
   333  					node.stakeVersion = 2
   334  					node.blockVersion = 3
   335  				}
   336  			},
   337  		},
   338  		{
   339  			name:          "headerStake 3 votes 2",
   340  			numNodes:      svh + svi*3,
   341  			expectVersion: 3,
   342  			set: func(node *blockNode) {
   343  				if node.height > svh {
   344  					appendFakeVotes(node, tpb, 2, 0)
   345  					node.stakeVersion = 3
   346  					node.blockVersion = 3
   347  				}
   348  			},
   349  		},
   350  	}
   351  
   352  	for _, test := range tests {
   353  		bc := newFakeChain(params)
   354  		node := bc.bestChain.Tip()
   355  
   356  		for i := int64(1); i <= test.numNodes; i++ {
   357  			node = newFakeNode(node, 1, 0, 0, time.Now())
   358  			test.set(node)
   359  			bc.bestChain.SetTip(node)
   360  		}
   361  
   362  		version := bc.calcStakeVersion(bc.bestChain.Tip())
   363  		if version != test.expectVersion {
   364  			t.Fatalf("version mismatch: got %v expected %v",
   365  				version, test.expectVersion)
   366  		}
   367  	}
   368  }
   369  
   370  // TestIsStakeMajorityVersion ensures that determining the current majority
   371  // stake version works as intended under a wide variety of scenarios.
   372  func TestIsStakeMajorityVersion(t *testing.T) {
   373  	params := &chaincfg.RegNetParams
   374  	svh := params.StakeValidationHeight
   375  	svi := params.StakeVersionInterval
   376  	tpb := params.TicketsPerBlock
   377  
   378  	// Calculate super majority for 5 and 3 ticket maxes.
   379  	maxTickets5 := int32(svi) * int32(tpb)
   380  	sm5 := maxTickets5 * params.StakeMajorityMultiplier / params.StakeMajorityDivisor
   381  	maxTickets3 := int32(svi) * int32(tpb-2)
   382  	sm3 := maxTickets3 * params.StakeMajorityMultiplier / params.StakeMajorityDivisor
   383  
   384  	// Keep track of ticketcount in set.  Must be reset every test.
   385  	ticketCount := int32(0)
   386  
   387  	tests := []struct {
   388  		name                 string
   389  		numNodes             int64
   390  		set                  func(*blockNode)
   391  		blockVersion         int32
   392  		startStakeVersion    uint32
   393  		expectedStakeVersion uint32
   394  		expectedCalcVersion  uint32
   395  		result               bool
   396  	}{
   397  		{
   398  			name:                 "too shallow",
   399  			numNodes:             svh + svi - 1,
   400  			startStakeVersion:    1,
   401  			expectedStakeVersion: 1,
   402  			expectedCalcVersion:  0,
   403  			result:               true,
   404  		},
   405  		{
   406  			name:                 "just enough",
   407  			numNodes:             svh + svi,
   408  			startStakeVersion:    1,
   409  			expectedStakeVersion: 1,
   410  			expectedCalcVersion:  0,
   411  			result:               true,
   412  		},
   413  		{
   414  			name:                 "odd",
   415  			numNodes:             svh + svi + 1,
   416  			startStakeVersion:    1,
   417  			expectedStakeVersion: 1,
   418  			expectedCalcVersion:  0,
   419  			result:               true,
   420  		},
   421  		{
   422  			name:     "100%",
   423  			numNodes: svh + svi,
   424  			set: func(node *blockNode) {
   425  				if node.height > svh {
   426  					appendFakeVotes(node, tpb, 2, 0)
   427  				}
   428  			},
   429  			startStakeVersion:    1,
   430  			expectedStakeVersion: 2,
   431  			expectedCalcVersion:  0,
   432  			result:               true,
   433  		},
   434  		{
   435  			name:     "50%",
   436  			numNodes: svh + (svi * 2),
   437  			set: func(node *blockNode) {
   438  				if node.height <= svh {
   439  					return
   440  				}
   441  
   442  				if node.height < svh+svi {
   443  					appendFakeVotes(node, tpb, 1, 0)
   444  					return
   445  				}
   446  
   447  				threshold := maxTickets5 / 2
   448  
   449  				v := uint32(1)
   450  				for i := 0; i < int(tpb); i++ {
   451  					if ticketCount >= threshold {
   452  						v = 2
   453  					}
   454  					appendFakeVotes(node, 1, v, 0)
   455  					ticketCount++
   456  				}
   457  			},
   458  			startStakeVersion:    1,
   459  			expectedStakeVersion: 2,
   460  			expectedCalcVersion:  0,
   461  			result:               false,
   462  		},
   463  		{
   464  			name:     "75%-1",
   465  			numNodes: svh + (svi * 2),
   466  			set: func(node *blockNode) {
   467  				if node.height < svh {
   468  					return
   469  				}
   470  
   471  				if node.height < svh+svi {
   472  					appendFakeVotes(node, tpb, 1, 0)
   473  					return
   474  				}
   475  
   476  				threshold := maxTickets5 - sm5 + 1
   477  
   478  				v := uint32(1)
   479  				for i := 0; i < int(tpb); i++ {
   480  					if ticketCount >= threshold {
   481  						v = 2
   482  					}
   483  					appendFakeVotes(node, 1, v, 0)
   484  					ticketCount++
   485  				}
   486  			},
   487  			startStakeVersion:    1,
   488  			expectedStakeVersion: 2,
   489  			expectedCalcVersion:  0,
   490  			result:               false,
   491  		},
   492  		{
   493  			name:     "75%",
   494  			numNodes: svh + (svi * 2),
   495  			set: func(node *blockNode) {
   496  				if node.height <= svh {
   497  					return
   498  				}
   499  
   500  				if node.height < svh+svi {
   501  					appendFakeVotes(node, tpb, 1, 0)
   502  					return
   503  				}
   504  
   505  				threshold := maxTickets5 - sm5
   506  
   507  				v := uint32(1)
   508  				for i := 0; i < int(tpb); i++ {
   509  					if ticketCount >= threshold {
   510  						v = 2
   511  					}
   512  					appendFakeVotes(node, 1, v, 0)
   513  					ticketCount++
   514  				}
   515  			},
   516  			startStakeVersion:    1,
   517  			expectedStakeVersion: 2,
   518  			expectedCalcVersion:  0,
   519  			result:               true,
   520  		},
   521  		{
   522  			name:     "100% after several non majority intervals",
   523  			numNodes: svh + (params.StakeVersionInterval * 222),
   524  			set: func(node *blockNode) {
   525  				if node.height <= svh {
   526  					return
   527  				}
   528  
   529  				if node.height < svh+svi {
   530  					appendFakeVotes(node, tpb, 1, 0)
   531  					return
   532  				}
   533  
   534  				for i := uint32(0); i < uint32(tpb); i++ {
   535  					appendFakeVotes(node, 1, i%5, 0)
   536  				}
   537  			},
   538  			startStakeVersion:    1,
   539  			expectedStakeVersion: 1,
   540  			expectedCalcVersion:  0,
   541  			result:               true,
   542  		},
   543  		{
   544  			name:     "no majority ever",
   545  			numNodes: svh + (svi * 8),
   546  			set: func(node *blockNode) {
   547  				if node.height <= svh {
   548  					return
   549  				}
   550  
   551  				for i := uint32(0); i < uint32(tpb); i++ {
   552  					appendFakeVotes(node, 1, i%5, 0)
   553  				}
   554  			},
   555  			startStakeVersion:    1,
   556  			expectedStakeVersion: 1,
   557  			expectedCalcVersion:  0,
   558  			result:               true,
   559  		},
   560  		{
   561  			name:     "75%-1 with 3 votes",
   562  			numNodes: svh + (svi * 2),
   563  			set: func(node *blockNode) {
   564  				if node.height < svh {
   565  					return
   566  				}
   567  
   568  				if node.height < svh+svi {
   569  					appendFakeVotes(node, tpb-2, 1, 0)
   570  					return
   571  				}
   572  
   573  				threshold := maxTickets3 - sm3 + 1
   574  
   575  				v := uint32(1)
   576  				for i := 0; i < int(tpb-2); i++ {
   577  					if ticketCount >= threshold {
   578  						v = 2
   579  					}
   580  					appendFakeVotes(node, 1, v, 0)
   581  					ticketCount++
   582  				}
   583  			},
   584  			startStakeVersion:    1,
   585  			expectedStakeVersion: 2,
   586  			expectedCalcVersion:  0,
   587  			result:               false,
   588  		},
   589  		{
   590  			name:     "75% with 3 votes",
   591  			numNodes: svh + (svi * 2),
   592  			set: func(node *blockNode) {
   593  				if node.height <= svh {
   594  					return
   595  				}
   596  
   597  				if node.height < svh+svi {
   598  					appendFakeVotes(node, tpb-2, 1, 0)
   599  					return
   600  				}
   601  
   602  				threshold := maxTickets3 - sm3
   603  
   604  				v := uint32(1)
   605  				for i := 0; i < int(tpb-2); i++ {
   606  					if ticketCount >= threshold {
   607  						v = 2
   608  					}
   609  					appendFakeVotes(node, 1, v, 0)
   610  					ticketCount++
   611  				}
   612  			},
   613  			startStakeVersion:    1,
   614  			expectedStakeVersion: 2,
   615  			expectedCalcVersion:  0,
   616  			result:               true,
   617  		},
   618  		{
   619  			name:     "75% with 3 votes blockversion 3",
   620  			numNodes: svh + (svi * 2),
   621  			set: func(node *blockNode) {
   622  				if node.height <= svh {
   623  					return
   624  				}
   625  
   626  				if node.height < svh+svi {
   627  					appendFakeVotes(node, tpb-2, 1, 0)
   628  					return
   629  				}
   630  
   631  				threshold := maxTickets3 - sm3
   632  
   633  				v := uint32(1)
   634  				for i := 0; i < int(tpb-2); i++ {
   635  					if ticketCount >= threshold {
   636  						v = 2
   637  					}
   638  					appendFakeVotes(node, 1, v, 0)
   639  					ticketCount++
   640  				}
   641  			},
   642  			blockVersion:         3,
   643  			startStakeVersion:    1,
   644  			expectedStakeVersion: 2,
   645  			expectedCalcVersion:  2,
   646  			result:               true,
   647  		},
   648  		{
   649  			name:     "75%-1 with 3 votes blockversion 3",
   650  			numNodes: svh + (svi * 2),
   651  			set: func(node *blockNode) {
   652  				if node.height < svh {
   653  					return
   654  				}
   655  
   656  				if node.height < svh+svi {
   657  					appendFakeVotes(node, tpb-2, 1, 0)
   658  					return
   659  				}
   660  
   661  				threshold := maxTickets3 - sm3 + 1
   662  
   663  				v := uint32(1)
   664  				for i := 0; i < int(tpb-2); i++ {
   665  					if ticketCount >= threshold {
   666  						v = 2
   667  					}
   668  					appendFakeVotes(node, 1, v, 0)
   669  					ticketCount++
   670  				}
   671  			},
   672  			blockVersion:         3,
   673  			startStakeVersion:    1,
   674  			expectedStakeVersion: 2,
   675  			expectedCalcVersion:  1,
   676  			result:               false,
   677  		},
   678  	}
   679  
   680  	for _, test := range tests {
   681  		// Create new BlockChain in order to blow away cache.
   682  		bc := newFakeChain(params)
   683  		node := bc.bestChain.Tip()
   684  		node.stakeVersion = test.startStakeVersion
   685  
   686  		ticketCount = 0
   687  
   688  		for i := int64(1); i <= test.numNodes; i++ {
   689  			node = newFakeNode(node, test.blockVersion,
   690  				test.startStakeVersion, 0, time.Now())
   691  
   692  			// Override version.
   693  			if test.set != nil {
   694  				test.set(node)
   695  			} else {
   696  				appendFakeVotes(node, tpb,
   697  					test.startStakeVersion, 0)
   698  			}
   699  
   700  			bc.bestChain.SetTip(node)
   701  		}
   702  
   703  		res := bc.isVoterMajorityVersion(test.expectedStakeVersion, node)
   704  		if res != test.result {
   705  			t.Fatalf("%v isVoterMajorityVersion", test.name)
   706  		}
   707  
   708  		// validate calcStakeVersion
   709  		version := bc.calcStakeVersion(node)
   710  		if version != test.expectedCalcVersion {
   711  			t.Fatalf("%v calcStakeVersionByNode got %v expected %v",
   712  				test.name, version, test.expectedCalcVersion)
   713  		}
   714  	}
   715  }
   716  
   717  func TestLarge(t *testing.T) {
   718  	params := &chaincfg.MainNetParams
   719  
   720  	numRuns := 5
   721  	numBlocks := params.StakeVersionInterval * 100
   722  	numBlocksShallow := params.StakeVersionInterval * 10
   723  	tests := []struct {
   724  		name                 string
   725  		numNodes             int64
   726  		blockVersion         int32
   727  		startStakeVersion    uint32
   728  		expectedStakeVersion uint32
   729  		expectedCalcVersion  uint32
   730  		result               bool
   731  	}{
   732  		{
   733  			name:                 "shallow cache",
   734  			numNodes:             numBlocksShallow,
   735  			startStakeVersion:    1,
   736  			expectedStakeVersion: 1,
   737  			expectedCalcVersion:  0,
   738  			result:               true,
   739  		},
   740  		{
   741  			name:                 "deep cache",
   742  			numNodes:             numBlocks,
   743  			startStakeVersion:    1,
   744  			expectedStakeVersion: 1,
   745  			expectedCalcVersion:  0,
   746  			result:               true,
   747  		},
   748  	}
   749  
   750  	for _, test := range tests {
   751  		// Create new BlockChain in order to blow away cache.
   752  		bc := newFakeChain(params)
   753  		node := bc.bestChain.Tip()
   754  		node.stakeVersion = test.startStakeVersion
   755  
   756  		for i := int64(1); i <= test.numNodes; i++ {
   757  			node = newFakeNode(node, test.blockVersion,
   758  				test.startStakeVersion, 0, time.Now())
   759  
   760  			// Override version.
   761  			appendFakeVotes(node, params.TicketsPerBlock,
   762  				test.startStakeVersion, 0)
   763  			bc.bestChain.SetTip(node)
   764  		}
   765  
   766  		for x := 0; x < numRuns; x++ {
   767  			start := time.Now()
   768  			res := bc.isVoterMajorityVersion(test.expectedStakeVersion, node)
   769  			if res != test.result {
   770  				t.Fatalf("%v isVoterMajorityVersion got %v expected %v", test.name, res, test.result)
   771  			}
   772  
   773  			// validate calcStakeVersion
   774  			version := bc.calcStakeVersion(node)
   775  			if version != test.expectedCalcVersion {
   776  				t.Fatalf("%v calcStakeVersion got %v expected %v",
   777  					test.name, version, test.expectedCalcVersion)
   778  			}
   779  			end := time.Now()
   780  
   781  			setup := "setup 0"
   782  			if x != 0 {
   783  				setup = fmt.Sprintf("run %v", x)
   784  			}
   785  
   786  			vkey := stakeMajorityCacheKeySize + 8 // bool on x86_64
   787  			key := chainhash.HashSize + 4         // size of uint32
   788  
   789  			cost := len(bc.isVoterMajorityVersionCache) * vkey
   790  			cost += len(bc.isStakeMajorityVersionCache) * vkey
   791  			cost += len(bc.calcPriorStakeVersionCache) * key
   792  			cost += len(bc.calcVoterVersionIntervalCache) * key
   793  			cost += len(bc.calcStakeVersionCache) * key
   794  			memoryCost := fmt.Sprintf("memory cost: %v", cost)
   795  
   796  			t.Logf("run time (%v) %v %v", setup, end.Sub(start),
   797  				memoryCost)
   798  		}
   799  	}
   800  }