dcrd/blockchain/votebits_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

1668 lines
39 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 (
"testing"
"time"
"github.com/decred/dcrd/blockchain/stake/v2"
"github.com/decred/dcrd/chaincfg/v2"
)
var (
posVersion = uint32(4)
powVersion = int32(4)
pedro = chaincfg.Vote{
Id: "voteforpedro",
Description: "You should always vote for Pedro",
Mask: 0x6, // 0b0110
Choices: []chaincfg.Choice{
{
Id: "Abstain",
Description: "Abstain voting for Pedro",
Bits: 0x0, // 0b0000
IsAbstain: true,
IsNo: false,
},
{
Id: "Yes",
Description: "Vote for Pedro",
Bits: 0x2, // 0b0010
IsAbstain: false,
IsNo: false,
},
{
Id: "No",
Description: "Dont vote for Pedro",
Bits: 0x4, // 0b0100
IsAbstain: false,
IsNo: true,
},
},
}
multipleChoice = chaincfg.Vote{
Id: "multiplechoice",
Description: "Pick one",
Mask: 0x70, // 0b0111 0000
Choices: []chaincfg.Choice{
{
Id: "Abstain",
Description: "Abstain multiple choice",
Bits: 0x0, // 0b0000 0000
IsAbstain: true,
IsNo: false,
},
{
Id: "one",
Description: "Choice 1",
Bits: 0x10, // 0b0001 0000
IsAbstain: false,
IsNo: false,
},
{
Id: "Vote against",
Description: "Vote against all multiple",
Bits: 0x20, // 0b0010 0000
IsAbstain: false,
IsNo: true,
},
{
Id: "two",
Description: "Choice 2",
Bits: 0x30, // 0b0011 0000
IsAbstain: false,
IsNo: false,
},
{
Id: "three",
Description: "Choice 3",
Bits: 0x40, // 0b0100 0000
IsAbstain: false,
IsNo: false,
},
{
Id: "four",
Description: "Choice 4",
Bits: 0x50, // 0b0101 0000
IsAbstain: false,
IsNo: false,
},
},
}
)
// defaultParams returns net parameters modified to have a single known
// deployment that is used throughout the various votebit tests.
func defaultParams(vote chaincfg.Vote) chaincfg.Params {
params := chaincfg.RegNetParams()
params.Deployments = make(map[uint32][]chaincfg.ConsensusDeployment)
params.Deployments[posVersion] = []chaincfg.ConsensusDeployment{{
Vote: vote,
StartTime: uint64(time.Now().Add(time.Duration(
params.RuleChangeActivationInterval) *
time.Second).Unix()),
ExpireTime: uint64(time.Now().Add(24 * time.Hour).Unix()),
}}
return *params
}
// TestNoQuorum ensures that the quorum behavior works as expected with no
// votes.
func TestNoQuorum(t *testing.T) {
params := defaultParams(pedro)
bc := newFakeChain(&params)
node := bc.bestChain.Tip()
node.stakeVersion = posVersion
// get to svi
curTimestamp := time.Now()
for i := uint32(0); i < uint32(params.StakeValidationHeight); i++ {
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
ts, err := bc.NextThresholdState(&node.hash, posVersion, pedro.Id)
if err != nil {
t.Fatalf("NextThresholdState(SVI): %v", err)
}
tse := ThresholdStateTuple{
State: ThresholdDefined,
Choice: invalidChoice,
}
if ts != tse {
t.Fatalf("expected %+v got %+v", ts, tse)
}
// get to started
for i := uint32(0); i < params.RuleChangeActivationInterval-1; i++ {
// Set stake versions and vote bits.
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
appendFakeVotes(node, params.TicketsPerBlock, posVersion, 0x01)
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
ts, err = bc.NextThresholdState(&node.hash, posVersion, pedro.Id)
if err != nil {
t.Fatalf("NextThresholdState(started): %v", err)
}
tse = ThresholdStateTuple{
State: ThresholdStarted,
Choice: invalidChoice,
}
if ts != tse {
t.Fatalf("expected %+v got %+v", ts, tse)
}
// get to quorum - 1
voteCount := uint32(0)
for i := uint32(0); i < params.RuleChangeActivationInterval; i++ {
// Set stake versions and vote bits.
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
for x := 0; x < int(params.TicketsPerBlock); x++ {
bits := uint16(0x01) // abstain
if voteCount < params.RuleChangeActivationQuorum-1 {
bits = 0x05 // vote no
}
appendFakeVotes(node, 1, posVersion, bits)
voteCount++
}
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
ts, err = bc.NextThresholdState(&node.hash, posVersion, pedro.Id)
if err != nil {
t.Fatalf("NextThresholdState(quorum-1): %v", err)
}
tse = ThresholdStateTuple{
State: ThresholdStarted,
Choice: invalidChoice,
}
if ts != tse {
t.Fatalf("expected %+v got %+v", ts, tse)
}
// get to exact quorum but with 75%%-1 yes votes
voteCount = uint32(0)
for i := uint32(0); i < params.RuleChangeActivationInterval; i++ {
// Set stake versions and vote bits.
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
for x := 0; x < int(params.TicketsPerBlock); x++ {
// 119 no, 41 yes -> 120 == 75% and 120 reaches quorum
bits := uint16(0x01) // abstain
quorum := params.RuleChangeActivationQuorum*
params.RuleChangeActivationMultiplier/
params.RuleChangeActivationDivisor - 1
if voteCount < quorum {
bits = 0x05 // vote no
} else if voteCount < params.RuleChangeActivationQuorum {
bits = 0x03 // vote yes
}
appendFakeVotes(node, 1, posVersion, bits)
voteCount++
}
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
ts, err = bc.NextThresholdState(&node.hash, posVersion, pedro.Id)
if err != nil {
t.Fatalf("NextThresholdState(quorum 75%%-1): %v", err)
}
tse = ThresholdStateTuple{
State: ThresholdStarted,
Choice: invalidChoice,
}
if ts != tse {
t.Fatalf("expected %+v got %+v", ts, tse)
}
// get to exact quorum with exactly 75% of votes
voteCount = uint32(0)
for i := uint32(0); i < params.RuleChangeActivationInterval; i++ {
// Set stake versions and vote bits.
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
for x := 0; x < int(params.TicketsPerBlock); x++ {
// 120 no, 40 yes -> 120 == 75% and 120 reaches quorum
bits := uint16(0x01) // abstain
quorum := params.RuleChangeActivationQuorum *
params.RuleChangeActivationMultiplier /
params.RuleChangeActivationDivisor
if voteCount < quorum {
bits = 0x05 // vote no
} else if voteCount < params.RuleChangeActivationQuorum {
bits = 0x03 // vote yes
}
appendFakeVotes(node, 1, posVersion, bits)
voteCount++
}
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
ts, err = bc.NextThresholdState(&node.hash, posVersion, pedro.Id)
if err != nil {
t.Fatalf("NextThresholdState(quorum 75%%): %v", err)
}
tse = ThresholdStateTuple{
State: ThresholdFailed,
Choice: 0x02,
}
if ts != tse {
t.Fatalf("expected %+v got %+v", ts, tse)
}
}
// TestNoQuorum ensures that the quorum behavior works as expected with yes
// votes.
func TestYesQuorum(t *testing.T) {
params := defaultParams(pedro)
bc := newFakeChain(&params)
node := bc.bestChain.Tip()
node.stakeVersion = posVersion
// get to svi
curTimestamp := time.Now()
for i := uint32(0); i < uint32(params.StakeValidationHeight); i++ {
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
ts, err := bc.NextThresholdState(&node.hash, posVersion, pedro.Id)
if err != nil {
t.Fatalf("NextThresholdState(SVI): %v", err)
}
tse := ThresholdStateTuple{
State: ThresholdDefined,
Choice: invalidChoice,
}
if ts != tse {
t.Fatalf("expected %+v got %+v", ts, tse)
}
// get to started
for i := uint32(0); i < params.RuleChangeActivationInterval-1; i++ {
// Set stake versions and vote bits.
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
appendFakeVotes(node, params.TicketsPerBlock, posVersion, 0x01)
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
ts, err = bc.NextThresholdState(&node.hash, posVersion, pedro.Id)
if err != nil {
t.Fatalf("NextThresholdState(started): %v", err)
}
tse = ThresholdStateTuple{
State: ThresholdStarted,
Choice: invalidChoice,
}
if ts != tse {
t.Fatalf("expected %+v got %+v", ts, tse)
}
// get to quorum - 1
voteCount := uint32(0)
for i := uint32(0); i < params.RuleChangeActivationInterval; i++ {
// Set stake versions and vote bits.
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
for x := 0; x < int(params.TicketsPerBlock); x++ {
bits := uint16(0x01) // abstain
if voteCount < params.RuleChangeActivationQuorum-1 {
bits = 0x03 // vote yes
}
appendFakeVotes(node, 1, posVersion, bits)
voteCount++
}
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
ts, err = bc.NextThresholdState(&node.hash, posVersion, pedro.Id)
if err != nil {
t.Fatalf("NextThresholdState(quorum-1): %v", err)
}
tse = ThresholdStateTuple{
State: ThresholdStarted,
Choice: invalidChoice,
}
if ts != tse {
t.Fatalf("expected %+v got %+v", ts, tse)
}
// get to exact quorum but with 75%-1 yes votes
voteCount = uint32(0)
for i := uint32(0); i < params.RuleChangeActivationInterval; i++ {
// Set stake versions and vote bits.
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
for x := 0; x < int(params.TicketsPerBlock); x++ {
// 119 yes, 41 no -> 120 == 75% and 120 reaches quorum
bits := uint16(0x01) // abstain
quorum := params.RuleChangeActivationQuorum*
params.RuleChangeActivationMultiplier/
params.RuleChangeActivationDivisor - 1
if voteCount < quorum {
bits = 0x03 // vote yes
} else if voteCount < params.RuleChangeActivationQuorum {
bits = 0x05 // vote no
}
appendFakeVotes(node, 1, posVersion, bits)
voteCount++
}
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
ts, err = bc.NextThresholdState(&node.hash, posVersion, pedro.Id)
if err != nil {
t.Fatalf("NextThresholdState(quorum 75%%-1): %v", err)
}
tse = ThresholdStateTuple{
State: ThresholdStarted,
Choice: invalidChoice,
}
if ts != tse {
t.Fatalf("expected %+v got %+v", ts, tse)
}
// get to exact quorum with exactly 75% of votes
voteCount = uint32(0)
for i := uint32(0); i < params.RuleChangeActivationInterval; i++ {
// Set stake versions and vote bits.
node = newFakeNode(node, powVersion, posVersion, 0, curTimestamp)
for x := 0; x < int(params.TicketsPerBlock); x++ {
// 120 yes, 40 no -> 120 == 75% and 120 reaches quorum
bits := uint16(0x01) // abstain
quorum := params.RuleChangeActivationQuorum *
params.RuleChangeActivationMultiplier /
params.RuleChangeActivationDivisor
if voteCount < quorum {
bits = 0x03 // vote yes
} else if voteCount < params.RuleChangeActivationQuorum {
bits = 0x05 // vote no
}
appendFakeVotes(node, 1, posVersion, bits)
voteCount++
}
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
ts, err = bc.NextThresholdState(&node.hash, posVersion, pedro.Id)
if err != nil {
t.Fatalf("NextThresholdState(quorum 75%%): %v", err)
}
tse = ThresholdStateTuple{
State: ThresholdLockedIn,
Choice: 0x01,
}
if ts != tse {
t.Fatalf("expected %+v got %+v", ts, tse)
}
}
// TestVoting ensure the overall voting of an agenda works as expected including
// a wide variety of conditions.
func TestVoting(t *testing.T) {
params := defaultParams(chaincfg.Vote{})
rci := params.RuleChangeActivationInterval
svh := uint32(params.StakeValidationHeight)
svi := uint32(params.StakeVersionInterval)
type voteCount struct {
vote stake.VoteVersionTuple
count uint32
}
tests := []struct {
name string
vote chaincfg.Vote
blockVersion int32
startStakeVersion uint32
end func(time.Time) uint64
voteBitsCounts []voteCount
expectedState []ThresholdStateTuple
}{
{
name: "pedro too shallow",
vote: pedro,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh - 1,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}},
},
{
name: "pedro greater PoS version",
vote: pedro,
blockVersion: powVersion,
startStakeVersion: posVersion - 1,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svi - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - svi,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 1,
}},
},
{
name: "pedro greater PoS version calcStakeVersion",
vote: pedro,
blockVersion: powVersion,
startStakeVersion: posVersion - 1,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion - 1,
Bits: 0x01,
},
count: svh + 2*svi - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci % svi,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 1,
}},
},
{
name: "pedro smaller PoS version",
vote: pedro,
blockVersion: powVersion,
startStakeVersion: posVersion + 1,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 0x01,
}, {
State: ThresholdActive,
Choice: 0x01,
}},
},
{
name: "pedro smaller PoW version",
vote: pedro,
blockVersion: powVersion - 1,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdDefined,
Choice: invalidChoice,
}},
},
{
name: "pedro greater PoW version",
vote: pedro,
blockVersion: powVersion + 1,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 0x01,
}, {
State: ThresholdActive,
Choice: 0x01,
}},
},
{
name: "pedro 100% yes, wrong version",
vote: pedro,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion + 1,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion + 1,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion + 1,
Bits: 0x03,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion + 1,
Bits: 0x03,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion + 1,
Bits: 0x01,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 0x01,
}, {
State: ThresholdActive,
Choice: 0x01,
}, {
State: ThresholdActive,
Choice: 0x01,
}},
},
{
name: "pedro 100% yes",
vote: pedro,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 0x01,
}, {
State: ThresholdActive,
Choice: 0x01,
}, {
State: ThresholdActive,
Choice: 0x01,
}},
},
{
name: "pedro 100% no",
vote: pedro,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x05,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x05,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdFailed,
Choice: 0x02,
}, {
State: ThresholdFailed,
Choice: 0x02,
}, {
State: ThresholdFailed,
Choice: 0x02,
}},
},
{
name: "pedro 100% invalid",
vote: pedro,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x06,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x06,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 0x01,
}, {
State: ThresholdActive,
Choice: 0x01,
}},
},
{
name: "pedro 100% abstain",
vote: pedro,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}},
},
{
name: "pedro expire before started",
vote: pedro,
blockVersion: powVersion,
startStakeVersion: posVersion - 1,
end: func(t time.Time) uint64 {
return uint64(t.Add(time.Second *
time.Duration(int64(svh)+int64(rci+rci/2))).Unix())
},
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion - 1,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion - 1,
Bits: 0x05,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion - 1,
Bits: 0x05,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdFailed,
Choice: invalidChoice,
}},
},
{
name: "pedro expire after started",
vote: pedro,
blockVersion: powVersion,
startStakeVersion: posVersion,
end: func(t time.Time) uint64 {
return uint64(t.Add(time.Second *
time.Duration(int64(svh)+int64(rci+rci/2))).Unix())
},
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x05,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x05,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdFailed,
Choice: invalidChoice,
}},
},
{
name: "pedro overlap",
vote: pedro,
blockVersion: powVersion,
startStakeVersion: posVersion - 1,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion - 1,
Bits: 0x01,
},
count: svh + 19*svi - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svi - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x03,
},
count: 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 1,
}, {
State: ThresholdActive,
Choice: 1,
}},
},
{
name: "multiple choice 100% abstain",
vote: multipleChoice,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}},
},
{
name: "multiple choice 100% no",
vote: multipleChoice,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x21,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdFailed,
Choice: 2,
}},
},
{
name: "multiple choice 100% choice 1",
vote: multipleChoice,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x11,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x11,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 1,
}, {
State: ThresholdActive,
Choice: 1,
}, {
State: ThresholdActive,
Choice: 1,
}},
},
{
name: "multiple choice 100% choice 2",
vote: multipleChoice,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x31,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x31,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 3,
}, {
State: ThresholdActive,
Choice: 3,
}, {
State: ThresholdActive,
Choice: 3,
}},
},
{
name: "multiple choice 100% choice 3",
vote: multipleChoice,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x41,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x41,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 4,
}, {
State: ThresholdActive,
Choice: 4,
}, {
State: ThresholdActive,
Choice: 4,
}},
},
{
name: "multiple choice 100% choice 4",
vote: multipleChoice,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x51,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x51,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 5,
}, {
State: ThresholdActive,
Choice: 5,
}, {
State: ThresholdActive,
Choice: 5,
}},
},
{
name: "multiple choice 100% choice 5 (invalid)",
vote: multipleChoice,
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: svh,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x61,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x61,
},
count: rci,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: rci,
}},
expectedState: []ThresholdStateTuple{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}},
},
}
for _, test := range tests {
// Reset params.
params = defaultParams(test.vote)
// We have to reset the cache for every test.
bc := newFakeChain(&params)
node := bc.bestChain.Tip()
node.stakeVersion = test.startStakeVersion
t.Logf("running: %v", test.name)
// Override start time.
curTimestamp := time.Now()
// Override expiration time.
if test.end != nil {
params.Deployments[posVersion][0].ExpireTime =
test.end(curTimestamp)
}
t.Logf("curTimestamp %v start %v expiration %v",
uint64(curTimestamp.Unix()),
params.Deployments[posVersion][0].StartTime,
params.Deployments[posVersion][0].ExpireTime)
for k := range test.expectedState {
for i := uint32(0); i < test.voteBitsCounts[k].count; i++ {
// Set stake versions and vote bits.
node = newFakeNode(node, test.blockVersion,
test.startStakeVersion, 0, curTimestamp)
vote := &test.voteBitsCounts[k].vote
appendFakeVotes(node, params.TicketsPerBlock,
vote.Version, vote.Bits)
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
t.Logf("Height %v, Start time %v, curTime %v, delta %v",
node.height, params.Deployments[4][0].StartTime,
node.timestamp, node.timestamp-
int64(params.Deployments[4][0].StartTime))
ts, err := bc.NextThresholdState(&node.hash, posVersion,
test.vote.Id)
if err != nil {
t.Fatalf("NextThresholdState(%v): %v", k, err)
}
if ts != test.expectedState[k] {
t.Fatalf("%v.%v (%v) got state %+v wanted "+
"state %+v", test.name, test.vote.Id, k,
ts, test.expectedState[k])
}
}
}
}
// defaultParallelParams returns net parameters modified to have two known
// deployments that are used throughout the parallel votebit tests.
func defaultParallelParams() chaincfg.Params {
params := chaincfg.RegNetParams()
params.Deployments = make(map[uint32][]chaincfg.ConsensusDeployment)
params.Deployments[posVersion] = []chaincfg.ConsensusDeployment{
{
Vote: testDummy1,
StartTime: uint64(time.Now().Add(time.Duration(params.RuleChangeActivationInterval) *
time.Second).Unix()),
ExpireTime: uint64(time.Now().Add(24 * time.Hour).Unix()),
},
{
Vote: testDummy2,
StartTime: uint64(time.Now().Add(time.Duration(params.RuleChangeActivationInterval) *
time.Second).Unix()),
ExpireTime: uint64(time.Now().Add(24 * time.Hour).Unix()),
},
}
return *params
}
// TestParallelVoting ensures that two agendas running at the same time progress
// through the expected states.
func TestParallelVoting(t *testing.T) {
params := defaultParallelParams()
type voteCount struct {
vote stake.VoteVersionTuple
count uint32
}
tests := []struct {
name string
vote []chaincfg.Vote
blockVersion int32
startStakeVersion uint32
voteBitsCounts []voteCount
expectedState [][]ThresholdStateTuple
}{
{
name: "parallel",
vote: []chaincfg.Vote{testDummy1, testDummy2},
blockVersion: powVersion,
startStakeVersion: posVersion,
voteBitsCounts: []voteCount{{
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: uint32(params.StakeValidationHeight),
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: params.RuleChangeActivationInterval - 1,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion - 1,
Bits: vbTestDummy1Yes | vbTestDummy2No,
},
count: params.RuleChangeActivationInterval,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: vbTestDummy1Yes | vbTestDummy2No,
},
count: params.RuleChangeActivationInterval,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: params.RuleChangeActivationInterval,
}, {
vote: stake.VoteVersionTuple{
Version: posVersion,
Bits: 0x01,
},
count: params.RuleChangeActivationInterval,
}},
expectedState: [][]ThresholdStateTuple{
// 0
{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdLockedIn,
Choice: 0x02,
}, {
State: ThresholdActive,
Choice: 0x02,
}, {
State: ThresholdActive,
Choice: 0x02,
}},
// 1
{{
State: ThresholdDefined,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdStarted,
Choice: invalidChoice,
}, {
State: ThresholdFailed,
Choice: 0x01,
}, {
State: ThresholdFailed,
Choice: 0x01,
}, {
State: ThresholdFailed,
Choice: 0x01,
}},
},
},
}
for _, test := range tests {
// Reset params.
params = defaultParallelParams()
// We have to reset the cache for every test.
bc := newFakeChain(&params)
node := bc.bestChain.Tip()
node.stakeVersion = test.startStakeVersion
curTimestamp := time.Now()
for k := range test.expectedState[0] {
for i := uint32(0); i < test.voteBitsCounts[k].count; i++ {
// Set stake versions and vote bits.
node = newFakeNode(node, test.blockVersion,
test.startStakeVersion, 0, curTimestamp)
vote := &test.voteBitsCounts[k].vote
appendFakeVotes(node, params.TicketsPerBlock,
vote.Version, vote.Bits)
bc.bestChain.SetTip(node)
bc.index.AddNode(node)
curTimestamp = curTimestamp.Add(time.Second)
}
for i := range test.vote {
ts, err := bc.NextThresholdState(&node.hash,
posVersion, test.vote[i].Id)
if err != nil {
t.Fatalf("NextThresholdState(%v): %v", k, err)
}
if ts != test.expectedState[i][k] {
t.Fatalf("%v.%v (%v) got state %+v "+
"wanted state %+v",
test.name, test.vote[i].Id, k,
ts, test.expectedState[i][k])
}
}
}
}
}