mirror of
https://github.com/btcsuite/btcd.git
synced 2024-11-19 09:50:08 +01:00
54f6fa948e
In this commit, we extract the BIP 9 state transition logic from the thresholdState method into a new thresholdStateTransition function that allows us to test all the defined state transitions, including the modified "speedy trial" logic.
319 lines
7.7 KiB
Go
319 lines
7.7 KiB
Go
// Copyright (c) 2016 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package blockchain
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
)
|
|
|
|
// TestThresholdStateStringer tests the stringized output for the
|
|
// ThresholdState type.
|
|
func TestThresholdStateStringer(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
in ThresholdState
|
|
want string
|
|
}{
|
|
{ThresholdDefined, "ThresholdDefined"},
|
|
{ThresholdStarted, "ThresholdStarted"},
|
|
{ThresholdLockedIn, "ThresholdLockedIn"},
|
|
{ThresholdActive, "ThresholdActive"},
|
|
{ThresholdFailed, "ThresholdFailed"},
|
|
{0xff, "Unknown ThresholdState (255)"},
|
|
}
|
|
|
|
// Detect additional threshold states that don't have the stringer added.
|
|
if len(tests)-1 != int(numThresholdsStates) {
|
|
t.Errorf("It appears a threshold statewas added without " +
|
|
"adding an associated stringer test")
|
|
}
|
|
|
|
t.Logf("Running %d tests", len(tests))
|
|
for i, test := range tests {
|
|
result := test.in.String()
|
|
if result != test.want {
|
|
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
|
test.want)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestThresholdStateCache ensure the threshold state cache works as intended
|
|
// including adding entries, updating existing entries, and flushing.
|
|
func TestThresholdStateCache(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
numEntries int
|
|
state ThresholdState
|
|
}{
|
|
{name: "2 entries defined", numEntries: 2, state: ThresholdDefined},
|
|
{name: "7 entries started", numEntries: 7, state: ThresholdStarted},
|
|
{name: "10 entries active", numEntries: 10, state: ThresholdActive},
|
|
{name: "5 entries locked in", numEntries: 5, state: ThresholdLockedIn},
|
|
{name: "3 entries failed", numEntries: 3, state: ThresholdFailed},
|
|
}
|
|
|
|
nextTest:
|
|
for _, test := range tests {
|
|
cache := &newThresholdCaches(1)[0]
|
|
for i := 0; i < test.numEntries; i++ {
|
|
var hash chainhash.Hash
|
|
hash[0] = uint8(i + 1)
|
|
|
|
// Ensure the hash isn't available in the cache already.
|
|
_, ok := cache.Lookup(&hash)
|
|
if ok {
|
|
t.Errorf("Lookup (%s): has entry for hash %v",
|
|
test.name, hash)
|
|
continue nextTest
|
|
}
|
|
|
|
// Ensure hash that was added to the cache reports it's
|
|
// available and the state is the expected value.
|
|
cache.Update(&hash, test.state)
|
|
state, ok := cache.Lookup(&hash)
|
|
if !ok {
|
|
t.Errorf("Lookup (%s): missing entry for hash "+
|
|
"%v", test.name, hash)
|
|
continue nextTest
|
|
}
|
|
if state != test.state {
|
|
t.Errorf("Lookup (%s): state mismatch - got "+
|
|
"%v, want %v", test.name, state,
|
|
test.state)
|
|
continue nextTest
|
|
}
|
|
|
|
// Ensure adding an existing hash with the same state
|
|
// doesn't break the existing entry.
|
|
cache.Update(&hash, test.state)
|
|
state, ok = cache.Lookup(&hash)
|
|
if !ok {
|
|
t.Errorf("Lookup (%s): missing entry after "+
|
|
"second add for hash %v", test.name,
|
|
hash)
|
|
continue nextTest
|
|
}
|
|
if state != test.state {
|
|
t.Errorf("Lookup (%s): state mismatch after "+
|
|
"second add - got %v, want %v",
|
|
test.name, state, test.state)
|
|
continue nextTest
|
|
}
|
|
|
|
// Ensure adding an existing hash with a different state
|
|
// updates the existing entry.
|
|
newState := ThresholdFailed
|
|
if newState == test.state {
|
|
newState = ThresholdStarted
|
|
}
|
|
cache.Update(&hash, newState)
|
|
state, ok = cache.Lookup(&hash)
|
|
if !ok {
|
|
t.Errorf("Lookup (%s): missing entry after "+
|
|
"state change for hash %v", test.name,
|
|
hash)
|
|
continue nextTest
|
|
}
|
|
if state != newState {
|
|
t.Errorf("Lookup (%s): state mismatch after "+
|
|
"state change - got %v, want %v",
|
|
test.name, state, newState)
|
|
continue nextTest
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type customDeploymentChecker struct {
|
|
started bool
|
|
ended bool
|
|
|
|
eligible bool
|
|
|
|
isSpeedy bool
|
|
|
|
conditionTrue bool
|
|
|
|
activationThreshold uint32
|
|
minerWindow uint32
|
|
}
|
|
|
|
func (c customDeploymentChecker) HasStarted(_ *blockNode) bool {
|
|
return c.started
|
|
}
|
|
|
|
func (c customDeploymentChecker) HasEnded(_ *blockNode) bool {
|
|
return c.ended
|
|
}
|
|
|
|
func (c customDeploymentChecker) RuleChangeActivationThreshold() uint32 {
|
|
return c.activationThreshold
|
|
}
|
|
|
|
func (c customDeploymentChecker) MinerConfirmationWindow() uint32 {
|
|
return c.minerWindow
|
|
}
|
|
|
|
func (c customDeploymentChecker) EligibleToActivate(_ *blockNode) bool {
|
|
return c.eligible
|
|
}
|
|
|
|
func (c customDeploymentChecker) IsSpeedy() bool {
|
|
return c.isSpeedy
|
|
}
|
|
|
|
func (c customDeploymentChecker) Condition(_ *blockNode) (bool, error) {
|
|
return c.conditionTrue, nil
|
|
}
|
|
|
|
// TestThresholdStateTransition tests that the thresholdStateTransition
|
|
// properly implements the BIP 009 state machine, along with the speedy trial
|
|
// augments.
|
|
func TestThresholdStateTransition(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Prev node always points back to itself, effectively creating an
|
|
// infinite chain for the purposes of this test.
|
|
prevNode := &blockNode{}
|
|
prevNode.parent = prevNode
|
|
|
|
window := int32(2016)
|
|
|
|
testCases := []struct {
|
|
currentState ThresholdState
|
|
nextState ThresholdState
|
|
|
|
checker thresholdConditionChecker
|
|
}{
|
|
// From defined, we stay there if we haven't started the
|
|
// window, and the window hasn't ended.
|
|
{
|
|
currentState: ThresholdDefined,
|
|
nextState: ThresholdDefined,
|
|
|
|
checker: &customDeploymentChecker{},
|
|
},
|
|
|
|
// From defined, we go to failed if the window has ended, and
|
|
// this isn't a speedy trial.
|
|
{
|
|
currentState: ThresholdDefined,
|
|
nextState: ThresholdFailed,
|
|
|
|
checker: &customDeploymentChecker{
|
|
ended: true,
|
|
},
|
|
},
|
|
|
|
// From defined, even if the window has ended, we go to started
|
|
// if this isn't a speedy trial.
|
|
{
|
|
currentState: ThresholdDefined,
|
|
nextState: ThresholdStarted,
|
|
|
|
checker: &customDeploymentChecker{
|
|
started: true,
|
|
},
|
|
},
|
|
|
|
// From started, we go to failed if this isn't speed, and the
|
|
// deployment has ended.
|
|
{
|
|
currentState: ThresholdStarted,
|
|
nextState: ThresholdFailed,
|
|
|
|
checker: &customDeploymentChecker{
|
|
ended: true,
|
|
},
|
|
},
|
|
|
|
// From started, we go to locked in if the window passed the
|
|
// condition.
|
|
{
|
|
currentState: ThresholdStarted,
|
|
nextState: ThresholdLockedIn,
|
|
|
|
checker: &customDeploymentChecker{
|
|
started: true,
|
|
conditionTrue: true,
|
|
},
|
|
},
|
|
|
|
// From started, we go to failed if this is a speedy trial, and
|
|
// the condition wasn't met in the window.
|
|
{
|
|
currentState: ThresholdStarted,
|
|
nextState: ThresholdFailed,
|
|
|
|
checker: &customDeploymentChecker{
|
|
started: true,
|
|
ended: true,
|
|
isSpeedy: true,
|
|
conditionTrue: false,
|
|
activationThreshold: 1815,
|
|
},
|
|
},
|
|
|
|
// From locked in, we go straight to active is this isn't a
|
|
// speedy trial.
|
|
{
|
|
currentState: ThresholdLockedIn,
|
|
nextState: ThresholdActive,
|
|
|
|
checker: &customDeploymentChecker{
|
|
eligible: true,
|
|
},
|
|
},
|
|
|
|
// From locked in, we remain in locked in if we're not yet
|
|
// eligible to activate.
|
|
{
|
|
currentState: ThresholdLockedIn,
|
|
nextState: ThresholdLockedIn,
|
|
|
|
checker: &customDeploymentChecker{},
|
|
},
|
|
|
|
// From active, we always stay here.
|
|
{
|
|
currentState: ThresholdActive,
|
|
nextState: ThresholdActive,
|
|
|
|
checker: &customDeploymentChecker{},
|
|
},
|
|
|
|
// From failed, we always stay here.
|
|
{
|
|
currentState: ThresholdFailed,
|
|
nextState: ThresholdFailed,
|
|
|
|
checker: &customDeploymentChecker{},
|
|
},
|
|
}
|
|
for i, testCase := range testCases {
|
|
nextState, err := thresholdStateTransition(
|
|
testCase.currentState, prevNode, testCase.checker,
|
|
window,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("#%v: unable to transition to next "+
|
|
"state: %v", i, err)
|
|
}
|
|
|
|
if nextState != testCase.nextState {
|
|
t.Fatalf("#%v: incorrect state transition: "+
|
|
"expected %v got %v", i, testCase.nextState,
|
|
nextState)
|
|
}
|
|
}
|
|
}
|