mirror of
https://github.com/btcsuite/btcd.git
synced 2026-08-13 12:32:51 +02:00
461 lines
15 KiB
Go
461 lines
15 KiB
Go
// Copyright (c) 2016-2017 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 (
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/btcsuite/btcd/chainhash/v2"
|
||
"github.com/btcsuite/btcd/wire/v2"
|
||
)
|
||
|
||
// ThresholdState define the various threshold states used when voting on
|
||
// consensus changes.
|
||
type ThresholdState byte
|
||
|
||
// These constants are used to identify specific threshold states.
|
||
const (
|
||
// ThresholdDefined is the first state for each deployment and is the
|
||
// state for the genesis block has by definition for all deployments.
|
||
ThresholdDefined ThresholdState = iota
|
||
|
||
// ThresholdStarted is the state for a deployment once its start time
|
||
// has been reached.
|
||
ThresholdStarted
|
||
|
||
// ThresholdLockedIn is the state for a deployment during the retarget
|
||
// period which is after the ThresholdStarted state period and the
|
||
// number of blocks that have voted for the deployment equal or exceed
|
||
// the required number of votes for the deployment.
|
||
ThresholdLockedIn
|
||
|
||
// ThresholdActive is the state for a deployment for all blocks after a
|
||
// retarget period in which the deployment was in the ThresholdLockedIn
|
||
// state.
|
||
ThresholdActive
|
||
|
||
// ThresholdFailed is the state for a deployment once its expiration
|
||
// time has been reached and it did not reach the ThresholdLockedIn
|
||
// state.
|
||
ThresholdFailed
|
||
|
||
// numThresholdsStates is the maximum number of threshold states used in
|
||
// tests.
|
||
numThresholdsStates
|
||
)
|
||
|
||
// thresholdStateStrings is a map of ThresholdState values back to their
|
||
// constant names for pretty printing.
|
||
var thresholdStateStrings = map[ThresholdState]string{
|
||
ThresholdDefined: "ThresholdDefined",
|
||
ThresholdStarted: "ThresholdStarted",
|
||
ThresholdLockedIn: "ThresholdLockedIn",
|
||
ThresholdActive: "ThresholdActive",
|
||
ThresholdFailed: "ThresholdFailed",
|
||
}
|
||
|
||
// String returns the ThresholdState as a human-readable name.
|
||
func (t ThresholdState) String() string {
|
||
if s := thresholdStateStrings[t]; s != "" {
|
||
return s
|
||
}
|
||
return fmt.Sprintf("Unknown ThresholdState (%d)", int(t))
|
||
}
|
||
|
||
// thresholdConditionChecker provides a generic interface that is invoked to
|
||
// determine when a consensus rule change threshold should be changed.
|
||
type thresholdConditionChecker interface {
|
||
// HasStarted returns true if based on the passed block blockNode the
|
||
// consensus is eligible for deployment.
|
||
HasStarted(*blockNode) bool
|
||
|
||
// HasEnded returns true if the target consensus rule change has
|
||
// expired or timed out.
|
||
HasEnded(*blockNode) bool
|
||
|
||
// RuleChangeActivationThreshold is the number of blocks for which the
|
||
// condition must be true in order to lock in a rule change.
|
||
RuleChangeActivationThreshold() uint32
|
||
|
||
// MinerConfirmationWindow is the number of blocks in each threshold
|
||
// state retarget window.
|
||
MinerConfirmationWindow() uint32
|
||
|
||
// EligibleToActivate returns true if a custom deployment can
|
||
// transition from the LockedIn to the Active state. For normal
|
||
// deployments, this always returns true. However, some deployments add
|
||
// extra rules like a minimum activation height, which can be
|
||
// abstracted into a generic arbitrary check at the final state via
|
||
// this method.
|
||
EligibleToActivate(*blockNode) bool
|
||
|
||
// IsSpeedy returns true if this is to be a "speedy" deployment. A
|
||
// speedy deployment differs from a regular one in that only after a
|
||
// miner block confirmation window can the deployment expire.
|
||
IsSpeedy() bool
|
||
|
||
// Condition returns whether or not the rule change activation
|
||
// condition has been met. This typically involves checking whether or
|
||
// not the bit associated with the condition is set, but can be more
|
||
// complex as needed.
|
||
Condition(*blockNode) (bool, error)
|
||
|
||
// ForceActive returns if the deployment should be forced to transition
|
||
// to the active state. This is useful on certain testnet, where we
|
||
// we'd like for a deployment to always be active.
|
||
ForceActive(*blockNode) bool
|
||
}
|
||
|
||
// thresholdStateCache provides a type to cache the threshold states of each
|
||
// threshold window for a set of IDs.
|
||
type thresholdStateCache struct {
|
||
entries map[chainhash.Hash]ThresholdState
|
||
}
|
||
|
||
// Lookup returns the threshold state associated with the given hash along with
|
||
// a boolean that indicates whether or not it is valid.
|
||
func (c *thresholdStateCache) Lookup(hash *chainhash.Hash) (ThresholdState, bool) {
|
||
state, ok := c.entries[*hash]
|
||
return state, ok
|
||
}
|
||
|
||
// Update updates the cache to contain the provided hash to threshold state
|
||
// mapping.
|
||
func (c *thresholdStateCache) Update(hash *chainhash.Hash, state ThresholdState) {
|
||
c.entries[*hash] = state
|
||
}
|
||
|
||
// newThresholdCaches returns a new array of caches to be used when calculating
|
||
// threshold states.
|
||
func newThresholdCaches(numCaches uint32) []thresholdStateCache {
|
||
caches := make([]thresholdStateCache, numCaches)
|
||
for i := 0; i < len(caches); i++ {
|
||
caches[i] = thresholdStateCache{
|
||
entries: make(map[chainhash.Hash]ThresholdState),
|
||
}
|
||
}
|
||
return caches
|
||
}
|
||
|
||
// PastMedianTime returns the past median time from the PoV of the passed block
|
||
// header. The past median time is the median time of the 11 blocks prior to
|
||
// the passed block header.
|
||
//
|
||
// NOTE: This is part of the chainfg.BlockClock interface
|
||
func (b *BlockChain) PastMedianTime(blockHeader *wire.BlockHeader) (time.Time, error) {
|
||
prevHash := blockHeader.PrevBlock
|
||
prevNode := b.index.LookupNode(&prevHash)
|
||
|
||
// If we can't find the previous node, then we can't compute the block
|
||
// time since it requires us to walk backwards from this node.
|
||
if prevNode == nil {
|
||
return time.Time{}, fmt.Errorf("blockHeader(%v) has no "+
|
||
"previous node", blockHeader.BlockHash())
|
||
}
|
||
|
||
blockNode := newBlockNode(blockHeader, prevNode)
|
||
|
||
return CalcPastMedianTime(blockNode), nil
|
||
}
|
||
|
||
// thresholdStateTransition given a state, a previous node, and a toeholds
|
||
// checker, this function transitions to the next state as defined by BIP 009.
|
||
// This state transition function is also aware of the "speedy trial"
|
||
// modifications made to BIP 0009 as part of the taproot softfork activation.
|
||
func thresholdStateTransition(state ThresholdState, prevNode *blockNode,
|
||
checker thresholdConditionChecker,
|
||
confirmationWindow int32) (ThresholdState, error) {
|
||
|
||
switch state {
|
||
case ThresholdDefined:
|
||
// The deployment of the rule change fails if it
|
||
// expires before it is accepted and locked in. However
|
||
// speed deployments can only transition to failed
|
||
// after a confirmation window.
|
||
if !checker.IsSpeedy() && checker.HasEnded(prevNode) {
|
||
log.Debugf("Moving from state=%v, to state=%v", state,
|
||
ThresholdFailed)
|
||
|
||
state = ThresholdFailed
|
||
break
|
||
}
|
||
|
||
// The state for the rule moves to the started state
|
||
// once its start time has been reached (and it hasn't
|
||
// already expired per the above).
|
||
if checker.HasStarted(prevNode) {
|
||
log.Debugf("Moving from state=%v, to state=%v", state,
|
||
ThresholdStarted)
|
||
|
||
state = ThresholdStarted
|
||
}
|
||
|
||
case ThresholdStarted:
|
||
// The deployment of the rule change fails if it
|
||
// expires before it is accepted and locked in, but
|
||
// only if this deployment isn't speedy.
|
||
if !checker.IsSpeedy() && checker.HasEnded(prevNode) {
|
||
log.Debugf("Moving from state=%v, to state=%v", state,
|
||
ThresholdFailed)
|
||
|
||
state = ThresholdFailed
|
||
break
|
||
}
|
||
|
||
// At this point, the rule change is still being voted
|
||
// on by the miners, so iterate backwards through the
|
||
// confirmation window to count all of the votes in it.
|
||
var count uint32
|
||
countNode := prevNode
|
||
for i := int32(0); i < confirmationWindow; i++ {
|
||
condition, err := checker.Condition(countNode)
|
||
if err != nil {
|
||
return ThresholdFailed, err
|
||
}
|
||
if condition {
|
||
count++
|
||
}
|
||
|
||
// Get the previous block node.
|
||
countNode = countNode.parent
|
||
}
|
||
|
||
switch {
|
||
// The state is locked in if the number of blocks in the
|
||
// period that voted for the rule change meets the
|
||
// activation threshold.
|
||
case count >= checker.RuleChangeActivationThreshold():
|
||
log.Debugf("Moving from state=%v, to state=%v", state,
|
||
ThresholdLockedIn)
|
||
|
||
state = ThresholdLockedIn
|
||
|
||
// If this is a speedy deployment, we didn't meet the
|
||
// threshold above, and the deployment has expired, then
|
||
// we transition to failed.
|
||
case checker.IsSpeedy() && checker.HasEnded(prevNode):
|
||
log.Debugf("Moving from state=%v, to state=%v", state,
|
||
ThresholdFailed)
|
||
|
||
state = ThresholdFailed
|
||
|
||
default:
|
||
log.Tracef("Still at state=%v, threshold=%v", state,
|
||
float64(count)/float64(checker.RuleChangeActivationThreshold()))
|
||
}
|
||
|
||
case ThresholdLockedIn:
|
||
// At this point, we'll consult the deployment see if a
|
||
// custom deployment has any other arbitrary conditions
|
||
// that need to pass before execution. This might be a
|
||
// minimum activation height or another policy.
|
||
//
|
||
// If we aren't eligible to active yet, then we'll just
|
||
// stay in the locked in position.
|
||
if !checker.EligibleToActivate(prevNode) {
|
||
log.Debugf("Moving from state=%v, to state=%v", state,
|
||
ThresholdLockedIn)
|
||
|
||
state = ThresholdLockedIn
|
||
} else {
|
||
log.Debugf("Moving from state=%v, to state=%v", state,
|
||
ThresholdActive)
|
||
|
||
// The new rule becomes active when its
|
||
// previous state was locked in assuming it's
|
||
// now eligible to activate.
|
||
state = ThresholdActive
|
||
}
|
||
|
||
// Nothing to do if the previous state is active or failed since
|
||
// they are both terminal states.
|
||
case ThresholdActive:
|
||
case ThresholdFailed:
|
||
}
|
||
|
||
return state, nil
|
||
}
|
||
|
||
// thresholdState returns the current rule change threshold state for the block
|
||
// AFTER the given node and deployment ID. The cache is used to ensure the
|
||
// threshold states for previous windows are only calculated once.
|
||
//
|
||
// This function MUST be called with the chain state lock held (for writes).
|
||
func (b *BlockChain) thresholdState(prevNode *blockNode,
|
||
checker thresholdConditionChecker,
|
||
cache *thresholdStateCache) (ThresholdState, error) {
|
||
|
||
// If the deployment has a nonzero AlwaysActiveHeight and the next
|
||
// block’s height is at or above that threshold, then force the state
|
||
// to Active.
|
||
if checker.ForceActive(prevNode) {
|
||
return ThresholdActive, nil
|
||
}
|
||
|
||
// The threshold state for the window that contains the genesis block is
|
||
// defined by definition.
|
||
confirmationWindow := int32(checker.MinerConfirmationWindow())
|
||
if prevNode == nil || (prevNode.height+1) < confirmationWindow {
|
||
return ThresholdDefined, nil
|
||
}
|
||
|
||
// Get the ancestor that is the last block of the previous confirmation
|
||
// window in order to get its threshold state. This can be done because
|
||
// the state is the same for all blocks within a given window.
|
||
prevNode = prevNode.Ancestor(prevNode.height -
|
||
(prevNode.height+1)%confirmationWindow)
|
||
|
||
// Iterate backwards through each of the previous confirmation windows
|
||
// to find the most recently cached threshold state.
|
||
var neededStates []*blockNode
|
||
for prevNode != nil {
|
||
// Nothing more to do if the state of the block is already
|
||
// cached.
|
||
if _, ok := cache.Lookup(&prevNode.hash); ok {
|
||
break
|
||
}
|
||
|
||
// The state is simply defined if the start time hasn't been
|
||
// been reached yet.
|
||
if !checker.HasStarted(prevNode) {
|
||
cache.Update(&prevNode.hash, ThresholdDefined)
|
||
break
|
||
}
|
||
|
||
// Add this node to the list of nodes that need the state
|
||
// calculated and cached.
|
||
neededStates = append(neededStates, prevNode)
|
||
|
||
// Get the ancestor that is the last block of the previous
|
||
// confirmation window.
|
||
prevNode = prevNode.RelativeAncestor(confirmationWindow)
|
||
}
|
||
|
||
// Start with the threshold state for the most recent confirmation
|
||
// window that has a cached state.
|
||
state := ThresholdDefined
|
||
if prevNode != nil {
|
||
var ok bool
|
||
state, ok = cache.Lookup(&prevNode.hash)
|
||
if !ok {
|
||
return ThresholdFailed, AssertError(fmt.Sprintf(
|
||
"thresholdState: cache lookup failed for %v",
|
||
prevNode.hash))
|
||
}
|
||
}
|
||
|
||
// Since each threshold state depends on the state of the previous
|
||
// window, iterate starting from the oldest unknown window.
|
||
var err error
|
||
for neededNum := len(neededStates) - 1; neededNum >= 0; neededNum-- {
|
||
prevNode := neededStates[neededNum]
|
||
|
||
// Based on the current state, the previous node, and the
|
||
// condition checker, transition to the next threshold state.
|
||
state, err = thresholdStateTransition(
|
||
state, prevNode, checker, confirmationWindow,
|
||
)
|
||
if err != nil {
|
||
return state, err
|
||
}
|
||
|
||
// Update the cache to avoid recalculating the state in the
|
||
// future.
|
||
cache.Update(&prevNode.hash, state)
|
||
}
|
||
|
||
return state, nil
|
||
}
|
||
|
||
// ThresholdState returns the current rule change threshold state of the given
|
||
// deployment ID for the block AFTER the end of the current best chain.
|
||
//
|
||
// This function is safe for concurrent access.
|
||
func (b *BlockChain) ThresholdState(deploymentID uint32) (ThresholdState, error) {
|
||
b.chainLock.Lock()
|
||
state, err := b.deploymentState(b.bestChain.Tip(), deploymentID)
|
||
b.chainLock.Unlock()
|
||
|
||
return state, err
|
||
}
|
||
|
||
// IsDeploymentActive returns true if the target deploymentID is active, and
|
||
// false otherwise.
|
||
//
|
||
// This function is safe for concurrent access.
|
||
func (b *BlockChain) IsDeploymentActive(deploymentID uint32) (bool, error) {
|
||
b.chainLock.Lock()
|
||
state, err := b.deploymentState(b.bestChain.Tip(), deploymentID)
|
||
b.chainLock.Unlock()
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
return state == ThresholdActive, nil
|
||
}
|
||
|
||
// deploymentState returns the current rule change threshold for a given
|
||
// deploymentID. The threshold is evaluated from the point of view of the block
|
||
// node passed in as the first argument to this method.
|
||
//
|
||
// It is important to note that, as the variable name indicates, this function
|
||
// expects the block node prior to the block for which the deployment state is
|
||
// desired. In other words, the returned deployment state is for the block
|
||
// AFTER the passed node.
|
||
//
|
||
// This function MUST be called with the chain state lock held (for writes).
|
||
func (b *BlockChain) deploymentState(prevNode *blockNode, deploymentID uint32) (ThresholdState, error) {
|
||
if deploymentID > uint32(len(b.chainParams.Deployments)) {
|
||
return ThresholdFailed, DeploymentError(deploymentID)
|
||
}
|
||
|
||
deployment := &b.chainParams.Deployments[deploymentID]
|
||
checker := deploymentChecker{deployment: deployment, chain: b}
|
||
cache := &b.deploymentCaches[deploymentID]
|
||
|
||
return b.thresholdState(prevNode, checker, cache)
|
||
}
|
||
|
||
// initThresholdCaches initializes the threshold state caches for each warning
|
||
// bit and defined deployment and provides warnings if the chain is current per
|
||
// the warnUnknownRuleActivations function.
|
||
func (b *BlockChain) initThresholdCaches() error {
|
||
// Initialize the warning and deployment caches by calculating the
|
||
// threshold state for each of them. This will ensure the caches are
|
||
// populated and any states that needed to be recalculated due to
|
||
// definition changes is done now.
|
||
prevNode := b.bestChain.Tip().parent
|
||
for bit := uint32(0); bit < vbNumBits; bit++ {
|
||
checker := bitConditionChecker{bit: bit, chain: b}
|
||
cache := &b.warningCaches[bit]
|
||
_, err := b.thresholdState(prevNode, checker, cache)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
for id := 0; id < len(b.chainParams.Deployments); id++ {
|
||
deployment := &b.chainParams.Deployments[id]
|
||
cache := &b.deploymentCaches[id]
|
||
checker := deploymentChecker{deployment: deployment, chain: b}
|
||
_, err := b.thresholdState(prevNode, checker, cache)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
// No warnings about unknown rules until the chain is current.
|
||
if b.isCurrent() {
|
||
bestNode := b.bestChain.Tip()
|
||
|
||
// Warn if any unknown new rules are either about to activate or
|
||
// have already been activated.
|
||
if err := b.warnUnknownRuleActivations(bestNode); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|