Skip to content

Commit

Permalink
chaincfg/blockchain: Parameterize more chain consts. (#732)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
davecgh authored Aug 10, 2016
1 parent bd4e64d commit a7b35d9
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 122 deletions.
31 changes: 24 additions & 7 deletions blockchain/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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,
Expand All @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion blockchain/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion blockchain/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
73 changes: 21 additions & 52 deletions blockchain/difficulty.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions blockchain/internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion blockchain/reorganization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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++ {
Expand Down
23 changes: 8 additions & 15 deletions blockchain/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 "+
Expand Down Expand Up @@ -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
}
Expand Down
Loading

0 comments on commit a7b35d9

Please sign in to comment.