chaincfg/blockchain: Parameterize more chain consts. (#732)

This moves several of the chain constants to the Params struct in the
chaincfg package which is intended for that purpose.  This is mostly a
backport of the same modifications made in Decred along with a few
additional things cleaned up.

The following is an overview of the changes:

- Comment all fields in the Params struct definition
- Add locals to BlockChain instance for the calculated values based on
  the provided chain params
- Rename the following param fields:
  - SubsidyHalvingInterval -> SubsidyReductionInterval
  - ResetMinDifficulty -> ReduceMinDifficulty
- Add new Param fields:
  - CoinbaseMaturity
  - TargetTimePerBlock
  - TargetTimespan
  - BlocksPerRetarget
  - RetargetAdjustmentFactor
  - MinDiffReductionTime
This commit is contained in:
Dave Collins 2016-08-10 16:02:23 -05:00 committed by GitHub
parent bd4e64d1d4
commit a7b35d9f9e
11 changed files with 183 additions and 123 deletions

View File

@ -24,12 +24,6 @@ const (
// maxOrphanBlocks is the maximum number of orphan blocks that can be
// queued.
maxOrphanBlocks = 100
// minMemoryNodes is the minimum number of consecutive nodes needed
// in memory in order to perform all necessary validation. It is used
// to determine when it's safe to prune nodes from memory without
// causing constant dynamic reloading.
minMemoryNodes = BlocksPerRetarget
)
// blockNode represents a block within the block chain and is primarily used to
@ -170,6 +164,22 @@ type BlockChain struct {
sigCache *txscript.SigCache
indexManager IndexManager
// The following fields are calculated based upon the provided chain
// parameters. They are also set when the instance is created and
// can't be changed afterwards, so there is no need to protect them with
// a separate mutex.
//
// minMemoryNodes is the minimum number of consecutive nodes needed
// in memory in order to perform all necessary validation. It is used
// to determine when it's safe to prune nodes from memory without
// causing constant dynamic reloading. This is typically the same value
// as blocksPerRetarget, but it is separated here for tweakability and
// testability.
minRetargetTimespan int64 // target timespan / adjustment factor
maxRetargetTimespan int64 // target timespan * adjustment factor
blocksPerRetarget int32 // target timespan / target time per block
minMemoryNodes int32
// chainLock protects concurrent access to the vast majority of the
// fields in this struct below this point.
chainLock sync.RWMutex
@ -553,7 +563,7 @@ func (b *BlockChain) pruneBlockNodes() error {
// the latter loads the node and the goal is to find nodes still in
// memory that can be pruned.
newRootNode := b.bestNode
for i := int32(0); i < minMemoryNodes-1 && newRootNode != nil; i++ {
for i := int32(0); i < b.minMemoryNodes-1 && newRootNode != nil; i++ {
newRootNode = newRootNode.parent
}
@ -1454,6 +1464,9 @@ func New(config *Config) (*BlockChain, error) {
}
}
targetTimespan := int64(params.TargetTimespan)
targetTimePerBlock := int64(params.TargetTimePerBlock)
adjustmentFactor := params.RetargetAdjustmentFactor
b := BlockChain{
checkpointsByHeight: checkpointsByHeight,
db: config.DB,
@ -1462,6 +1475,10 @@ func New(config *Config) (*BlockChain, error) {
notifications: config.Notifications,
sigCache: config.SigCache,
indexManager: config.IndexManager,
minRetargetTimespan: targetTimespan / adjustmentFactor,
maxRetargetTimespan: targetTimespan * adjustmentFactor,
blocksPerRetarget: int32(targetTimespan / targetTimePerBlock),
minMemoryNodes: int32(targetTimespan / targetTimePerBlock),
bestNode: nil,
index: make(map[chainhash.Hash]*blockNode),
depNodes: make(map[chainhash.Hash][]*blockNode),

View File

@ -46,7 +46,7 @@ func TestHaveBlock(t *testing.T) {
// Since we're not dealing with the real block chain, disable
// checkpoints and set the coinbase maturity to 1.
chain.DisableCheckpoints(true)
blockchain.TstSetCoinbaseMaturity(1)
chain.TstSetCoinbaseMaturity(1)
for i := 1; i < len(blocks); i++ {
isOrphan, err := chain.ProcessBlock(blocks[i], blockchain.BFNone)

View File

@ -107,10 +107,14 @@ func chainSetup(dbName string) (*blockchain.BlockChain, func(), error) {
}
}
// Copy the chain params to ensure any modifications the tests do to
// the chain parameters do not affect the global instance.
mainNetParams := chaincfg.MainNetParams
// Create the main chain instance.
chain, err := blockchain.New(&blockchain.Config{
DB: db,
ChainParams: &chaincfg.MainNetParams,
ChainParams: &mainNetParams,
TimeSource: blockchain.NewMedianTime(),
})
if err != nil {

View File

@ -11,37 +11,6 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
const (
// targetTimespan is the desired amount of time that should elapse
// before block difficulty requirement is examined to determine how
// it should be changed in order to maintain the desired block
// generation rate.
targetTimespan = time.Hour * 24 * 14
// targetSpacing is the desired amount of time to generate each block.
targetSpacing = time.Minute * 10
// BlocksPerRetarget is the number of blocks between each difficulty
// retarget. It is calculated based on the desired block generation
// rate.
BlocksPerRetarget = int32(targetTimespan / targetSpacing)
// retargetAdjustmentFactor is the adjustment factor used to limit
// the minimum and maximum amount of adjustment that can occur between
// difficulty retargets.
retargetAdjustmentFactor = 4
// minRetargetTimespan is the minimum amount of adjustment that can
// occur between difficulty retargets. It equates to 25% of the
// previous difficulty.
minRetargetTimespan = int64(targetTimespan / retargetAdjustmentFactor)
// maxRetargetTimespan is the maximum amount of adjustment that can
// occur between difficulty retargets. It equates to 400% of the
// previous difficulty.
maxRetargetTimespan = int64(targetTimespan * retargetAdjustmentFactor)
)
var (
// bigOne is 1 represented as a big.Int. It is defined here to avoid
// the overhead of creating it multiple times.
@ -190,13 +159,13 @@ func CalcWork(bits uint32) *big.Int {
func (b *BlockChain) calcEasiestDifficulty(bits uint32, duration time.Duration) uint32 {
// Convert types used in the calculations below.
durationVal := int64(duration)
adjustmentFactor := big.NewInt(retargetAdjustmentFactor)
adjustmentFactor := big.NewInt(b.chainParams.RetargetAdjustmentFactor)
// The test network rules allow minimum difficulty blocks after more
// than twice the desired amount of time needed to generate a block has
// elapsed.
if b.chainParams.ResetMinDifficulty {
if durationVal > int64(targetSpacing)*2 {
if b.chainParams.ReduceMinDifficulty {
if durationVal > int64(b.chainParams.MinDiffReductionTime) {
return b.chainParams.PowLimitBits
}
}
@ -208,7 +177,7 @@ func (b *BlockChain) calcEasiestDifficulty(bits uint32, duration time.Duration)
newTarget := CompactToBig(bits)
for durationVal > 0 && newTarget.Cmp(b.chainParams.PowLimit) < 0 {
newTarget.Mul(newTarget, adjustmentFactor)
durationVal -= maxRetargetTimespan
durationVal -= b.maxRetargetTimespan
}
// Limit new value to the proof of work limit.
@ -227,7 +196,7 @@ func (b *BlockChain) findPrevTestNetDifficulty(startNode *blockNode) (uint32, er
// Search backwards through the chain for the last block without
// the special rule applied.
iterNode := startNode
for iterNode != nil && iterNode.height%BlocksPerRetarget != 0 &&
for iterNode != nil && iterNode.height%b.blocksPerRetarget != 0 &&
iterNode.bits == b.chainParams.PowLimitBits {
// Get the previous block node. This function is used over
@ -267,15 +236,15 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
// Return the previous block's difficulty requirements if this block
// is not at a difficulty retarget interval.
if (lastNode.height+1)%BlocksPerRetarget != 0 {
// The test network rules allow minimum difficulty blocks after
// more than twice the desired amount of time needed to generate
// a block has elapsed.
if b.chainParams.ResetMinDifficulty {
// Return minimum difficulty when more than twice the
// desired amount of time needed to generate a block has
// elapsed.
allowMinTime := lastNode.timestamp.Add(targetSpacing * 2)
if (lastNode.height+1)%b.blocksPerRetarget != 0 {
// For networks that support it, allow special reduction of the
// required difficulty once too much time has elapsed without
// mining a block.
if b.chainParams.ReduceMinDifficulty {
// Return minimum difficulty when more than the desired
// amount of time has elapsed without mining a block.
reductionTime := b.chainParams.MinDiffReductionTime
allowMinTime := lastNode.timestamp.Add(reductionTime)
if newBlockTime.After(allowMinTime) {
return b.chainParams.PowLimitBits, nil
}
@ -298,7 +267,7 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
// Get the block node at the previous retarget (targetTimespan days
// worth of blocks).
firstNode := lastNode
for i := int32(0); i < BlocksPerRetarget-1 && firstNode != nil; i++ {
for i := int32(0); i < b.blocksPerRetarget-1 && firstNode != nil; i++ {
// Get the previous block node. This function is used over
// simply accessing firstNode.parent directly as it will
// dynamically create previous block nodes as needed. This
@ -319,10 +288,10 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
// difficulty.
actualTimespan := lastNode.timestamp.UnixNano() - firstNode.timestamp.UnixNano()
adjustedTimespan := actualTimespan
if actualTimespan < minRetargetTimespan {
adjustedTimespan = minRetargetTimespan
} else if actualTimespan > maxRetargetTimespan {
adjustedTimespan = maxRetargetTimespan
if actualTimespan < b.minRetargetTimespan {
adjustedTimespan = b.minRetargetTimespan
} else if actualTimespan > b.maxRetargetTimespan {
adjustedTimespan = b.maxRetargetTimespan
}
// Calculate new target difficulty as:
@ -332,7 +301,7 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
// result.
oldTarget := CompactToBig(lastNode.bits)
newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan))
newTarget.Div(newTarget, big.NewInt(int64(targetTimespan)))
newTarget.Div(newTarget, big.NewInt(int64(b.chainParams.TargetTimespan)))
// Limit new value to the proof of work limit.
if newTarget.Cmp(b.chainParams.PowLimit) > 0 {
@ -349,7 +318,7 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
log.Debugf("New target %08x (%064x)", newTargetBits, CompactToBig(newTargetBits))
log.Debugf("Actual timespan %v, adjusted timespan %v, target timespan %v",
time.Duration(actualTimespan), time.Duration(adjustedTimespan),
targetTimespan)
b.chainParams.TargetTimespan)
return newTargetBits, nil
}

View File

@ -19,8 +19,8 @@ import (
// TstSetCoinbaseMaturity makes the ability to set the coinbase maturity
// available to the test package.
func TstSetCoinbaseMaturity(maturity int32) {
coinbaseMaturity = maturity
func (b *BlockChain) TstSetCoinbaseMaturity(maturity uint16) {
b.chainParams.CoinbaseMaturity = maturity
}
// TstTimeSorter makes the internal timeSorter type available to the test

View File

@ -56,7 +56,7 @@ func TestReorganization(t *testing.T) {
// Since we're not dealing with the real block chain, disable
// checkpoints and set the coinbase maturity to 1.
chain.DisableCheckpoints(true)
blockchain.TstSetCoinbaseMaturity(1)
chain.TstSetCoinbaseMaturity(1)
expectedOrphans := map[int]struct{}{5: {}, 6: {}}
for i := 1; i < len(blocks); i++ {

View File

@ -45,18 +45,9 @@ const (
// baseSubsidy is the starting subsidy amount for mined blocks. This
// value is halved every SubsidyHalvingInterval blocks.
baseSubsidy = 50 * btcutil.SatoshiPerBitcoin
// CoinbaseMaturity is the number of blocks required before newly
// mined bitcoins (coinbase transactions) can be spent.
CoinbaseMaturity = 100
)
var (
// coinbaseMaturity is the internal variable used for validating the
// spending of coinbase outputs. A variable rather than the exported
// constant is used because the tests need the ability to modify it.
coinbaseMaturity = int32(CoinbaseMaturity)
// zeroHash is the zero value for a chainhash.Hash and is defined as
// a package level variable to avoid the need to create a new instance
// every time a check is needed.
@ -182,18 +173,18 @@ func isBIP0030Node(node *blockNode) bool {
// newly generated blocks awards as well as validating the coinbase for blocks
// has the expected value.
//
// The subsidy is halved every SubsidyHalvingInterval blocks. Mathematically
// this is: baseSubsidy / 2^(height/subsidyHalvingInterval)
// The subsidy is halved every SubsidyReductionInterval blocks. Mathematically
// this is: baseSubsidy / 2^(height/SubsidyReductionInterval)
//
// At the target block generation rate for the main network, this is
// approximately every 4 years.
func CalcBlockSubsidy(height int32, chainParams *chaincfg.Params) int64 {
if chainParams.SubsidyHalvingInterval == 0 {
if chainParams.SubsidyReductionInterval == 0 {
return baseSubsidy
}
// Equivalent to: baseSubsidy / 2^(height/subsidyHalvingInterval)
return baseSubsidy >> uint(height/chainParams.SubsidyHalvingInterval)
return baseSubsidy >> uint(height/chainParams.SubsidyReductionInterval)
}
// CheckTransactionSanity performs some preliminary checks on a transaction to
@ -833,7 +824,7 @@ func (b *BlockChain) checkBIP0030(node *blockNode, block *btcutil.Block, view *U
//
// NOTE: The transaction MUST have already been sanity checked with the
// CheckTransactionSanity function prior to calling this function.
func CheckTransactionInputs(tx *btcutil.Tx, txHeight int32, utxoView *UtxoViewpoint) (int64, error) {
func CheckTransactionInputs(tx *btcutil.Tx, txHeight int32, utxoView *UtxoViewpoint, chainParams *chaincfg.Params) (int64, error) {
// Coinbase transactions have no inputs.
if IsCoinBase(tx) {
return 0, nil
@ -857,6 +848,7 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int32, utxoView *UtxoViewpo
if utxoEntry.IsCoinBase() {
originHeight := int32(utxoEntry.BlockHeight())
blocksSincePrev := txHeight - originHeight
coinbaseMaturity := int32(chainParams.CoinbaseMaturity)
if blocksSincePrev < coinbaseMaturity {
str := fmt.Sprintf("tried to spend coinbase "+
"transaction %v from height %v at "+
@ -1050,7 +1042,8 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi
// bounds.
var totalFees int64
for _, tx := range transactions {
txFee, err := CheckTransactionInputs(tx, node.height, view)
txFee, err := CheckTransactionInputs(tx, node.height, view,
b.chainParams)
if err != nil {
return err
}

View File

@ -7,6 +7,7 @@ package chaincfg
import (
"errors"
"math/big"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
@ -53,19 +54,70 @@ type Checkpoint struct {
// used by Bitcoin applications to differentiate networks as well as addresses
// and keys for one network from those intended for use on another network.
type Params struct {
Name string
Net wire.BitcoinNet
DefaultPort string
DNSSeeds []string
// Name defines a human-readable identifier for the network.
Name string
// Chain parameters
GenesisBlock *wire.MsgBlock
GenesisHash *chainhash.Hash
PowLimit *big.Int
PowLimitBits uint32
SubsidyHalvingInterval int32
ResetMinDifficulty bool
GenerateSupported bool
// Net defines the magic bytes used to identify the network.
Net wire.BitcoinNet
// DefaultPort defines the default peer-to-peer port for the network.
DefaultPort string
// DNSSeeds defines a list of DNS seeds for the network that are used
// as one method to discover peers.
DNSSeeds []string
// GenesisBlock defines the first block of the chain.
GenesisBlock *wire.MsgBlock
// GenesisHash is the starting block hash.
GenesisHash *chainhash.Hash
// PowLimit defines the highest allowed proof of work value for a block
// as a uint256.
PowLimit *big.Int
// PowLimitBits defines the highest allowed proof of work value for a
// block in compact form.
PowLimitBits uint32
// CoinbaseMaturity is the number of blocks required before newly mined
// coins (coinbase transactions) can be spent.
CoinbaseMaturity uint16
// SubsidyReductionInterval is the interval of blocks before the subsidy
// is reduced.
SubsidyReductionInterval int32
// TargetTimespan is the desired amount of time that should elapse
// before the block difficulty requirement is examined to determine how
// it should be changed in order to maintain the desired block
// generation rate.
TargetTimespan time.Duration
// TargetTimePerBlock is the desired amount of time to generate each
// block.
TargetTimePerBlock time.Duration
// RetargetAdjustmentFactor is the adjustment factor used to limit
// the minimum and maximum amount of adjustment that can occur between
// difficulty retargets.
RetargetAdjustmentFactor int64
// ReduceMinDifficulty defines whether the network should reduce the
// minimum required difficulty after a long enough period of time has
// passed without finding a block. This is really only useful for test
// networks and should not be set on a main network.
ReduceMinDifficulty bool
// MinDiffReductionTime is the amount of time after which the minimum
// required difficulty should be reduced when a block hasn't been found.
//
// NOTE: This only applies if ReduceMinDifficulty is true.
MinDiffReductionTime time.Duration
// GenerateSupported specifies whether or not CPU mining is allowed.
GenerateSupported bool
// Checkpoints ordered from oldest to newest.
Checkpoints []Checkpoint
@ -114,13 +166,18 @@ var MainNetParams = Params{
},
// Chain parameters
GenesisBlock: &genesisBlock,
GenesisHash: &genesisHash,
PowLimit: mainPowLimit,
PowLimitBits: 0x1d00ffff,
SubsidyHalvingInterval: 210000,
ResetMinDifficulty: false,
GenerateSupported: false,
GenesisBlock: &genesisBlock,
GenesisHash: &genesisHash,
PowLimit: mainPowLimit,
PowLimitBits: 0x1d00ffff,
CoinbaseMaturity: 100,
SubsidyReductionInterval: 210000,
TargetTimespan: time.Hour * 24 * 14, // 14 days
TargetTimePerBlock: time.Minute * 10, // 10 minutes
RetargetAdjustmentFactor: 4, // 25% less, 400% more
ReduceMinDifficulty: false,
MinDiffReductionTime: 0,
GenerateSupported: false,
// Checkpoints ordered from oldest to newest.
Checkpoints: []Checkpoint{
@ -181,13 +238,18 @@ var RegressionNetParams = Params{
DNSSeeds: []string{},
// Chain parameters
GenesisBlock: &regTestGenesisBlock,
GenesisHash: &regTestGenesisHash,
PowLimit: regressionPowLimit,
PowLimitBits: 0x207fffff,
SubsidyHalvingInterval: 150,
ResetMinDifficulty: true,
GenerateSupported: true,
GenesisBlock: &regTestGenesisBlock,
GenesisHash: &regTestGenesisHash,
PowLimit: regressionPowLimit,
PowLimitBits: 0x207fffff,
CoinbaseMaturity: 100,
SubsidyReductionInterval: 150,
TargetTimespan: time.Hour * 24 * 14, // 14 days
TargetTimePerBlock: time.Minute * 10, // 10 minutes
RetargetAdjustmentFactor: 4, // 25% less, 400% more
ReduceMinDifficulty: true,
MinDiffReductionTime: time.Minute * 20, // TargetTimePerBlock * 2
GenerateSupported: true,
// Checkpoints ordered from oldest to newest.
Checkpoints: nil,
@ -233,13 +295,18 @@ var TestNet3Params = Params{
},
// Chain parameters
GenesisBlock: &testNet3GenesisBlock,
GenesisHash: &testNet3GenesisHash,
PowLimit: testNet3PowLimit,
PowLimitBits: 0x1d00ffff,
SubsidyHalvingInterval: 210000,
ResetMinDifficulty: true,
GenerateSupported: false,
GenesisBlock: &testNet3GenesisBlock,
GenesisHash: &testNet3GenesisHash,
PowLimit: testNet3PowLimit,
PowLimitBits: 0x1d00ffff,
CoinbaseMaturity: 100,
SubsidyReductionInterval: 210000,
TargetTimespan: time.Hour * 24 * 14, // 14 days
TargetTimePerBlock: time.Minute * 10, // 10 minutes
RetargetAdjustmentFactor: 4, // 25% less, 400% more
ReduceMinDifficulty: true,
MinDiffReductionTime: time.Minute * 20, // TargetTimePerBlock * 2
GenerateSupported: false,
// Checkpoints ordered from oldest to newest.
Checkpoints: []Checkpoint{
@ -287,13 +354,18 @@ var SimNetParams = Params{
DNSSeeds: []string{}, // NOTE: There must NOT be any seeds.
// Chain parameters
GenesisBlock: &simNetGenesisBlock,
GenesisHash: &simNetGenesisHash,
PowLimit: simNetPowLimit,
PowLimitBits: 0x207fffff,
SubsidyHalvingInterval: 210000,
ResetMinDifficulty: true,
GenerateSupported: true,
GenesisBlock: &simNetGenesisBlock,
GenesisHash: &simNetGenesisHash,
PowLimit: simNetPowLimit,
PowLimitBits: 0x207fffff,
CoinbaseMaturity: 100,
SubsidyReductionInterval: 210000,
TargetTimespan: time.Hour * 24 * 14, // 14 days
TargetTimePerBlock: time.Minute * 10, // 10 minutes
RetargetAdjustmentFactor: 4, // 25% less, 400% more
ReduceMinDifficulty: true,
MinDiffReductionTime: time.Minute * 20, // TargetTimePerBlock * 2
GenerateSupported: true,
// Checkpoints ordered from oldest to newest.
Checkpoints: nil,

View File

@ -596,7 +596,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit boo
// Also returns the fees associated with the transaction which will be
// used later.
txFee, err := blockchain.CheckTransactionInputs(tx, nextBlockHeight,
utxoView)
utxoView, activeNetParams.Params)
if err != nil {
if cerr, ok := err.(blockchain.RuleError); ok {
return nil, chainRuleError(cerr)

View File

@ -651,7 +651,7 @@ mempoolLoop:
// Ensure the transaction inputs pass all of the necessary
// preconditions before allowing it to be added to the block.
_, err = blockchain.CheckTransactionInputs(tx, nextBlockHeight,
blockUtxos)
blockUtxos, activeNetParams.Params)
if err != nil {
minrLog.Tracef("Skipping tx %s due to error in "+
"CheckTransactionInputs: %v", tx.Hash(), err)
@ -781,7 +781,7 @@ func UpdateBlockTime(msgBlock *wire.MsgBlock, bManager *blockManager) error {
// If running on a network that requires recalculating the difficulty,
// do so now.
if activeNetParams.ResetMinDifficulty {
if activeNetParams.ReduceMinDifficulty {
difficulty, err := bManager.chain.CalcNextRequiredDifficulty(
newTimestamp)
if err != nil {

View File

@ -2104,6 +2104,11 @@ func handleGetNetworkHashPS(s *rpcServer, cmd interface{}, closeChan <-chan stru
endHeight = best.Height
}
// Calculate the number of blocks per retarget interval based on the
// chain parameters.
blocksPerRetarget := int32(s.server.chainParams.TargetTimespan /
s.server.chainParams.TargetTimePerBlock)
// Calculate the starting block height based on the passed number of
// blocks. When the passed value is negative, use the last block the
// difficulty changed as the starting height. Also make sure the
@ -2114,7 +2119,7 @@ func handleGetNetworkHashPS(s *rpcServer, cmd interface{}, closeChan <-chan stru
}
var startHeight int32
if numBlocks <= 0 {
startHeight = endHeight - ((endHeight % blockchain.BlocksPerRetarget) + 1)
startHeight = endHeight - ((endHeight % blocksPerRetarget) + 1)
} else {
startHeight = endHeight - numBlocks
}