diff --git a/blockchain/chain.go b/blockchain/chain.go index 240c1738..6b485302 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -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), diff --git a/blockchain/chain_test.go b/blockchain/chain_test.go index eed0de32..e964d13f 100644 --- a/blockchain/chain_test.go +++ b/blockchain/chain_test.go @@ -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) diff --git a/blockchain/common_test.go b/blockchain/common_test.go index df188b74..27b33c25 100644 --- a/blockchain/common_test.go +++ b/blockchain/common_test.go @@ -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 { diff --git a/blockchain/difficulty.go b/blockchain/difficulty.go index acff5163..2b8dd5f5 100644 --- a/blockchain/difficulty.go +++ b/blockchain/difficulty.go @@ -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 } diff --git a/blockchain/internal_test.go b/blockchain/internal_test.go index 2f681f2c..1508b4dd 100644 --- a/blockchain/internal_test.go +++ b/blockchain/internal_test.go @@ -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 diff --git a/blockchain/reorganization_test.go b/blockchain/reorganization_test.go index efd6706f..ecd2ff6d 100644 --- a/blockchain/reorganization_test.go +++ b/blockchain/reorganization_test.go @@ -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++ { diff --git a/blockchain/validate.go b/blockchain/validate.go index 8603d17a..6a477a25 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -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 } diff --git a/chaincfg/params.go b/chaincfg/params.go index c1fde2b6..120fe0c4 100644 --- a/chaincfg/params.go +++ b/chaincfg/params.go @@ -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: ®TestGenesisBlock, - GenesisHash: ®TestGenesisHash, - PowLimit: regressionPowLimit, - PowLimitBits: 0x207fffff, - SubsidyHalvingInterval: 150, - ResetMinDifficulty: true, - GenerateSupported: true, + GenesisBlock: ®TestGenesisBlock, + GenesisHash: ®TestGenesisHash, + 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, diff --git a/mempool.go b/mempool.go index 385bf50c..e1303d3f 100644 --- a/mempool.go +++ b/mempool.go @@ -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) diff --git a/mining.go b/mining.go index ef8277ce..79036ffb 100644 --- a/mining.go +++ b/mining.go @@ -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 { diff --git a/rpcserver.go b/rpcserver.go index 92276eac..4821868c 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -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 }