dcrd/blockchain/thresholdstate_test.go
Aaron Campbell 8be96a8729 multi: Correct typos.
Correct typos found by reading code, ispell, and creative grepping.
2019-08-22 10:20:03 -05:00

608 lines
24 KiB
Go

// Copyright (c) 2017-2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockchain
import (
"fmt"
"math"
"testing"
"time"
"github.com/decred/dcrd/blockchain/v2/chaingen"
"github.com/decred/dcrd/chaincfg/v2"
)
const (
// vbPrevBlockValid defines the vote bit necessary to vote yes to the
// previous block being valid.
vbPrevBlockValid = 0x01
// testDummy1ID is the human-readable ID for the first test dummy voting
// agenda.
testDummy1ID = "testdummy1"
// testDummy1YesIndex is the offset in the choices slice of the first
// test dummy agenda for the yes choice.
testDummy1YesIndex = 2
// testDummy2NoIndex is the offset in the choices slice of the second
// test dummy agenda for the no choice.
testDummy2NoIndex = 1
// vbTestDummy1No defines the vote bits necessary to vote no on the first
// test dummy agenda as well as yes to the previous block being valid.
vbTestDummy1No = 0x02
// vbTestDummy1Yes defines the vote bits necessary to vote yes on the
// first test dummy agenda as well as yes to the previous block being
// valid.
vbTestDummy1Yes = 0x04
// testDummy2ID is the human-readable ID for the second test dummy
// voting agenda.
testDummy2ID = "testdummy2"
// vbTestDummy2No defines the vote bits necessary to vote no on the
// second test dummy agenda as well as yes to the previous block being
// valid.
vbTestDummy2No = 0x08
// vbTestDummy2Yes defines the vote bits necessary to vote yes on the
// second test dummy agenda as well as yes to the previous block being
// valid.
vbTestDummy2Yes = 0x10
)
var (
// testDummy1 is a voting agenda used throughout these tests.
testDummy1 = chaincfg.Vote{
Id: testDummy1ID,
Description: "",
Mask: 0x6, // 0b0110
Choices: []chaincfg.Choice{{
Id: "abstain",
Description: "abstain voting for change",
Bits: 0x0000,
IsAbstain: true,
IsNo: false,
}, {
Id: "no",
Description: "vote no",
Bits: 0x0002, // Bit 1
IsAbstain: false,
IsNo: true,
}, {
Id: "yes",
Description: "vote yes",
Bits: 0x0004, // Bit 2
IsAbstain: false,
IsNo: false,
}},
}
// testDummy2 is a voting agenda used throughout these tests.
testDummy2 = chaincfg.Vote{
Id: testDummy2ID,
Description: "",
Mask: 0x18, // 0b11000
Choices: []chaincfg.Choice{{
Id: "abstain",
Description: "abstain voting for change",
Bits: 0x0000,
IsAbstain: true,
IsNo: false,
}, {
Id: "no",
Description: "vote no",
Bits: 0x0008, // Bit 3
IsAbstain: false,
IsNo: true,
}, {
Id: "yes",
Description: "vote yes",
Bits: 0x0010, // Bit 4
IsAbstain: false,
IsNo: false,
}},
}
)
// TestThresholdState ensures that the threshold state function progresses
// through the states correctly.
func TestThresholdState(t *testing.T) {
// Create chain params based on regnet params, but add a specific test
// dummy deployment and set the proof-of-work difficulty readjustment
// size to a really large number so that the test chain can be generated
// more quickly.
posVersion := uint32(4)
params := chaincfg.RegNetParams()
params.WorkDiffWindowSize = 200000
params.WorkDiffWindows = 1
params.TargetTimespan = params.TargetTimePerBlock *
time.Duration(params.WorkDiffWindowSize)
if params.Deployments == nil {
params.Deployments = make(map[uint32][]chaincfg.ConsensusDeployment)
}
params.Deployments[posVersion] = append(params.Deployments[posVersion],
chaincfg.ConsensusDeployment{
Vote: testDummy1,
StartTime: 0,
ExpireTime: math.MaxUint64,
})
params.Deployments[posVersion] = append(params.Deployments[posVersion],
chaincfg.ConsensusDeployment{
Vote: testDummy2,
StartTime: 0,
ExpireTime: math.MaxUint64,
})
// Create a test harness initialized with the genesis block as the tip.
g, teardownFunc := newChaingenHarness(t, params, "thresholdstatetest")
defer teardownFunc()
// Shorter versions of useful params for convenience.
ticketsPerBlock := int64(params.TicketsPerBlock)
coinbaseMaturity := params.CoinbaseMaturity
stakeEnabledHeight := params.StakeEnabledHeight
stakeValidationHeight := params.StakeValidationHeight
stakeVerInterval := params.StakeVersionInterval
ruleChangeInterval := int64(params.RuleChangeActivationInterval)
powNumToCheck := int64(params.BlockUpgradeNumToCheck)
ruleChangeQuorum := int64(params.RuleChangeActivationQuorum)
ruleChangeMult := int64(params.RuleChangeActivationMultiplier)
ruleChangeDiv := int64(params.RuleChangeActivationDivisor)
// ---------------------------------------------------------------------
// Block One.
//
// NOTE: The advance funcs on the harness are intentionally not used in
// these tests since they need to manually test the threshold state at
// all key heights.
// ---------------------------------------------------------------------
// Add the required initial block.
//
// genesis -> bfb
g.CreateBlockOne("bfb", 0)
g.AssertTipHeight(1)
g.AcceptTipBlock()
g.TestThresholdStateChoice(testDummy1ID, ThresholdDefined, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdDefined, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to have mature coinbase outputs to work with.
//
// genesis -> bfb -> bm0 -> bm1 -> ... -> bm#
// ---------------------------------------------------------------------
for i := uint16(0); i < coinbaseMaturity; i++ {
blockName := fmt.Sprintf("bm%d", i)
g.NextBlock(blockName, nil, nil)
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(coinbaseMaturity) + 1)
g.TestThresholdStateChoice(testDummy1ID, ThresholdDefined, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdDefined, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to reach the stake enabled height while
// creating ticket purchases that spend from the coinbases matured
// above. This will also populate the pool of immature tickets.
//
// ... -> bm# ... -> bse0 -> bse1 -> ... -> bse#
// ---------------------------------------------------------------------
var ticketsPurchased int
for i := int64(0); int64(g.Tip().Header.Height) < stakeEnabledHeight; i++ {
outs := g.OldestCoinbaseOuts()
ticketOuts := outs[1:]
ticketsPurchased += len(ticketOuts)
blockName := fmt.Sprintf("bse%d", i)
g.NextBlock(blockName, nil, ticketOuts)
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeEnabledHeight))
g.TestThresholdStateChoice(testDummy1ID, ThresholdDefined, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdDefined, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to reach the stake validation height while
// continuing to purchase tickets using the coinbases matured above and
// allowing the immature tickets to mature and thus become live.
//
// The blocks are also generated with version 3 to ensure stake version
// enforcement is reached.
// ---------------------------------------------------------------------
targetPoolSize := int64(g.Params().TicketPoolSize) * ticketsPerBlock
for i := int64(0); int64(g.Tip().Header.Height) < stakeValidationHeight; i++ {
// Only purchase tickets until the target ticket pool size is
// reached.
outs := g.OldestCoinbaseOuts()
ticketOuts := outs[1:]
if ticketsPurchased+len(ticketOuts) > int(targetPoolSize) {
ticketsNeeded := int(targetPoolSize) - ticketsPurchased
if ticketsNeeded > 0 {
ticketOuts = ticketOuts[1 : ticketsNeeded+1]
} else {
ticketOuts = nil
}
}
ticketsPurchased += len(ticketOuts)
blockName := fmt.Sprintf("bsv%d", i)
g.NextBlock(blockName, nil, ticketOuts,
chaingen.ReplaceBlockVersion(3))
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight))
g.TestThresholdStateChoice(testDummy1ID, ThresholdDefined, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdDefined, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to reach one block before the next stake
// version interval with block version 3, stake version 0, and vote
// version 3.
//
// This will result in triggering enforcement of the stake version and
// that the stake version is 3. The threshold state for the test dummy
// deployments must still be defined since a v4 majority proof-of-work
// and proof-of-stake upgrade are required before moving to started.
// ---------------------------------------------------------------------
blocksNeeded := stakeValidationHeight + stakeVerInterval - 1 -
int64(g.Tip().Header.Height)
for i := int64(0); i < blocksNeeded; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bsvtA%d", i)
g.NextBlock(blockName, nil, outs[1:],
chaingen.ReplaceBlockVersion(3),
chaingen.ReplaceStakeVersion(0),
chaingen.ReplaceVoteVersions(3))
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight + stakeVerInterval - 1))
g.AssertBlockVersion(3)
g.AssertStakeVersion(0)
g.TestThresholdStateChoice(testDummy1ID, ThresholdDefined, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdDefined, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to reach one block before the next rule change
// interval with block version 3, stake version 3, and vote version 3.
//
// The threshold state for the dummy deployments must still be defined
// since it can only change on a rule change boundary and it requires a
// v4 majority proof-of-work and proof-of-stake upgrade before moving to
// started.
// ---------------------------------------------------------------------
blocksNeeded = stakeValidationHeight + ruleChangeInterval - 2 -
int64(g.Tip().Header.Height)
for i := int64(0); i < blocksNeeded; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bsvtB%d", i)
g.NextBlock(blockName, nil, outs[1:],
chaingen.ReplaceBlockVersion(3),
chaingen.ReplaceStakeVersion(3),
chaingen.ReplaceVoteVersions(3))
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval - 2))
g.AssertBlockVersion(3)
g.AssertStakeVersion(3)
g.TestThresholdStateChoice(testDummy1ID, ThresholdDefined, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdDefined, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to reach one block before the next stake
// version interval with block version 3, stake version 3, and vote
// version 4.
//
// This will result in achieving stake version 4 enforcement.
//
// The threshold state for the dummy deployments must still be defined
// since it can only change on a rule change boundary and it still
// requires a v4 majority proof-of-work upgrade before moving to
// started.
// ---------------------------------------------------------------------
blocksNeeded = stakeValidationHeight + stakeVerInterval*4 - 1 -
int64(g.Tip().Header.Height)
for i := int64(0); i < blocksNeeded; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bsvtC%d", i)
g.NextBlock(blockName, nil, outs[1:],
chaingen.ReplaceBlockVersion(3),
chaingen.ReplaceStakeVersion(3),
chaingen.ReplaceVoteVersions(4))
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight + stakeVerInterval*4 - 1))
g.AssertBlockVersion(3)
g.AssertStakeVersion(3)
g.TestThresholdStateChoice(testDummy1ID, ThresholdDefined, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdDefined, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
// block version 3 majority, stake version 4, and vote version 4. Set
// the final two blocks to block version 4 so that majority version 4
// is not achieved, but the final block in the interval is version 4.
//
// The threshold state for the dummy deployments must still be defined
// since it still requires a v4 majority proof-of-work upgrade before
// moving to started.
// ---------------------------------------------------------------------
blocksNeeded = stakeValidationHeight + ruleChangeInterval*2 - 1 -
int64(g.Tip().Header.Height)
for i := int64(0); i < blocksNeeded; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bsvtD%d", i)
blockVersion := int32(3)
if i >= blocksNeeded-2 {
blockVersion = 4
}
g.NextBlock(blockName, nil, outs[1:],
chaingen.ReplaceBlockVersion(blockVersion),
chaingen.ReplaceStakeVersion(4),
chaingen.ReplaceVoteVersions(4))
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*2 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
g.TestThresholdStateChoice(testDummy1ID, ThresholdDefined, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdDefined, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to achieve proof-of-work block version lockin
// with block version 4, stake version 4, and vote version 4. Also, set
// the vote bits to include yes votes for the first test dummy agenda
// and no for the second test dummy agenda for an upcoming test.
//
// Since v4 majority proof-of-stake upgrade has been already been
// achieved and this will achieve v4 majority proof-of-work upgrade,
// voting can begin at the next rule change interval.
//
// The threshold state for the dummy deployments must still be defined
// since even though all required upgrade conditions are met, the state
// change must not happen until the start of the next rule change
// interval.
// ---------------------------------------------------------------------
for i := int64(0); i < powNumToCheck; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bsvtE%d", i)
g.NextBlock(blockName, nil, outs[1:],
chaingen.ReplaceBlockVersion(4),
chaingen.ReplaceStakeVersion(4),
chaingen.ReplaceVotes(vbPrevBlockValid|vbTestDummy1Yes|
vbTestDummy2No, 4))
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*2 -
1 + powNumToCheck))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
g.TestThresholdStateChoice(testDummy1ID, ThresholdDefined, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdDefined, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
// block version 4, stake version 4, and vote version 4. Also, set the
// vote bits to include yes votes for the first test dummy agenda and
// no for the second test dummy agenda to ensure they aren't counted.
//
// The threshold state for the dummy deployments must move to started.
// Even though the majority of the votes have already been voting yes
// for the first test dummy agenda, and no for the second one, they must
// not count, otherwise it would move straight to lockedin or failed,
// respectively.
// ---------------------------------------------------------------------
blocksNeeded = stakeValidationHeight + ruleChangeInterval*3 - 1 -
int64(g.Tip().Header.Height)
for i := int64(0); i < blocksNeeded; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bsvtF%d", i)
g.NextBlock(blockName, nil, outs[1:],
chaingen.ReplaceBlockVersion(4),
chaingen.ReplaceStakeVersion(4),
chaingen.ReplaceVotes(vbPrevBlockValid|vbTestDummy1Yes|
vbTestDummy2No, 4))
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*3 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
g.TestThresholdStateChoice(testDummy1ID, ThresholdStarted, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdStarted, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
// block version 4, stake version 4, and vote version 3. Also, set the
// vote bits to include yes votes for the first test dummy agenda and
// no for the second test dummy agenda to ensure they aren't counted.
//
// The threshold state for the dummy deployments must remain in started
// because the votes are an old version and thus have a different
// definition and don't apply to version 4.
// ---------------------------------------------------------------------
blocksNeeded = stakeValidationHeight + ruleChangeInterval*4 - 1 -
int64(g.Tip().Header.Height)
for i := int64(0); i < blocksNeeded; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bsvtG%d", i)
g.NextBlock(blockName, nil, outs[1:],
chaingen.ReplaceBlockVersion(4),
chaingen.ReplaceStakeVersion(4),
chaingen.ReplaceVotes(vbPrevBlockValid|vbTestDummy1Yes|
vbTestDummy2No, 3))
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*4 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
g.TestThresholdStateChoice(testDummy1ID, ThresholdStarted, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdStarted, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
// block version 4, stake version 4, and vote version 4. Set the vote
// bits such that quorum is not reached, but there is a majority yes
// votes for the first test dummy agenda and a majority no for the
// second test dummy agenda.
//
// The threshold state for the dummy deployments must remain in started
// because quorum was not reached.
// ---------------------------------------------------------------------
var totalVotes int64
blocksNeeded = stakeValidationHeight + ruleChangeInterval*5 - 1 -
int64(g.Tip().Header.Height)
for i := int64(0); i < blocksNeeded; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bsvtH%d", i)
voteBits := uint16(vbPrevBlockValid) // Abstain both test dummy
if totalVotes+ticketsPerBlock < ruleChangeQuorum {
voteBits = vbPrevBlockValid | vbTestDummy1Yes |
vbTestDummy2No
}
g.NextBlock(blockName, nil, outs[1:],
chaingen.ReplaceBlockVersion(4),
chaingen.ReplaceStakeVersion(4),
chaingen.ReplaceVotes(voteBits, 4))
totalVotes += ticketsPerBlock
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*5 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
g.TestThresholdStateChoice(testDummy1ID, ThresholdStarted, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdStarted, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
// block version 4, stake version 4, and vote version 4. Set the vote
// bits such that quorum is reached, but there are a few votes shy of a
// majority yes for the first test dummy agenda and a few votes shy of a
// majority no for the second test dummy agenda.
//
// The threshold state for the dummy deployments must remain in started
// because even though quorum was reached, a required majority was not.
// ---------------------------------------------------------------------
blocksNeeded = stakeValidationHeight + ruleChangeInterval*6 - 1 -
int64(g.Tip().Header.Height)
totalVotes = 0
numActiveNeeded := ruleChangeQuorum * 2
numMinorityNeeded := numActiveNeeded*ruleChangeMult/ruleChangeDiv - 1
if numActiveNeeded > ticketsPerBlock*blocksNeeded {
numActiveNeeded = ticketsPerBlock * blocksNeeded
}
for i := int64(0); i < blocksNeeded; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bsvtI%d", i)
voteBits := uint16(vbPrevBlockValid) // Abstain both test dummy
if totalVotes+ticketsPerBlock < numMinorityNeeded {
voteBits = vbPrevBlockValid | vbTestDummy1Yes |
vbTestDummy2No
} else if totalVotes+ticketsPerBlock <= numActiveNeeded {
voteBits = vbPrevBlockValid | vbTestDummy1No |
vbTestDummy2Yes
}
g.NextBlock(blockName, nil, outs[1:],
chaingen.ReplaceBlockVersion(4),
chaingen.ReplaceStakeVersion(4),
chaingen.ReplaceVotes(voteBits, 4))
totalVotes += ticketsPerBlock
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*6 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
g.TestThresholdStateChoice(testDummy1ID, ThresholdStarted, invalidChoice)
g.TestThresholdStateChoice(testDummy2ID, ThresholdStarted, invalidChoice)
// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
// block version 4, stake version 4, and vote version 4. Also, set the
// vote bits to yes for the first test dummy agenda and no to the second
// one.
//
// The threshold state for the first dummy deployment must move to
// lockedin since a majority yes vote was achieved while the second
// dummy deployment must move to failed since a majority no vote was
// achieved.
// ---------------------------------------------------------------------
blocksNeeded = stakeValidationHeight + ruleChangeInterval*7 - 1 -
int64(g.Tip().Header.Height)
for i := int64(0); i < blocksNeeded; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bsvtJ%d", i)
g.NextBlock(blockName, nil, outs[1:],
chaingen.ReplaceBlockVersion(4),
chaingen.ReplaceStakeVersion(4),
chaingen.ReplaceVotes(vbPrevBlockValid|vbTestDummy1Yes|
vbTestDummy2No, 4))
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*7 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
g.TestThresholdStateChoice(testDummy1ID, ThresholdLockedIn, testDummy1YesIndex)
g.TestThresholdStateChoice(testDummy2ID, ThresholdFailed, testDummy2NoIndex)
// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
// block version 4, stake version 4, and vote version 4. Also, set the
// vote bits to include no votes for the first test dummy agenda and
// yes votes for the second one.
//
// The threshold state for the first dummy deployment must move to
// active since even though the interval had a majority no votes,
// lockedin status has already been achieved and can't be undone without
// a new agenda. Similarly, the second one must remain in failed even
// though the interval had a majority yes votes since a failed state
// can't be undone.
// ---------------------------------------------------------------------
blocksNeeded = stakeValidationHeight + ruleChangeInterval*8 - 1 -
int64(g.Tip().Header.Height)
for i := int64(0); i < blocksNeeded; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bsvtK%d", i)
g.NextBlock(blockName, nil, outs[1:],
chaingen.ReplaceBlockVersion(4),
chaingen.ReplaceStakeVersion(4),
chaingen.ReplaceVotes(vbPrevBlockValid|vbTestDummy1No|
vbTestDummy2Yes, 4))
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*8 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
g.TestThresholdStateChoice(testDummy1ID, ThresholdActive, testDummy1YesIndex)
g.TestThresholdStateChoice(testDummy2ID, ThresholdFailed, testDummy2NoIndex)
}