mirror of
https://github.com/btcsuite/btcd.git
synced 2025-03-13 11:35:52 +01:00
This commit introduces the concept of `AlwaysActiveHeight` to the deployment mechanism, allowing a deployment to be forced into the active state if the next block's height meets or exceeds this threshold. This is intended primarily to be used alongside the new Testnet4 deployment, as the past major soft forks are meant to be active from the very first block height.
322 lines
7.7 KiB
Go
322 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
|
|
}
|
|
|
|
func (c customDeploymentChecker) ForceActive(_ *blockNode) bool {
|
|
return false
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
}
|