diff --git a/blockchain/chaingen/generator.go b/blockchain/chaingen/generator.go index c3bf7c49eb..d87817bcaa 100644 --- a/blockchain/chaingen/generator.go +++ b/blockchain/chaingen/generator.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2021 The Decred developers +// Copyright (c) 2016-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -163,6 +163,35 @@ type spendableOutsSnap struct { prevCollectedHash chainhash.Hash } +// PowHashAlgorithm defines the supported proof of work hash functions the +// generator can use when generating solved blocks. +type PowHashAlgorithm uint8 + +const ( + // PHABlake256r14 specifies the original blake256 hashing algorithm with 14 + // rounds that was in effect at initial launch. + // + // This is the algorithm the generator will use by default. + PHABlake256r14 PowHashAlgorithm = iota + + // PHABlake3 specifies the blake3 hashing algorithm introduced by DCP0011. + PHABlake3 +) + +// PowDifficultyAlgorithm defines the supported proof of work difficulty +// algorithms the generator can use when generating solved blocks. +type PowDifficultyAlgorithm uint8 + +const ( + // PDAEma specifies the original exponential moving average proof of work + // difficulty algorithm that was in effect at initial launch. + PDAEma PowDifficultyAlgorithm = iota + + // PDAAsert specifies the ASERT proof of work difficulty algorithm + // introduced by DCP0011. + PDAAsert +) + // Generator houses state used to ease the process of generating test blocks // that build from one another along with housing other useful things such as // available spendable outputs and generic payment scripts used throughout the @@ -192,6 +221,11 @@ type Generator struct { expiredTickets []*stakeTicket revokedTickets map[chainhash.Hash][]*stakeTicket missedVotes map[chainhash.Hash]*stakeTicket + + // Used for tracking different behavior depending on voting agendas. + powHashAlgo PowHashAlgorithm + powDiffAlgo PowDifficultyAlgorithm + powDiffAnchor *wire.BlockHeader } // MakeGenerator returns a generator instance initialized with the genesis block @@ -224,9 +258,69 @@ func MakeGenerator(params *chaincfg.Params) (Generator, error) { wonTickets: make(map[chainhash.Hash][]*stakeTicket), revokedTickets: make(map[chainhash.Hash][]*stakeTicket), missedVotes: make(map[chainhash.Hash]*stakeTicket), + powHashAlgo: PHABlake256r14, + powDiffAlgo: PDAEma, }, nil } +// UsePowHashAlgo specifies the proof of work hashing algorithm the generator +// should use when generating blocks. +// +// It will panic if an unsupported algorithm is provided. +func (g *Generator) UsePowHashAlgo(algo PowHashAlgorithm) { + switch algo { + case PHABlake256r14: + case PHABlake3: + default: + panic(fmt.Sprintf("unsupported proof of work hash algorithm %d", algo)) + } + + g.powHashAlgo = algo +} + +// UsePowDiffAlgo specifies the proof of work difficulty algorithm the generator +// should use when generating blocks. It will panic if an unsupported algorithm +// is provided. +// +// The second options parameter depends on the specified algorithm as follows: +// +// PDAEma: +// - No additional options may be specified. The function will panic if any +// are specified. +// +// PDAAsert: +// - The name of the anchor block to use. The function will panic if no +// anchor block is specified or the specified block does not exist. +func (g *Generator) UsePowDiffAlgo(algo PowDifficultyAlgorithm, options ...string) { + switch algo { + case PDAEma: + if len(options) != 0 { + panic("no options may be specified for the EMA proof of work " + + "difficulty algorithm") + } + g.powDiffAlgo = algo + g.powDiffAnchor = nil + return + + case PDAAsert: + if len(options) != 1 { + panic("an anchor block must be specified for the ASERT proof of " + + "work difficulty algorithm") + } + + anchorName := options[0] + anchor, ok := g.blocksByName[anchorName] + if !ok { + panic(fmt.Sprintf("block name %s does not exist", anchorName)) + } + g.powDiffAlgo = algo + g.powDiffAnchor = &anchor.Header + return + } + + panic(fmt.Sprintf("unsupported proof of work difficulty algorithm %d", algo)) +} + // Params returns the chain params associated with the generator instance. func (g *Generator) Params() *chaincfg.Params { return g.params @@ -886,8 +980,9 @@ func (g *Generator) limitRetarget(oldDiff, newDiff int64) int64 { return newDiff } -// CalcNextRequiredDifficulty returns the required proof-of-work difficulty for -// the block after the current tip block the generator is associated with. +// calcNextRequiredDiffEMA returns the required proof-of-work difficulty for the +// block after the current tip block the generator is associated with using the +// original EMA difficulty algorithm. // // An overview of the algorithm is as follows: // 1. Use the proof-of-work limit for all blocks before the first retarget @@ -900,7 +995,7 @@ func (g *Generator) limitRetarget(oldDiff, newDiff int64) int64 { // the highest weight // 4. Calculate the final retarget difficulty based on the exponential weighted // average and ensure it is limited to the max retarget adjustment factor -func (g *Generator) CalcNextRequiredDifficulty() uint32 { +func (g *Generator) calcNextRequiredDiffEMA() uint32 { // Target difficulty before the first retarget interval is the pow // limit. nextHeight := g.tip.Header.Height + 1 @@ -987,6 +1082,309 @@ func (g *Generator) CalcNextRequiredDifficulty() uint32 { return uint32(nextDiff) } +// compactToBig converts a compact representation of a whole number N to an +// unsigned 32-bit number. The representation is similar to IEEE754 floating +// point numbers. +// +// Like IEEE754 floating point, there are three basic components: the sign, +// the exponent, and the mantissa. They are broken out as follows: +// +// 1. the most significant 8 bits represent the unsigned base 256 exponent +// 2. zero-based bit 23 (the 24th bit) represents the sign bit +// 3. the least significant 23 bits represent the mantissa +// +// Diagram: +// +// ------------------------------------------------- +// | Exponent | Sign | Mantissa | +// |-----------------------------------------------| +// | 8 bits [31-24] | 1 bit [23] | 23 bits [22-00] | +// ------------------------------------------------- +// +// The formula to calculate N is: +// +// N = (-1^sign) * mantissa * 256^(exponent-3) +// +// This compact form is only used in Decred to encode unsigned 256-bit numbers +// which represent difficulty targets, thus there really is not a need for a +// sign bit, but it is implemented here to stay consistent with bitcoind. +func compactToBig(compact uint32) *big.Int { + // Extract the mantissa, sign bit, and exponent. + mantissa := compact & 0x007fffff + isNegative := compact&0x00800000 != 0 + exponent := uint(compact >> 24) + + // Since the base for the exponent is 256, the exponent can be treated as + // the number of bytes to represent the full 256-bit number. So, treat the + // exponent as the number of bytes and shift the mantissa right or left + // accordingly. This is equivalent to: + // N = mantissa * 256^(exponent-3) + var bn *big.Int + if exponent <= 3 { + mantissa >>= 8 * (3 - exponent) + bn = big.NewInt(int64(mantissa)) + } else { + bn = big.NewInt(int64(mantissa)) + bn.Lsh(bn, 8*(exponent-3)) + } + + // Make it negative if the sign bit is set. + if isNegative { + bn = bn.Neg(bn) + } + + return bn +} + +// bigToCompact converts a whole number N to a compact representation using an +// unsigned 32-bit number. The compact representation only provides 23 bits of +// precision, so values larger than (2^23 - 1) only encode the most significant +// digits of the number. See compactToBig for details. +func bigToCompact(n *big.Int) uint32 { + // No need to do any work if it's zero. + if n.Sign() == 0 { + return 0 + } + + // Since the base for the exponent is 256, the exponent can be treated as + // the number of bytes. So, shift the number right or left accordingly. + // This is equivalent to: + // mantissa = mantissa / 256^(exponent-3) + var mantissa uint32 + exponent := uint(len(n.Bytes())) + if exponent <= 3 { + mantissa = uint32(n.Bits()[0]) + mantissa <<= 8 * (3 - exponent) + } else { + // Use a copy to avoid modifying the caller's original number. + tn := new(big.Int).Set(n) + mantissa = uint32(tn.Rsh(tn, 8*(exponent-3)).Bits()[0]) + } + + // When the mantissa already has the sign bit set, the number is too large + // to fit into the available 23-bits, so divide the number by 256 and + // increment the exponent accordingly. + if mantissa&0x00800000 != 0 { + mantissa >>= 8 + exponent++ + } + + // Pack the exponent, sign bit, and mantissa into an unsigned 32-bit int and + // return it. + compact := uint32(exponent<<24) | mantissa + if n.Sign() < 0 { + compact |= 0x00800000 + } + return compact +} + +// calcASERTDiff calculates an absolutely scheduled exponentially weighted +// target difficulty for the given set of parameters using the algorithm defined +// in DCP0011. +// +// The Absolutely Scheduled Exponentially weighted Rising Targets (ASERT) +// algorithm defines an ideal schedule for block issuance and calculates the +// difficulty based on how far the most recent block's timestamp is ahead or +// behind that schedule. +// +// The target difficulty is set exponentially such that it is doubled or halved +// for every multiple of the half life the most recent block is ahead or behind +// the ideal schedule. +// +// The starting difficulty bits parameter is the initial target difficulty all +// calculations use as a reference. This value is defined on a per-chain basis. +// It must be non-zero and less than or equal to the provided proof of work +// limit or the function will panic. +// +// The time delta is the number of seconds that have elapsed between the most +// recent block and an initial reference timestamp. +// +// The height delta is the number of blocks between the most recent block height +// and an initial reference height. It must be non-negative or the function +// will panic. +// +// This function is safe for concurrent access. +func calcASERTDiff(startDiffBits uint32, powLimit *big.Int, targetSecsPerBlock, + timeDelta, heightDelta, halfLife int64) uint32 { + + // Ensure parameter assumptions are not violated. + // + // 1. The starting target difficulty must be in the range [1, powLimit] + // 2. The height to calculate the difficulty for must come after the height + // of the reference block + startDiff := compactToBig(startDiffBits) + if startDiff.Sign() <= 0 || startDiff.Cmp(powLimit) > 0 { + panic(fmt.Sprintf("starting difficulty %064x is not in the valid "+ + "range [1, %064x]", startDiff, powLimit)) + } + if heightDelta < 0 { + panic(fmt.Sprintf("provided height delta %d is negative", heightDelta)) + } + + // Calculate the target difficulty by multiplying the provided starting + // target difficulty by an exponential scaling factor that is determined + // based on how far ahead or behind the ideal schedule the given time delta + // is along with a half life that acts as a smoothing factor. + // + // Per DCP0011, the goal equation is: + // + // nextDiff = min(max(startDiff * 2^((Δt - Δh*Ib)/halfLife), 1), powLimit) + // + // However, in order to avoid the need to perform floating point math which + // is problematic across languages due to uncertainty in floating point math + // libs, the formula is implemented using a combination of fixed-point + // integer arithmetic and a cubic polynomial approximation to the 2^x term. + // + // In particular, the goal cubic polynomial approximation over the interval + // 0 <= x < 1 is: + // + // 2^x ~= 1 + 0.695502049712533x + 0.2262697964x^2 + 0.0782318x^3 + // + // This approximation provides an absolute error margin < 0.013% over the + // aforementioned interval of [0,1) which is well under the 0.1% error + // margin needed for good results. Note that since the input domain is not + // constrained to that interval, the exponent is decomposed into an integer + // part, n, and a fractional part, f, such that f is in the desired range of + // [0,1). By exponent rules 2^(n + f) = 2^n * 2^f, so the strategy is to + // calculate the result by applying the cubic polynomial approximation to + // the fractional part and using the fact that multiplying by 2^n is + // equivalent to an arithmetic left or right shift depending on the sign. + // + // In other words, start by calculating the exponent (x) using 64.16 fixed + // point and decompose it into integer (n) and fractional (f) parts as + // follows: + // + // 2^16 * (Δt - Δh*Ib) (Δt - Δh*Ib) << 16 + // x = ------------------- = ------------------ + // halfLife halfLife + // + // x + // n = ---- = x >> 16 + // 2^16 + // + // f = x (mod 2^16) = x & 0xffff + // + // The use of 64.16 fixed point for the exponent means both the integer (n) + // and fractional (f) parts have an additional factor of 2^16. Since the + // fractional part of the exponent is cubed in the polynomial approximation + // and (2^16)^3 = 2^48, the addition step in the approximation is internally + // performed using 16.48 fixed point to compensate. + // + // In other words, the fixed point formulation of the goal cubic polynomial + // approximation for the fractional part is: + // + // 195766423245049*f + 971821376*f^2 + 5127*f^3 + 2^47 + // 2^f ~= 2^16 + --------------------------------------------------- + // 2^48 + // + // Finally, the final target difficulty is calculated using x.16 fixed point + // and then clamped to the valid range as follows: + // + // startDiff * 2^f * 2^n + // nextDiff = --------------------- + // 2^16 + // + // nextDiff = min(max(nextDiff, 1), powLimit) + // + // NOTE: The division by the half life uses Quo instead of Div because it + // must be truncated division (which is truncated towards zero as Quo + // implements) as opposed to the Euclidean division that Div implements. + idealTimeDelta := heightDelta * targetSecsPerBlock + exponentBig := big.NewInt(timeDelta - idealTimeDelta) + exponentBig.Lsh(exponentBig, 16) + exponentBig.Quo(exponentBig, big.NewInt(halfLife)) + + // Decompose the exponent into integer and fractional parts. Since the + // exponent is using 64.16 fixed point, the bottom 16 bits are the + // fractional part and the integer part is the exponent arithmetic right + // shifted by 16. + frac64 := uint64(exponentBig.Int64() & 0xffff) + shifts := exponentBig.Rsh(exponentBig, 16).Int64() + + // Calculate 2^16 * 2^(fractional part) of the exponent. + // + // Note that a full unsigned 64-bit type is required to avoid overflow in + // the internal 16.48 fixed point calculation. Also, the overall result is + // guaranteed to be positive and a maximum of 17 bits, so it is safe to cast + // to a uint32. + const ( + polyCoeff1 uint64 = 195766423245049 // 0.695502049 * 2^48 + polyCoeff2 uint64 = 971821376 // 0.2262698 * 2^32 + polyCoeff3 uint64 = 5127 // 0.0782318 * 2^16 + ) + fracFactor := uint32(1<<16 + (polyCoeff1*frac64+ + polyCoeff2*frac64*frac64+ + polyCoeff3*frac64*frac64*frac64+ + 1<<47)>>48) + + // Calculate the target difficulty per the previous discussion: + // + // startDiff * 2^f * 2^n + // nextDiff = --------------------- + // 2^16 + // + // Note that by exponent rules 2^n / 2^16 = 2^(n - 16). This takes + // advantage of that property to reduce the multiplication by 2^n and + // division by 2^16 to a single shift. + // + // This approach also has the benefit of lowering the maximum magnitude + // relative to what would be the case when first left shifting by a larger + // value and then right shifting after. Since arbitrary precision integers + // are used for this implementation, it doesn't make any difference from a + // correctness standpoint, however, it does potentially lower the amount of + // memory for the arbitrary precision type and can be used to help prevent + // overflow in implementations that use fixed precision types. + nextDiff := new(big.Int).Set(startDiff) + nextDiff.Mul(nextDiff, big.NewInt(int64(fracFactor))) + shifts -= 16 + if shifts >= 0 { + nextDiff.Lsh(nextDiff, uint(shifts)) + } else { + nextDiff.Rsh(nextDiff, uint(-shifts)) + } + + // Limit the target difficulty to the valid hardest and easiest values. + // The valid range is [1, powLimit]. + if nextDiff.Sign() == 0 { + // The hardest valid target difficulty is 1 since it would be impossible + // to find a non-negative integer less than 0. + nextDiff.SetInt64(1) + } else if nextDiff.Cmp(powLimit) > 0 { + nextDiff.Set(powLimit) + } + + // Convert the difficulty to the compact representation and return it. + return bigToCompact(nextDiff) +} + +// calcNextRequiredDiffASERT returns the required proof-of-work difficulty for +// the block after the current tip block the generator is associated with using +// the ASERT difficulty algorithm defined in DCP0011. +func (g *Generator) calcNextRequiredDiffASERT() uint32 { + anchor := g.powDiffAnchor + timeDelta := g.tip.Header.Timestamp.Unix() - anchor.Timestamp.Unix() + heightDelta := int64(g.tip.Header.Height - anchor.Height) + return calcASERTDiff(g.params.WorkDiffV2Blake3StartBits, g.params.PowLimit, + int64(g.params.TargetTimePerBlock.Seconds()), timeDelta, heightDelta, + g.params.WorkDiffV2HalfLifeSecs) +} + +// calcNextRequiredDifficultyEMA returns the required proof-of-work difficulty +// for the block after the current tip block the generator is associated with +// using the difficulty algorithm configured for the generator. +func (g *Generator) CalcNextRequiredDifficulty() uint32 { + switch g.powDiffAlgo { + case PDAEma: + return g.calcNextRequiredDiffEMA() + + case PDAAsert: + return g.calcNextRequiredDiffASERT() + } + + panic(fmt.Sprintf("unsupported proof of work difficulty algorithm %d", + g.powDiffAlgo)) +} + // sumPurchasedTickets returns the sum of the number of tickets purchased in the // most recent specified number of blocks from the point of view of the provided // block. @@ -1448,65 +1846,22 @@ func hashToBig(hash *chainhash.Hash) *big.Int { return new(big.Int).SetBytes(buf[:]) } -// compactToBig converts a compact representation of a whole number N to an -// unsigned 32-bit number. The representation is similar to IEEE754 floating -// point numbers. -// -// Like IEEE754 floating point, there are three basic components: the sign, -// the exponent, and the mantissa. They are broken out as follows: -// -// 1. the most significant 8 bits represent the unsigned base 256 exponent -// 2. zero-based bit 23 (the 24th bit) represents the sign bit -// 3. the least significant 23 bits represent the mantissa -// -// Diagram: -// -// ------------------------------------------------- -// | Exponent | Sign | Mantissa | -// |-----------------------------------------------| -// | 8 bits [31-24] | 1 bit [23] | 23 bits [22-00] | -// ------------------------------------------------- -// -// The formula to calculate N is: -// -// N = (-1^sign) * mantissa * 256^(exponent-3) -// -// This compact form is only used in Decred to encode unsigned 256-bit numbers -// which represent difficulty targets, thus there really is not a need for a -// sign bit, but it is implemented here to stay consistent with bitcoind. -func compactToBig(compact uint32) *big.Int { - // Extract the mantissa, sign bit, and exponent. - mantissa := compact & 0x007fffff - isNegative := compact&0x00800000 != 0 - exponent := uint(compact >> 24) - - // Since the base for the exponent is 256, the exponent can be treated - // as the number of bytes to represent the full 256-bit number. So, - // treat the exponent as the number of bytes and shift the mantissa - // right or left accordingly. This is equivalent to: - // N = mantissa * 256^(exponent-3) - var bn *big.Int - if exponent <= 3 { - mantissa >>= 8 * (3 - exponent) - bn = big.NewInt(int64(mantissa)) - } else { - bn = big.NewInt(int64(mantissa)) - bn.Lsh(bn, 8*(exponent-3)) - } - - // Make it negative if the sign bit is set. - if isNegative { - bn = bn.Neg(bn) - } - - return bn -} - // IsSolved returns whether or not the header hashes to a value that is less -// than or equal to the target difficulty as specified by its bits field. -func IsSolved(header *wire.BlockHeader) bool { +// than or equal to the target difficulty as specified by its bits field while +// respecting the proof of work hashing algorithm associated with the generator +// state. +func (g *Generator) IsSolved(header *wire.BlockHeader) bool { targetDifficulty := compactToBig(header.Bits) - hash := header.BlockHash() + var hash chainhash.Hash + switch g.powHashAlgo { + case PHABlake256r14: + hash = header.PowHashV1() + case PHABlake3: + hash = header.PowHashV2() + default: + panic(fmt.Sprintf("unsupported proof of work hash algorithm %d", + g.powHashAlgo)) + } return hashToBig(&hash).Cmp(targetDifficulty) <= 0 } @@ -1518,7 +1873,7 @@ func IsSolved(header *wire.BlockHeader) bool { // NOTE: This function will never solve blocks with a nonce of 0. This is done // so the 'NextBlock' function can properly detect when a nonce was modified by // a munge function. -func solveBlock(header *wire.BlockHeader) bool { +func (g *Generator) solveBlock(header *wire.BlockHeader) bool { // sbResult is used by the solver goroutines to send results. type sbResult struct { found bool @@ -1531,6 +1886,19 @@ func solveBlock(header *wire.BlockHeader) bool { quit := make(chan bool) results := make(chan sbResult) solver := func(hdr wire.BlockHeader, startNonce, stopNonce uint32) { + // Choose which proof of work hash algorithm to use based on the + // associated state. + var powHashFn func() chainhash.Hash + switch g.powHashAlgo { + case PHABlake256r14: + powHashFn = hdr.PowHashV1 + case PHABlake3: + powHashFn = hdr.PowHashV2 + default: + panic(fmt.Sprintf("unsupported proof of work hash algorithm %d", + g.powHashAlgo)) + } + // We need to modify the nonce field of the header, so make sure // we work with a copy of the original header. for i := startNonce; i >= startNonce && i <= stopNonce; i++ { @@ -1540,10 +1908,8 @@ func solveBlock(header *wire.BlockHeader) bool { return default: hdr.Nonce = i - hash := hdr.BlockHash() - if hashToBig(&hash).Cmp( - targetDifficulty) <= 0 { - + hash := powHashFn() + if hashToBig(&hash).Cmp(targetDifficulty) <= 0 { results <- sbResult{true, i} return } @@ -2239,8 +2605,8 @@ func updateVoteCommitments(block *wire.MsgBlock) { // invoked with the block prior to solving it. This provides callers with the // opportunity to modify the block which is especially useful for testing. // -// In order to simply the logic in the munge functions, the following rules are -// applied after all munge functions have been invoked: +// In order to simplify the logic in the munge functions, the following rules +// are applied after all munge functions have been invoked: // - All votes will have their commitments updated if the previous hash or // height was manually changed after stake validation height has been reached // - The merkle root will be recalculated unless it was manually changed @@ -2441,7 +2807,7 @@ func (g *Generator) NextBlock(blockName string, spend *SpendableOut, ticketSpend // Only solve the block if the nonce wasn't manually changed by a munge // function. - if block.Header.Nonce == curNonce && !solveBlock(&block.Header) { + if block.Header.Nonce == curNonce && !g.solveBlock(&block.Header) { panic(fmt.Sprintf("unable to solve block at height %d", block.Header.Height)) } diff --git a/blockchain/fullblocktests/generate.go b/blockchain/fullblocktests/generate.go index bb24b9e36c..df30c0e2a7 100644 --- a/blockchain/fullblocktests/generate.go +++ b/blockchain/fullblocktests/generate.go @@ -1,5 +1,5 @@ // Copyright (c) 2016 The btcsuite developers -// Copyright (c) 2016-2022 The Decred developers +// Copyright (c) 2016-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -1689,7 +1689,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // it's not solved and then replace it in the generator's state. { origHash := bmf3.BlockHash() - for chaingen.IsSolved(&bmf3.Header) { + for g.IsSolved(&bmf3.Header) { bmf3.Header.Nonce++ } g.UpdateBlockState("bmf3", origHash, "bmf3", bmf3) diff --git a/blockchain/fullblocktests/params.go b/blockchain/fullblocktests/params.go index f909675caf..452d0f24e1 100644 --- a/blockchain/fullblocktests/params.go +++ b/blockchain/fullblocktests/params.go @@ -106,22 +106,28 @@ var regNetParams = &chaincfg.Params{ DNSSeeds: nil, // NOTE: There must NOT be any seeds. // Chain parameters - GenesisBlock: ®NetGenesisBlock, - GenesisHash: *newHashFromStr("2ced94b4ae95bba344cfa043268732d230649c640f92dce2d9518823d3057cb0"), - PowLimit: regNetPowLimit, - PowLimitBits: 0x207fffff, - ReduceMinDifficulty: false, - MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false - GenerateSupported: true, - MaximumBlockSizes: []int{1000000, 1310720}, - MaxTxSize: 1000000, - TargetTimePerBlock: time.Second, + GenesisBlock: ®NetGenesisBlock, + GenesisHash: *newHashFromStr("2ced94b4ae95bba344cfa043268732d230649c640f92dce2d9518823d3057cb0"), + PowLimit: regNetPowLimit, + PowLimitBits: 0x207fffff, + ReduceMinDifficulty: false, + MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false + GenerateSupported: true, + MaximumBlockSizes: []int{1000000, 1310720}, + MaxTxSize: 1000000, + TargetTimePerBlock: time.Second, + + // Version 1 difficulty algorithm (EMA + BLAKE256) parameters. WorkDiffAlpha: 1, WorkDiffWindowSize: 8, WorkDiffWindows: 4, TargetTimespan: time.Second * 8, // TimePerBlock * WindowSize RetargetAdjustmentFactor: 4, + // Version 2 difficulty algorithm (ASERT + BLAKE3) parameters. + WorkDiffV2Blake3StartBits: 0x207fffff, + WorkDiffV2HalfLifeSecs: 6, // 6 * TimePerBlock + // Subsidy parameters. BaseSubsidy: 50000000000, MulSubsidy: 100, diff --git a/blockchain/go.mod b/blockchain/go.mod index bcf48b87f8..d2c2c5c065 100644 --- a/blockchain/go.mod +++ b/blockchain/go.mod @@ -20,6 +20,11 @@ require ( github.com/decred/dcrd/crypto/ripemd160 v1.0.1 // indirect github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 // indirect github.com/decred/slog v1.2.0 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + lukechampine.com/blake3 v1.2.1 // indirect ) -replace github.com/decred/dcrd/chaincfg/v3 => ../chaincfg +replace ( + github.com/decred/dcrd/chaincfg/v3 => ../chaincfg + github.com/decred/dcrd/wire => ../wire +) diff --git a/blockchain/go.sum b/blockchain/go.sum index ddcf59a3d5..8b3f38fdde 100644 --- a/blockchain/go.sum +++ b/blockchain/go.sum @@ -23,7 +23,9 @@ github.com/decred/dcrd/dcrutil/v4 v4.0.0 h1:AY00fWy/ETrMHN0DNV3XUbH1aip2RG1AoTy5 github.com/decred/dcrd/dcrutil/v4 v4.0.0/go.mod h1:QQpX5WVH3/ixVtiW15xZMe+neugXX3l2bsrYgq6nz4M= github.com/decred/dcrd/txscript/v4 v4.0.0 h1:BwaBUCMCmg58MCYoBhxVjL8ZZKUIfoJuxu/djmh8h58= github.com/decred/dcrd/txscript/v4 v4.0.0/go.mod h1:OJtxNc5RqwQyfrRnG2gG8uMeNPo8IAJp+TD1UKXkqk8= -github.com/decred/dcrd/wire v1.5.0 h1:3SgcEzSjqAMQvOugP0a8iX7yQSpiVT1yNi9bc4iOXVg= -github.com/decred/dcrd/wire v1.5.0/go.mod h1:fzAjVqw32LkbAZIt5mnrvBR751GTa3e0rRQdOIhPY3w= github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM= github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= diff --git a/blockchain/standalone/README.md b/blockchain/standalone/README.md index a15cbcd7c7..6f2780cda6 100644 --- a/blockchain/standalone/README.md +++ b/blockchain/standalone/README.md @@ -26,6 +26,7 @@ The provided functions fall into the following categories: - Calculating work values based on the compact target difficulty - Checking a block hash satisfies a target difficulty and that target difficulty is within a valid range + - Calculating required target difficulties using the ASERT algorithm - Merkle root calculation - Calculation from individual leaf hashes - Calculation from a slice of transactions diff --git a/blockchain/standalone/doc.go b/blockchain/standalone/doc.go index cfdc1de411..22aef4aa65 100644 --- a/blockchain/standalone/doc.go +++ b/blockchain/standalone/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022 The Decred developers +// Copyright (c) 2019-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -34,6 +34,7 @@ The provided functions fall into the following categories: - Calculating work values based on the compact target difficulty - Checking a block hash satisfies a target difficulty and that target difficulty is within a valid range + - Calculating required target difficulties using the ASERT algorithm # Merkle root calculation diff --git a/blockchain/standalone/error.go b/blockchain/standalone/error.go index a3edcdb457..92bd2d22b8 100644 --- a/blockchain/standalone/error.go +++ b/blockchain/standalone/error.go @@ -1,9 +1,18 @@ -// Copyright (c) 2019-2022 The Decred developers +// Copyright (c) 2019-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package standalone +import "fmt" + +// panicf is a convenience function that formats according to the given format +// specifier and arguments and panics with it. +func panicf(format string, args ...interface{}) { + str := fmt.Sprintf(format, args...) + panic(str) +} + // ErrorKind identifies a kind of error. It has full support for errors.Is and // errors.As, so the caller can directly check against an error kind when // determining the reason for an error. diff --git a/blockchain/standalone/pow.go b/blockchain/standalone/pow.go index 11fb6eda09..7ec7f4d1d7 100644 --- a/blockchain/standalone/pow.go +++ b/blockchain/standalone/pow.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2019 The Decred developers +// Copyright (c) 2015-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -182,22 +182,223 @@ func CheckProofOfWorkRange(difficultyBits uint32, powLimit *big.Int) error { return checkProofOfWorkRange(target, powLimit) } -// CheckProofOfWork ensures the provided block hash is less than the provided -// compact target difficulty and that the target difficulty is in min/max range -// per the provided proof-of-work limit. -func CheckProofOfWork(blockHash *chainhash.Hash, difficultyBits uint32, powLimit *big.Int) error { +// checkProofOfWorkHash ensures the provided hash is less than the provided +// target difficulty. +func checkProofOfWorkHash(powHash *chainhash.Hash, target *big.Int) error { + // The proof of work hash must be less than the target difficulty. + hashNum := HashToBig(powHash) + if hashNum.Cmp(target) > 0 { + str := fmt.Sprintf("proof of work hash %064x is higher than "+ + "expected max of %064x", hashNum, target) + return ruleError(ErrHighHash, str) + } + + return nil +} + +// CheckProofOfWorkHash ensures the provided hash is less than the provided +// compact target difficulty. +func CheckProofOfWorkHash(powHash *chainhash.Hash, difficultyBits uint32) error { + target := CompactToBig(difficultyBits) + return checkProofOfWorkHash(powHash, target) +} + +// CheckProofOfWork ensures the provided hash is less than the provided compact +// target difficulty and that the target difficulty is in min/max range per the +// provided proof-of-work limit. +// +// This is semantically equivalent to and slightly more efficient than calling +// CheckProofOfWorkRange followed by CheckProofOfWorkHash. +func CheckProofOfWork(powHash *chainhash.Hash, difficultyBits uint32, powLimit *big.Int) error { target := CompactToBig(difficultyBits) if err := checkProofOfWorkRange(target, powLimit); err != nil { return err } - // The block hash must be less than the target difficulty. - hashNum := HashToBig(blockHash) - if hashNum.Cmp(target) > 0 { - str := fmt.Sprintf("block hash of %064x is higher than expected max "+ - "of %064x", hashNum, target) - return ruleError(ErrHighHash, str) + // The proof of work hash must be less than the target difficulty. + return checkProofOfWorkHash(powHash, target) +} + +// CalcASERTDiff calculates an absolutely scheduled exponentially weighted +// target difficulty for the given set of parameters using the algorithm defined +// in DCP0011. +// +// The Absolutely Scheduled Exponentially weighted Rising Targets (ASERT) +// algorithm defines an ideal schedule for block issuance and calculates the +// difficulty based on how far the most recent block's timestamp is ahead or +// behind that schedule. +// +// The target difficulty is set exponentially such that it is doubled or halved +// for every multiple of the half life the most recent block is ahead or behind +// the ideal schedule. +// +// The starting difficulty bits parameter is the initial target difficulty all +// calculations use as a reference. This value is defined on a per-chain basis. +// It must be non-zero and less than or equal to the provided proof of work +// limit or the function will panic. +// +// The time delta is the number of seconds that have elapsed between the most +// recent block and an initial reference timestamp. +// +// The height delta is the number of blocks between the most recent block height +// and an initial reference height. It must be non-negative or the function +// will panic. +// +// NOTE: This only performs the primary target difficulty calculation and does +// not include any additional special network rules such as enforcing a maximum +// allowed test network difficulty. It is up to the caller to impose any such +// additional restrictions. +// +// This function is safe for concurrent access. +func CalcASERTDiff(startDiffBits uint32, powLimit *big.Int, targetSecsPerBlock, + timeDelta, heightDelta, halfLife int64) uint32 { + + // Ensure parameter assumptions are not violated. + // + // 1. The starting target difficulty must be in the range [1, powLimit] + // 2. The height to calculate the difficulty for must come after the height + // of the reference block + startDiff := CompactToBig(startDiffBits) + if startDiff.Sign() <= 0 || startDiff.Cmp(powLimit) > 0 { + panicf("starting difficulty %064x is not in the valid range [1, %064x]", + startDiff, powLimit) + } + if heightDelta < 0 { + panicf("provided height delta %d is negative", heightDelta) } - return nil + // Calculate the target difficulty by multiplying the provided starting + // target difficulty by an exponential scaling factor that is determined + // based on how far ahead or behind the ideal schedule the given time delta + // is along with a half life that acts as a smoothing factor. + // + // Per DCP0011, the goal equation is: + // + // nextDiff = min(max(startDiff * 2^((Δt - Δh*Ib)/halfLife), 1), powLimit) + // + // However, in order to avoid the need to perform floating point math which + // is problematic across languages due to uncertainty in floating point math + // libs, the formula is implemented using a combination of fixed-point + // integer arithmetic and a cubic polynomial approximation to the 2^x term. + // + // In particular, the goal cubic polynomial approximation over the interval + // 0 <= x < 1 is: + // + // 2^x ~= 1 + 0.695502049712533x + 0.2262697964x^2 + 0.0782318x^3 + // + // This approximation provides an absolute error margin < 0.013% over the + // aforementioned interval of [0,1) which is well under the 0.1% error + // margin needed for good results. Note that since the input domain is not + // constrained to that interval, the exponent is decomposed into an integer + // part, n, and a fractional part, f, such that f is in the desired range of + // [0,1). By exponent rules 2^(n + f) = 2^n * 2^f, so the strategy is to + // calculate the result by applying the cubic polynomial approximation to + // the fractional part and using the fact that multiplying by 2^n is + // equivalent to an arithmetic left or right shift depending on the sign. + // + // In other words, start by calculating the exponent (x) using 64.16 fixed + // point and decompose it into integer (n) and fractional (f) parts as + // follows: + // + // 2^16 * (Δt - Δh*Ib) (Δt - Δh*Ib) << 16 + // x = ------------------- = ------------------ + // halfLife halfLife + // + // x + // n = ---- = x >> 16 + // 2^16 + // + // f = x (mod 2^16) = x & 0xffff + // + // The use of 64.16 fixed point for the exponent means both the integer (n) + // and fractional (f) parts have an additional factor of 2^16. Since the + // fractional part of the exponent is cubed in the polynomial approximation + // and (2^16)^3 = 2^48, the addition step in the approximation is internally + // performed using 16.48 fixed point to compensate. + // + // In other words, the fixed point formulation of the goal cubic polynomial + // approximation for the fractional part is: + // + // 195766423245049*f + 971821376*f^2 + 5127*f^3 + 2^47 + // 2^f ~= 2^16 + --------------------------------------------------- + // 2^48 + // + // Finally, the final target difficulty is calculated using x.16 fixed point + // and then clamped to the valid range as follows: + // + // startDiff * 2^f * 2^n + // nextDiff = --------------------- + // 2^16 + // + // nextDiff = min(max(nextDiff, 1), powLimit) + // + // NOTE: The division by the half life uses Quo instead of Div because it + // must be truncated division (which is truncated towards zero as Quo + // implements) as opposed to the Euclidean division that Div implements. + idealTimeDelta := heightDelta * targetSecsPerBlock + exponentBig := big.NewInt(timeDelta - idealTimeDelta) + exponentBig.Lsh(exponentBig, 16) + exponentBig.Quo(exponentBig, big.NewInt(halfLife)) + + // Decompose the exponent into integer and fractional parts. Since the + // exponent is using 64.16 fixed point, the bottom 16 bits are the + // fractional part and the integer part is the exponent arithmetic right + // shifted by 16. + frac64 := uint64(exponentBig.Int64() & 0xffff) + shifts := exponentBig.Rsh(exponentBig, 16).Int64() + + // Calculate 2^16 * 2^(fractional part) of the exponent. + // + // Note that a full unsigned 64-bit type is required to avoid overflow in + // the internal 16.48 fixed point calculation. Also, the overall result is + // guaranteed to be positive and a maximum of 17 bits, so it is safe to cast + // to a uint32. + const ( + polyCoeff1 uint64 = 195766423245049 // ceil(0.695502049712533 * 2^48) + polyCoeff2 uint64 = 971821376 // ceil(0.2262697964 * 2^32) + polyCoeff3 uint64 = 5127 // ceil(0.0782318 * 2^16) + ) + fracFactor := uint32(1<<16 + (polyCoeff1*frac64+ + polyCoeff2*frac64*frac64+ + polyCoeff3*frac64*frac64*frac64+ + 1<<47)>>48) + + // Calculate the target difficulty per the previous discussion: + // + // startDiff * 2^f * 2^n + // nextDiff = --------------------- + // 2^16 + // + // Note that by exponent rules 2^n / 2^16 = 2^(n - 16). This takes + // advantage of that property to reduce the multiplication by 2^n and + // division by 2^16 to a single shift. + // + // This approach also has the benefit of lowering the maximum magnitude + // relative to what would be the case when first left shifting by a larger + // value and then right shifting after. Since arbitrary precision integers + // are used for this implementation, it doesn't make any difference from a + // correctness standpoint, however, it does potentially lower the amount of + // memory for the arbitrary precision type and can be used to help prevent + // overflow in implementations that use fixed precision types. + nextDiff := new(big.Int).Set(startDiff) + nextDiff.Mul(nextDiff, big.NewInt(int64(fracFactor))) + shifts -= 16 + if shifts >= 0 { + nextDiff.Lsh(nextDiff, uint(shifts)) + } else { + nextDiff.Rsh(nextDiff, uint(-shifts)) + } + + // Limit the target difficulty to the valid hardest and easiest values. + // The valid range is [1, powLimit]. + if nextDiff.Sign() == 0 { + // The hardest valid target difficulty is 1 since it would be impossible + // to find a non-negative integer less than 0. + nextDiff.SetInt64(1) + } else if nextDiff.Cmp(powLimit) > 0 { + nextDiff.Set(powLimit) + } + + // Convert the difficulty to the compact representation and return it. + return BigToCompact(nextDiff) } diff --git a/blockchain/standalone/pow_test.go b/blockchain/standalone/pow_test.go index fa9c55f7f1..e8b3c0f630 100644 --- a/blockchain/standalone/pow_test.go +++ b/blockchain/standalone/pow_test.go @@ -5,8 +5,11 @@ package standalone import ( + "encoding/json" "errors" "math/big" + "os" + "path/filepath" "testing" "github.com/decred/dcrd/chaincfg/chainhash" @@ -278,24 +281,64 @@ func TestCheckProofOfWorkRange(t *testing.T) { } } -// TestCheckProofOfWorkRange ensures hashes and target difficulties that are -// outside of the acceptable ranges are detected as an error and those inside -// are not. +// TestCheckProofOfWorkHash ensures hashes that do not satisfy a given target +// difficulty are detected as an error and those that do are not. +func TestCheckProofOfWorkHash(t *testing.T) { + tests := []struct { + name string // test description + hash string // proof of work hash to test + bits uint32 // compact target difficulty bits to test + err error // expected error + }{{ + name: "mainnet block 1 pow hash", + hash: "000000000000437482b6d47f82f374cde539440ddb108b0a76886f0d87d126b9", + bits: 0x1b01ffff, + err: nil, + }, { + name: "mainnet block 288 pow hash", + hash: "000000000000e0ab546b8fc19f6d94054d47ffa5fe79e17611d170662c8b702b", + bits: 0x1b01330e, + err: nil, + }, { + name: "high hash", + hash: "000000000001ffff000000000000000000000000000000000000000000000001", + bits: 0x1b01ffff, + err: ErrHighHash, + }} + + for _, test := range tests { + hash, err := chainhash.NewHashFromStr(test.hash) + if err != nil { + t.Errorf("%q: unexpected err parsing test hash: %v", test.name, err) + continue + } + + err = CheckProofOfWorkHash(hash, test.bits) + if !errors.Is(err, test.err) { + t.Errorf("%q: unexpected err -- got %v, want %v", test.name, err, + test.err) + continue + } + } +} + +// TestCheckProofOfWork ensures hashes and target difficulties that are outside +// of the acceptable ranges are detected as an error and those inside are not. func TestCheckProofOfWork(t *testing.T) { tests := []struct { name string // test description - hash string // block hash to test + hash string // proof of work hash to test bits uint32 // compact target difficulty bits to test powLimit string // proof of work limit err error // expected error }{{ - name: "mainnet block 1 hash", + name: "mainnet block 1 pow hash", hash: "000000000000437482b6d47f82f374cde539440ddb108b0a76886f0d87d126b9", bits: 0x1b01ffff, powLimit: mockMainNetPowLimit(), err: nil, }, { - name: "mainnet block 288 hash", + name: "mainnet block 288 pow hash", hash: "000000000000e0ab546b8fc19f6d94054d47ffa5fe79e17611d170662c8b702b", bits: 0x1b01330e, powLimit: mockMainNetPowLimit(), @@ -353,3 +396,142 @@ func TestCheckProofOfWork(t *testing.T) { } } } + +// TestCalcASERTDiff ensures the proof-of-work target difficulty calculation for +// the algorithm defined by DCP0011 works as expected by using the reference +// test vectors. +func TestCalcASERTDiff(t *testing.T) { + t.Parallel() + + // Read and parse the reference test vectors. + f, err := os.ReadFile(filepath.Join("testdata", "asert_test_vectors.json")) + if err != nil { + t.Fatalf("failed to read test vectors: %v", err) + } + var testData struct { + Comments []string `json:"comments"` + Params map[string]struct { + PowLimit string `json:"powLimit"` + PowLimitBits uint32 `json:"powLimitBits"` + TargetSecsPerBlock int64 `json:"targetSecsPerBlock"` + HalfLifeSecs int64 `json:"halfLifeSecs"` + } `json:"params"` + Scenarios []struct { + Desc string `json:"description"` + Params string `json:"params"` + StartDiffBits uint32 `json:"startDiffBits"` + StartHeight int64 `json:"startHeight"` + StartTime int64 `json:"startTime"` + Tests []struct { + Height uint64 `json:"height"` + Timestamp int64 `json:"timestamp"` + ExpectedDiffBits uint32 `json:"expectedDiffBits"` + } `json:"tests"` + } `json:"scenarios"` + } + err = json.Unmarshal(f, &testData) + if err != nil { + t.Fatal(err) + } + + // Basic sanity check to ensure scenarios parsed. + if len(testData.Scenarios) == 0 { + t.Fatal("No test scenarios found") + } + + for _, scenario := range testData.Scenarios { + // Basic sanity check to ensure test cases parsed. + if len(scenario.Tests) == 0 { + t.Fatalf("%q: No test cases found", scenario.Desc) + } + + // Lookup the associated network parameters and parse the proof of work + // limit hexadecimal to a uint256. + paramsKey := scenario.Params + params, ok := testData.Params[paramsKey] + if !ok { + t.Errorf("%q: bad network params key %q", scenario.Desc, paramsKey) + continue + } + powLimit, ok := new(big.Int).SetString(params.PowLimit, 16) + if !ok { + t.Errorf("%q: malformed pow limit %q", paramsKey, params.PowLimit) + continue + } + + for _, test := range scenario.Tests { + // Calculate the time and height deltas from the test data. + heightDelta := int64(test.Height - uint64(scenario.StartHeight)) + timeDelta := test.Timestamp - scenario.StartTime + + // Ensure the calculated difficulty matches the expected result. + gotDiff := CalcASERTDiff(scenario.StartDiffBits, powLimit, + params.TargetSecsPerBlock, timeDelta, heightDelta, + params.HalfLifeSecs) + if gotDiff != test.ExpectedDiffBits { + t.Errorf("%q@height %d: did not get expected difficulty bits "+ + "-- got %08x, want %08x", scenario.Desc, test.Height, + gotDiff, test.ExpectedDiffBits) + continue + } + } + } +} + +// TestCalcASERTDiffPanics ensures the proof-of-work target difficulty +// calculation for the algorithm defined by DCP0011 panics when called +// improperly. +func TestCalcASERTDiffPanics(t *testing.T) { + testPanic := func(fn func()) (paniced bool) { + // Setup a defer to catch the expected panic and update the return + // variable. + defer func() { + if err := recover(); err != nil { + paniced = true + } + }() + + fn() + return false + } + + // Parameters used in the tests below. + const ( + startDiffBits = 0x1b00a5a6 + powLimitBits = 0x1d00ffff + targetSecsPerBlock = 300 + halfLifeSecs = 43200 + ) + powLimit := CompactToBig(powLimitBits) + + // Ensure attempting to calculate a target difficulty with an invalid + // starting target difficulty of 0 panics. + paniced := testPanic(func() { + CalcASERTDiff(0, powLimit, targetSecsPerBlock, 0, 0, halfLifeSecs) + }) + if !paniced { + t.Fatal("CalcASERTDiff did not panic with zero starting difficulty") + } + + // Ensure attempting to calculate a target difficulty with a starting target + // difficulty greater than the proof of work limit panics. + paniced = testPanic(func() { + invalidBits := uint32(powLimitBits + 1) + CalcASERTDiff(invalidBits, powLimit, targetSecsPerBlock, 0, 0, + halfLifeSecs) + }) + if !paniced { + t.Fatal("CalcASERTDiff did not panic with a starting difficulty " + + "greater than the proof of work limit") + } + + // Ensure attempting to calculate a target difficulty with a negative height + // delta panics. + paniced = testPanic(func() { + CalcASERTDiff(startDiffBits, powLimit, targetSecsPerBlock, 0, -1, + halfLifeSecs) + }) + if !paniced { + t.Fatal("CalcASERTDiff did not panic with a negative height delta") + } +} diff --git a/blockchain/standalone/testdata/asert_test_vectors.json b/blockchain/standalone/testdata/asert_test_vectors.json new file mode 100644 index 0000000000..26372eb369 --- /dev/null +++ b/blockchain/standalone/testdata/asert_test_vectors.json @@ -0,0 +1,1597 @@ +{ + "comments": [ + "Each test consists of a set of parameters, starting conditions, and a ", + "series of block heights and timestamps relative to those starting ", + "conditions along with the expected resulting difficulty bits for them.", + "The params field for each test references the keys in the params object.", + "The powLimit field is a hex-encoded unsigned 256-bit integer.", + "The powLimitBits, startDiffBits, and expectedDiffBits fields are unsigned 32-bit integers.", + "The targetSecsPerBlock, halfLifeSecs, startHeight, and startTime fields are signed 64-bit integers.", + "The height field is an unsigned 64-bit integer.", + "halfLifeSecs is the 𝜏 (tau) parameter for the equations in the specification." + ], + "params": { + "mainnet": { + "powLimit": "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "powLimitBits": 486604799, + "targetSecsPerBlock": 300, + "halfLifeSecs": 43200 + }, + "testnet": { + "powLimit": "000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "powLimitBits": 486604799, + "targetSecsPerBlock": 120, + "halfLifeSecs": 720 + }, + "simnet": { + "powLimit": "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "powLimitBits": 545259519, + "targetSecsPerBlock": 1, + "halfLifeSecs": 6 + } + }, + "scenarios": [ + { + "description": "mainnet: steady 300s blocks at proof of work limit", + "params": "mainnet", + "startDiffBits": 486604799, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 1, "timestamp": 300, "expectedDiffBits": 486604799}, + {"height": 2, "timestamp": 600, "expectedDiffBits": 486604799}, + {"height": 3, "timestamp": 900, "expectedDiffBits": 486604799}, + {"height": 4, "timestamp": 1200, "expectedDiffBits": 486604799}, + {"height": 5, "timestamp": 1500, "expectedDiffBits": 486604799}, + {"height": 6, "timestamp": 1800, "expectedDiffBits": 486604799}, + {"height": 7, "timestamp": 2100, "expectedDiffBits": 486604799}, + {"height": 8, "timestamp": 2400, "expectedDiffBits": 486604799}, + {"height": 9, "timestamp": 2700, "expectedDiffBits": 486604799}, + {"height": 10, "timestamp": 3000, "expectedDiffBits": 486604799} + ] + }, + { + "description": "mainnet: steady 300s blocks at difficulty 0x1b00a5a6", + "params": "mainnet", + "startDiffBits": 453027238, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 0, "timestamp": 0, "expectedDiffBits": 453027238}, + {"height": 1, "timestamp": 300, "expectedDiffBits": 453027238}, + {"height": 2, "timestamp": 600, "expectedDiffBits": 453027238}, + {"height": 3, "timestamp": 900, "expectedDiffBits": 453027238}, + {"height": 4, "timestamp": 1200, "expectedDiffBits": 453027238}, + {"height": 5, "timestamp": 1500, "expectedDiffBits": 453027238}, + {"height": 6, "timestamp": 1800, "expectedDiffBits": 453027238}, + {"height": 7, "timestamp": 2100, "expectedDiffBits": 453027238}, + {"height": 8, "timestamp": 2400, "expectedDiffBits": 453027238}, + {"height": 9, "timestamp": 2700, "expectedDiffBits": 453027238}, + {"height": 10, "timestamp": 3000, "expectedDiffBits": 453027238} + ] + }, + { + "description": "mainnet: steady 300s blocks at hardest difficulty", + "params": "mainnet", + "startDiffBits": 16842752, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 1, "timestamp": 300, "expectedDiffBits": 16842752}, + {"height": 2, "timestamp": 600, "expectedDiffBits": 16842752}, + {"height": 3, "timestamp": 900, "expectedDiffBits": 16842752}, + {"height": 4, "timestamp": 1200, "expectedDiffBits": 16842752}, + {"height": 5, "timestamp": 1500, "expectedDiffBits": 16842752}, + {"height": 6, "timestamp": 1800, "expectedDiffBits": 16842752}, + {"height": 7, "timestamp": 2100, "expectedDiffBits": 16842752}, + {"height": 8, "timestamp": 2400, "expectedDiffBits": 16842752}, + {"height": 9, "timestamp": 2700, "expectedDiffBits": 16842752}, + {"height": 10, "timestamp": 3000, "expectedDiffBits": 16842752} + ] + }, + { + "description": "mainnet: half life blocks from hardest to easiest difficulty", + "params": "mainnet", + "startDiffBits": 16842752, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 1, "timestamp": 43500, "expectedDiffBits": 16908288}, + {"height": 2, "timestamp": 87000, "expectedDiffBits": 17039360}, + {"height": 3, "timestamp": 130500, "expectedDiffBits": 17301504}, + {"height": 4, "timestamp": 174000, "expectedDiffBits": 17825792}, + {"height": 5, "timestamp": 217500, "expectedDiffBits": 18874368}, + {"height": 6, "timestamp": 261000, "expectedDiffBits": 20971520}, + {"height": 7, "timestamp": 304500, "expectedDiffBits": 33587200}, + {"height": 8, "timestamp": 348000, "expectedDiffBits": 33619968}, + {"height": 9, "timestamp": 391500, "expectedDiffBits": 33685504}, + {"height": 10, "timestamp": 435000, "expectedDiffBits": 33816576}, + {"height": 11, "timestamp": 478500, "expectedDiffBits": 34078720}, + {"height": 12, "timestamp": 522000, "expectedDiffBits": 34603008}, + {"height": 13, "timestamp": 565500, "expectedDiffBits": 35651584}, + {"height": 14, "timestamp": 609000, "expectedDiffBits": 37748736}, + {"height": 15, "timestamp": 652500, "expectedDiffBits": 50364416}, + {"height": 16, "timestamp": 696000, "expectedDiffBits": 50397184}, + {"height": 17, "timestamp": 739500, "expectedDiffBits": 50462720}, + {"height": 18, "timestamp": 783000, "expectedDiffBits": 50593792}, + {"height": 19, "timestamp": 826500, "expectedDiffBits": 50855936}, + {"height": 20, "timestamp": 870000, "expectedDiffBits": 51380224}, + {"height": 21, "timestamp": 913500, "expectedDiffBits": 52428800}, + {"height": 22, "timestamp": 957000, "expectedDiffBits": 54525952}, + {"height": 23, "timestamp": 1000500, "expectedDiffBits": 67141632}, + {"height": 24, "timestamp": 1044000, "expectedDiffBits": 67174400}, + {"height": 25, "timestamp": 1087500, "expectedDiffBits": 67239936}, + {"height": 26, "timestamp": 1131000, "expectedDiffBits": 67371008}, + {"height": 27, "timestamp": 1174500, "expectedDiffBits": 67633152}, + {"height": 28, "timestamp": 1218000, "expectedDiffBits": 68157440}, + {"height": 29, "timestamp": 1261500, "expectedDiffBits": 69206016}, + {"height": 30, "timestamp": 1305000, "expectedDiffBits": 71303168}, + {"height": 31, "timestamp": 1348500, "expectedDiffBits": 83918848}, + {"height": 32, "timestamp": 1392000, "expectedDiffBits": 83951616}, + {"height": 33, "timestamp": 1435500, "expectedDiffBits": 84017152}, + {"height": 34, "timestamp": 1479000, "expectedDiffBits": 84148224}, + {"height": 35, "timestamp": 1522500, "expectedDiffBits": 84410368}, + {"height": 36, "timestamp": 1566000, "expectedDiffBits": 84934656}, + {"height": 37, "timestamp": 1609500, "expectedDiffBits": 85983232}, + {"height": 38, "timestamp": 1653000, "expectedDiffBits": 88080384}, + {"height": 39, "timestamp": 1696500, "expectedDiffBits": 100696064}, + {"height": 40, "timestamp": 1740000, "expectedDiffBits": 100728832}, + {"height": 41, "timestamp": 1783500, "expectedDiffBits": 100794368}, + {"height": 42, "timestamp": 1827000, "expectedDiffBits": 100925440}, + {"height": 43, "timestamp": 1870500, "expectedDiffBits": 101187584}, + {"height": 44, "timestamp": 1914000, "expectedDiffBits": 101711872}, + {"height": 45, "timestamp": 1957500, "expectedDiffBits": 102760448}, + {"height": 46, "timestamp": 2001000, "expectedDiffBits": 104857600}, + {"height": 47, "timestamp": 2044500, "expectedDiffBits": 117473280}, + {"height": 48, "timestamp": 2088000, "expectedDiffBits": 117506048}, + {"height": 49, "timestamp": 2131500, "expectedDiffBits": 117571584}, + {"height": 50, "timestamp": 2175000, "expectedDiffBits": 117702656}, + {"height": 51, "timestamp": 2218500, "expectedDiffBits": 117964800}, + {"height": 52, "timestamp": 2262000, "expectedDiffBits": 118489088}, + {"height": 53, "timestamp": 2305500, "expectedDiffBits": 119537664}, + {"height": 54, "timestamp": 2349000, "expectedDiffBits": 121634816}, + {"height": 55, "timestamp": 2392500, "expectedDiffBits": 134250496}, + {"height": 56, "timestamp": 2436000, "expectedDiffBits": 134283264}, + {"height": 57, "timestamp": 2479500, "expectedDiffBits": 134348800}, + {"height": 58, "timestamp": 2523000, "expectedDiffBits": 134479872}, + {"height": 59, "timestamp": 2566500, "expectedDiffBits": 134742016}, + {"height": 60, "timestamp": 2610000, "expectedDiffBits": 135266304}, + {"height": 61, "timestamp": 2653500, "expectedDiffBits": 136314880}, + {"height": 62, "timestamp": 2697000, "expectedDiffBits": 138412032}, + {"height": 63, "timestamp": 2740500, "expectedDiffBits": 151027712}, + {"height": 64, "timestamp": 2784000, "expectedDiffBits": 151060480}, + {"height": 65, "timestamp": 2827500, "expectedDiffBits": 151126016}, + {"height": 66, "timestamp": 2871000, "expectedDiffBits": 151257088}, + {"height": 67, "timestamp": 2914500, "expectedDiffBits": 151519232}, + {"height": 68, "timestamp": 2958000, "expectedDiffBits": 152043520}, + {"height": 69, "timestamp": 3001500, "expectedDiffBits": 153092096}, + {"height": 70, "timestamp": 3045000, "expectedDiffBits": 155189248}, + {"height": 71, "timestamp": 3088500, "expectedDiffBits": 167804928}, + {"height": 72, "timestamp": 3132000, "expectedDiffBits": 167837696}, + {"height": 73, "timestamp": 3175500, "expectedDiffBits": 167903232}, + {"height": 74, "timestamp": 3219000, "expectedDiffBits": 168034304}, + {"height": 75, "timestamp": 3262500, "expectedDiffBits": 168296448}, + {"height": 76, "timestamp": 3306000, "expectedDiffBits": 168820736}, + {"height": 77, "timestamp": 3349500, "expectedDiffBits": 169869312}, + {"height": 78, "timestamp": 3393000, "expectedDiffBits": 171966464}, + {"height": 79, "timestamp": 3436500, "expectedDiffBits": 184582144}, + {"height": 80, "timestamp": 3480000, "expectedDiffBits": 184614912}, + {"height": 81, "timestamp": 3523500, "expectedDiffBits": 184680448}, + {"height": 82, "timestamp": 3567000, "expectedDiffBits": 184811520}, + {"height": 83, "timestamp": 3610500, "expectedDiffBits": 185073664}, + {"height": 84, "timestamp": 3654000, "expectedDiffBits": 185597952}, + {"height": 85, "timestamp": 3697500, "expectedDiffBits": 186646528}, + {"height": 86, "timestamp": 3741000, "expectedDiffBits": 188743680}, + {"height": 87, "timestamp": 3784500, "expectedDiffBits": 201359360}, + {"height": 88, "timestamp": 3828000, "expectedDiffBits": 201392128}, + {"height": 89, "timestamp": 3871500, "expectedDiffBits": 201457664}, + {"height": 90, "timestamp": 3915000, "expectedDiffBits": 201588736}, + {"height": 91, "timestamp": 3958500, "expectedDiffBits": 201850880}, + {"height": 92, "timestamp": 4002000, "expectedDiffBits": 202375168}, + {"height": 93, "timestamp": 4045500, "expectedDiffBits": 203423744}, + {"height": 94, "timestamp": 4089000, "expectedDiffBits": 205520896}, + {"height": 95, "timestamp": 4132500, "expectedDiffBits": 218136576}, + {"height": 96, "timestamp": 4176000, "expectedDiffBits": 218169344}, + {"height": 97, "timestamp": 4219500, "expectedDiffBits": 218234880}, + {"height": 98, "timestamp": 4263000, "expectedDiffBits": 218365952}, + {"height": 99, "timestamp": 4306500, "expectedDiffBits": 218628096}, + {"height": 100, "timestamp": 4350000, "expectedDiffBits": 219152384}, + {"height": 101, "timestamp": 4393500, "expectedDiffBits": 220200960}, + {"height": 102, "timestamp": 4437000, "expectedDiffBits": 222298112}, + {"height": 103, "timestamp": 4480500, "expectedDiffBits": 234913792}, + {"height": 104, "timestamp": 4524000, "expectedDiffBits": 234946560}, + {"height": 105, "timestamp": 4567500, "expectedDiffBits": 235012096}, + {"height": 106, "timestamp": 4611000, "expectedDiffBits": 235143168}, + {"height": 107, "timestamp": 4654500, "expectedDiffBits": 235405312}, + {"height": 108, "timestamp": 4698000, "expectedDiffBits": 235929600}, + {"height": 109, "timestamp": 4741500, "expectedDiffBits": 236978176}, + {"height": 110, "timestamp": 4785000, "expectedDiffBits": 239075328}, + {"height": 111, "timestamp": 4828500, "expectedDiffBits": 251691008}, + {"height": 112, "timestamp": 4872000, "expectedDiffBits": 251723776}, + {"height": 113, "timestamp": 4915500, "expectedDiffBits": 251789312}, + {"height": 114, "timestamp": 4959000, "expectedDiffBits": 251920384}, + {"height": 115, "timestamp": 5002500, "expectedDiffBits": 252182528}, + {"height": 116, "timestamp": 5046000, "expectedDiffBits": 252706816}, + {"height": 117, "timestamp": 5089500, "expectedDiffBits": 253755392}, + {"height": 118, "timestamp": 5133000, "expectedDiffBits": 255852544}, + {"height": 119, "timestamp": 5176500, "expectedDiffBits": 268468224}, + {"height": 120, "timestamp": 5220000, "expectedDiffBits": 268500992}, + {"height": 121, "timestamp": 5263500, "expectedDiffBits": 268566528}, + {"height": 122, "timestamp": 5307000, "expectedDiffBits": 268697600}, + {"height": 123, "timestamp": 5350500, "expectedDiffBits": 268959744}, + {"height": 124, "timestamp": 5394000, "expectedDiffBits": 269484032}, + {"height": 125, "timestamp": 5437500, "expectedDiffBits": 270532608}, + {"height": 126, "timestamp": 5481000, "expectedDiffBits": 272629760}, + {"height": 127, "timestamp": 5524500, "expectedDiffBits": 285245440}, + {"height": 128, "timestamp": 5568000, "expectedDiffBits": 285278208}, + {"height": 129, "timestamp": 5611500, "expectedDiffBits": 285343744}, + {"height": 130, "timestamp": 5655000, "expectedDiffBits": 285474816}, + {"height": 131, "timestamp": 5698500, "expectedDiffBits": 285736960}, + {"height": 132, "timestamp": 5742000, "expectedDiffBits": 286261248}, + {"height": 133, "timestamp": 5785500, "expectedDiffBits": 287309824}, + {"height": 134, "timestamp": 5829000, "expectedDiffBits": 289406976}, + {"height": 135, "timestamp": 5872500, "expectedDiffBits": 302022656}, + {"height": 136, "timestamp": 5916000, "expectedDiffBits": 302055424}, + {"height": 137, "timestamp": 5959500, "expectedDiffBits": 302120960}, + {"height": 138, "timestamp": 6003000, "expectedDiffBits": 302252032}, + {"height": 139, "timestamp": 6046500, "expectedDiffBits": 302514176}, + {"height": 140, "timestamp": 6090000, "expectedDiffBits": 303038464}, + {"height": 141, "timestamp": 6133500, "expectedDiffBits": 304087040}, + {"height": 142, "timestamp": 6177000, "expectedDiffBits": 306184192}, + {"height": 143, "timestamp": 6220500, "expectedDiffBits": 318799872}, + {"height": 144, "timestamp": 6264000, "expectedDiffBits": 318832640}, + {"height": 145, "timestamp": 6307500, "expectedDiffBits": 318898176}, + {"height": 146, "timestamp": 6351000, "expectedDiffBits": 319029248}, + {"height": 147, "timestamp": 6394500, "expectedDiffBits": 319291392}, + {"height": 148, "timestamp": 6438000, "expectedDiffBits": 319815680}, + {"height": 149, "timestamp": 6481500, "expectedDiffBits": 320864256}, + {"height": 150, "timestamp": 6525000, "expectedDiffBits": 322961408}, + {"height": 151, "timestamp": 6568500, "expectedDiffBits": 335577088}, + {"height": 152, "timestamp": 6612000, "expectedDiffBits": 335609856}, + {"height": 153, "timestamp": 6655500, "expectedDiffBits": 335675392}, + {"height": 154, "timestamp": 6699000, "expectedDiffBits": 335806464}, + {"height": 155, "timestamp": 6742500, "expectedDiffBits": 336068608}, + {"height": 156, "timestamp": 6786000, "expectedDiffBits": 336592896}, + {"height": 157, "timestamp": 6829500, "expectedDiffBits": 337641472}, + {"height": 158, "timestamp": 6873000, "expectedDiffBits": 339738624}, + {"height": 159, "timestamp": 6916500, "expectedDiffBits": 352354304}, + {"height": 160, "timestamp": 6960000, "expectedDiffBits": 352387072}, + {"height": 161, "timestamp": 7003500, "expectedDiffBits": 352452608}, + {"height": 162, "timestamp": 7047000, "expectedDiffBits": 352583680}, + {"height": 163, "timestamp": 7090500, "expectedDiffBits": 352845824}, + {"height": 164, "timestamp": 7134000, "expectedDiffBits": 353370112}, + {"height": 165, "timestamp": 7177500, "expectedDiffBits": 354418688}, + {"height": 166, "timestamp": 7221000, "expectedDiffBits": 356515840}, + {"height": 167, "timestamp": 7264500, "expectedDiffBits": 369131520}, + {"height": 168, "timestamp": 7308000, "expectedDiffBits": 369164288}, + {"height": 169, "timestamp": 7351500, "expectedDiffBits": 369229824}, + {"height": 170, "timestamp": 7395000, "expectedDiffBits": 369360896}, + {"height": 171, "timestamp": 7438500, "expectedDiffBits": 369623040}, + {"height": 172, "timestamp": 7482000, "expectedDiffBits": 370147328}, + {"height": 173, "timestamp": 7525500, "expectedDiffBits": 371195904}, + {"height": 174, "timestamp": 7569000, "expectedDiffBits": 373293056}, + {"height": 175, "timestamp": 7612500, "expectedDiffBits": 385908736}, + {"height": 176, "timestamp": 7656000, "expectedDiffBits": 385941504}, + {"height": 177, "timestamp": 7699500, "expectedDiffBits": 386007040}, + {"height": 178, "timestamp": 7743000, "expectedDiffBits": 386138112}, + {"height": 179, "timestamp": 7786500, "expectedDiffBits": 386400256}, + {"height": 180, "timestamp": 7830000, "expectedDiffBits": 386924544}, + {"height": 181, "timestamp": 7873500, "expectedDiffBits": 387973120}, + {"height": 182, "timestamp": 7917000, "expectedDiffBits": 390070272}, + {"height": 183, "timestamp": 7960500, "expectedDiffBits": 402685952}, + {"height": 184, "timestamp": 8004000, "expectedDiffBits": 402718720}, + {"height": 185, "timestamp": 8047500, "expectedDiffBits": 402784256}, + {"height": 186, "timestamp": 8091000, "expectedDiffBits": 402915328}, + {"height": 187, "timestamp": 8134500, "expectedDiffBits": 403177472}, + {"height": 188, "timestamp": 8178000, "expectedDiffBits": 403701760}, + {"height": 189, "timestamp": 8221500, "expectedDiffBits": 404750336}, + {"height": 190, "timestamp": 8265000, "expectedDiffBits": 406847488}, + {"height": 191, "timestamp": 8308500, "expectedDiffBits": 419463168}, + {"height": 192, "timestamp": 8352000, "expectedDiffBits": 419495936}, + {"height": 193, "timestamp": 8395500, "expectedDiffBits": 419561472}, + {"height": 194, "timestamp": 8439000, "expectedDiffBits": 419692544}, + {"height": 195, "timestamp": 8482500, "expectedDiffBits": 419954688}, + {"height": 196, "timestamp": 8526000, "expectedDiffBits": 420478976}, + {"height": 197, "timestamp": 8569500, "expectedDiffBits": 421527552}, + {"height": 198, "timestamp": 8613000, "expectedDiffBits": 423624704}, + {"height": 199, "timestamp": 8656500, "expectedDiffBits": 436240384}, + {"height": 200, "timestamp": 8700000, "expectedDiffBits": 436273152}, + {"height": 201, "timestamp": 8743500, "expectedDiffBits": 436338688}, + {"height": 202, "timestamp": 8787000, "expectedDiffBits": 436469760}, + {"height": 203, "timestamp": 8830500, "expectedDiffBits": 436731904}, + {"height": 204, "timestamp": 8874000, "expectedDiffBits": 437256192}, + {"height": 205, "timestamp": 8917500, "expectedDiffBits": 438304768}, + {"height": 206, "timestamp": 8961000, "expectedDiffBits": 440401920}, + {"height": 207, "timestamp": 9004500, "expectedDiffBits": 453017600}, + {"height": 208, "timestamp": 9048000, "expectedDiffBits": 453050368}, + {"height": 209, "timestamp": 9091500, "expectedDiffBits": 453115904}, + {"height": 210, "timestamp": 9135000, "expectedDiffBits": 453246976}, + {"height": 211, "timestamp": 9178500, "expectedDiffBits": 453509120}, + {"height": 212, "timestamp": 9222000, "expectedDiffBits": 454033408}, + {"height": 213, "timestamp": 9265500, "expectedDiffBits": 455081984}, + {"height": 214, "timestamp": 9309000, "expectedDiffBits": 457179136}, + {"height": 215, "timestamp": 9352500, "expectedDiffBits": 469794816}, + {"height": 216, "timestamp": 9396000, "expectedDiffBits": 469827584}, + {"height": 217, "timestamp": 9439500, "expectedDiffBits": 469893120}, + {"height": 218, "timestamp": 9483000, "expectedDiffBits": 470024192}, + {"height": 219, "timestamp": 9526500, "expectedDiffBits": 470286336}, + {"height": 220, "timestamp": 9570000, "expectedDiffBits": 470810624}, + {"height": 221, "timestamp": 9613500, "expectedDiffBits": 471859200}, + {"height": 222, "timestamp": 9657000, "expectedDiffBits": 473956352}, + {"height": 223, "timestamp": 9700500, "expectedDiffBits": 486572032}, + {"height": 224, "timestamp": 9744000, "expectedDiffBits": 486604799}, + {"height": 225, "timestamp": 9787500, "expectedDiffBits": 486604799} + ] + }, + { + "description": "mainnet: 0s height-based half life blocks from easiest to hardest difficulty", + "params": "mainnet", + "startDiffBits": 486604799, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 144, "timestamp": 0, "expectedDiffBits": 478150528}, + {"height": 288, "timestamp": 0, "expectedDiffBits": 473956288}, + {"height": 432, "timestamp": 0, "expectedDiffBits": 471859168}, + {"height": 576, "timestamp": 0, "expectedDiffBits": 470810608}, + {"height": 720, "timestamp": 0, "expectedDiffBits": 470286328}, + {"height": 864, "timestamp": 0, "expectedDiffBits": 470024188}, + {"height": 1008, "timestamp": 0, "expectedDiffBits": 469893118}, + {"height": 1152, "timestamp": 0, "expectedDiffBits": 469827583}, + {"height": 1296, "timestamp": 0, "expectedDiffBits": 461373312}, + {"height": 1440, "timestamp": 0, "expectedDiffBits": 457179072}, + {"height": 1584, "timestamp": 0, "expectedDiffBits": 455081952}, + {"height": 1728, "timestamp": 0, "expectedDiffBits": 454033392}, + {"height": 1872, "timestamp": 0, "expectedDiffBits": 453509112}, + {"height": 2016, "timestamp": 0, "expectedDiffBits": 453246972}, + {"height": 2160, "timestamp": 0, "expectedDiffBits": 453115902}, + {"height": 2304, "timestamp": 0, "expectedDiffBits": 453050367}, + {"height": 2448, "timestamp": 0, "expectedDiffBits": 444596096}, + {"height": 2592, "timestamp": 0, "expectedDiffBits": 440401856}, + {"height": 2736, "timestamp": 0, "expectedDiffBits": 438304736}, + {"height": 2880, "timestamp": 0, "expectedDiffBits": 437256176}, + {"height": 3024, "timestamp": 0, "expectedDiffBits": 436731896}, + {"height": 3168, "timestamp": 0, "expectedDiffBits": 436469756}, + {"height": 3312, "timestamp": 0, "expectedDiffBits": 436338686}, + {"height": 3456, "timestamp": 0, "expectedDiffBits": 436273151}, + {"height": 3600, "timestamp": 0, "expectedDiffBits": 427818880}, + {"height": 3744, "timestamp": 0, "expectedDiffBits": 423624640}, + {"height": 3888, "timestamp": 0, "expectedDiffBits": 421527520}, + {"height": 4032, "timestamp": 0, "expectedDiffBits": 420478960}, + {"height": 4176, "timestamp": 0, "expectedDiffBits": 419954680}, + {"height": 4320, "timestamp": 0, "expectedDiffBits": 419692540}, + {"height": 4464, "timestamp": 0, "expectedDiffBits": 419561470}, + {"height": 4608, "timestamp": 0, "expectedDiffBits": 419495935}, + {"height": 4752, "timestamp": 0, "expectedDiffBits": 411041664}, + {"height": 4896, "timestamp": 0, "expectedDiffBits": 406847424}, + {"height": 5040, "timestamp": 0, "expectedDiffBits": 404750304}, + {"height": 5184, "timestamp": 0, "expectedDiffBits": 403701744}, + {"height": 5328, "timestamp": 0, "expectedDiffBits": 403177464}, + {"height": 5472, "timestamp": 0, "expectedDiffBits": 402915324}, + {"height": 5616, "timestamp": 0, "expectedDiffBits": 402784254}, + {"height": 5760, "timestamp": 0, "expectedDiffBits": 402718719}, + {"height": 5904, "timestamp": 0, "expectedDiffBits": 394264448}, + {"height": 6048, "timestamp": 0, "expectedDiffBits": 390070208}, + {"height": 6192, "timestamp": 0, "expectedDiffBits": 387973088}, + {"height": 6336, "timestamp": 0, "expectedDiffBits": 386924528}, + {"height": 6480, "timestamp": 0, "expectedDiffBits": 386400248}, + {"height": 6624, "timestamp": 0, "expectedDiffBits": 386138108}, + {"height": 6768, "timestamp": 0, "expectedDiffBits": 386007038}, + {"height": 6912, "timestamp": 0, "expectedDiffBits": 385941503}, + {"height": 7056, "timestamp": 0, "expectedDiffBits": 377487232}, + {"height": 7200, "timestamp": 0, "expectedDiffBits": 373292992}, + {"height": 7344, "timestamp": 0, "expectedDiffBits": 371195872}, + {"height": 7488, "timestamp": 0, "expectedDiffBits": 370147312}, + {"height": 7632, "timestamp": 0, "expectedDiffBits": 369623032}, + {"height": 7776, "timestamp": 0, "expectedDiffBits": 369360892}, + {"height": 7920, "timestamp": 0, "expectedDiffBits": 369229822}, + {"height": 8064, "timestamp": 0, "expectedDiffBits": 369164287}, + {"height": 8208, "timestamp": 0, "expectedDiffBits": 360710016}, + {"height": 8352, "timestamp": 0, "expectedDiffBits": 356515776}, + {"height": 8496, "timestamp": 0, "expectedDiffBits": 354418656}, + {"height": 8640, "timestamp": 0, "expectedDiffBits": 353370096}, + {"height": 8784, "timestamp": 0, "expectedDiffBits": 352845816}, + {"height": 8928, "timestamp": 0, "expectedDiffBits": 352583676}, + {"height": 9072, "timestamp": 0, "expectedDiffBits": 352452606}, + {"height": 9216, "timestamp": 0, "expectedDiffBits": 352387071}, + {"height": 9360, "timestamp": 0, "expectedDiffBits": 343932800}, + {"height": 9504, "timestamp": 0, "expectedDiffBits": 339738560}, + {"height": 9648, "timestamp": 0, "expectedDiffBits": 337641440}, + {"height": 9792, "timestamp": 0, "expectedDiffBits": 336592880}, + {"height": 9936, "timestamp": 0, "expectedDiffBits": 336068600}, + {"height": 10080, "timestamp": 0, "expectedDiffBits": 335806460}, + {"height": 10224, "timestamp": 0, "expectedDiffBits": 335675390}, + {"height": 10368, "timestamp": 0, "expectedDiffBits": 335609855}, + {"height": 10512, "timestamp": 0, "expectedDiffBits": 327155584}, + {"height": 10656, "timestamp": 0, "expectedDiffBits": 322961344}, + {"height": 10800, "timestamp": 0, "expectedDiffBits": 320864224}, + {"height": 10944, "timestamp": 0, "expectedDiffBits": 319815664}, + {"height": 11088, "timestamp": 0, "expectedDiffBits": 319291384}, + {"height": 11232, "timestamp": 0, "expectedDiffBits": 319029244}, + {"height": 11376, "timestamp": 0, "expectedDiffBits": 318898174}, + {"height": 11520, "timestamp": 0, "expectedDiffBits": 318832639}, + {"height": 11664, "timestamp": 0, "expectedDiffBits": 310378368}, + {"height": 11808, "timestamp": 0, "expectedDiffBits": 306184128}, + {"height": 11952, "timestamp": 0, "expectedDiffBits": 304087008}, + {"height": 12096, "timestamp": 0, "expectedDiffBits": 303038448}, + {"height": 12240, "timestamp": 0, "expectedDiffBits": 302514168}, + {"height": 12384, "timestamp": 0, "expectedDiffBits": 302252028}, + {"height": 12528, "timestamp": 0, "expectedDiffBits": 302120958}, + {"height": 12672, "timestamp": 0, "expectedDiffBits": 302055423}, + {"height": 12816, "timestamp": 0, "expectedDiffBits": 293601152}, + {"height": 12960, "timestamp": 0, "expectedDiffBits": 289406912}, + {"height": 13104, "timestamp": 0, "expectedDiffBits": 287309792}, + {"height": 13248, "timestamp": 0, "expectedDiffBits": 286261232}, + {"height": 13392, "timestamp": 0, "expectedDiffBits": 285736952}, + {"height": 13536, "timestamp": 0, "expectedDiffBits": 285474812}, + {"height": 13680, "timestamp": 0, "expectedDiffBits": 285343742}, + {"height": 13824, "timestamp": 0, "expectedDiffBits": 285278207}, + {"height": 13968, "timestamp": 0, "expectedDiffBits": 276823936}, + {"height": 14112, "timestamp": 0, "expectedDiffBits": 272629696}, + {"height": 14256, "timestamp": 0, "expectedDiffBits": 270532576}, + {"height": 14400, "timestamp": 0, "expectedDiffBits": 269484016}, + {"height": 14544, "timestamp": 0, "expectedDiffBits": 268959736}, + {"height": 14688, "timestamp": 0, "expectedDiffBits": 268697596}, + {"height": 14832, "timestamp": 0, "expectedDiffBits": 268566526}, + {"height": 14976, "timestamp": 0, "expectedDiffBits": 268500991}, + {"height": 15120, "timestamp": 0, "expectedDiffBits": 260046720}, + {"height": 15264, "timestamp": 0, "expectedDiffBits": 255852480}, + {"height": 15408, "timestamp": 0, "expectedDiffBits": 253755360}, + {"height": 15552, "timestamp": 0, "expectedDiffBits": 252706800}, + {"height": 15696, "timestamp": 0, "expectedDiffBits": 252182520}, + {"height": 15840, "timestamp": 0, "expectedDiffBits": 251920380}, + {"height": 15984, "timestamp": 0, "expectedDiffBits": 251789310}, + {"height": 16128, "timestamp": 0, "expectedDiffBits": 251723775}, + {"height": 16272, "timestamp": 0, "expectedDiffBits": 243269504}, + {"height": 16416, "timestamp": 0, "expectedDiffBits": 239075264}, + {"height": 16560, "timestamp": 0, "expectedDiffBits": 236978144}, + {"height": 16704, "timestamp": 0, "expectedDiffBits": 235929584}, + {"height": 16848, "timestamp": 0, "expectedDiffBits": 235405304}, + {"height": 16992, "timestamp": 0, "expectedDiffBits": 235143164}, + {"height": 17136, "timestamp": 0, "expectedDiffBits": 235012094}, + {"height": 17280, "timestamp": 0, "expectedDiffBits": 234946559}, + {"height": 17424, "timestamp": 0, "expectedDiffBits": 226492288}, + {"height": 17568, "timestamp": 0, "expectedDiffBits": 222298048}, + {"height": 17712, "timestamp": 0, "expectedDiffBits": 220200928}, + {"height": 17856, "timestamp": 0, "expectedDiffBits": 219152368}, + {"height": 18000, "timestamp": 0, "expectedDiffBits": 218628088}, + {"height": 18144, "timestamp": 0, "expectedDiffBits": 218365948}, + {"height": 18288, "timestamp": 0, "expectedDiffBits": 218234878}, + {"height": 18432, "timestamp": 0, "expectedDiffBits": 218169343}, + {"height": 18576, "timestamp": 0, "expectedDiffBits": 209715072}, + {"height": 18720, "timestamp": 0, "expectedDiffBits": 205520832}, + {"height": 18864, "timestamp": 0, "expectedDiffBits": 203423712}, + {"height": 19008, "timestamp": 0, "expectedDiffBits": 202375152}, + {"height": 19152, "timestamp": 0, "expectedDiffBits": 201850872}, + {"height": 19296, "timestamp": 0, "expectedDiffBits": 201588732}, + {"height": 19440, "timestamp": 0, "expectedDiffBits": 201457662}, + {"height": 19584, "timestamp": 0, "expectedDiffBits": 201392127}, + {"height": 19728, "timestamp": 0, "expectedDiffBits": 192937856}, + {"height": 19872, "timestamp": 0, "expectedDiffBits": 188743616}, + {"height": 20016, "timestamp": 0, "expectedDiffBits": 186646496}, + {"height": 20160, "timestamp": 0, "expectedDiffBits": 185597936}, + {"height": 20304, "timestamp": 0, "expectedDiffBits": 185073656}, + {"height": 20448, "timestamp": 0, "expectedDiffBits": 184811516}, + {"height": 20592, "timestamp": 0, "expectedDiffBits": 184680446}, + {"height": 20736, "timestamp": 0, "expectedDiffBits": 184614911}, + {"height": 20880, "timestamp": 0, "expectedDiffBits": 176160640}, + {"height": 21024, "timestamp": 0, "expectedDiffBits": 171966400}, + {"height": 21168, "timestamp": 0, "expectedDiffBits": 169869280}, + {"height": 21312, "timestamp": 0, "expectedDiffBits": 168820720}, + {"height": 21456, "timestamp": 0, "expectedDiffBits": 168296440}, + {"height": 21600, "timestamp": 0, "expectedDiffBits": 168034300}, + {"height": 21744, "timestamp": 0, "expectedDiffBits": 167903230}, + {"height": 21888, "timestamp": 0, "expectedDiffBits": 167837695}, + {"height": 22032, "timestamp": 0, "expectedDiffBits": 159383424}, + {"height": 22176, "timestamp": 0, "expectedDiffBits": 155189184}, + {"height": 22320, "timestamp": 0, "expectedDiffBits": 153092064}, + {"height": 22464, "timestamp": 0, "expectedDiffBits": 152043504}, + {"height": 22608, "timestamp": 0, "expectedDiffBits": 151519224}, + {"height": 22752, "timestamp": 0, "expectedDiffBits": 151257084}, + {"height": 22896, "timestamp": 0, "expectedDiffBits": 151126014}, + {"height": 23040, "timestamp": 0, "expectedDiffBits": 151060479}, + {"height": 23184, "timestamp": 0, "expectedDiffBits": 142606208}, + {"height": 23328, "timestamp": 0, "expectedDiffBits": 138411968}, + {"height": 23472, "timestamp": 0, "expectedDiffBits": 136314848}, + {"height": 23616, "timestamp": 0, "expectedDiffBits": 135266288}, + {"height": 23760, "timestamp": 0, "expectedDiffBits": 134742008}, + {"height": 23904, "timestamp": 0, "expectedDiffBits": 134479868}, + {"height": 24048, "timestamp": 0, "expectedDiffBits": 134348798}, + {"height": 24192, "timestamp": 0, "expectedDiffBits": 134283263}, + {"height": 24336, "timestamp": 0, "expectedDiffBits": 125828992}, + {"height": 24480, "timestamp": 0, "expectedDiffBits": 121634752}, + {"height": 24624, "timestamp": 0, "expectedDiffBits": 119537632}, + {"height": 24768, "timestamp": 0, "expectedDiffBits": 118489072}, + {"height": 24912, "timestamp": 0, "expectedDiffBits": 117964792}, + {"height": 25056, "timestamp": 0, "expectedDiffBits": 117702652}, + {"height": 25200, "timestamp": 0, "expectedDiffBits": 117571582}, + {"height": 25344, "timestamp": 0, "expectedDiffBits": 117506047}, + {"height": 25488, "timestamp": 0, "expectedDiffBits": 109051776}, + {"height": 25632, "timestamp": 0, "expectedDiffBits": 104857536}, + {"height": 25776, "timestamp": 0, "expectedDiffBits": 102760416}, + {"height": 25920, "timestamp": 0, "expectedDiffBits": 101711856}, + {"height": 26064, "timestamp": 0, "expectedDiffBits": 101187576}, + {"height": 26208, "timestamp": 0, "expectedDiffBits": 100925436}, + {"height": 26352, "timestamp": 0, "expectedDiffBits": 100794366}, + {"height": 26496, "timestamp": 0, "expectedDiffBits": 100728831}, + {"height": 26640, "timestamp": 0, "expectedDiffBits": 92274560}, + {"height": 26784, "timestamp": 0, "expectedDiffBits": 88080320}, + {"height": 26928, "timestamp": 0, "expectedDiffBits": 85983200}, + {"height": 27072, "timestamp": 0, "expectedDiffBits": 84934640}, + {"height": 27216, "timestamp": 0, "expectedDiffBits": 84410360}, + {"height": 27360, "timestamp": 0, "expectedDiffBits": 84148220}, + {"height": 27504, "timestamp": 0, "expectedDiffBits": 84017150}, + {"height": 27648, "timestamp": 0, "expectedDiffBits": 83951615}, + {"height": 27792, "timestamp": 0, "expectedDiffBits": 75497344}, + {"height": 27936, "timestamp": 0, "expectedDiffBits": 71303104}, + {"height": 28080, "timestamp": 0, "expectedDiffBits": 69205984}, + {"height": 28224, "timestamp": 0, "expectedDiffBits": 68157424}, + {"height": 28368, "timestamp": 0, "expectedDiffBits": 67633144}, + {"height": 28512, "timestamp": 0, "expectedDiffBits": 67371004}, + {"height": 28656, "timestamp": 0, "expectedDiffBits": 67239934}, + {"height": 28800, "timestamp": 0, "expectedDiffBits": 67174399}, + {"height": 28944, "timestamp": 0, "expectedDiffBits": 58720128}, + {"height": 29088, "timestamp": 0, "expectedDiffBits": 54525888}, + {"height": 29232, "timestamp": 0, "expectedDiffBits": 52428768}, + {"height": 29376, "timestamp": 0, "expectedDiffBits": 51380208}, + {"height": 29520, "timestamp": 0, "expectedDiffBits": 50855928}, + {"height": 29664, "timestamp": 0, "expectedDiffBits": 50593788}, + {"height": 29808, "timestamp": 0, "expectedDiffBits": 50462718}, + {"height": 29952, "timestamp": 0, "expectedDiffBits": 50397183}, + {"height": 30096, "timestamp": 0, "expectedDiffBits": 41942784}, + {"height": 30240, "timestamp": 0, "expectedDiffBits": 37748480}, + {"height": 30384, "timestamp": 0, "expectedDiffBits": 35651328}, + {"height": 30528, "timestamp": 0, "expectedDiffBits": 34602752}, + {"height": 30672, "timestamp": 0, "expectedDiffBits": 34078464}, + {"height": 30816, "timestamp": 0, "expectedDiffBits": 33816320}, + {"height": 30960, "timestamp": 0, "expectedDiffBits": 33685248}, + {"height": 31104, "timestamp": 0, "expectedDiffBits": 33619712}, + {"height": 31248, "timestamp": 0, "expectedDiffBits": 25100288}, + {"height": 31392, "timestamp": 0, "expectedDiffBits": 20905984}, + {"height": 31536, "timestamp": 0, "expectedDiffBits": 18808832}, + {"height": 31680, "timestamp": 0, "expectedDiffBits": 17760256}, + {"height": 31824, "timestamp": 0, "expectedDiffBits": 17235968}, + {"height": 31968, "timestamp": 0, "expectedDiffBits": 16973824}, + {"height": 32112, "timestamp": 0, "expectedDiffBits": 16842752}, + {"height": 32256, "timestamp": 0, "expectedDiffBits": 16842752} + ] + }, + { + "description": "mainnet: random solve times per Poisson distribution for a stable hashrate", + "params": "mainnet", + "startDiffBits": 453027238, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 1, "timestamp": 591, "expectedDiffBits": 453027436}, + {"height": 2, "timestamp": 631, "expectedDiffBits": 453027259}, + {"height": 3, "timestamp": 903, "expectedDiffBits": 453027239}, + {"height": 4, "timestamp": 1048, "expectedDiffBits": 453027135}, + {"height": 5, "timestamp": 1542, "expectedDiffBits": 453027266}, + {"height": 6, "timestamp": 2080, "expectedDiffBits": 453027429}, + {"height": 7, "timestamp": 2476, "expectedDiffBits": 453027495}, + {"height": 8, "timestamp": 2692, "expectedDiffBits": 453027437}, + {"height": 9, "timestamp": 3141, "expectedDiffBits": 453027540}, + {"height": 10, "timestamp": 3274, "expectedDiffBits": 453027425}, + {"height": 11, "timestamp": 3684, "expectedDiffBits": 453027500}, + {"height": 12, "timestamp": 3723, "expectedDiffBits": 453027321}, + {"height": 13, "timestamp": 3988, "expectedDiffBits": 453027298}, + {"height": 14, "timestamp": 4375, "expectedDiffBits": 453027357}, + {"height": 15, "timestamp": 4824, "expectedDiffBits": 453027459}, + {"height": 16, "timestamp": 5295, "expectedDiffBits": 453027577}, + {"height": 17, "timestamp": 5364, "expectedDiffBits": 453027418}, + {"height": 18, "timestamp": 6212, "expectedDiffBits": 453027795}, + {"height": 19, "timestamp": 6718, "expectedDiffBits": 453027938}, + {"height": 20, "timestamp": 7094, "expectedDiffBits": 453027990}, + {"height": 21, "timestamp": 7636, "expectedDiffBits": 453028158}, + {"height": 22, "timestamp": 7826, "expectedDiffBits": 453028082}, + {"height": 23, "timestamp": 8680, "expectedDiffBits": 453028469}, + {"height": 24, "timestamp": 8748, "expectedDiffBits": 453028306}, + {"height": 25, "timestamp": 9870, "expectedDiffBits": 453028885}, + {"height": 26, "timestamp": 10353, "expectedDiffBits": 453029014}, + {"height": 27, "timestamp": 10862, "expectedDiffBits": 453029163}, + {"height": 28, "timestamp": 10943, "expectedDiffBits": 453029007}, + {"height": 29, "timestamp": 11567, "expectedDiffBits": 453029238}, + {"height": 30, "timestamp": 11598, "expectedDiffBits": 453029047}, + {"height": 31, "timestamp": 11658, "expectedDiffBits": 453028877}, + {"height": 32, "timestamp": 11799, "expectedDiffBits": 453028764}, + {"height": 33, "timestamp": 11931, "expectedDiffBits": 453028646}, + {"height": 34, "timestamp": 12303, "expectedDiffBits": 453028696}, + {"height": 35, "timestamp": 12713, "expectedDiffBits": 453028774}, + {"height": 36, "timestamp": 13311, "expectedDiffBits": 453028985}, + {"height": 37, "timestamp": 13426, "expectedDiffBits": 453028854}, + {"height": 38, "timestamp": 14550, "expectedDiffBits": 453029440}, + {"height": 39, "timestamp": 15010, "expectedDiffBits": 453029555}, + {"height": 40, "timestamp": 15580, "expectedDiffBits": 453029749}, + {"height": 41, "timestamp": 16801, "expectedDiffBits": 453030418}, + {"height": 42, "timestamp": 16901, "expectedDiffBits": 453030272}, + {"height": 43, "timestamp": 17487, "expectedDiffBits": 453030481}, + {"height": 44, "timestamp": 17503, "expectedDiffBits": 453030274}, + {"height": 45, "timestamp": 18174, "expectedDiffBits": 453030545}, + {"height": 46, "timestamp": 18304, "expectedDiffBits": 453030420}, + {"height": 47, "timestamp": 18547, "expectedDiffBits": 453030379}, + {"height": 48, "timestamp": 18779, "expectedDiffBits": 453030329}, + {"height": 49, "timestamp": 18782, "expectedDiffBits": 453030112}, + {"height": 50, "timestamp": 18802, "expectedDiffBits": 453029909}, + {"height": 51, "timestamp": 18805, "expectedDiffBits": 453029695}, + {"height": 52, "timestamp": 18991, "expectedDiffBits": 453029614}, + {"height": 53, "timestamp": 19023, "expectedDiffBits": 453029421}, + {"height": 54, "timestamp": 19107, "expectedDiffBits": 453029267}, + {"height": 55, "timestamp": 19281, "expectedDiffBits": 453029176}, + {"height": 56, "timestamp": 19300, "expectedDiffBits": 453028977}, + {"height": 57, "timestamp": 19384, "expectedDiffBits": 453028823}, + {"height": 58, "timestamp": 19688, "expectedDiffBits": 453028827}, + {"height": 59, "timestamp": 19988, "expectedDiffBits": 453028827}, + {"height": 60, "timestamp": 20153, "expectedDiffBits": 453028732}, + {"height": 61, "timestamp": 20175, "expectedDiffBits": 453028536}, + {"height": 62, "timestamp": 20704, "expectedDiffBits": 453028697}, + {"height": 63, "timestamp": 20710, "expectedDiffBits": 453028490}, + {"height": 64, "timestamp": 21585, "expectedDiffBits": 453028895}, + {"height": 65, "timestamp": 21767, "expectedDiffBits": 453028812}, + {"height": 66, "timestamp": 21785, "expectedDiffBits": 453028613}, + {"height": 67, "timestamp": 21988, "expectedDiffBits": 453028545}, + {"height": 68, "timestamp": 22606, "expectedDiffBits": 453028768}, + {"height": 69, "timestamp": 22842, "expectedDiffBits": 453028724}, + {"height": 70, "timestamp": 22950, "expectedDiffBits": 453028589}, + {"height": 71, "timestamp": 22990, "expectedDiffBits": 453028406}, + {"height": 72, "timestamp": 23140, "expectedDiffBits": 453028301}, + {"height": 73, "timestamp": 23890, "expectedDiffBits": 453028616}, + {"height": 74, "timestamp": 24352, "expectedDiffBits": 453028731}, + {"height": 75, "timestamp": 24375, "expectedDiffBits": 453028536}, + {"height": 76, "timestamp": 24780, "expectedDiffBits": 453028609}, + {"height": 77, "timestamp": 25031, "expectedDiffBits": 453028575}, + {"height": 78, "timestamp": 25206, "expectedDiffBits": 453028487}, + {"height": 79, "timestamp": 25527, "expectedDiffBits": 453028502}, + {"height": 80, "timestamp": 25623, "expectedDiffBits": 453028360}, + {"height": 81, "timestamp": 26356, "expectedDiffBits": 453028663}, + {"height": 82, "timestamp": 27160, "expectedDiffBits": 453029020}, + {"height": 83, "timestamp": 27626, "expectedDiffBits": 453029137}, + {"height": 84, "timestamp": 27750, "expectedDiffBits": 453029012}, + {"height": 85, "timestamp": 27995, "expectedDiffBits": 453028974}, + {"height": 86, "timestamp": 28231, "expectedDiffBits": 453028928}, + {"height": 87, "timestamp": 28278, "expectedDiffBits": 453028749}, + {"height": 88, "timestamp": 28503, "expectedDiffBits": 453028696}, + {"height": 89, "timestamp": 29143, "expectedDiffBits": 453028937}, + {"height": 90, "timestamp": 29267, "expectedDiffBits": 453028812}, + {"height": 91, "timestamp": 29469, "expectedDiffBits": 453028743}, + {"height": 92, "timestamp": 30126, "expectedDiffBits": 453028996}, + {"height": 93, "timestamp": 30196, "expectedDiffBits": 453028833}, + {"height": 94, "timestamp": 30299, "expectedDiffBits": 453028693}, + {"height": 95, "timestamp": 30476, "expectedDiffBits": 453028607}, + {"height": 96, "timestamp": 30525, "expectedDiffBits": 453028430}, + {"height": 97, "timestamp": 30624, "expectedDiffBits": 453028290}, + {"height": 98, "timestamp": 31117, "expectedDiffBits": 453028425}, + {"height": 99, "timestamp": 31160, "expectedDiffBits": 453028245}, + {"height": 100, "timestamp": 31301, "expectedDiffBits": 453028134}, + {"height": 101, "timestamp": 31429, "expectedDiffBits": 453028015}, + {"height": 102, "timestamp": 31702, "expectedDiffBits": 453027996}, + {"height": 103, "timestamp": 33048, "expectedDiffBits": 453028728}, + {"height": 104, "timestamp": 33221, "expectedDiffBits": 453028638}, + {"height": 105, "timestamp": 33566, "expectedDiffBits": 453028670}, + {"height": 106, "timestamp": 33998, "expectedDiffBits": 453028763}, + {"height": 107, "timestamp": 34214, "expectedDiffBits": 453028704}, + {"height": 108, "timestamp": 34218, "expectedDiffBits": 453028495}, + {"height": 109, "timestamp": 34267, "expectedDiffBits": 453028320}, + {"height": 110, "timestamp": 34375, "expectedDiffBits": 453028185}, + {"height": 111, "timestamp": 34793, "expectedDiffBits": 453028268}, + {"height": 112, "timestamp": 34968, "expectedDiffBits": 453028181}, + {"height": 113, "timestamp": 35332, "expectedDiffBits": 453028226}, + {"height": 114, "timestamp": 35665, "expectedDiffBits": 453028249}, + {"height": 115, "timestamp": 36553, "expectedDiffBits": 453028661}, + {"height": 116, "timestamp": 36573, "expectedDiffBits": 453028464}, + {"height": 117, "timestamp": 36665, "expectedDiffBits": 453028319}, + {"height": 118, "timestamp": 36836, "expectedDiffBits": 453028228}, + {"height": 119, "timestamp": 37222, "expectedDiffBits": 453028288}, + {"height": 120, "timestamp": 37455, "expectedDiffBits": 453028242}, + {"height": 121, "timestamp": 38318, "expectedDiffBits": 453028636}, + {"height": 122, "timestamp": 38673, "expectedDiffBits": 453028675}, + {"height": 123, "timestamp": 39059, "expectedDiffBits": 453028735}, + {"height": 124, "timestamp": 39370, "expectedDiffBits": 453028743}, + {"height": 125, "timestamp": 40220, "expectedDiffBits": 453029133}, + {"height": 126, "timestamp": 40516, "expectedDiffBits": 453029130}, + {"height": 127, "timestamp": 41458, "expectedDiffBits": 453029590}, + {"height": 128, "timestamp": 41533, "expectedDiffBits": 453029428}, + {"height": 129, "timestamp": 41605, "expectedDiffBits": 453029265}, + {"height": 130, "timestamp": 41727, "expectedDiffBits": 453029138}, + {"height": 131, "timestamp": 41917, "expectedDiffBits": 453029060}, + {"height": 132, "timestamp": 42260, "expectedDiffBits": 453029091}, + {"height": 133, "timestamp": 42872, "expectedDiffBits": 453029313}, + {"height": 134, "timestamp": 43071, "expectedDiffBits": 453029241}, + {"height": 135, "timestamp": 43119, "expectedDiffBits": 453029062}, + {"height": 136, "timestamp": 43611, "expectedDiffBits": 453029198}, + {"height": 137, "timestamp": 44666, "expectedDiffBits": 453029739}, + {"height": 138, "timestamp": 44834, "expectedDiffBits": 453029644}, + {"height": 139, "timestamp": 45193, "expectedDiffBits": 453029687}, + {"height": 140, "timestamp": 45418, "expectedDiffBits": 453029633}, + {"height": 141, "timestamp": 45506, "expectedDiffBits": 453029480}, + {"height": 142, "timestamp": 45760, "expectedDiffBits": 453029447}, + {"height": 143, "timestamp": 45932, "expectedDiffBits": 453029355}, + {"height": 144, "timestamp": 46014, "expectedDiffBits": 453029200}, + {"height": 145, "timestamp": 46119, "expectedDiffBits": 453029062}, + {"height": 146, "timestamp": 46179, "expectedDiffBits": 453028891}, + {"height": 147, "timestamp": 46243, "expectedDiffBits": 453028724}, + {"height": 148, "timestamp": 46307, "expectedDiffBits": 453028558}, + {"height": 149, "timestamp": 46587, "expectedDiffBits": 453028544}, + {"height": 150, "timestamp": 46637, "expectedDiffBits": 453028369}, + {"height": 151, "timestamp": 46756, "expectedDiffBits": 453028242}, + {"height": 152, "timestamp": 46817, "expectedDiffBits": 453028076}, + {"height": 153, "timestamp": 47161, "expectedDiffBits": 453028107}, + {"height": 154, "timestamp": 47305, "expectedDiffBits": 453027998}, + {"height": 155, "timestamp": 47358, "expectedDiffBits": 453027827}, + {"height": 156, "timestamp": 47639, "expectedDiffBits": 453027813}, + {"height": 157, "timestamp": 47937, "expectedDiffBits": 453027812}, + {"height": 158, "timestamp": 48015, "expectedDiffBits": 453027659}, + {"height": 159, "timestamp": 48017, "expectedDiffBits": 453027454}, + {"height": 160, "timestamp": 48241, "expectedDiffBits": 453027402}, + {"height": 161, "timestamp": 48709, "expectedDiffBits": 453027518}, + {"height": 162, "timestamp": 48769, "expectedDiffBits": 453027353}, + {"height": 163, "timestamp": 49032, "expectedDiffBits": 453027327}, + {"height": 164, "timestamp": 49546, "expectedDiffBits": 453027474}, + {"height": 165, "timestamp": 49665, "expectedDiffBits": 453027350}, + {"height": 166, "timestamp": 50095, "expectedDiffBits": 453027439}, + {"height": 167, "timestamp": 50553, "expectedDiffBits": 453027547}, + {"height": 168, "timestamp": 51101, "expectedDiffBits": 453027718}, + {"height": 169, "timestamp": 51449, "expectedDiffBits": 453027752}, + {"height": 170, "timestamp": 51846, "expectedDiffBits": 453027819}, + {"height": 171, "timestamp": 52235, "expectedDiffBits": 453027880}, + {"height": 172, "timestamp": 52813, "expectedDiffBits": 453028074}, + {"height": 173, "timestamp": 53303, "expectedDiffBits": 453028206}, + {"height": 174, "timestamp": 53878, "expectedDiffBits": 453028398}, + {"height": 175, "timestamp": 53997, "expectedDiffBits": 453028272}, + {"height": 176, "timestamp": 54120, "expectedDiffBits": 453028147}, + {"height": 177, "timestamp": 54154, "expectedDiffBits": 453027962}, + {"height": 178, "timestamp": 54227, "expectedDiffBits": 453027806}, + {"height": 179, "timestamp": 54630, "expectedDiffBits": 453027877}, + {"height": 180, "timestamp": 55295, "expectedDiffBits": 453028130}, + {"height": 181, "timestamp": 55625, "expectedDiffBits": 453028151}, + {"height": 182, "timestamp": 55834, "expectedDiffBits": 453028088}, + {"height": 183, "timestamp": 55836, "expectedDiffBits": 453027881}, + {"height": 184, "timestamp": 56646, "expectedDiffBits": 453028235}, + {"height": 185, "timestamp": 56675, "expectedDiffBits": 453028046}, + {"height": 186, "timestamp": 56704, "expectedDiffBits": 453027859}, + {"height": 187, "timestamp": 56916, "expectedDiffBits": 453027798}, + {"height": 188, "timestamp": 57009, "expectedDiffBits": 453027655}, + {"height": 189, "timestamp": 57679, "expectedDiffBits": 453027910}, + {"height": 190, "timestamp": 58049, "expectedDiffBits": 453027959}, + {"height": 191, "timestamp": 59054, "expectedDiffBits": 453028451}, + {"height": 192, "timestamp": 59492, "expectedDiffBits": 453028548}, + {"height": 193, "timestamp": 59503, "expectedDiffBits": 453028345}, + {"height": 194, "timestamp": 59569, "expectedDiffBits": 453028182}, + {"height": 195, "timestamp": 59723, "expectedDiffBits": 453028080}, + {"height": 196, "timestamp": 59804, "expectedDiffBits": 453027928}, + {"height": 197, "timestamp": 59808, "expectedDiffBits": 453027723}, + {"height": 198, "timestamp": 61461, "expectedDiffBits": 453028666}, + {"height": 199, "timestamp": 61709, "expectedDiffBits": 453028630}, + {"height": 200, "timestamp": 61761, "expectedDiffBits": 453028456}, + {"height": 201, "timestamp": 62382, "expectedDiffBits": 453028681}, + {"height": 202, "timestamp": 63043, "expectedDiffBits": 453028937}, + {"height": 203, "timestamp": 63273, "expectedDiffBits": 453028887}, + {"height": 204, "timestamp": 63484, "expectedDiffBits": 453028823}, + {"height": 205, "timestamp": 63933, "expectedDiffBits": 453028929}, + {"height": 206, "timestamp": 64389, "expectedDiffBits": 453029040}, + {"height": 207, "timestamp": 64436, "expectedDiffBits": 453028860}, + {"height": 208, "timestamp": 64441, "expectedDiffBits": 453028653}, + {"height": 209, "timestamp": 64598, "expectedDiffBits": 453028552}, + {"height": 210, "timestamp": 64639, "expectedDiffBits": 453028371}, + {"height": 211, "timestamp": 64690, "expectedDiffBits": 453028196}, + {"height": 212, "timestamp": 64756, "expectedDiffBits": 453028033}, + {"height": 213, "timestamp": 64841, "expectedDiffBits": 453027885}, + {"height": 214, "timestamp": 65591, "expectedDiffBits": 453028197}, + {"height": 215, "timestamp": 65742, "expectedDiffBits": 453028094}, + {"height": 216, "timestamp": 65770, "expectedDiffBits": 453027905}, + {"height": 217, "timestamp": 66147, "expectedDiffBits": 453027958}, + {"height": 218, "timestamp": 67296, "expectedDiffBits": 453028550}, + {"height": 219, "timestamp": 67327, "expectedDiffBits": 453028362}, + {"height": 220, "timestamp": 67330, "expectedDiffBits": 453028154}, + {"height": 221, "timestamp": 67429, "expectedDiffBits": 453028015}, + {"height": 222, "timestamp": 68626, "expectedDiffBits": 453028642}, + {"height": 223, "timestamp": 69288, "expectedDiffBits": 453028897}, + {"height": 224, "timestamp": 69395, "expectedDiffBits": 453028761}, + {"height": 225, "timestamp": 69400, "expectedDiffBits": 453028554}, + {"height": 226, "timestamp": 69401, "expectedDiffBits": 453028343}, + {"height": 227, "timestamp": 70132, "expectedDiffBits": 453028646}, + {"height": 228, "timestamp": 70143, "expectedDiffBits": 453028443}, + {"height": 229, "timestamp": 70251, "expectedDiffBits": 453028308}, + {"height": 230, "timestamp": 70272, "expectedDiffBits": 453028114}, + {"height": 231, "timestamp": 70710, "expectedDiffBits": 453028211}, + {"height": 232, "timestamp": 70903, "expectedDiffBits": 453028136}, + {"height": 233, "timestamp": 70997, "expectedDiffBits": 453027993}, + {"height": 234, "timestamp": 71960, "expectedDiffBits": 453028455}, + {"height": 235, "timestamp": 72005, "expectedDiffBits": 453028277}, + {"height": 236, "timestamp": 73007, "expectedDiffBits": 453028770}, + {"height": 237, "timestamp": 73325, "expectedDiffBits": 453028782}, + {"height": 238, "timestamp": 73334, "expectedDiffBits": 453028577}, + {"height": 239, "timestamp": 73384, "expectedDiffBits": 453028402}, + {"height": 240, "timestamp": 74110, "expectedDiffBits": 453028701}, + {"height": 241, "timestamp": 74297, "expectedDiffBits": 453028622}, + {"height": 242, "timestamp": 74597, "expectedDiffBits": 453028622}, + {"height": 243, "timestamp": 74611, "expectedDiffBits": 453028420}, + {"height": 244, "timestamp": 74887, "expectedDiffBits": 453028404}, + {"height": 245, "timestamp": 74981, "expectedDiffBits": 453028260}, + {"height": 246, "timestamp": 75134, "expectedDiffBits": 453028157}, + {"height": 247, "timestamp": 75435, "expectedDiffBits": 453028158}, + {"height": 248, "timestamp": 75523, "expectedDiffBits": 453028011}, + {"height": 249, "timestamp": 75830, "expectedDiffBits": 453028015}, + {"height": 250, "timestamp": 76209, "expectedDiffBits": 453028070}, + {"height": 251, "timestamp": 76427, "expectedDiffBits": 453028013}, + {"height": 252, "timestamp": 76481, "expectedDiffBits": 453027843}, + {"height": 253, "timestamp": 76495, "expectedDiffBits": 453027645}, + {"height": 254, "timestamp": 76586, "expectedDiffBits": 453027502}, + {"height": 255, "timestamp": 76627, "expectedDiffBits": 453027324}, + {"height": 256, "timestamp": 77155, "expectedDiffBits": 453027480}, + {"height": 257, "timestamp": 77735, "expectedDiffBits": 453027673}, + {"height": 258, "timestamp": 77745, "expectedDiffBits": 453027474}, + {"height": 259, "timestamp": 78146, "expectedDiffBits": 453027543}, + {"height": 260, "timestamp": 78350, "expectedDiffBits": 453027477}, + {"height": 261, "timestamp": 79170, "expectedDiffBits": 453027835}, + {"height": 262, "timestamp": 79645, "expectedDiffBits": 453027956}, + {"height": 263, "timestamp": 79708, "expectedDiffBits": 453027792}, + {"height": 264, "timestamp": 79872, "expectedDiffBits": 453027698}, + {"height": 265, "timestamp": 80202, "expectedDiffBits": 453027719}, + {"height": 266, "timestamp": 80573, "expectedDiffBits": 453027768}, + {"height": 267, "timestamp": 80937, "expectedDiffBits": 453027812}, + {"height": 268, "timestamp": 81063, "expectedDiffBits": 453027692}, + {"height": 269, "timestamp": 81172, "expectedDiffBits": 453027561}, + {"height": 270, "timestamp": 81424, "expectedDiffBits": 453027528}, + {"height": 271, "timestamp": 81770, "expectedDiffBits": 453027560}, + {"height": 272, "timestamp": 82222, "expectedDiffBits": 453027664}, + {"height": 273, "timestamp": 82550, "expectedDiffBits": 453027683}, + {"height": 274, "timestamp": 82661, "expectedDiffBits": 453027553}, + {"height": 275, "timestamp": 82670, "expectedDiffBits": 453027353}, + {"height": 276, "timestamp": 82838, "expectedDiffBits": 453027263}, + {"height": 277, "timestamp": 83214, "expectedDiffBits": 453027315}, + {"height": 278, "timestamp": 83439, "expectedDiffBits": 453027264}, + {"height": 279, "timestamp": 83442, "expectedDiffBits": 453027063}, + {"height": 280, "timestamp": 84318, "expectedDiffBits": 453027455}, + {"height": 281, "timestamp": 84554, "expectedDiffBits": 453027411}, + {"height": 282, "timestamp": 84586, "expectedDiffBits": 453027228}, + {"height": 283, "timestamp": 84697, "expectedDiffBits": 453027100}, + {"height": 284, "timestamp": 84707, "expectedDiffBits": 453026905}, + {"height": 285, "timestamp": 84826, "expectedDiffBits": 453026783}, + {"height": 286, "timestamp": 85253, "expectedDiffBits": 453026868}, + {"height": 287, "timestamp": 86103, "expectedDiffBits": 453027239}, + {"height": 288, "timestamp": 86342, "expectedDiffBits": 453027199} + ] + }, + { + "description": "mainnet: random solve times per Poisson distribution for an increasing hashrate (average 15s)", + "params": "mainnet", + "startDiffBits": 453027238, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 1, "timestamp": 2, "expectedDiffBits": 453027036}, + {"height": 2, "timestamp": 4, "expectedDiffBits": 453026835}, + {"height": 3, "timestamp": 16, "expectedDiffBits": 453026642}, + {"height": 4, "timestamp": 35, "expectedDiffBits": 453026454}, + {"height": 5, "timestamp": 49, "expectedDiffBits": 453026264}, + {"height": 6, "timestamp": 53, "expectedDiffBits": 453026068}, + {"height": 7, "timestamp": 70, "expectedDiffBits": 453025882}, + {"height": 8, "timestamp": 74, "expectedDiffBits": 453025687}, + {"height": 9, "timestamp": 92, "expectedDiffBits": 453025503}, + {"height": 10, "timestamp": 98, "expectedDiffBits": 453025312}, + {"height": 11, "timestamp": 104, "expectedDiffBits": 453025122}, + {"height": 12, "timestamp": 129, "expectedDiffBits": 453024944}, + {"height": 13, "timestamp": 139, "expectedDiffBits": 453024758}, + {"height": 14, "timestamp": 140, "expectedDiffBits": 453024567}, + {"height": 15, "timestamp": 142, "expectedDiffBits": 453024378}, + {"height": 16, "timestamp": 145, "expectedDiffBits": 453024190}, + {"height": 17, "timestamp": 148, "expectedDiffBits": 453024003}, + {"height": 18, "timestamp": 175, "expectedDiffBits": 453023832}, + {"height": 19, "timestamp": 182, "expectedDiffBits": 453023649}, + {"height": 20, "timestamp": 205, "expectedDiffBits": 453023477}, + {"height": 21, "timestamp": 231, "expectedDiffBits": 453023308}, + {"height": 22, "timestamp": 251, "expectedDiffBits": 453023135}, + {"height": 23, "timestamp": 252, "expectedDiffBits": 453022951}, + {"height": 24, "timestamp": 265, "expectedDiffBits": 453022776}, + {"height": 25, "timestamp": 282, "expectedDiffBits": 453022605}, + {"height": 26, "timestamp": 289, "expectedDiffBits": 453022427}, + {"height": 27, "timestamp": 331, "expectedDiffBits": 453022272}, + {"height": 28, "timestamp": 339, "expectedDiffBits": 453022097}, + {"height": 29, "timestamp": 347, "expectedDiffBits": 453021923}, + {"height": 30, "timestamp": 368, "expectedDiffBits": 453021757}, + {"height": 31, "timestamp": 404, "expectedDiffBits": 453021600}, + {"height": 32, "timestamp": 420, "expectedDiffBits": 453021433}, + {"height": 33, "timestamp": 453, "expectedDiffBits": 453021276}, + {"height": 34, "timestamp": 459, "expectedDiffBits": 453021105}, + {"height": 35, "timestamp": 469, "expectedDiffBits": 453020936}, + {"height": 36, "timestamp": 482, "expectedDiffBits": 453020770}, + {"height": 37, "timestamp": 495, "expectedDiffBits": 453020605}, + {"height": 38, "timestamp": 515, "expectedDiffBits": 453020445}, + {"height": 39, "timestamp": 534, "expectedDiffBits": 453020284}, + {"height": 40, "timestamp": 545, "expectedDiffBits": 453020120}, + {"height": 41, "timestamp": 547, "expectedDiffBits": 453019952}, + {"height": 42, "timestamp": 557, "expectedDiffBits": 453019788}, + {"height": 43, "timestamp": 568, "expectedDiffBits": 453019626}, + {"height": 44, "timestamp": 573, "expectedDiffBits": 453019462}, + {"height": 45, "timestamp": 578, "expectedDiffBits": 453019298}, + {"height": 46, "timestamp": 611, "expectedDiffBits": 453019150}, + {"height": 47, "timestamp": 620, "expectedDiffBits": 453018991}, + {"height": 48, "timestamp": 637, "expectedDiffBits": 453018836}, + {"height": 49, "timestamp": 650, "expectedDiffBits": 453018679}, + {"height": 50, "timestamp": 705, "expectedDiffBits": 453018546}, + {"height": 51, "timestamp": 743, "expectedDiffBits": 453018405}, + {"height": 52, "timestamp": 763, "expectedDiffBits": 453018254}, + {"height": 53, "timestamp": 793, "expectedDiffBits": 453018110}, + {"height": 54, "timestamp": 793, "expectedDiffBits": 453017950}, + {"height": 55, "timestamp": 863, "expectedDiffBits": 453017828}, + {"height": 56, "timestamp": 911, "expectedDiffBits": 453017694}, + {"height": 57, "timestamp": 927, "expectedDiffBits": 444582138}, + {"height": 58, "timestamp": 938, "expectedDiffBits": 444543376}, + {"height": 59, "timestamp": 940, "expectedDiffBits": 444503621}, + {"height": 60, "timestamp": 950, "expectedDiffBits": 444465024}, + {"height": 61, "timestamp": 979, "expectedDiffBits": 444429162}, + {"height": 62, "timestamp": 997, "expectedDiffBits": 444392056}, + {"height": 63, "timestamp": 1011, "expectedDiffBits": 444354537}, + {"height": 64, "timestamp": 1021, "expectedDiffBits": 444316686}, + {"height": 65, "timestamp": 1034, "expectedDiffBits": 444279415}, + {"height": 66, "timestamp": 1065, "expectedDiffBits": 444244629}, + {"height": 67, "timestamp": 1084, "expectedDiffBits": 444208435}, + {"height": 68, "timestamp": 1090, "expectedDiffBits": 444170750}, + {"height": 69, "timestamp": 1136, "expectedDiffBits": 444138283}, + {"height": 70, "timestamp": 1138, "expectedDiffBits": 444100515}, + {"height": 71, "timestamp": 1146, "expectedDiffBits": 444063575}, + {"height": 72, "timestamp": 1161, "expectedDiffBits": 444027713}, + {"height": 73, "timestamp": 1178, "expectedDiffBits": 443992264}, + {"height": 74, "timestamp": 1196, "expectedDiffBits": 443957146}, + {"height": 75, "timestamp": 1214, "expectedDiffBits": 443922112}, + {"height": 76, "timestamp": 1235, "expectedDiffBits": 443887657}, + {"height": 77, "timestamp": 1239, "expectedDiffBits": 443851297}, + {"height": 78, "timestamp": 1246, "expectedDiffBits": 443815351}, + {"height": 79, "timestamp": 1263, "expectedDiffBits": 443780979}, + {"height": 80, "timestamp": 1293, "expectedDiffBits": 443748181}, + {"height": 81, "timestamp": 1296, "expectedDiffBits": 443712401}, + {"height": 82, "timestamp": 1316, "expectedDiffBits": 443678691}, + {"height": 83, "timestamp": 1329, "expectedDiffBits": 443644319}, + {"height": 84, "timestamp": 1350, "expectedDiffBits": 443611107}, + {"height": 85, "timestamp": 1365, "expectedDiffBits": 443577397}, + {"height": 86, "timestamp": 1367, "expectedDiffBits": 443542280}, + {"height": 87, "timestamp": 1419, "expectedDiffBits": 443513126}, + {"height": 88, "timestamp": 1422, "expectedDiffBits": 443478422}, + {"height": 89, "timestamp": 1442, "expectedDiffBits": 443445790}, + {"height": 90, "timestamp": 1463, "expectedDiffBits": 443413488}, + {"height": 91, "timestamp": 1492, "expectedDiffBits": 443382263}, + {"height": 92, "timestamp": 1508, "expectedDiffBits": 443349631}, + {"height": 93, "timestamp": 1530, "expectedDiffBits": 443317826}, + {"height": 94, "timestamp": 1535, "expectedDiffBits": 443284365}, + {"height": 95, "timestamp": 1598, "expectedDiffBits": 443257447}, + {"height": 96, "timestamp": 1612, "expectedDiffBits": 443225229}, + {"height": 97, "timestamp": 1620, "expectedDiffBits": 443192430}, + {"height": 98, "timestamp": 1627, "expectedDiffBits": 443159715}, + {"height": 99, "timestamp": 1631, "expectedDiffBits": 443126834}, + {"height": 100, "timestamp": 1646, "expectedDiffBits": 443095195}, + {"height": 101, "timestamp": 1651, "expectedDiffBits": 443062810}, + {"height": 102, "timestamp": 1657, "expectedDiffBits": 443030509}, + {"height": 103, "timestamp": 1664, "expectedDiffBits": 442998539}, + {"height": 104, "timestamp": 1670, "expectedDiffBits": 442966652}, + {"height": 105, "timestamp": 1688, "expectedDiffBits": 442936172}, + {"height": 106, "timestamp": 1691, "expectedDiffBits": 442904202}, + {"height": 107, "timestamp": 1698, "expectedDiffBits": 442872895}, + {"height": 108, "timestamp": 1698, "expectedDiffBits": 442840842}, + {"height": 109, "timestamp": 1702, "expectedDiffBits": 442809451}, + {"height": 110, "timestamp": 1726, "expectedDiffBits": 442780380}, + {"height": 111, "timestamp": 1729, "expectedDiffBits": 442749155}, + {"height": 112, "timestamp": 1766, "expectedDiffBits": 442721575}, + {"height": 113, "timestamp": 1781, "expectedDiffBits": 442691924}, + {"height": 114, "timestamp": 1857, "expectedDiffBits": 442668733}, + {"height": 115, "timestamp": 1877, "expectedDiffBits": 442639744}, + {"height": 116, "timestamp": 1919, "expectedDiffBits": 442613241}, + {"height": 117, "timestamp": 1929, "expectedDiffBits": 442583507}, + {"height": 118, "timestamp": 1936, "expectedDiffBits": 442553607}, + {"height": 119, "timestamp": 1952, "expectedDiffBits": 442524867}, + {"height": 120, "timestamp": 1963, "expectedDiffBits": 442495630}, + {"height": 121, "timestamp": 1978, "expectedDiffBits": 442467056}, + {"height": 122, "timestamp": 1998, "expectedDiffBits": 442438978}, + {"height": 123, "timestamp": 2002, "expectedDiffBits": 442409493}, + {"height": 124, "timestamp": 2032, "expectedDiffBits": 442382658}, + {"height": 125, "timestamp": 2033, "expectedDiffBits": 442353173}, + {"height": 126, "timestamp": 2034, "expectedDiffBits": 442323770}, + {"height": 127, "timestamp": 2047, "expectedDiffBits": 442295693}, + {"height": 128, "timestamp": 2054, "expectedDiffBits": 442267201}, + {"height": 129, "timestamp": 2058, "expectedDiffBits": 442238461}, + {"height": 130, "timestamp": 2067, "expectedDiffBits": 442210466}, + {"height": 131, "timestamp": 2090, "expectedDiffBits": 442183880}, + {"height": 132, "timestamp": 2091, "expectedDiffBits": 442155223}, + {"height": 133, "timestamp": 2093, "expectedDiffBits": 442126897}, + {"height": 134, "timestamp": 2096, "expectedDiffBits": 442098737}, + {"height": 135, "timestamp": 2102, "expectedDiffBits": 442070990}, + {"height": 136, "timestamp": 2111, "expectedDiffBits": 442043741}, + {"height": 137, "timestamp": 2120, "expectedDiffBits": 442016492}, + {"height": 138, "timestamp": 2145, "expectedDiffBits": 441990899}, + {"height": 139, "timestamp": 2168, "expectedDiffBits": 441965307}, + {"height": 140, "timestamp": 2190, "expectedDiffBits": 441939631}, + {"height": 141, "timestamp": 2193, "expectedDiffBits": 441912382}, + {"height": 142, "timestamp": 2253, "expectedDiffBits": 441890434}, + {"height": 143, "timestamp": 2260, "expectedDiffBits": 441863681}, + {"height": 144, "timestamp": 2269, "expectedDiffBits": 441837343}, + {"height": 145, "timestamp": 2275, "expectedDiffBits": 441810840}, + {"height": 146, "timestamp": 2287, "expectedDiffBits": 441784916}, + {"height": 147, "timestamp": 2290, "expectedDiffBits": 441758329}, + {"height": 148, "timestamp": 2300, "expectedDiffBits": 441732488}, + {"height": 149, "timestamp": 2314, "expectedDiffBits": 441707144}, + {"height": 150, "timestamp": 2326, "expectedDiffBits": 441681717}, + {"height": 151, "timestamp": 2326, "expectedDiffBits": 441655378}, + {"height": 152, "timestamp": 2356, "expectedDiffBits": 441631815}, + {"height": 153, "timestamp": 2362, "expectedDiffBits": 441606347}, + {"height": 154, "timestamp": 2370, "expectedDiffBits": 441581168}, + {"height": 155, "timestamp": 2382, "expectedDiffBits": 441556445}, + {"height": 156, "timestamp": 2383, "expectedDiffBits": 441530894}, + {"height": 157, "timestamp": 2384, "expectedDiffBits": 441505467}, + {"height": 158, "timestamp": 2388, "expectedDiffBits": 441480412}, + {"height": 159, "timestamp": 2416, "expectedDiffBits": 441457470}, + {"height": 160, "timestamp": 2439, "expectedDiffBits": 441434238}, + {"height": 161, "timestamp": 2461, "expectedDiffBits": 441411006}, + {"height": 162, "timestamp": 2470, "expectedDiffBits": 441386821}, + {"height": 163, "timestamp": 2503, "expectedDiffBits": 441364707}, + {"height": 164, "timestamp": 2518, "expectedDiffBits": 441341143}, + {"height": 165, "timestamp": 2530, "expectedDiffBits": 441317497}, + {"height": 166, "timestamp": 2543, "expectedDiffBits": 441294058}, + {"height": 167, "timestamp": 2558, "expectedDiffBits": 441270867}, + {"height": 168, "timestamp": 2570, "expectedDiffBits": 441247552}, + {"height": 169, "timestamp": 2573, "expectedDiffBits": 441223574}, + {"height": 170, "timestamp": 2580, "expectedDiffBits": 441200094}, + {"height": 171, "timestamp": 2589, "expectedDiffBits": 441176820}, + {"height": 172, "timestamp": 2598, "expectedDiffBits": 441153671}, + {"height": 173, "timestamp": 2604, "expectedDiffBits": 441130397}, + {"height": 174, "timestamp": 2608, "expectedDiffBits": 441107082}, + {"height": 175, "timestamp": 2608, "expectedDiffBits": 441083519}, + {"height": 176, "timestamp": 2619, "expectedDiffBits": 441060949}, + {"height": 177, "timestamp": 2628, "expectedDiffBits": 441038379}, + {"height": 178, "timestamp": 2630, "expectedDiffBits": 441015313}, + {"height": 179, "timestamp": 2639, "expectedDiffBits": 440992868}, + {"height": 180, "timestamp": 2643, "expectedDiffBits": 440970215}, + {"height": 181, "timestamp": 2651, "expectedDiffBits": 440947935}, + {"height": 182, "timestamp": 2660, "expectedDiffBits": 440925863}, + {"height": 183, "timestamp": 2681, "expectedDiffBits": 440904743}, + {"height": 184, "timestamp": 2683, "expectedDiffBits": 440882297}, + {"height": 185, "timestamp": 2694, "expectedDiffBits": 440860680}, + {"height": 186, "timestamp": 2710, "expectedDiffBits": 440839519}, + {"height": 187, "timestamp": 2753, "expectedDiffBits": 440820428}, + {"height": 188, "timestamp": 2776, "expectedDiffBits": 440799970}, + {"height": 189, "timestamp": 2800, "expectedDiffBits": 440779637}, + {"height": 190, "timestamp": 2838, "expectedDiffBits": 440760463}, + {"height": 191, "timestamp": 2839, "expectedDiffBits": 440738639}, + {"height": 192, "timestamp": 2863, "expectedDiffBits": 440718595}, + {"height": 193, "timestamp": 2867, "expectedDiffBits": 440697185}, + {"height": 194, "timestamp": 2877, "expectedDiffBits": 440676313}, + {"height": 195, "timestamp": 2883, "expectedDiffBits": 440655276}, + {"height": 196, "timestamp": 2902, "expectedDiffBits": 440635233}, + {"height": 197, "timestamp": 2902, "expectedDiffBits": 440613988}, + {"height": 198, "timestamp": 2906, "expectedDiffBits": 440593075}, + {"height": 199, "timestamp": 2944, "expectedDiffBits": 440574688}, + {"height": 200, "timestamp": 2961, "expectedDiffBits": 440554852}, + {"height": 201, "timestamp": 2969, "expectedDiffBits": 440534477}, + {"height": 202, "timestamp": 3030, "expectedDiffBits": 440517953}, + {"height": 203, "timestamp": 3040, "expectedDiffBits": 440497910}, + {"height": 204, "timestamp": 3041, "expectedDiffBits": 440477328}, + {"height": 205, "timestamp": 3058, "expectedDiffBits": 440457989}, + {"height": 206, "timestamp": 3106, "expectedDiffBits": 440440844}, + {"height": 207, "timestamp": 3114, "expectedDiffBits": 440421008}, + {"height": 208, "timestamp": 3115, "expectedDiffBits": 440400799}, + {"height": 209, "timestamp": 3128, "expectedDiffBits": 440381542}, + {"height": 210, "timestamp": 3173, "expectedDiffBits": 440364480}, + {"height": 211, "timestamp": 3200, "expectedDiffBits": 440346300}, + {"height": 212, "timestamp": 3218, "expectedDiffBits": 440327582}, + {"height": 213, "timestamp": 3249, "expectedDiffBits": 440309858}, + {"height": 214, "timestamp": 3312, "expectedDiffBits": 440294245}, + {"height": 215, "timestamp": 3318, "expectedDiffBits": 440274989}, + {"height": 216, "timestamp": 3320, "expectedDiffBits": 440255566}, + {"height": 217, "timestamp": 3321, "expectedDiffBits": 440236186}, + {"height": 218, "timestamp": 3325, "expectedDiffBits": 440217053}, + {"height": 219, "timestamp": 3341, "expectedDiffBits": 440198873}, + {"height": 220, "timestamp": 3344, "expectedDiffBits": 440179865}, + {"height": 221, "timestamp": 3357, "expectedDiffBits": 440161602}, + {"height": 222, "timestamp": 3375, "expectedDiffBits": 440143754}, + {"height": 223, "timestamp": 3388, "expectedDiffBits": 440125615}, + {"height": 224, "timestamp": 3390, "expectedDiffBits": 440106938}, + {"height": 225, "timestamp": 3424, "expectedDiffBits": 440090332}, + {"height": 226, "timestamp": 3428, "expectedDiffBits": 440071945}, + {"height": 227, "timestamp": 3433, "expectedDiffBits": 440053682}, + {"height": 228, "timestamp": 3435, "expectedDiffBits": 440035337}, + {"height": 229, "timestamp": 3437, "expectedDiffBits": 440017074}, + {"height": 230, "timestamp": 3447, "expectedDiffBits": 439999350}, + {"height": 231, "timestamp": 3448, "expectedDiffBits": 439981253}, + {"height": 232, "timestamp": 3450, "expectedDiffBits": 439963197}, + {"height": 233, "timestamp": 3459, "expectedDiffBits": 439945721}, + {"height": 234, "timestamp": 3475, "expectedDiffBits": 439928742}, + {"height": 235, "timestamp": 3489, "expectedDiffBits": 439911680}, + {"height": 236, "timestamp": 3496, "expectedDiffBits": 439894329}, + {"height": 237, "timestamp": 3512, "expectedDiffBits": 439877557}, + {"height": 238, "timestamp": 3548, "expectedDiffBits": 439862069}, + {"height": 239, "timestamp": 3554, "expectedDiffBits": 439844841}, + {"height": 240, "timestamp": 3576, "expectedDiffBits": 439828690}, + {"height": 241, "timestamp": 3596, "expectedDiffBits": 439812457}, + {"height": 242, "timestamp": 3599, "expectedDiffBits": 439795312}, + {"height": 243, "timestamp": 3600, "expectedDiffBits": 439778168}, + {"height": 244, "timestamp": 3624, "expectedDiffBits": 439762390}, + {"height": 245, "timestamp": 3625, "expectedDiffBits": 439745411}, + {"height": 246, "timestamp": 3627, "expectedDiffBits": 439728514}, + {"height": 247, "timestamp": 3659, "expectedDiffBits": 439713440}, + {"height": 248, "timestamp": 3663, "expectedDiffBits": 439696834}, + {"height": 249, "timestamp": 3673, "expectedDiffBits": 439680642}, + {"height": 250, "timestamp": 3683, "expectedDiffBits": 439664533}, + {"height": 251, "timestamp": 3691, "expectedDiffBits": 439648423}, + {"height": 252, "timestamp": 3728, "expectedDiffBits": 439633929}, + {"height": 253, "timestamp": 3737, "expectedDiffBits": 439617986}, + {"height": 254, "timestamp": 3757, "expectedDiffBits": 439602704}, + {"height": 255, "timestamp": 3795, "expectedDiffBits": 439588459}, + {"height": 256, "timestamp": 3801, "expectedDiffBits": 439572598}, + {"height": 257, "timestamp": 3806, "expectedDiffBits": 439556737}, + {"height": 258, "timestamp": 3816, "expectedDiffBits": 439541207}, + {"height": 259, "timestamp": 3825, "expectedDiffBits": 439525678}, + {"height": 260, "timestamp": 3874, "expectedDiffBits": 439512385}, + {"height": 261, "timestamp": 3903, "expectedDiffBits": 439498015}, + {"height": 262, "timestamp": 3950, "expectedDiffBits": 439484721}, + {"height": 263, "timestamp": 3973, "expectedDiffBits": 439470186}, + {"height": 264, "timestamp": 3987, "expectedDiffBits": 439455319}, + {"height": 265, "timestamp": 4005, "expectedDiffBits": 439440659}, + {"height": 266, "timestamp": 4023, "expectedDiffBits": 439426082}, + {"height": 267, "timestamp": 4024, "expectedDiffBits": 439410677}, + {"height": 268, "timestamp": 4041, "expectedDiffBits": 439396182}, + {"height": 269, "timestamp": 4043, "expectedDiffBits": 439380984}, + {"height": 270, "timestamp": 4062, "expectedDiffBits": 439366738}, + {"height": 271, "timestamp": 4118, "expectedDiffBits": 439354439}, + {"height": 272, "timestamp": 4121, "expectedDiffBits": 439339448}, + {"height": 273, "timestamp": 4139, "expectedDiffBits": 439325326}, + {"height": 274, "timestamp": 4142, "expectedDiffBits": 439310542}, + {"height": 275, "timestamp": 4148, "expectedDiffBits": 439295965}, + {"height": 276, "timestamp": 4151, "expectedDiffBits": 439281264}, + {"height": 277, "timestamp": 4153, "expectedDiffBits": 439266645}, + {"height": 278, "timestamp": 4156, "expectedDiffBits": 439252110}, + {"height": 279, "timestamp": 4169, "expectedDiffBits": 439238112}, + {"height": 280, "timestamp": 4200, "expectedDiffBits": 439225109}, + {"height": 281, "timestamp": 4224, "expectedDiffBits": 439211774}, + {"height": 282, "timestamp": 4273, "expectedDiffBits": 439199682}, + {"height": 283, "timestamp": 4291, "expectedDiffBits": 439186223}, + {"height": 284, "timestamp": 4309, "expectedDiffBits": 439172764}, + {"height": 285, "timestamp": 4311, "expectedDiffBits": 439158601}, + {"height": 286, "timestamp": 4313, "expectedDiffBits": 439144562}, + {"height": 287, "timestamp": 4316, "expectedDiffBits": 439130565}, + {"height": 288, "timestamp": 4320, "expectedDiffBits": 439116733} + ] + }, + { + "description": "mainnet: random solve times per Poisson distribution for a decreasing hashrate (average 1000s)", + "params": "mainnet", + "startDiffBits": 439140090, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 1, "timestamp": 517, "expectedDiffBits": 439150336}, + {"height": 2, "timestamp": 596, "expectedDiffBits": 439139911}, + {"height": 3, "timestamp": 1370, "expectedDiffBits": 439162373}, + {"height": 4, "timestamp": 2448, "expectedDiffBits": 439199557}, + {"height": 5, "timestamp": 5275, "expectedDiffBits": 439323503}, + {"height": 6, "timestamp": 5347, "expectedDiffBits": 439312138}, + {"height": 7, "timestamp": 6236, "expectedDiffBits": 439341625}, + {"height": 8, "timestamp": 6989, "expectedDiffBits": 439364491}, + {"height": 9, "timestamp": 7129, "expectedDiffBits": 439356392}, + {"height": 10, "timestamp": 8069, "expectedDiffBits": 439388877}, + {"height": 11, "timestamp": 11290, "expectedDiffBits": 439541461}, + {"height": 12, "timestamp": 11496, "expectedDiffBits": 439536405}, + {"height": 13, "timestamp": 12044, "expectedDiffBits": 439549694}, + {"height": 14, "timestamp": 12637, "expectedDiffBits": 439565445}, + {"height": 15, "timestamp": 12880, "expectedDiffBits": 439562358}, + {"height": 16, "timestamp": 12957, "expectedDiffBits": 439550366}, + {"height": 17, "timestamp": 13423, "expectedDiffBits": 439559270}, + {"height": 18, "timestamp": 13718, "expectedDiffBits": 439559002}, + {"height": 19, "timestamp": 14419, "expectedDiffBits": 439580659}, + {"height": 20, "timestamp": 16167, "expectedDiffBits": 439659814}, + {"height": 21, "timestamp": 18685, "expectedDiffBits": 439784745}, + {"height": 22, "timestamp": 20889, "expectedDiffBits": 439895536}, + {"height": 23, "timestamp": 21309, "expectedDiffBits": 439902651}, + {"height": 24, "timestamp": 21387, "expectedDiffBits": 439889540}, + {"height": 25, "timestamp": 23194, "expectedDiffBits": 439979569}, + {"height": 26, "timestamp": 24153, "expectedDiffBits": 440019662}, + {"height": 27, "timestamp": 24730, "expectedDiffBits": 440036620}, + {"height": 28, "timestamp": 26140, "expectedDiffBits": 440105395}, + {"height": 29, "timestamp": 26262, "expectedDiffBits": 440094298}, + {"height": 30, "timestamp": 28189, "expectedDiffBits": 440197035}, + {"height": 31, "timestamp": 28371, "expectedDiffBits": 440189473}, + {"height": 32, "timestamp": 28596, "expectedDiffBits": 440184685}, + {"height": 33, "timestamp": 30364, "expectedDiffBits": 440279457}, + {"height": 34, "timestamp": 31261, "expectedDiffBits": 440318699}, + {"height": 35, "timestamp": 31302, "expectedDiffBits": 440301651}, + {"height": 36, "timestamp": 31571, "expectedDiffBits": 440299593}, + {"height": 37, "timestamp": 32967, "expectedDiffBits": 440372215}, + {"height": 38, "timestamp": 33512, "expectedDiffBits": 440388592}, + {"height": 39, "timestamp": 33550, "expectedDiffBits": 440371097}, + {"height": 40, "timestamp": 35519, "expectedDiffBits": 440484125}, + {"height": 41, "timestamp": 35561, "expectedDiffBits": 440466450}, + {"height": 42, "timestamp": 36564, "expectedDiffBits": 440514776}, + {"height": 43, "timestamp": 36984, "expectedDiffBits": 440523099}, + {"height": 44, "timestamp": 39551, "expectedDiffBits": 440683111}, + {"height": 45, "timestamp": 39938, "expectedDiffBits": 440689330}, + {"height": 46, "timestamp": 40362, "expectedDiffBits": 440698280}, + {"height": 47, "timestamp": 41489, "expectedDiffBits": 440758329}, + {"height": 48, "timestamp": 41877, "expectedDiffBits": 440764772}, + {"height": 49, "timestamp": 42965, "expectedDiffBits": 440822808}, + {"height": 50, "timestamp": 44382, "expectedDiffBits": 440906348}, + {"height": 51, "timestamp": 44641, "expectedDiffBits": 440903261}, + {"height": 52, "timestamp": 44894, "expectedDiffBits": 440899726}, + {"height": 53, "timestamp": 46865, "expectedDiffBits": 441027386}, + {"height": 54, "timestamp": 47511, "expectedDiffBits": 441054189}, + {"height": 55, "timestamp": 50921, "expectedDiffBits": 441302395}, + {"height": 56, "timestamp": 53298, "expectedDiffBits": 441475160}, + {"height": 57, "timestamp": 53337, "expectedDiffBits": 441453100}, + {"height": 58, "timestamp": 53441, "expectedDiffBits": 441436678}, + {"height": 59, "timestamp": 54893, "expectedDiffBits": 441534269}, + {"height": 60, "timestamp": 55124, "expectedDiffBits": 441528363}, + {"height": 61, "timestamp": 55146, "expectedDiffBits": 441504647}, + {"height": 62, "timestamp": 56390, "expectedDiffBits": 441585503}, + {"height": 63, "timestamp": 57085, "expectedDiffBits": 441619689}, + {"height": 64, "timestamp": 57657, "expectedDiffBits": 441643360}, + {"height": 65, "timestamp": 58950, "expectedDiffBits": 441730615}, + {"height": 66, "timestamp": 58986, "expectedDiffBits": 441707257}, + {"height": 67, "timestamp": 59676, "expectedDiffBits": 441741801}, + {"height": 68, "timestamp": 61201, "expectedDiffBits": 441851474}, + {"height": 69, "timestamp": 61311, "expectedDiffBits": 441834336}, + {"height": 70, "timestamp": 61597, "expectedDiffBits": 441833083}, + {"height": 71, "timestamp": 63844, "expectedDiffBits": 442011261}, + {"height": 72, "timestamp": 65999, "expectedDiffBits": 442186755}, + {"height": 73, "timestamp": 66939, "expectedDiffBits": 442248594}, + {"height": 74, "timestamp": 67333, "expectedDiffBits": 442257722}, + {"height": 75, "timestamp": 67785, "expectedDiffBits": 442272578}, + {"height": 76, "timestamp": 68119, "expectedDiffBits": 442275889}, + {"height": 77, "timestamp": 69085, "expectedDiffBits": 442341129}, + {"height": 78, "timestamp": 74617, "expectedDiffBits": 442878171}, + {"height": 79, "timestamp": 74727, "expectedDiffBits": 442857856}, + {"height": 80, "timestamp": 76344, "expectedDiffBits": 442999701}, + {"height": 81, "timestamp": 76958, "expectedDiffBits": 443034066}, + {"height": 82, "timestamp": 77154, "expectedDiffBits": 443022611}, + {"height": 83, "timestamp": 78075, "expectedDiffBits": 443090804}, + {"height": 84, "timestamp": 79245, "expectedDiffBits": 443187455}, + {"height": 85, "timestamp": 79360, "expectedDiffBits": 443166782}, + {"height": 86, "timestamp": 80019, "expectedDiffBits": 443206964}, + {"height": 87, "timestamp": 82283, "expectedDiffBits": 443430694}, + {"height": 88, "timestamp": 83275, "expectedDiffBits": 443511326}, + {"height": 89, "timestamp": 83627, "expectedDiffBits": 443517412}, + {"height": 90, "timestamp": 84278, "expectedDiffBits": 443558578}, + {"height": 91, "timestamp": 84493, "expectedDiffBits": 443548555}, + {"height": 92, "timestamp": 86918, "expectedDiffBits": 443802981}, + {"height": 93, "timestamp": 87106, "expectedDiffBits": 443789378}, + {"height": 94, "timestamp": 89115, "expectedDiffBits": 444000042}, + {"height": 95, "timestamp": 89819, "expectedDiffBits": 444050695}, + {"height": 96, "timestamp": 90153, "expectedDiffBits": 444054901}, + {"height": 97, "timestamp": 91427, "expectedDiffBits": 444178579}, + {"height": 98, "timestamp": 91488, "expectedDiffBits": 444147973}, + {"height": 99, "timestamp": 94827, "expectedDiffBits": 444544870}, + {"height": 100, "timestamp": 95126, "expectedDiffBits": 444544691}, + {"height": 101, "timestamp": 96766, "expectedDiffBits": 453018106}, + {"height": 102, "timestamp": 97271, "expectedDiffBits": 453018216}, + {"height": 103, "timestamp": 98492, "expectedDiffBits": 453018713}, + {"height": 104, "timestamp": 98648, "expectedDiffBits": 453018635}, + {"height": 105, "timestamp": 99193, "expectedDiffBits": 453018768}, + {"height": 106, "timestamp": 99323, "expectedDiffBits": 453018676}, + {"height": 107, "timestamp": 99477, "expectedDiffBits": 453018597}, + {"height": 108, "timestamp": 99774, "expectedDiffBits": 453018595}, + {"height": 109, "timestamp": 100204, "expectedDiffBits": 453018666}, + {"height": 110, "timestamp": 102033, "expectedDiffBits": 453019507}, + {"height": 111, "timestamp": 102707, "expectedDiffBits": 453019715}, + {"height": 112, "timestamp": 102752, "expectedDiffBits": 453019573}, + {"height": 113, "timestamp": 104057, "expectedDiffBits": 453020138}, + {"height": 114, "timestamp": 104798, "expectedDiffBits": 453020389}, + {"height": 115, "timestamp": 105804, "expectedDiffBits": 453020794}, + {"height": 116, "timestamp": 107455, "expectedDiffBits": 453021584}, + {"height": 117, "timestamp": 108032, "expectedDiffBits": 453021747}, + {"height": 118, "timestamp": 109288, "expectedDiffBits": 453022319}, + {"height": 119, "timestamp": 109896, "expectedDiffBits": 453022505}, + {"height": 120, "timestamp": 110040, "expectedDiffBits": 453022410}, + {"height": 121, "timestamp": 111184, "expectedDiffBits": 453022923}, + {"height": 122, "timestamp": 111637, "expectedDiffBits": 453023017}, + {"height": 123, "timestamp": 112061, "expectedDiffBits": 453023093}, + {"height": 124, "timestamp": 112448, "expectedDiffBits": 453023147}, + {"height": 125, "timestamp": 113676, "expectedDiffBits": 453023722}, + {"height": 126, "timestamp": 113806, "expectedDiffBits": 453023615}, + {"height": 127, "timestamp": 115138, "expectedDiffBits": 453024264}, + {"height": 128, "timestamp": 116344, "expectedDiffBits": 453024841}, + {"height": 129, "timestamp": 117336, "expectedDiffBits": 453025289}, + {"height": 130, "timestamp": 117553, "expectedDiffBits": 453025235}, + {"height": 131, "timestamp": 118521, "expectedDiffBits": 453025671}, + {"height": 132, "timestamp": 120455, "expectedDiffBits": 453026756}, + {"height": 133, "timestamp": 121104, "expectedDiffBits": 453026991}, + {"height": 134, "timestamp": 123433, "expectedDiffBits": 453028385}, + {"height": 135, "timestamp": 123714, "expectedDiffBits": 453028371}, + {"height": 136, "timestamp": 125398, "expectedDiffBits": 453029348}, + {"height": 137, "timestamp": 125687, "expectedDiffBits": 453029340}, + {"height": 138, "timestamp": 129978, "expectedDiffBits": 453032285}, + {"height": 139, "timestamp": 130367, "expectedDiffBits": 453032352}, + {"height": 140, "timestamp": 130940, "expectedDiffBits": 453032562}, + {"height": 141, "timestamp": 132167, "expectedDiffBits": 453033277}, + {"height": 142, "timestamp": 133214, "expectedDiffBits": 453033861}, + {"height": 143, "timestamp": 133536, "expectedDiffBits": 453033879}, + {"height": 144, "timestamp": 135418, "expectedDiffBits": 453035140}, + {"height": 145, "timestamp": 137304, "expectedDiffBits": 453036436}, + {"height": 146, "timestamp": 137813, "expectedDiffBits": 453036609}, + {"height": 147, "timestamp": 138689, "expectedDiffBits": 453037089}, + {"height": 148, "timestamp": 139497, "expectedDiffBits": 453037516}, + {"height": 149, "timestamp": 139694, "expectedDiffBits": 453037429}, + {"height": 150, "timestamp": 146187, "expectedDiffBits": 453042917}, + {"height": 151, "timestamp": 146263, "expectedDiffBits": 453042709}, + {"height": 152, "timestamp": 147348, "expectedDiffBits": 453043442}, + {"height": 153, "timestamp": 148362, "expectedDiffBits": 453044117}, + {"height": 154, "timestamp": 149410, "expectedDiffBits": 453044832}, + {"height": 155, "timestamp": 150081, "expectedDiffBits": 453045191}, + {"height": 156, "timestamp": 151073, "expectedDiffBits": 453045864}, + {"height": 157, "timestamp": 152489, "expectedDiffBits": 453046967}, + {"height": 158, "timestamp": 154392, "expectedDiffBits": 453048585}, + {"height": 159, "timestamp": 156476, "expectedDiffBits": 453050438}, + {"height": 160, "timestamp": 157225, "expectedDiffBits": 453050912}, + {"height": 161, "timestamp": 157854, "expectedDiffBits": 453051262}, + {"height": 162, "timestamp": 159696, "expectedDiffBits": 453052927}, + {"height": 163, "timestamp": 161964, "expectedDiffBits": 453055113}, + {"height": 164, "timestamp": 163436, "expectedDiffBits": 453056449}, + {"height": 165, "timestamp": 163788, "expectedDiffBits": 453056509}, + {"height": 166, "timestamp": 164514, "expectedDiffBits": 453057001}, + {"height": 167, "timestamp": 164680, "expectedDiffBits": 453056846}, + {"height": 168, "timestamp": 165545, "expectedDiffBits": 453057503}, + {"height": 169, "timestamp": 165590, "expectedDiffBits": 453057205}, + {"height": 170, "timestamp": 166231, "expectedDiffBits": 453057602}, + {"height": 171, "timestamp": 166389, "expectedDiffBits": 453057437}, + {"height": 172, "timestamp": 168387, "expectedDiffBits": 453059444}, + {"height": 173, "timestamp": 169021, "expectedDiffBits": 453059845}, + {"height": 174, "timestamp": 172337, "expectedDiffBits": 453063568}, + {"height": 175, "timestamp": 173055, "expectedDiffBits": 453064099}, + {"height": 176, "timestamp": 173364, "expectedDiffBits": 453064110}, + {"height": 177, "timestamp": 173480, "expectedDiffBits": 453063876}, + {"height": 178, "timestamp": 173823, "expectedDiffBits": 453063931}, + {"height": 179, "timestamp": 175240, "expectedDiffBits": 453065362}, + {"height": 180, "timestamp": 175736, "expectedDiffBits": 453065616}, + {"height": 181, "timestamp": 176854, "expectedDiffBits": 453066683}, + {"height": 182, "timestamp": 177008, "expectedDiffBits": 453066492}, + {"height": 183, "timestamp": 179460, "expectedDiffBits": 453069362}, + {"height": 184, "timestamp": 179853, "expectedDiffBits": 453069487}, + {"height": 185, "timestamp": 180196, "expectedDiffBits": 453069546}, + {"height": 186, "timestamp": 180198, "expectedDiffBits": 453069142}, + {"height": 187, "timestamp": 181331, "expectedDiffBits": 453070276}, + {"height": 188, "timestamp": 184144, "expectedDiffBits": 453073788}, + {"height": 189, "timestamp": 184158, "expectedDiffBits": 453073381}, + {"height": 190, "timestamp": 184205, "expectedDiffBits": 453073022}, + {"height": 191, "timestamp": 185213, "expectedDiffBits": 453074028}, + {"height": 192, "timestamp": 185602, "expectedDiffBits": 453074156}, + {"height": 193, "timestamp": 186006, "expectedDiffBits": 453074305}, + {"height": 194, "timestamp": 187124, "expectedDiffBits": 453075485}, + {"height": 195, "timestamp": 187646, "expectedDiffBits": 453075808}, + {"height": 196, "timestamp": 188544, "expectedDiffBits": 453076684}, + {"height": 197, "timestamp": 190249, "expectedDiffBits": 453078783}, + {"height": 198, "timestamp": 191164, "expectedDiffBits": 453079715}, + {"height": 199, "timestamp": 191565, "expectedDiffBits": 453079871}, + {"height": 200, "timestamp": 192001, "expectedDiffBits": 453080079}, + {"height": 201, "timestamp": 193594, "expectedDiffBits": 453082076}, + {"height": 202, "timestamp": 194260, "expectedDiffBits": 453082650}, + {"height": 203, "timestamp": 194331, "expectedDiffBits": 453082291}, + {"height": 204, "timestamp": 194434, "expectedDiffBits": 453081983}, + {"height": 205, "timestamp": 194619, "expectedDiffBits": 453081804}, + {"height": 206, "timestamp": 195881, "expectedDiffBits": 453083313}, + {"height": 207, "timestamp": 196444, "expectedDiffBits": 453083729}, + {"height": 208, "timestamp": 196590, "expectedDiffBits": 453083485}, + {"height": 209, "timestamp": 196856, "expectedDiffBits": 453083432}, + {"height": 210, "timestamp": 199025, "expectedDiffBits": 453086431}, + {"height": 211, "timestamp": 201561, "expectedDiffBits": 453090141}, + {"height": 212, "timestamp": 202064, "expectedDiffBits": 453090484}, + {"height": 213, "timestamp": 202129, "expectedDiffBits": 453090087}, + {"height": 214, "timestamp": 202995, "expectedDiffBits": 453091046}, + {"height": 215, "timestamp": 203835, "expectedDiffBits": 453091969}, + {"height": 216, "timestamp": 204338, "expectedDiffBits": 453092318}, + {"height": 217, "timestamp": 204877, "expectedDiffBits": 453092729}, + {"height": 218, "timestamp": 204903, "expectedDiffBits": 453092258}, + {"height": 219, "timestamp": 205632, "expectedDiffBits": 453092999}, + {"height": 220, "timestamp": 205874, "expectedDiffBits": 453092899}, + {"height": 221, "timestamp": 206204, "expectedDiffBits": 453092950}, + {"height": 222, "timestamp": 206334, "expectedDiffBits": 453092655}, + {"height": 223, "timestamp": 207461, "expectedDiffBits": 453094094}, + {"height": 224, "timestamp": 207798, "expectedDiffBits": 453094159}, + {"height": 225, "timestamp": 209861, "expectedDiffBits": 453097291}, + {"height": 226, "timestamp": 210310, "expectedDiffBits": 453097561}, + {"height": 227, "timestamp": 212352, "expectedDiffBits": 453100753}, + {"height": 228, "timestamp": 213061, "expectedDiffBits": 453101515}, + {"height": 229, "timestamp": 213866, "expectedDiffBits": 453102465}, + {"height": 230, "timestamp": 214010, "expectedDiffBits": 453102170}, + {"height": 231, "timestamp": 214755, "expectedDiffBits": 453103010}, + {"height": 232, "timestamp": 216599, "expectedDiffBits": 453105972}, + {"height": 233, "timestamp": 217956, "expectedDiffBits": 453108044}, + {"height": 234, "timestamp": 218265, "expectedDiffBits": 453108062}, + {"height": 235, "timestamp": 218666, "expectedDiffBits": 453108262}, + {"height": 236, "timestamp": 219092, "expectedDiffBits": 453108511}, + {"height": 237, "timestamp": 220061, "expectedDiffBits": 453109846}, + {"height": 238, "timestamp": 220709, "expectedDiffBits": 453110545}, + {"height": 239, "timestamp": 224046, "expectedDiffBits": 453116824}, + {"height": 240, "timestamp": 224645, "expectedDiffBits": 453117460}, + {"height": 241, "timestamp": 224776, "expectedDiffBits": 453117099}, + {"height": 242, "timestamp": 225931, "expectedDiffBits": 453118927}, + {"height": 243, "timestamp": 227347, "expectedDiffBits": 453121352}, + {"height": 244, "timestamp": 228730, "expectedDiffBits": 453123747}, + {"height": 245, "timestamp": 228786, "expectedDiffBits": 453123204}, + {"height": 246, "timestamp": 232885, "expectedDiffBits": 453131910}, + {"height": 247, "timestamp": 234487, "expectedDiffBits": 453135020}, + {"height": 248, "timestamp": 238752, "expectedDiffBits": 453144894}, + {"height": 249, "timestamp": 240068, "expectedDiffBits": 453147525}, + {"height": 250, "timestamp": 240451, "expectedDiffBits": 453147742}, + {"height": 251, "timestamp": 241294, "expectedDiffBits": 453149168}, + {"height": 252, "timestamp": 241463, "expectedDiffBits": 453148823}, + {"height": 253, "timestamp": 241739, "expectedDiffBits": 453148758}, + {"height": 254, "timestamp": 242421, "expectedDiffBits": 453149768}, + {"height": 255, "timestamp": 243530, "expectedDiffBits": 453151923}, + {"height": 256, "timestamp": 245349, "expectedDiffBits": 453156045}, + {"height": 257, "timestamp": 245960, "expectedDiffBits": 453156899}, + {"height": 258, "timestamp": 248398, "expectedDiffBits": 453162898}, + {"height": 259, "timestamp": 251608, "expectedDiffBits": 453171406}, + {"height": 260, "timestamp": 251751, "expectedDiffBits": 453170936}, + {"height": 261, "timestamp": 252369, "expectedDiffBits": 453171892}, + {"height": 262, "timestamp": 252464, "expectedDiffBits": 453171277}, + {"height": 263, "timestamp": 254646, "expectedDiffBits": 453177002}, + {"height": 264, "timestamp": 255954, "expectedDiffBits": 453180137}, + {"height": 265, "timestamp": 256032, "expectedDiffBits": 453179440}, + {"height": 266, "timestamp": 257031, "expectedDiffBits": 453181636}, + {"height": 267, "timestamp": 260900, "expectedDiffBits": 453193230}, + {"height": 268, "timestamp": 262070, "expectedDiffBits": 453196156}, + {"height": 269, "timestamp": 264763, "expectedDiffBits": 453204417}, + {"height": 270, "timestamp": 266001, "expectedDiffBits": 453207742}, + {"height": 271, "timestamp": 267700, "expectedDiffBits": 453212796}, + {"height": 272, "timestamp": 268109, "expectedDiffBits": 453213198}, + {"height": 273, "timestamp": 268967, "expectedDiffBits": 453215248}, + {"height": 274, "timestamp": 269474, "expectedDiffBits": 453216012}, + {"height": 275, "timestamp": 272998, "expectedDiffBits": 453228278}, + {"height": 276, "timestamp": 276133, "expectedDiffBits": 453239607}, + {"height": 277, "timestamp": 278129, "expectedDiffBits": 453246635}, + {"height": 278, "timestamp": 279647, "expectedDiffBits": 453251803}, + {"height": 279, "timestamp": 280800, "expectedDiffBits": 453255486}, + {"height": 280, "timestamp": 282215, "expectedDiffBits": 453260375}, + {"height": 281, "timestamp": 282483, "expectedDiffBits": 453260232}, + {"height": 282, "timestamp": 282605, "expectedDiffBits": 453259446}, + {"height": 283, "timestamp": 282735, "expectedDiffBits": 453258699}, + {"height": 284, "timestamp": 283383, "expectedDiffBits": 453260232}, + {"height": 285, "timestamp": 283833, "expectedDiffBits": 453260898}, + {"height": 286, "timestamp": 286035, "expectedDiffBits": 453269461}, + {"height": 287, "timestamp": 287862, "expectedDiffBits": 453276528}, + {"height": 288, "timestamp": 288002, "expectedDiffBits": 453275778} + ] + }, + { + "description": "mainnet: 300s blocks across signed 32-bit int height", + "params": "mainnet", + "startDiffBits": 453027238, + "startHeight": 2147483645, + "startTime": 7157978, + "tests": [ + {"height": 2147483646, "timestamp": 7158278, "expectedDiffBits": 453027238}, + {"height": 2147483647, "timestamp": 7158578, "expectedDiffBits": 453027238}, + {"height": 2147483648, "timestamp": 7158878, "expectedDiffBits": 453027238} + ] + }, + { + "description": "mainnet: 300s blocks across max 64-bit signed int height and 32-bit max signed int time", + "params": "mainnet", + "startDiffBits": 453027238, + "startHeight": 9223372036854775804, + "startTime": 2147482747, + "tests": [ + {"height": 9223372036854775805, "timestamp": 2147483047, "expectedDiffBits": 453027238}, + {"height": 9223372036854775806, "timestamp": 2147483347, "expectedDiffBits": 453027238}, + {"height": 9223372036854775807, "timestamp": 2147483647, "expectedDiffBits": 453027238}, + {"height": 9223372036854775808, "timestamp": 2147483947, "expectedDiffBits": 453027238}, + {"height": 9223372036854775809, "timestamp": 2147484247, "expectedDiffBits": 453027238} + ] + }, + { + "description": "mainnet: blocks across potential internal calculation overflow boundaries", + "params": "mainnet", + "startDiffBits": 486604799, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 1, "timestamp": 691200, "expectedDiffBits": 486604799, "notes": "needs 64 bits in inner frac add"}, + {"height": 2, "timestamp": 734400, "expectedDiffBits": 486604799, "notes": "needs 257 bits if *2^n / 2^16"}, + {"height": 3, "timestamp": 1382400, "expectedDiffBits": 486604799}, + {"height": 4, "timestamp": 1425600, "expectedDiffBits": 486604799, "notes": "needs 273 bits if *2^n / 2^16"}, + {"height": 5, "timestamp": 11750400, "expectedDiffBits": 486604799}, + {"height": 6, "timestamp": 11793600, "expectedDiffBits": 486604799, "notes": "needs 513 bits if *2^n / 2^16"} + ] + }, + { + "description": "mainnet: blocks that exercise max fractional part in internal calculation", + "params": "mainnet", + "startDiffBits": 453027238, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 1, "timestamp": 299, "expectedDiffBits": 453027237}, + {"height": 2, "timestamp": 599, "expectedDiffBits": 453027237}, + {"height": 3, "timestamp": 899, "expectedDiffBits": 453027237}, + {"height": 4, "timestamp": 1199, "expectedDiffBits": 453027237} + ] + }, + { + "description": "testnet: steady 120s blocks at proof of work limit", + "params": "testnet", + "startDiffBits": 503382015, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 1, "timestamp": 120, "expectedDiffBits": 503382015}, + {"height": 2, "timestamp": 240, "expectedDiffBits": 503382015}, + {"height": 3, "timestamp": 360, "expectedDiffBits": 503382015}, + {"height": 4, "timestamp": 480, "expectedDiffBits": 503382015}, + {"height": 5, "timestamp": 600, "expectedDiffBits": 503382015}, + {"height": 6, "timestamp": 720, "expectedDiffBits": 503382015}, + {"height": 7, "timestamp": 840, "expectedDiffBits": 503382015}, + {"height": 8, "timestamp": 960, "expectedDiffBits": 503382015}, + {"height": 9, "timestamp": 1080, "expectedDiffBits": 503382015}, + {"height": 10, "timestamp": 1200, "expectedDiffBits": 503382015} + ] + }, + { + "description": "testnet: random solve times per Poisson distribution for a stable hashrate", + "params": "testnet", + "startDiffBits": 487015304, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 1, "timestamp": 41, "expectedDiffBits": 486980492}, + {"height": 2, "timestamp": 336, "expectedDiffBits": 487061450}, + {"height": 3, "timestamp": 489, "expectedDiffBits": 487078288}, + {"height": 4, "timestamp": 565, "expectedDiffBits": 487055944}, + {"height": 5, "timestamp": 584, "expectedDiffBits": 487008047}, + {"height": 6, "timestamp": 675, "expectedDiffBits": 486995157}, + {"height": 7, "timestamp": 839, "expectedDiffBits": 487014846}, + {"height": 8, "timestamp": 1026, "expectedDiffBits": 487046581}, + {"height": 9, "timestamp": 1049, "expectedDiffBits": 487001339}, + {"height": 10, "timestamp": 1077, "expectedDiffBits": 486962191}, + {"height": 11, "timestamp": 1100, "expectedDiffBits": 486924455}, + {"height": 12, "timestamp": 1130, "expectedDiffBits": 486892458}, + {"height": 13, "timestamp": 1148, "expectedDiffBits": 486859412}, + {"height": 14, "timestamp": 1227, "expectedDiffBits": 486847027}, + {"height": 15, "timestamp": 1278, "expectedDiffBits": 486827269}, + {"height": 16, "timestamp": 1616, "expectedDiffBits": 486894503}, + {"height": 17, "timestamp": 2201, "expectedDiffBits": 487095140}, + {"height": 18, "timestamp": 2575, "expectedDiffBits": 487249052}, + {"height": 19, "timestamp": 2579, "expectedDiffBits": 487174032}, + {"height": 20, "timestamp": 2628, "expectedDiffBits": 487132134}, + {"height": 21, "timestamp": 2805, "expectedDiffBits": 487165541}, + {"height": 22, "timestamp": 2807, "expectedDiffBits": 487098350}, + {"height": 23, "timestamp": 2851, "expectedDiffBits": 487058944}, + {"height": 24, "timestamp": 2880, "expectedDiffBits": 487015304} + ] + }, + { + "description": "simnet: steady 1s blocks at proof of work limit", + "params": "simnet", + "startDiffBits": 545259519, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 1, "timestamp": 1, "expectedDiffBits": 545259519}, + {"height": 2, "timestamp": 2, "expectedDiffBits": 545259519}, + {"height": 3, "timestamp": 3, "expectedDiffBits": 545259519}, + {"height": 4, "timestamp": 4, "expectedDiffBits": 545259519}, + {"height": 5, "timestamp": 5, "expectedDiffBits": 545259519}, + {"height": 6, "timestamp": 6, "expectedDiffBits": 545259519}, + {"height": 7, "timestamp": 7, "expectedDiffBits": 545259519}, + {"height": 8, "timestamp": 8, "expectedDiffBits": 545259519}, + {"height": 9, "timestamp": 9, "expectedDiffBits": 545259519}, + {"height": 10, "timestamp": 10, "expectedDiffBits": 545259519} + ] + }, + { + "description": "simnet: random solve times per Poisson distribution for a stable hashrate", + "params": "simnet", + "startDiffBits": 505106049, + "startHeight": 0, + "startTime": 0, + "tests": [ + {"height": 1, "timestamp": 1, "expectedDiffBits": 505106049}, + {"height": 2, "timestamp": 3, "expectedDiffBits": 505325376}, + {"height": 3, "timestamp": 4, "expectedDiffBits": 505325376}, + {"height": 4, "timestamp": 6, "expectedDiffBits": 505571108}, + {"height": 5, "timestamp": 7, "expectedDiffBits": 505571108}, + {"height": 6, "timestamp": 7, "expectedDiffBits": 505325376}, + {"height": 7, "timestamp": 7, "expectedDiffBits": 505106049}, + {"height": 8, "timestamp": 8, "expectedDiffBits": 505106049}, + {"height": 9, "timestamp": 8, "expectedDiffBits": 504910983}, + {"height": 10, "timestamp": 10, "expectedDiffBits": 505106049}, + {"height": 11, "timestamp": 10, "expectedDiffBits": 504910983}, + {"height": 12, "timestamp": 12, "expectedDiffBits": 505106049} + ] + } + ] +} \ No newline at end of file diff --git a/chaincfg/mainnetparams.go b/chaincfg/mainnetparams.go index 5a6e74312f..6415814670 100644 --- a/chaincfg/mainnetparams.go +++ b/chaincfg/mainnetparams.go @@ -19,6 +19,15 @@ func MainNetParams() *Params { // for the main network. It is the value 2^224 - 1. mainPowLimit := new(big.Int).Sub(new(big.Int).Lsh(bigOne, 224), bigOne) + // mainNetPowLimitBits is the main network proof of work limit in its + // compact representation. + // + // Note that due to the limited precision of the compact representation, + // this is not exactly equal to the pow limit. It is the value: + // + // 0x00000000ffff0000000000000000000000000000000000000000000000000000 + const mainPowLimitBits = 0x1d00ffff // 486604799 + // genesisBlock defines the genesis block of the block chain which serves as // the public transaction ledger for the main network. // @@ -84,22 +93,28 @@ func MainNetParams() *Params { }, // Chain parameters - GenesisBlock: &genesisBlock, - GenesisHash: genesisBlock.BlockHash(), - PowLimit: mainPowLimit, - PowLimitBits: 0x1d00ffff, - ReduceMinDifficulty: false, - MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false - GenerateSupported: false, - MaximumBlockSizes: []int{393216}, - MaxTxSize: 393216, - TargetTimePerBlock: time.Minute * 5, + GenesisBlock: &genesisBlock, + GenesisHash: genesisBlock.BlockHash(), + PowLimit: mainPowLimit, + PowLimitBits: mainPowLimitBits, + ReduceMinDifficulty: false, + MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false + GenerateSupported: false, + MaximumBlockSizes: []int{393216}, + MaxTxSize: 393216, + TargetTimePerBlock: time.Minute * 5, + + // Version 1 difficulty algorithm (EMA + BLAKE256) parameters. WorkDiffAlpha: 1, WorkDiffWindowSize: 144, WorkDiffWindows: 20, TargetTimespan: time.Minute * 5 * 144, // TimePerBlock * WindowSize RetargetAdjustmentFactor: 4, + // Version 2 difficulty algorithm (ASERT + BLAKE3) parameters. + WorkDiffV2Blake3StartBits: 0x1b00a5a6, + WorkDiffV2HalfLifeSecs: 43200, // 144 * TimePerBlock (12 hours) + // Subsidy parameters. BaseSubsidy: 3119582664, // 21m MulSubsidy: 100, diff --git a/chaincfg/params.go b/chaincfg/params.go index 9d2069d7e2..8a2726f77d 100644 --- a/chaincfg/params.go +++ b/chaincfg/params.go @@ -297,16 +297,26 @@ type Params struct { // block. TargetTimePerBlock time.Duration + // ------------------------------------------------------------------------- + // Version 1 difficulty algorithm (EMA + BLAKE256) parameters. + // ------------------------------------------------------------------------- + // WorkDiffAlpha is the stake difficulty EMA calculation alpha (smoothing) // value. It is different from a normal EMA alpha. Closer to 1 --> smoother. + // + // This only applies to the version 1 difficulty retarget algorithm. WorkDiffAlpha int64 // WorkDiffWindowSize is the number of windows (intervals) used for calculation // of the exponentially weighted average. + // + // This only applies to the version 1 difficulty retarget algorithm. WorkDiffWindowSize int64 // WorkDiffWindows is the number of windows (intervals) used for calculation // of the exponentially weighted average. + // + // This only applies to the version 1 difficulty retarget algorithm. WorkDiffWindows int64 // TargetTimespan is the desired amount of time that should elapse @@ -314,13 +324,32 @@ type Params struct { // it should be changed in order to maintain the desired block // generation rate. This value should correspond to the product of // WorkDiffWindowSize and TimePerBlock above. + // + // This only applies to the version 1 difficulty retarget algorithm. TargetTimespan time.Duration // RetargetAdjustmentFactor is the adjustment factor used to limit // the minimum and maximum amount of adjustment that can occur between // difficulty retargets. + // + // This only applies to the version 1 difficulty retarget algorithm. RetargetAdjustmentFactor int64 + // ------------------------------------------------------------------------- + // Version 2 difficulty algorithm (ASERT + BLAKE3) parameters. + // ------------------------------------------------------------------------- + + // WorkDiffV2Blake3StartBits is the starting difficulty bits to use for + // proof of work under BLAKE3. + WorkDiffV2Blake3StartBits uint32 + + // WorkDiffV2HalfLife is the number of seconds to use for the relaxation + // time when calculating how difficult it is to solve a block. The + // algorithm sets the difficulty exponentially such that it is halved or + // doubled for every multiple of this value the most recent block is behind + // or ahead of the ideal schedule. + WorkDiffV2HalfLifeSecs int64 + // Subsidy parameters. // // Subsidy calculation for exponential reductions: diff --git a/chaincfg/regnetparams.go b/chaincfg/regnetparams.go index 59a89432d3..a6c1701a49 100644 --- a/chaincfg/regnetparams.go +++ b/chaincfg/regnetparams.go @@ -28,6 +28,15 @@ func RegNetParams() *Params { // can have for the regression test network. It is the value 2^255 - 1. regNetPowLimit := new(big.Int).Sub(new(big.Int).Lsh(bigOne, 255), bigOne) + // regNetPowLimitBits is the regression test network proof of work limit in + // its compact representation. + // + // Note that due to the limited precision of the compact representation, + // this is not exactly equal to the pow limit. It is the value: + // + // 0x7fffff0000000000000000000000000000000000000000000000000000000000 + const regNetPowLimitBits = 0x207fffff // 545259519 + // genesisBlock defines the genesis block of the block chain which serves as // the public transaction ledger for the regression test network. genesisBlock := wire.MsgBlock{ @@ -43,7 +52,7 @@ func RegNetParams() *Params { Revocations: 0, Timestamp: time.Unix(1538524800, 0), // 2018-10-03 00:00:00 +0000 UTC PoolSize: 0, - Bits: 0x207fffff, // 545259519 [7fffff0000000000000000000000000000000000000000000000000000000000] + Bits: regNetPowLimitBits, SBits: 0, Nonce: 0, StakeVersion: 0, @@ -84,22 +93,28 @@ func RegNetParams() *Params { DNSSeeds: nil, // NOTE: There must NOT be any seeds. // Chain parameters - GenesisBlock: &genesisBlock, - GenesisHash: genesisBlock.BlockHash(), - PowLimit: regNetPowLimit, - PowLimitBits: 0x207fffff, - ReduceMinDifficulty: false, - MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false - GenerateSupported: true, - MaximumBlockSizes: []int{1000000, 1310720}, - MaxTxSize: 1000000, - TargetTimePerBlock: time.Second, + GenesisBlock: &genesisBlock, + GenesisHash: genesisBlock.BlockHash(), + PowLimit: regNetPowLimit, + PowLimitBits: regNetPowLimitBits, + ReduceMinDifficulty: false, + MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false + GenerateSupported: true, + MaximumBlockSizes: []int{1000000, 1310720}, + MaxTxSize: 1000000, + TargetTimePerBlock: time.Second, + + // Version 1 difficulty algorithm (EMA + BLAKE256) parameters. WorkDiffAlpha: 1, WorkDiffWindowSize: 8, WorkDiffWindows: 4, TargetTimespan: time.Second * 8, // TimePerBlock * WindowSize RetargetAdjustmentFactor: 4, + // Version 2 difficulty algorithm (ASERT + BLAKE3) parameters. + WorkDiffV2Blake3StartBits: regNetPowLimitBits, + WorkDiffV2HalfLifeSecs: 6, // 6 * TimePerBlock + // Subsidy parameters. BaseSubsidy: 50000000000, MulSubsidy: 100, diff --git a/chaincfg/simnetparams.go b/chaincfg/simnetparams.go index ebb2612887..cbd785d123 100644 --- a/chaincfg/simnetparams.go +++ b/chaincfg/simnetparams.go @@ -30,6 +30,15 @@ func SimNetParams() *Params { // for the simulation test network. It is the value 2^255 - 1. simNetPowLimit := new(big.Int).Sub(new(big.Int).Lsh(bigOne, 255), bigOne) + // simNetPowLimitBits is the simulation test network proof of work limit in + // its compact representation. + // + // Note that due to the limited precision of the compact representation, + // this is not exactly equal to the pow limit. It is the value: + // + // 0x7fffff0000000000000000000000000000000000000000000000000000000000 + const simNetPowLimitBits = 0x207fffff // 545259519 + // genesisBlock defines the genesis block of the block chain which serves // as the public transaction ledger for the simulation test network. genesisBlock := wire.MsgBlock{ @@ -43,9 +52,9 @@ func SimNetParams() *Params { Voters: 0, FreshStake: 0, Revocations: 0, - Timestamp: time.Unix(1401292357, 0), // 2009-01-08 20:54:25 -0600 CST + Timestamp: time.Unix(1401292357, 0), // 2014-05-28 15:52:37 +0000 UTC PoolSize: 0, - Bits: 0x207fffff, // 545259519 [7fffff0000000000000000000000000000000000000000000000000000000000] + Bits: simNetPowLimitBits, SBits: 0, Nonce: 0, StakeVersion: 0, @@ -84,22 +93,28 @@ func SimNetParams() *Params { DNSSeeds: nil, // NOTE: There must NOT be any seeds. // Chain parameters - GenesisBlock: &genesisBlock, - GenesisHash: genesisBlock.BlockHash(), - PowLimit: simNetPowLimit, - PowLimitBits: 0x207fffff, - ReduceMinDifficulty: false, - MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false - GenerateSupported: true, - MaximumBlockSizes: []int{1000000, 1310720}, - MaxTxSize: 1000000, - TargetTimePerBlock: time.Second, + GenesisBlock: &genesisBlock, + GenesisHash: genesisBlock.BlockHash(), + PowLimit: simNetPowLimit, + PowLimitBits: simNetPowLimitBits, + ReduceMinDifficulty: false, + MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false + GenerateSupported: true, + MaximumBlockSizes: []int{1000000, 1310720}, + MaxTxSize: 1000000, + TargetTimePerBlock: time.Second, + + // Version 1 difficulty algorithm (EMA + BLAKE256) parameters. WorkDiffAlpha: 1, WorkDiffWindowSize: 8, WorkDiffWindows: 4, TargetTimespan: time.Second * 8, // TimePerBlock * WindowSize RetargetAdjustmentFactor: 4, + // Version 2 difficulty algorithm (ASERT + BLAKE3) parameters. + WorkDiffV2Blake3StartBits: simNetPowLimitBits, + WorkDiffV2HalfLifeSecs: 6, // 6 * TimePerBlock + // Subsidy parameters. BaseSubsidy: 50000000000, MulSubsidy: 100, diff --git a/chaincfg/testnetparams.go b/chaincfg/testnetparams.go index d27aa9d748..ecdb32204d 100644 --- a/chaincfg/testnetparams.go +++ b/chaincfg/testnetparams.go @@ -21,6 +21,15 @@ func TestNet3Params() *Params { // can have for the test network. It is the value 2^232 - 1. testNetPowLimit := new(big.Int).Sub(new(big.Int).Lsh(bigOne, 232), bigOne) + // testNetPowLimitBits is the test network proof of work limit in its + // compact representation. + // + // Note that due to the limited precision of the compact representation, + // this is not exactly equal to the pow limit. It is the value: + // + // 0x000000ffff000000000000000000000000000000000000000000000000000000 + const testNetPowLimitBits = 0x1e00ffff // 503382015 + // genesisBlock defines the genesis block of the block chain which serves as // the public transaction ledger for the test network (version 3). genesisBlock := wire.MsgBlock{ @@ -29,7 +38,7 @@ func TestNet3Params() *Params { PrevBlock: chainhash.Hash{}, // MerkleRoot: Calculated below. Timestamp: time.Unix(1533513600, 0), // 2018-08-06 00:00:00 +0000 UTC - Bits: 0x1e00ffff, // Difficulty 1 [000000ffff000000000000000000000000000000000000000000000000000000] + Bits: testNetPowLimitBits, // Difficulty 1 SBits: 20000000, Nonce: 0x18aea41a, StakeVersion: 6, @@ -82,22 +91,28 @@ func TestNet3Params() *Params { // // Note that the minimum difficulty reduction parameter only applies up // to and including block height 962927. - GenesisBlock: &genesisBlock, - GenesisHash: genesisBlock.BlockHash(), - PowLimit: testNetPowLimit, - PowLimitBits: 0x1e00ffff, - ReduceMinDifficulty: true, - MinDiffReductionTime: time.Minute * 10, // ~99.3% chance to be mined before reduction - GenerateSupported: true, - MaximumBlockSizes: []int{1310720}, - MaxTxSize: 1000000, - TargetTimePerBlock: time.Minute * 2, + GenesisBlock: &genesisBlock, + GenesisHash: genesisBlock.BlockHash(), + PowLimit: testNetPowLimit, + PowLimitBits: testNetPowLimitBits, + ReduceMinDifficulty: true, + MinDiffReductionTime: time.Minute * 10, // ~99.3% chance to be mined before reduction + GenerateSupported: true, + MaximumBlockSizes: []int{1310720}, + MaxTxSize: 1000000, + TargetTimePerBlock: time.Minute * 2, + + // Version 1 difficulty algorithm (EMA + BLAKE256) parameters. WorkDiffAlpha: 1, WorkDiffWindowSize: 144, WorkDiffWindows: 20, TargetTimespan: time.Minute * 2 * 144, // TimePerBlock * WindowSize RetargetAdjustmentFactor: 4, + // Version 2 difficulty algorithm (ASERT + BLAKE3) parameters. + WorkDiffV2Blake3StartBits: testNetPowLimitBits, + WorkDiffV2HalfLifeSecs: 720, // 6 * TimePerBlock (12 minutes) + // Subsidy parameters. BaseSubsidy: 2500000000, // 25 Coin MulSubsidy: 100, diff --git a/go.mod b/go.mod index b770716760..d8fa07c986 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 golang.org/x/sys v0.5.0 golang.org/x/term v0.5.0 + lukechampine.com/blake3 v1.2.1 ) require ( @@ -48,6 +49,7 @@ require ( github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 // indirect github.com/decred/dcrd/hdkeychain/v3 v3.1.0 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect ) replace ( diff --git a/go.sum b/go.sum index eb0abb8879..faced0d15b 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ github.com/jrick/bitset v1.0.0 h1:Ws0PXV3PwXqWK2n7Vz6idCdrV/9OrBXgHEJi27ZB9Dw= github.com/jrick/bitset v1.0.0/go.mod h1:ZOYB5Uvkla7wIEY4FEssPVi3IQXa02arznRaYaAEPe4= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -92,3 +94,5 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= diff --git a/internal/blockchain/agendas_test.go b/internal/blockchain/agendas_test.go index 094c13cfea..8cbe87d745 100644 --- a/internal/blockchain/agendas_test.go +++ b/internal/blockchain/agendas_test.go @@ -1017,6 +1017,176 @@ func TestSubsidySplitDeployment(t *testing.T) { testSubsidySplitDeployment(t, chaincfg.RegNetParams()) } +// testBlake3PowDeployment ensures the deployment of the blake3 proof of work +// agenda activates for the provided network parameters. +func testBlake3PowDeployment(t *testing.T, params *chaincfg.Params) { + // Clone the parameters so they can be mutated, find the correct deployment + // for the modified subsidy split agenda as well as the yes vote choice + // within it, and, finally, ensure it is always available to vote by + // removing the time constraints to prevent test failures when the real + // expiration time passes. + const voteID = chaincfg.VoteIDBlake3Pow + params = cloneParams(params) + deploymentVer, deployment := findDeployment(t, params, voteID) + yesChoice := findDeploymentChoice(t, deployment, "yes") + removeDeploymentTimeConstraints(deployment) + + // Shorter versions of params for convenience. + stakeValidationHeight := uint32(params.StakeValidationHeight) + ruleChangeActivationInterval := params.RuleChangeActivationInterval + + // blake3AnchorHeight is the height of the expected blake3 anchor block + // given the test conditions below. + blake3AnchorHeight := int64(stakeValidationHeight + + ruleChangeActivationInterval*3 - 1) + + tests := []struct { + name string + numNodes uint32 // num fake nodes to create + curActive bool // whether agenda active for current block + nextActive bool // whether agenda active for NEXT block + resetAnchor bool // whether or not to reset cached blake3 anchors + }{{ + name: "stake validation height", + numNodes: stakeValidationHeight, + curActive: false, + nextActive: false, + }, { + name: "started", + numNodes: ruleChangeActivationInterval, + curActive: false, + nextActive: false, + }, { + name: "lockedin", + numNodes: ruleChangeActivationInterval, + curActive: false, + nextActive: false, + }, { + name: "one before active", + numNodes: ruleChangeActivationInterval - 1, + curActive: false, + nextActive: true, + }, { + name: "exactly active", + numNodes: 1, + curActive: true, + nextActive: true, + }, { + name: "one after active", + numNodes: 1, + curActive: true, + nextActive: true, + }, { + name: "one before next rcai after active", + numNodes: ruleChangeActivationInterval - 2, + curActive: true, + nextActive: true, + }, { + name: "exactly next rcai after active", + numNodes: 1, + curActive: true, + nextActive: true, + }, { + name: "one after next rcai after active", + numNodes: 1, + curActive: true, + nextActive: true, + }, { + name: "one before 2nd rcai after active with anchor reset", + numNodes: ruleChangeActivationInterval - 2, + curActive: true, + nextActive: true, + resetAnchor: true, + }, { + name: "exactly 2nd rcai after active with anchor reset", + numNodes: 1, + curActive: true, + nextActive: true, + resetAnchor: true, + }, { + name: "one after 2nd rcai after active with anchor reset", + numNodes: 1, + curActive: true, + nextActive: true, + resetAnchor: true, + }} + + curTimestamp := time.Now() + bc := newFakeChain(params) + node := bc.bestChain.Tip() + for _, test := range tests { + for i := uint32(0); i < test.numNodes; i++ { + node = newFakeNode(node, int32(deploymentVer), deploymentVer, 0, + curTimestamp) + + // Create fake votes that vote yes on the agenda to ensure it is + // activated. + for j := uint16(0); j < params.TicketsPerBlock; j++ { + node.votes = append(node.votes, stake.VoteVersionTuple{ + Version: deploymentVer, + Bits: yesChoice.Bits | 0x01, + }) + } + bc.index.AddNode(node) + bc.bestChain.SetTip(node) + curTimestamp = curTimestamp.Add(time.Second) + } + + // Ensure the agenda reports the expected activation status for the + // current block. + gotActive, err := bc.isBlake3PowAgendaActive(node.parent) + if err != nil { + t.Errorf("%s: unexpected err: %v", test.name, err) + continue + } + if gotActive != test.curActive { + t.Errorf("%s: mismatched current active status - got: %v, want: %v", + test.name, gotActive, test.curActive) + continue + } + + // Ensure the agenda reports the expected activation status for the NEXT + // block + gotActive, err = bc.IsBlake3PowAgendaActive(&node.hash) + if err != nil { + t.Errorf("%s: unexpected err: %v", test.name, err) + continue + } + if gotActive != test.nextActive { + t.Errorf("%s: mismatched next active status - got: %v, want: %v", + test.name, gotActive, test.nextActive) + continue + } + + // Reset the cached blake3 anchor block when requested by the test flag. + // This helps ensure the logic that walks backwards to find the anchor + // works as intended. + if test.resetAnchor { + bc.cachedBlake3WorkDiffCandidateAnchor.Store(nil) + bc.cachedBlake3WorkDiffAnchor.Store(nil) + } + + // Ensure the blake3 anchor block is the expected value once the agenda + // is active. + if test.nextActive { + wantAnchor := bc.bestChain.nodeByHeight(blake3AnchorHeight) + gotAnchor := bc.blake3WorkDiffAnchor(node) + if gotAnchor != wantAnchor { + t.Errorf("%s: mistmatched blake3 anchor - got: %s, want %s", + test.name, gotAnchor, wantAnchor) + continue + } + } + } +} + +// TestBlake3PowDeployment ensures the deployment of the blake3 proof of work +// agenda activates as expected. +func TestBlake3PowDeployment(t *testing.T) { + testBlake3PowDeployment(t, chaincfg.MainNetParams()) + testBlake3PowDeployment(t, chaincfg.RegNetParams()) +} + // testSubsidySplitR2Deployment ensures the deployment of the 1/89/10 subsidy // split agenda activates for the provided network parameters. func testSubsidySplitR2Deployment(t *testing.T, params *chaincfg.Params) { diff --git a/internal/blockchain/blockindex.go b/internal/blockchain/blockindex.go index 17bb9341d5..d5a7a0991c 100644 --- a/internal/blockchain/blockindex.go +++ b/internal/blockchain/blockindex.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2017 The btcsuite developers -// Copyright (c) 2018-2022 The Decred developers +// Copyright (c) 2018-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -440,7 +440,7 @@ func compareHashesAsUint256LE(a, b *chainhash.Hash) int { // 1. More total cumulative work // 2. Having block data available // 3. Receiving data earlier -// 4. Hash that represents more work (smaller value as a little-endian uint256) +// 4. Hash that represents a smaller value as a little-endian uint256 // // This function MUST be called with the block index lock held (for reads). func betterCandidate(a, b *blockNode) bool { @@ -479,9 +479,17 @@ func betterCandidate(a, b *blockNode) bool { // can be the same when the block data for a given header is not yet known // as well. // - // Note that it is more difficult to find hashes with more leading zeros - // when treated as a little-endian uint256, so smaller values represent more - // work and are therefore better candidates. + // Note that this logic was originally based on the block hash and proof of + // work hash being the same and the fact that it is more difficult to find + // hashes with more leading zeros when treated as a little-endian uint256, + // meaning that smaller values represent more work and were therefore + // considered better candidates. + // + // While the initial motivation no longer applies now that the block hash + // and proof of work hash are independent, it is still necessary to break + // the tie to have a stable order and continuing to consider hashes with + // smaller values when treated as a little-endian uint256 as better + // candidates serves that purpose well. return compareHashesAsUint256LE(&a.hash, &b.hash) < 0 } @@ -625,14 +633,26 @@ func (bi *blockIndex) HaveBlock(hash *chainhash.Hash) bool { // as a key in the block index. func shortBlockKey(hash *chainhash.Hash) uint32 { // Use the first bytes of the hash directly since it is the result of a hash - // function that produces a uniformly-random distribution. It is also worth - // noting that the mining process reduces entropy by zeroing the bits at the - // other end of the array, but there would need to be effectively impossible - // to achieve hash rates exceeding ~2^215.77 hashes/sec (aka ~89.8 peta - // yotta yotta hashes/sec) in order to start zeroing out the bits used here. - // Not only is that impossible for all intents and purposes, the only effect - // would be reducing overall memory savings due to increased collisions - // among the shortened keys. + // function that produces a uniformly-random distribution. + // + // It is also worth noting that the choice of bytes was made based on the + // possibility of the block hash and proof of work hash being the same and + // considering that the mining process reduces entropy by zeroing the bits + // at the other end of the array. + // + // Since the block hash and proof of work hash are independent, the entropy + // concern may or may not be relevant dependening on whether or not the same + // hash function is used (or was ever used historically in the case of hash + // algorithm changes), but this choice of bytes covers both possibilities + // without any notable downside. + // + // For the case when the block hash and proof of work hash are the same, + // there would need to be effectively impossible to achieve hash rates + // exceeding ~2^215.77 hashes/sec (aka ~89.8 peta yotta yotta hashes/sec) in + // order to start zeroing out the bits used here. Not only is that + // impossible for all intents and purposes, the only effect would be + // reducing overall memory savings due to increased collisions among the + // shortened keys. return binary.BigEndian.Uint32(hash[0:4]) } diff --git a/internal/blockchain/chain.go b/internal/blockchain/chain.go index 4d6d193435..1c897e78ef 100644 --- a/internal/blockchain/chain.go +++ b/internal/blockchain/chain.go @@ -15,6 +15,7 @@ import ( "math/bits" "strings" "sync" + "sync/atomic" "time" "github.com/decred/dcrd/blockchain/stake/v5" @@ -167,6 +168,7 @@ type BlockChain struct { deploymentData map[string]deploymentInfo minKnownWork *uint256.Uint256 minTestNetTarget *big.Int + minTestNetDiffBits uint32 db database.DB dbInfo *databaseInfo chainParams *chaincfg.Params @@ -271,6 +273,24 @@ type BlockChain struct { calcVoterVersionIntervalCache map[[chainhash.HashSize]byte]uint32 calcStakeVersionCache map[[chainhash.HashSize]byte]uint32 + // cachedBlake3WorkDiffCandidateAnchor houses a cached candidate anchor + // point to use for the difficulty algorithm defined in DCP0011 before the + // state of the blake3 proof of work agenda can be determined definitively. + // + // This is set based on heuristics that make it very likely to be the + // correct anchor point and exists to avoid additional work that would + // otherwise be required during the initial header sync. + cachedBlake3WorkDiffCandidateAnchor atomic.Pointer[blockNode] + + // cachedBlake3WorkDiffAnchor houses a cached anchor point to use for the + // difficulty algorithm defined in DCP0011. + // + // It is only set when the blake3 proof of work agenda has been determined + // to be active and will be the block just prior to the activation of the + // agenda. It will not be set for networks where the agenda is always + // active such as the simulation network. + cachedBlake3WorkDiffAnchor atomic.Pointer[blockNode] + // bulkImportMode provides a mechanism to indicate that several validation // checks can be avoided when bulk importing blocks already known to be valid. // It is protected by the chain lock. @@ -2372,10 +2392,12 @@ func New(ctx context.Context, config *Config) (*BlockChain, error) { // difficulty on testnet by ASICs and GPUs since it's not reasonable to // require high-powered hardware to keep the test network running smoothly. var minTestNetTarget *big.Int + var minTestNetDiffBits uint32 if params.Net == wire.TestNet3 { // This equates to a maximum difficulty of 2^6 = 64. const maxTestDiffShift = 6 minTestNetTarget = new(big.Int).Rsh(params.PowLimit, maxTestDiffShift) + minTestNetDiffBits = standalone.BigToCompact(minTestNetTarget) } // Either use the subsidy cache provided by the caller or create a new @@ -2405,6 +2427,7 @@ func New(ctx context.Context, config *Config) (*BlockChain, error) { deploymentData: deploymentData, minKnownWork: minKnownWork, minTestNetTarget: minTestNetTarget, + minTestNetDiffBits: minTestNetDiffBits, db: config.DB, chainParams: params, timeSource: config.TimeSource, diff --git a/internal/blockchain/difficulty.go b/internal/blockchain/difficulty.go index 75f47fd832..e28dcb67ff 100644 --- a/internal/blockchain/difficulty.go +++ b/internal/blockchain/difficulty.go @@ -24,8 +24,6 @@ var ( // findPrevTestNetDifficulty returns the difficulty of the previous block which // did not have the special testnet minimum difficulty rule applied. -// -// This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) findPrevTestNetDifficulty(startNode *blockNode) uint32 { // Search backwards through the chain for the last block without // the special rule applied. @@ -47,9 +45,10 @@ func (b *BlockChain) findPrevTestNetDifficulty(startNode *blockNode) uint32 { return lastBits } -// calcNextRequiredDifficulty calculates the required difficulty for the block -// after the passed previous block node based on the difficulty retarget rules. -func (b *BlockChain) calcNextRequiredDifficulty(prevNode *blockNode, newBlockTime time.Time) uint32 { +// calcNextBlake256Diff calculates the required difficulty for the block AFTER +// the passed previous block node based on the difficulty retarget rules for the +// blake256 hash algorithm used at Decred launch. +func (b *BlockChain) calcNextBlake256Diff(prevNode *blockNode, newBlockTime time.Time) uint32 { // Get the old difficulty; if we aren't at a block height where it changes, // just return this. oldDiff := prevNode.bits @@ -212,20 +211,186 @@ func (b *BlockChain) calcNextRequiredDifficulty(prevNode *blockNode, newBlockTim return nextDiffBits } +// calcNextBlake3DiffFromAnchor calculates the required difficulty for the block +// AFTER the passed previous block node relative to the given anchor block based +// on the difficulty retarget rules defined in DCP0011. +// +// This function is safe for concurrent access. +func (b *BlockChain) calcNextBlake3DiffFromAnchor(prevNode *blockNode, blake3Anchor *blockNode) uint32 { + // Calculate the time and height deltas as the difference between the + // provided block and the blake3 anchor block. + // + // Notice that if the difficulty prior to the activation point were being + // maintained, this would need to be the timestamp and height of the parent + // of the blake3 anchor block (except when the anchor is the genesis block) + // in order for the absolute calculations to exactly match the behavior of + // relative calculations. + // + // However, since the initial difficulty is reset with the agenda, no + // additional offsets are needed. + timeDelta := prevNode.timestamp - blake3Anchor.timestamp + heightDelta := prevNode.height - blake3Anchor.height + + // Calculate the next target difficulty using the ASERT algorithm. + // + // Note that the difficulty of the anchor block is NOT used for the initial + // difficulty because the difficulty must be reset due to the change to + // blake3 for proof of work. The initial difficulty comes from the chain + // parameters instead. + params := b.chainParams + nextDiff := standalone.CalcASERTDiff(params.WorkDiffV2Blake3StartBits, + params.PowLimit, int64(params.TargetTimePerBlock.Seconds()), timeDelta, + heightDelta, params.WorkDiffV2HalfLifeSecs) + + // Prevent the difficulty from going higher than a maximum allowed + // difficulty on the test network. This is to prevent runaway difficulty on + // testnet by ASICs and GPUs since it's not reasonable to require + // high-powered hardware to keep the test network running smoothly. + // + // Smaller numbers result in a higher difficulty, so imposing a maximum + // difficulty equates to limiting the minimum target value. + if b.minTestNetTarget != nil && nextDiff < b.minTestNetDiffBits { + nextDiff = b.minTestNetDiffBits + } + + return nextDiff +} + +// blake3WorkDiffAnchor returns the block to treat as the anchor block for the +// purposes of determining how far ahead or behind the ideal schedule the +// provided block is when calculating the blake3 target difficulty for the block +// AFTER the passed previous block node. +// +// This function MUST only be called with the blake3 proof of work agenda active +// after having gone through a vote. That is to say it MUST NOT be called when +// the agenda is forced to always be active. +// +// This function MUST be called with the chain state lock held (for writes). +func (b *BlockChain) blake3WorkDiffAnchor(prevNode *blockNode) *blockNode { + // Use the previously cached anchor when it exists and is actually an + // ancestor of the passed node. + anchor := b.cachedBlake3WorkDiffAnchor.Load() + if anchor != nil && anchor.IsAncestorOf(prevNode) { + return anchor + } + + // Find the block just prior to the activation of the blake3 proof of work + // agenda from the perspective of the block AFTER the passed block. + // + // The state of an agenda can only change at a rule change activation + // boundary, so determine the final block of the rule change activation + // interval just prior to the interval the block AFTER the provided one + // falls in and then loop backwards one interval at a time until the agenda + // is no longer active. + rcai := int64(b.chainParams.RuleChangeActivationInterval) + svh := b.chainParams.StakeValidationHeight + finalNodeHeight := calcWantHeight(svh, rcai, prevNode.height+1) + candidate := prevNode.Ancestor(finalNodeHeight) + for candidate != nil && candidate.parent != nil { + // Since isBlake3PowAgendaActive returns the state for the block AFTER + // the provided one and the goal here is to determine the state of the + // agenda for the candidate anchor (which is the final block of the rule + // change activation interval under test), use the parent to get the + // state of the candidate itself. + isActive, err := b.isBlake3PowAgendaActive(candidate.parent) + if err != nil { + panicf("known good agenda state lookup failed for block node "+ + "hash %v (height %v) -- %v", candidate.parent.hash, + candidate.parent.height, err) + } + if !isActive { + anchor = candidate + break + } + + // Skip back to the ancestor that is the final block of the previous + // rule change interval. + candidate = candidate.RelativeAncestor(rcai) + } + + // Update the cached anchor to the discovered one since it is highly likely + // the next call will involve a descendant of this anchor as opposed to some + // other anchor on an entirely unrelated side chain. + if anchor != nil { + b.cachedBlake3WorkDiffAnchor.Store(anchor) + } + + return anchor +} + +// calcNextBlake3Diff calculates the required difficulty for the block AFTER the +// passed previous block node based on the difficulty retarget rules defined in +// DCP0011. +// +// This function MUST only be called with the blake3 proof of work agenda active +// and, unless the agenda is always forced to be active, with the chain state +// lock held (for writes). +func (b *BlockChain) calcNextBlake3Diff(prevNode *blockNode) uint32 { + // Apply special handling for networks where the agenda is always active to + // always require the initial starting difficulty for the first block and to + // treat the first block as the anchor once it has been mined. + // + // This is to done to help provide better difficulty target behavior for the + // initial blocks on such networks since the genesis block will necessarily + // have a hard-coded timestamp that will very likely be outdated by the time + // mining starts. As a result, merely using the genesis block as the anchor + // for all blocks would likely result in a lot of the initial blocks having + // a significantly lower difficulty than desired because they would all be + // behind the ideal schedule relative to that outdated timestamp. + if b.isBlake3PowAgendaForcedActive() { + // Use the initial starting difficulty for the first block. + if prevNode.height == 0 { + return b.chainParams.WorkDiffV2Blake3StartBits + } + + // Treat the first block as the anchor for all descendants of it. + anchor := prevNode.Ancestor(1) + return b.calcNextBlake3DiffFromAnchor(prevNode, anchor) + } + + // Determine the block to treat as the anchor block for the purposes of + // determining how far ahead or behind the ideal schedule the provided block + // is when calculating the blake3 target difficulty. + // + // This will be the block just prior to the activation of the blake3 proof + // of work agenda. + anchor := b.blake3WorkDiffAnchor(prevNode) + return b.calcNextBlake3DiffFromAnchor(prevNode, anchor) +} + +// calcNextRequiredDifficulty calculates the required difficulty for the block +// AFTER the passed previous block node based on the active difficulty retarget +// rules. +// +// This function MUST be called with the chain state lock held (for writes). +func (b *BlockChain) calcNextRequiredDifficulty(prevNode *blockNode, newBlockTime time.Time) (uint32, error) { + // Choose the difficulty algorithm based on the result of the vote for the + // blake3 proof of work agenda. + isActive, err := b.isBlake3PowAgendaActive(prevNode) + if err != nil { + return 0, err + } + if isActive { + return b.calcNextBlake3Diff(prevNode), nil + } + + return b.calcNextBlake256Diff(prevNode, newBlockTime), nil +} + // CalcNextRequiredDifficulty calculates the required difficulty for the block -// after the given block based on the difficulty retarget rules. +// AFTER the given block based on the active difficulty retarget rules. // // This function is safe for concurrent access. func (b *BlockChain) CalcNextRequiredDifficulty(hash *chainhash.Hash, timestamp time.Time) (uint32, error) { node := b.index.LookupNode(hash) - if node == nil { + if node == nil || !b.index.CanValidate(node) { return 0, unknownBlockError(hash) } b.chainLock.Lock() - difficulty := b.calcNextRequiredDifficulty(node, timestamp) + difficulty, err := b.calcNextRequiredDifficulty(node, timestamp) b.chainLock.Unlock() - return difficulty, nil + return difficulty, err } // mergeDifficulty takes an original stake difficulty and two new, scaled diff --git a/internal/blockchain/difficulty_test.go b/internal/blockchain/difficulty_test.go index 8db0d76cdd..1b1261cf00 100644 --- a/internal/blockchain/difficulty_test.go +++ b/internal/blockchain/difficulty_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2014 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -1116,7 +1116,10 @@ func TestMinDifficultyReduction(t *testing.T) { // Update the block time according to the test data and calculate // the difficulty for the next block. blockTime = blockTime.Add(test.timeAdjustment(i)) - diff := bc.calcNextRequiredDifficulty(node, blockTime) + diff, err := bc.calcNextRequiredDifficulty(node, blockTime) + if err != nil { + t.Fatalf("%s: unexpected err: %v", test.name, err) + } // Ensure the calculated difficulty matches the expected value. expectedDiff := test.expectedDiff(i) diff --git a/internal/blockchain/process.go b/internal/blockchain/process.go index a0da54ae0b..92a3170cdd 100644 --- a/internal/blockchain/process.go +++ b/internal/blockchain/process.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2022 The Decred developers +// Copyright (c) 2015-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -290,7 +290,7 @@ func (b *BlockChain) ProcessBlockHeader(header *wire.BlockHeader) error { // the code is changed to violate that assumption. // // The flags do not modify the behavior of this function directly, however they -// are needed to pass along to checkBlockPositional. +// are needed to pass along to checkBlockDataPositional. // // This function MUST be called with the chain lock held (for writes). func (b *BlockChain) maybeAcceptBlockData(node *blockNode, block *dcrutil.Block, flags BehaviorFlags) ([]*blockNode, error) { diff --git a/internal/blockchain/thresholdstate.go b/internal/blockchain/thresholdstate.go index d7a94954dd..bcba26b389 100644 --- a/internal/blockchain/thresholdstate.go +++ b/internal/blockchain/thresholdstate.go @@ -844,6 +844,67 @@ func (b *BlockChain) IsSubsidySplitAgendaActive(prevHash *chainhash.Hash) (bool, return isActive, err } +// isBlake3PowAgendaForcedActive returns whether or not the agenda to change the +// proof of work hash function to blake3, as defined in DCP0011, is forced +// active by the chain parameters. +func (b *BlockChain) isBlake3PowAgendaForcedActive() bool { + const deploymentID = chaincfg.VoteIDBlake3Pow + deployment, ok := b.deploymentData[deploymentID] + if !ok { + return false + } + + state := deployment.forcedState + return state != nil && state.State == ThresholdActive +} + +// isBlake3PowAgendaActive returns whether or not the agenda to change the proof +// of work hash function to blake3, as defined in DCP0011, has passed and is now +// active from the point of view of the passed block node. +// +// 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) isBlake3PowAgendaActive(prevNode *blockNode) (bool, error) { + const deploymentID = chaincfg.VoteIDBlake3Pow + deployment, ok := b.deploymentData[deploymentID] + if !ok { + str := fmt.Sprintf("deployment ID %s does not exist", deploymentID) + return false, contextError(ErrUnknownDeploymentID, str) + } + + // NOTE: The choice field of the return threshold state is not examined + // here because there is only one possible choice that can be active for + // the agenda, which is yes, so there is no need to check it. + state := b.deploymentState(prevNode, &deployment) + return state.State == ThresholdActive, nil +} + +// IsBlake3PowAgendaActive returns whether or not the agenda to change the proof +// of work hash function to blake3, as defined in DCP0011, has passed and is now +// active for the block AFTER the given block. +// +// This function is safe for concurrent access. +func (b *BlockChain) IsBlake3PowAgendaActive(prevHash *chainhash.Hash) (bool, error) { + // The agenda is never active for the genesis block. + if *prevHash == *zeroHash { + return false, nil + } + + prevNode := b.index.LookupNode(prevHash) + if prevNode == nil || !b.index.CanValidate(prevNode) { + return false, unknownBlockError(prevHash) + } + + b.chainLock.Lock() + isActive, err := b.isBlake3PowAgendaActive(prevNode) + b.chainLock.Unlock() + return isActive, err +} + // isSubsidySplitR2AgendaActive returns whether or not the agenda to change the // block reward subsidy split to 1/89/10, as defined in DCP0012, has passed and // is now active from the point of view of the passed block node. diff --git a/internal/blockchain/validate.go b/internal/blockchain/validate.go index 1ecda43336..0313a27d44 100644 --- a/internal/blockchain/validate.go +++ b/internal/blockchain/validate.go @@ -689,14 +689,19 @@ func standaloneToChainRuleError(err error) error { return err } -// checkProofOfWork ensures the block header bits which indicate the target -// difficulty is in min/max range and that the block hash is less than the -// target difficulty as claimed. +// checkProofOfWorkSanity ensures the block header bits which indicate the +// target difficulty is in min/max range and that the proof of work hash is less +// than the target difficulty as claimed. This check is context free. +// +// NOTE: This version differs from checkProofOfWorkContext in that it is run in +// the context free sanity checks where the result of the vote to change the +// proof of work hash function is not available. See the comments in the +// function itself for more details. // // The flags modify the behavior of this function as follows: // - BFNoPoWCheck: The check to ensure the block hash is less than the target // difficulty is not performed. -func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags BehaviorFlags) error { +func checkProofOfWorkSanity(header *wire.BlockHeader, powLimit *big.Int, flags BehaviorFlags) error { // Only ensure the target difficulty bits are in the valid range when the // the flag to avoid proof of work checks is set. if flags&BFNoPoWCheck == BFNoPoWCheck { @@ -708,9 +713,25 @@ func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags Behavio // // - The target difficulty must be larger than zero. // - The target difficulty must be less than the maximum allowed. - // - The block hash must be less than the claimed target. - blockHash := header.BlockHash() - err := standalone.CheckProofOfWork(&blockHash, header.Bits, powLimit) + // - The proof of work hash must be less than the claimed target. + // + // The ability to determine whether or not the blake3 proof of work agenda + // is active is not possible here because that relies on additional context + // that is not available in the context free checks. However, it is + // important to check proof of work here in the context free checks to + // protect against various forms of malicious behavior. + // + // Thus, allow valid proof of work under both algorithms while rejecting + // blocks that satisify neither here in the context free checks and allow + // the contextual checks that happen later to ensure the proof of work is + // valid specifically for the correct hash algorithm as determined by the + // state of the blake3 proof of work agenda. + powHashV1 := header.PowHashV1() + err := standalone.CheckProofOfWork(&powHashV1, header.Bits, powLimit) + if err != nil { + powHashV2 := header.PowHashV2() + err = standalone.CheckProofOfWork(&powHashV2, header.Bits, powLimit) + } return standaloneToChainRuleError(err) } @@ -719,7 +740,7 @@ func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags Behavio // context free. // // The flags do not modify the behavior of this function directly, however they -// are needed to pass along to checkProofOfWork. +// are needed to pass along to checkProofOfWorkSanity. func checkBlockHeaderSanity(header *wire.BlockHeader, timeSource MedianTimeSource, flags BehaviorFlags, chainParams *chaincfg.Params) error { // The stake validation height should always be at least stake enabled // height, so assert it because the code below relies on that assumption. @@ -731,10 +752,10 @@ func checkBlockHeaderSanity(header *wire.BlockHeader, timeSource MedianTimeSourc "height %d", stakeEnabledHeight, stakeValidationHeight)) } - // Ensure the proof of work bits in the block header is in min/max - // range and the block hash is less than the target value described by - // the bits. - err := checkProofOfWork(header, chainParams.PowLimit, flags) + // Ensure the proof of work bits in the block header is in min/max range and + // the proof of work hash is less than the target value described by the + // bits. + err := checkProofOfWorkSanity(header, chainParams.PowLimit, flags) if err != nil { return err } @@ -1014,6 +1035,172 @@ func (b *BlockChain) isOldBlockVersionByMajority(header *wire.BlockHeader, block return b.isMajorityVersion(nextVersion, prevNode, rejectNumRequired) } +// checkDifficultyPositional ensures the difficulty specified in the block +// header matches the calculated difficulty based on the difficulty retarget +// rules. These checks do not, and must not, rely on having the full block data +// of all ancestors available. +// +// This function is safe for concurrent access. +func (b *BlockChain) checkDifficultyPositional(header *wire.BlockHeader, prevNode *blockNode) error { + // ------------------------------------------------------------------------- + // The ability to determine whether or not the blake3 proof of work agenda + // is active is not possible in the general case here because that relies on + // additional context that is not available in the positional checks. + // However, it is important to check for valid bits in the positional checks + // to protect against various forms of malicious behavior. + // + // Thus, with the exception of the special cases where it is possible to + // definitively determine the agenda is active, allow valid difficulty bits + // under both difficulty algorithms while rejecting blocks that satisify + // neither here in the positional checks and allow the contextual checks + // that happen later to ensure the difficulty bits are valid specifically + // for the correct difficulty algorithm as determined by the state of the + // blake3 proof of work agenda. + // ------------------------------------------------------------------------- + + // Ensure the difficulty specified in the block header matches the + // calculated difficulty using the algorithm defined in DCP0011 when the + // blake3 proof of work agenda is always active. + if b.isBlake3PowAgendaForcedActive() { + blake3Diff := b.calcNextBlake3Diff(prevNode) + if header.Bits != blake3Diff { + str := fmt.Sprintf("block difficulty of %d is not the expected "+ + "value of %d (difficulty algorithm: ASERT)", header.Bits, + blake3Diff) + return ruleError(ErrUnexpectedDifficulty, str) + } + + return nil + } + + // A cached anchor point for the difficulty algorithm defined in DCP0011 is + // set when the contextual checks have determined that the agenda is + // actually active. + // + // Since contextual checks for previous block(s) may or may not have been + // done yet, this can't be relied on entirely, however, it is a nice + // optimization when it can be used. + // + // In practice, once the chain is synced, and the agenda is active, the + // anchor will be set and thus this can run immediately. + cachedAnchor := b.cachedBlake3WorkDiffAnchor.Load() + if cachedAnchor != nil && cachedAnchor.IsAncestorOf(prevNode) { + blake3Diff := b.calcNextBlake3DiffFromAnchor(prevNode, cachedAnchor) + if header.Bits != blake3Diff { + str := fmt.Sprintf("block difficulty of %d is not the expected "+ + "value of %d (difficulty algorithm: ASERT)", header.Bits, + blake3Diff) + return ruleError(ErrUnexpectedDifficulty, str) + } + + return nil + } + + // Only the original difficulty algorithm needs to be checked when it is + // impossible for the blake3 proof of work agenda to be active or the block + // is not solved for blake3. + // + // Note that since the case where the blake3 proof of work agenda is always + // active is already handled above, the only remaining way for the agenda to + // be active is for it to have been voted in which requires voting to be + // possible (stake validation height), at least one interval of voting, and + // one interval of being locked in. + isSolvedBlake3 := func(header *wire.BlockHeader) bool { + powHash := header.PowHashV2() + err := standalone.CheckProofOfWorkHash(&powHash, header.Bits) + return err == nil + } + rcai := int64(b.chainParams.RuleChangeActivationInterval) + svh := b.chainParams.StakeValidationHeight + firstPossibleActivationHeight := svh + rcai*2 + minBlake3BlockVersion := uint32(10) + if !isMainNet(b.chainParams) { + minBlake3BlockVersion++ + } + isBlake3PossiblyActive := uint32(header.Version) >= minBlake3BlockVersion && + int64(header.Height) >= firstPossibleActivationHeight + if !isBlake3PossiblyActive || !isSolvedBlake3(header) { + // Ensure the difficulty specified in the block header matches the + // calculated difficulty based on the previous block and difficulty + // retarget rules for the blake256 hash algorithm used at Decred launch. + blake256Diff := b.calcNextBlake256Diff(prevNode, header.Timestamp) + if header.Bits != blake256Diff { + str := fmt.Sprintf("block difficulty of %d is not the expected "+ + "value of %d (difficulty algorithm: EMA)", header.Bits, + blake256Diff) + return ruleError(ErrUnexpectedDifficulty, str) + } + + return nil + } + + // At this point, the blake3 proof of work agenda might possibly be active + // and the block is solved using blake3, so the agenda is very likely + // active. + // + // Calculating the required difficulty once the agenda activates for the + // algorithm defined in DCP0011 requires the block prior to the activation + // of the agenda as an anchor. However, as previously discussed, the + // additional context needed to definitively determine when the agenda + // activated is not available here in the positional checks. + // + // In light of that, the following logic uses the fact that the agenda could + // have only possibly activated at a rule change activation interval to + // iterate backwards one interval at a time through all possible candidate + // anchors until one of them results in a required difficulty that matches. + // + // In the case there is a match, the header is assumed to be valid enough to + // make it through the positional checks. + // + // As an additional optimization to avoid a bunch of extra work during the + // initial header sync, a candidate anchor that results in a matching + // required difficulty is cached and tried first on subsequent descendant + // headers since it is very likely to be the correct one. + cachedCandidate := b.cachedBlake3WorkDiffCandidateAnchor.Load() + if cachedCandidate != nil && cachedCandidate.IsAncestorOf(prevNode) { + blake3Diff := b.calcNextBlake3DiffFromAnchor(prevNode, cachedCandidate) + if header.Bits == blake3Diff { + return nil + } + } + + // Iterate backwards through all possible anchor candidates which + // consist of the final blocks of previous rule change activation + // intervals so long as the block also has a version that is at least + // the minimum version that is enforced before voting on the agenda + // could have even started and the agenda could still possibly be + // active. + finalNodeHeight := calcWantHeight(svh, rcai, int64(header.Height)) + candidate := prevNode.Ancestor(finalNodeHeight) + for candidate != nil && + uint32(candidate.blockVersion) >= minBlake3BlockVersion && + candidate.height >= firstPossibleActivationHeight-1 { + + blake3Diff := b.calcNextBlake3DiffFromAnchor(prevNode, candidate) + if header.Bits == blake3Diff { + b.cachedBlake3WorkDiffCandidateAnchor.Store(candidate) + return nil + } + candidate = candidate.RelativeAncestor(rcai) + } + + // At this point, none of the possible difficulties for blake3 matched, so + // the agenda is very likely not actually active and therefore the only + // remaining valid option is the original difficulty algorithm. + // + // Ensure the difficulty specified in the block header matches the + // calculated difficulty based on the previous block and difficulty retarget + // rules for the blake256 hash algorithm used at Decred launch. + blake256Diff := b.calcNextBlake256Diff(prevNode, header.Timestamp) + if header.Bits != blake256Diff { + str := fmt.Sprintf("block difficulty of %d is not the expected value "+ + "of %d (difficulty algorithm: EMA)", header.Bits, blake256Diff) + return ruleError(ErrUnexpectedDifficulty, str) + } + + return nil +} + // checkBlockHeaderPositional performs several validation checks on the block // header which depend on its position within the block chain and having the // headers of all ancestors available. These checks do not, and must not, rely @@ -1032,17 +1219,6 @@ func (b *BlockChain) checkBlockHeaderPositional(header *wire.BlockHeader, prevNo fastAdd := flags&BFFastAdd == BFFastAdd if !fastAdd { - // Ensure the difficulty specified in the block header matches - // the calculated difficulty based on the previous block and - // difficulty retarget rules. - expDiff := b.calcNextRequiredDifficulty(prevNode, header.Timestamp) - blockDifficulty := header.Bits - if blockDifficulty != expDiff { - str := fmt.Sprintf("block difficulty of %d is not the "+ - "expected value of %d", blockDifficulty, expDiff) - return ruleError(ErrUnexpectedDifficulty, str) - } - // Ensure the timestamp for the block header is after the // median time of the last several blocks (medianTimeBlocks). medianTime := prevNode.CalcPastMedianTime() @@ -1062,8 +1238,8 @@ func (b *BlockChain) checkBlockHeaderPositional(header *wire.BlockHeader, prevNo // diff activation height has been reached. blockHeight := prevNode.height + 1 if b.minTestNetTarget != nil && - expDiff <= standalone.BigToCompact(b.minTestNetTarget) && - (!b.isTestNet3() || blockHeight >= testNet3MaxDiffActivationHeight) { + header.Bits <= b.minTestNetDiffBits && (!b.isTestNet3() || + blockHeight >= testNet3MaxDiffActivationHeight) { minTime := time.Unix(prevNode.timestamp, 0).Add(time.Minute) if header.Timestamp.Before(minTime) { @@ -1072,6 +1248,13 @@ func (b *BlockChain) checkBlockHeaderPositional(header *wire.BlockHeader, prevNo return ruleError(ErrTimeTooOld, str) } } + + // Ensure the difficulty specified in the block header matches the + // calculated difficulty based on the difficulty retarget rules. + err := b.checkDifficultyPositional(header, prevNode) + if err != nil { + return err + } } // The height of this block is one more than the referenced previous @@ -1201,6 +1384,39 @@ func (b *BlockChain) checkBlockPositional(block *dcrutil.Block, prevNode *blockN return b.checkBlockDataPositional(block, prevNode, flags) } +// checkProofOfWorkContext ensures the proof of work hash is less than the +// target difficulty indicated by the block header difficulty bits. +// +// NOTE: This does not ensure the target difficulty is in the valid min/max +// range since that is already handled in checkProofOfWorkSanity which is +// required to have been called prior. +// +// The flags modify the behavior of this function as follows: +// - BFNoPoWCheck: The check to ensure the block hash is less than the target +// difficulty is not performed. +func (b *BlockChain) checkProofOfWorkContext(header *wire.BlockHeader, prevNode *blockNode, flags BehaviorFlags) error { + // Nothing to do when the flag to avoid proof of work checks is set. + if flags&BFNoPoWCheck == BFNoPoWCheck { + return nil + } + + // Choose the proof of work mining algorithm based on the result of the vote + // for the blake3 proof of work agenda. + isBlake3PowActive, err := b.isBlake3PowAgendaActive(prevNode) + if err != nil { + return err + } + powHashFn := header.PowHashV1 + if isBlake3PowActive { + powHashFn = header.PowHashV2 + } + + // Ensure the proof of work hash is less than the claimed target. + powHash := powHashFn() + err = standalone.CheckProofOfWorkHash(&powHash, header.Bits) + return standaloneToChainRuleError(err) +} + // checkBlockHeaderContext performs several validation checks on the block // header which depend on having the full block data for all of its ancestors // available. This includes checks which depend on tallying the results of @@ -1215,7 +1431,19 @@ func (b *BlockChain) checkBlockPositional(block *dcrutil.Block, prevNode *blockN // vote will necessarily need to be transitioned to this function. // // The flags modify the behavior of this function as follows: -// - BFFastAdd: No check are performed. +// +// BFFastAdd: +// - The difficulty bits value is not checked to ensure it matches the next +// required calculated value +// - The stake difficulty is not checked to ensure it matches the next +// required calculated value +// - The stake version is not check to ensure it matches the expected version +// - The pool size commitment is not checked +// - The ticket lottery final state is not checked +// +// BFNoPoWCheck: +// - The check to ensure the block hash is less than the target difficulty is +// not performed // // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode *blockNode, flags BehaviorFlags) error { @@ -1224,8 +1452,32 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode return nil } + // Ensure the proof of work bits in the block header is in min/max range and + // the proof of work hash is less than the target value described by the + // bits. + // + // Note that the bits have already been validated to ensure they are in the + // min/max range in the header sanity checks. + err := b.checkProofOfWorkContext(header, prevNode, flags) + if err != nil { + return err + } + fastAdd := flags&BFFastAdd == BFFastAdd if !fastAdd { + // Ensure the difficulty specified in the block header matches the + // calculated difficulty based on the previous block and difficulty + // retarget rules. + expDiff, err := b.calcNextRequiredDifficulty(prevNode, header.Timestamp) + if err != nil { + return err + } + if header.Bits != expDiff { + str := fmt.Sprintf("block difficulty of %d is not the expected "+ + "value of %d", header.Bits, expDiff) + return ruleError(ErrUnexpectedDifficulty, str) + } + // Ensure the stake difficulty specified in the block header // matches the calculated difficulty based on the previous block // and difficulty retarget rules. diff --git a/internal/blockchain/validate_test.go b/internal/blockchain/validate_test.go index 5a394b9e6a..6f46204b0a 100644 --- a/internal/blockchain/validate_test.go +++ b/internal/blockchain/validate_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2022 The Decred developers +// Copyright (c) 2015-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -455,7 +455,7 @@ func TestCheckConnectBlockTemplate(t *testing.T) { // generator's state. { origHash := bfbunsolved.BlockHash() - for chaingen.IsSolved(&bfbunsolved.Header) { + for g.IsSolved(&bfbunsolved.Header) { bfbunsolved.Header.Nonce++ } g.UpdateBlockState("bfbunsolved", origHash, "bfbunsolved", bfbunsolved) @@ -2091,6 +2091,198 @@ func TestModifiedSubsidySplitSemantics(t *testing.T) { g.AcceptTipBlock() } +// TestBlake3PowSemantics ensures that the various semantics enforced by the +// blake3 proof of work agenda behave as intended. +func TestBlake3PowSemantics(t *testing.T) { + t.Parallel() + + // Use a set of test chain parameters which allow for quicker vote + // activation as compared to various existing network params. + params := quickVoteActivationParams() + + // Clone the parameters so they can be mutated, find the correct deployment + // for the blake3 proof of work agenda and ensure it is always available to + // vote by removing the time constraints to prevent test failures when the + // real expiration time passes. + const voteID = chaincfg.VoteIDBlake3Pow + params = cloneParams(params) + deploymentVer, deployment := findDeployment(t, params, voteID) + removeDeploymentTimeConstraints(deployment) + + // Create a test harness initialized with the genesis block as the tip. + g := newChaingenHarness(t, params) + + // Convenience funcs to determine if a block is solved for the original and + // updated hash algorithms. + powLimit := params.PowLimit + isSolvedV1 := func(header *wire.BlockHeader) bool { + powHash := header.PowHashV1() + err := standalone.CheckProofOfWork(&powHash, header.Bits, powLimit) + return err == nil + } + isSolvedV2 := func(header *wire.BlockHeader) bool { + powHash := header.PowHashV2() + err := standalone.CheckProofOfWork(&powHash, header.Bits, powLimit) + return err == nil + } + + // ------------------------------------------------------------------------- + // Generate and accept enough blocks to reach stake validation height. + // + // Note that this also ensures the proof of work requirements prior to the + // activation of the agenda remains unaffected. + // ------------------------------------------------------------------------- + + g.AdvanceToStakeValidationHeight() + + // ------------------------------------------------------------------------- + // Create a block that is solved with the new hash algorithm, and unsolved + // with the original hash algorithm. + // + // The block should be rejected because the agenda is NOT active. + // + // ... + // \-> bsvhbad + // ------------------------------------------------------------------------- + + tipName := g.TipName() + outs := g.OldestCoinbaseOuts() + g.UsePowHashAlgo(chaingen.PHABlake3) + g.UsePowDiffAlgo(chaingen.PDAAsert, "bfb") + bsvhbad := g.NextBlock("bsvhbad", &outs[0], outs[1:]) + // This test requires the block to be solved for the new hash algorithm and + // unsolved for the original one, but since the difficulty is so low in the + // tests, the block might still end up being inadvertently solved. It can't + // be checked inside a munger because the block is finalized after the + // function returns and those changes could also inadvertently solve the + // block. Thus, just increment the nonce until both conditions are + // satisfied and then replace it in the generator's state. + { + origHash := bsvhbad.BlockHash() + for !isSolvedV2(&bsvhbad.Header) || isSolvedV1(&bsvhbad.Header) { + bsvhbad.Header.Nonce++ + } + g.UpdateBlockState("bsvhbad", origHash, "bsvhbad", bsvhbad) + } + g.RejectTipBlock(ErrHighHash) + + // ------------------------------------------------------------------------- + // Generate and accept enough blocks with the appropriate vote bits set to + // reach one block prior to the blake3 proof of work agenda becoming + // active. + // + // Note that this also ensures the proof of work hash prior to the + // activation of the agenda remains unaffected. + // ------------------------------------------------------------------------- + + g.SetTip(tipName) + g.UsePowHashAlgo(chaingen.PHABlake256r14) + g.UsePowDiffAlgo(chaingen.PDAEma) + g.AdvanceFromSVHToActiveAgendas(voteID) + + // replaceVers is a munge function which modifies the provided block by + // replacing the block, stake, and vote versions with the blake3 proof of + // work deployment version. + replaceVers := func(b *wire.MsgBlock) { + chaingen.ReplaceBlockVersion(int32(deploymentVer))(b) + chaingen.ReplaceStakeVersion(deploymentVer)(b) + chaingen.ReplaceVoteVersions(deploymentVer)(b) + } + + // ------------------------------------------------------------------------- + // Create a block that is solved with the original hash algorithm and + // unsolved with the new one. + // + // The block should be rejected because the agenda is active. + // + // ... + // \-> b1bad + // ------------------------------------------------------------------------- + + tipName = g.TipName() + blake3AnchorName := tipName + outs = g.OldestCoinbaseOuts() + b1bad := g.NextBlock("b1bad", &outs[0], outs[1:], replaceVers) + // This test requires the block to be solved for the original hash + // algorithm and unsolved for the updated one, but since the difficulty is + // so low in the tests, the block might still end up being inadvertently + // solved. It can't be checked inside a munger because the block is + // finalized after the function returns and those changes could also + // inadvertently solve the block. Thus, just increment the nonce until both + // conditions are satisfied and then replace it in the generator's state. + { + origHash := b1bad.BlockHash() + for !isSolvedV1(&b1bad.Header) || isSolvedV2(&b1bad.Header) { + b1bad.Header.Nonce++ + } + g.UpdateBlockState("b1bad", origHash, "b1bad", b1bad) + } + g.RejectTipBlock(ErrHighHash) + + // ------------------------------------------------------------------------- + // Create a block that is mined with the new hash and difficulty algorithms. + // + // The block should be accepted because the agenda is active. + // + // ... -> b1 + // ------------------------------------------------------------------------- + + g.SetTip(tipName) + g.UsePowHashAlgo(chaingen.PHABlake3) + g.UsePowDiffAlgo(chaingen.PDAAsert, blake3AnchorName) + g.NextBlock("b1", &outs[0], outs[1:], replaceVers) + g.SaveTipCoinbaseOuts() + g.AcceptTipBlock() + + // ------------------------------------------------------------------------- + // Create a block that is mined with the new hash and difficulty algorithms + // and has a timestamp that is behind schedule in order to cause the + // required difficulty of the next block to rise according to the new + // difficulty algorithm. + // + // The block should be accepted because the agenda is active. + // + // ... -> b1 -> b2 + // ------------------------------------------------------------------------- + + outs = g.OldestCoinbaseOuts() + g.NextBlock("b2", &outs[0], outs[1:], replaceVers, func(b *wire.MsgBlock) { + b.Header.Timestamp = b.Header.Timestamp.Add(-time.Second) + }) + g.SaveTipCoinbaseOuts() + g.AcceptTipBlock() + + // ------------------------------------------------------------------------- + // Create a block that is mined with the new hash algorithm and uses the + // version 1 difficulty algorithm. + // + // The block should be rejected because the agenda is active and the block + // claims the wrong required difficulty. + // + // ... -> b1 -> b2 + // \-> b3bad + // ------------------------------------------------------------------------- + + tipName = g.TipName() + g.UsePowDiffAlgo(chaingen.PDAEma) + outs = g.OldestCoinbaseOuts() + g.NextBlock("b3bad", &outs[0], outs[1:], replaceVers) + g.RejectTipBlock(ErrUnexpectedDifficulty) + + // ------------------------------------------------------------------------- + // Create a block that is mined with the new hash and difficulty algorithms. + // + // The block should be accepted because the agenda is active. + // + // ... -> b1 -> b2 -> b3 + // ------------------------------------------------------------------------- + + g.SetTip(tipName) + g.UsePowDiffAlgo(chaingen.PDAAsert, blake3AnchorName) + g.NextBlock("b3", &outs[0], outs[1:], replaceVers) + g.AcceptTipBlock() +} + // TestModifiedSubsidySplitR2Semantics ensures that the various semantics // enforced by the modified subsidy split round 2 agenda behave as intended. func TestModifiedSubsidySplitR2Semantics(t *testing.T) { diff --git a/internal/mining/cpuminer/cpuminer.go b/internal/mining/cpuminer/cpuminer.go index 7a04766751..ba0b4e6477 100644 --- a/internal/mining/cpuminer/cpuminer.go +++ b/internal/mining/cpuminer/cpuminer.go @@ -17,11 +17,13 @@ import ( "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/chaincfg/v3" + "github.com/decred/dcrd/crypto/blake256" "github.com/decred/dcrd/dcrutil/v4" "github.com/decred/dcrd/internal/blockchain" "github.com/decred/dcrd/internal/mining" "github.com/decred/dcrd/internal/staging/primitives" "github.com/decred/dcrd/wire" + "lukechampine.com/blake3" ) const ( @@ -97,6 +99,11 @@ type Config struct { // not either the provided block is itself known to be invalid or is // known to have an invalid ancestor. IsKnownInvalidBlock func(*chainhash.Hash) bool + + // IsBlake3PowAgendaActive returns whether or not the agenda to change the + // proof of work hash function to blake3, as defined in DCP0011, has passed + // and is now active for the block AFTER the given block. + IsBlake3PowAgendaActive func(prevHash *chainhash.Hash) (bool, error) } // CPUMiner provides facilities for solving blocks (mining) using the CPU in a @@ -193,7 +200,7 @@ out: // submitBlock submits the passed block to network after ensuring it passes all // of the consensus validation rules. -func (m *CPUMiner) submitBlock(block *dcrutil.Block) bool { +func (m *CPUMiner) submitBlock(block *dcrutil.Block, isBlake3PowActive bool) bool { m.submitBlockLock.Lock() defer m.submitBlockLock.Unlock() @@ -222,14 +229,18 @@ func (m *CPUMiner) submitBlock(block *dcrutil.Block) bool { } // The block was accepted. - coinbaseTxOuts := block.MsgBlock().Transactions[0].TxOut - coinbaseTxGenerated := int64(0) - for _, out := range coinbaseTxOuts { - coinbaseTxGenerated += out.Value + blockHash := block.Hash() + var powHashStr string + powHashFn := block.MsgBlock().PowHashV1 + if isBlake3PowActive { + powHashFn = block.MsgBlock().PowHashV2 + } + powHash := powHashFn() + if powHash != *blockHash { + powHashStr = ", pow hash " + powHash.String() } - log.Infof("Block submitted via CPU miner accepted (hash %s, height %v, "+ - "amount %v)", block.Hash(), block.Height(), - dcrutil.Amount(coinbaseTxGenerated)) + log.Infof("Block submitted via CPU miner accepted (hash %s, height %d%s)", + blockHash, block.Height(), powHashStr) return true } @@ -241,7 +252,9 @@ func (m *CPUMiner) submitBlock(block *dcrutil.Block) bool { // // This function will return early with false when the provided context is // cancelled or an unexpected error happens. -func (m *CPUMiner) solveBlock(ctx context.Context, header *wire.BlockHeader, stats *speedStats) bool { +func (m *CPUMiner) solveBlock(ctx context.Context, header *wire.BlockHeader, + stats *speedStats, isBlake3PowActive bool) bool { + // Choose a random extra nonce offset for this block template and // worker. enOffset, err := wire.RandomUint64() @@ -259,6 +272,12 @@ func (m *CPUMiner) solveBlock(ctx context.Context, header *wire.BlockHeader, sta return false } + // Choose the hash function depending on the active agendas. + powHashFn := blake256.Sum256 + if isBlake3PowActive { + powHashFn = blake3.Sum256 + } + // Serialize the header once so only the specific bytes that need to be // updated can be done in the main loops below. hdrBytes, err := header.Bytes() @@ -327,7 +346,7 @@ func (m *CPUMiner) solveBlock(ctx context.Context, header *wire.BlockHeader, sta // compute the block header hash. const nonceSerOffset = 140 littleEndian.PutUint32(hdrBytes[nonceSerOffset:], nonce) - hash := chainhash.HashH(hdrBytes) + hash := chainhash.Hash(powHashFn(hdrBytes)) hashesCompleted++ // The block is solved when the new block hash is less than the @@ -358,7 +377,9 @@ func (m *CPUMiner) solveBlock(ctx context.Context, header *wire.BlockHeader, sta // are mined when on the simulation network. // // It must be run as a goroutine. -func (m *CPUMiner) solver(ctx context.Context, template *mining.BlockTemplate, speedStats *speedStats) { +func (m *CPUMiner) solver(ctx context.Context, template *mining.BlockTemplate, + speedStats *speedStats, isBlake3PowActive bool) { + defer m.workerWg.Done() for { @@ -408,7 +429,8 @@ func (m *CPUMiner) solver(ctx context.Context, template *mining.BlockTemplate, s // The block in the template is shallow copied to avoid mutating the // data of the shared template. shallowBlockCopy := *template.Block - if m.solveBlock(ctx, &shallowBlockCopy.Header, speedStats) { + shallowBlockHdr := &shallowBlockCopy.Header + if m.solveBlock(ctx, shallowBlockHdr, speedStats, isBlake3PowActive) { // Avoid submitting any solutions that might have been found in // between the time a worker was signalled to stop and it actually // stopping. @@ -417,7 +439,7 @@ func (m *CPUMiner) solver(ctx context.Context, template *mining.BlockTemplate, s } block := dcrutil.NewBlock(&shallowBlockCopy) - if !m.submitBlock(block) { + if !m.submitBlock(block, isBlake3PowActive) { m.Lock() m.minedOnParents[prevBlock]++ m.Unlock() @@ -466,9 +488,9 @@ func (m *CPUMiner) generateBlocks(ctx context.Context, workerID uint64) { case templateNtfn := <-templateSub.C(): // Clean up the map that tracks the number of blocks mined on a // given parent whenever a template is received due to a new parent. + prevHash := templateNtfn.Template.Block.Header.PrevBlock if m.cfg.PermitConnectionlessMining { if templateNtfn.Reason == mining.TURNewParent { - prevHash := templateNtfn.Template.Block.Header.PrevBlock m.Lock() for k := range m.minedOnParents { if k != prevHash { @@ -479,14 +501,24 @@ func (m *CPUMiner) generateBlocks(ctx context.Context, workerID uint64) { } } - // Ensure the previous solver goroutine (if any) is stopped and - // start another one for the new template. + // Ensure the previous solver goroutine (if any) is stopped. if solverCancel != nil { solverCancel() } + + // Determine the state of the blake3 proof of work agenda. An error + // should never really happen here in practice, but just loop around + // and wait for another template if it does. + isBlake3PowActive, err := m.cfg.IsBlake3PowAgendaActive(&prevHash) + if err != nil { + continue + } + + // Start another goroutine for the new template. solverCtx, solverCancel = context.WithCancel(ctx) m.workerWg.Add(1) - go m.solver(solverCtx, templateNtfn.Template, &speedStats) + go m.solver(solverCtx, templateNtfn.Template, &speedStats, + isBlake3PowActive) case <-ctx.Done(): // Ensure resources associated with the solver goroutine context are @@ -751,12 +783,21 @@ out: discretePrev := m.discretePrevHash discreteBlockHash := m.discreteBlockHash m.Unlock() - if templateNtfn.Template.Block.Header.PrevBlock == discretePrev && + prevHash := templateNtfn.Template.Block.Header.PrevBlock + if prevHash == discretePrev && !m.cfg.IsKnownInvalidBlock(&discreteBlockHash) { continue } + // Determine the state of the blake3 proof of work agenda. An error + // should never really happen here in practice, but just loop around and + // wait for another template if it does. + isBlake3PowActive, err := m.cfg.IsBlake3PowAgendaActive(&prevHash) + if err != nil { + continue + } + // Attempt to solve the block. // // The function will exit with false if the block was not solved for any @@ -773,11 +814,12 @@ out: // The block in the template is shallow copied to avoid mutating the // data of the shared template. shallowBlockCopy := *templateNtfn.Template.Block - if m.solveBlock(ctx, &shallowBlockCopy.Header, &stats) { + shallowBlockHdr := &shallowBlockCopy.Header + if m.solveBlock(ctx, shallowBlockHdr, &stats, isBlake3PowActive) { block := dcrutil.NewBlock(&shallowBlockCopy) - if m.submitBlock(block) { + if m.submitBlock(block, isBlake3PowActive) { m.Lock() - m.discretePrevHash = shallowBlockCopy.Header.PrevBlock + m.discretePrevHash = shallowBlockHdr.PrevBlock m.discreteBlockHash = *block.Hash() m.Unlock() blockHashes = append(blockHashes, block.Hash()) diff --git a/internal/mining/mining.go b/internal/mining/mining.go index 73496a516a..8e313938ea 100644 --- a/internal/mining/mining.go +++ b/internal/mining/mining.go @@ -1,5 +1,5 @@ // Copyright (c) 2014-2016 The btcsuite developers -// Copyright (c) 2015-2022 The Decred developers +// Copyright (c) 2015-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -1474,7 +1474,7 @@ mempoolLoop: bestHeaderBytes, err := bestHeader.Bytes() if err != nil { str := fmt.Sprintf("failed to serialize header for block %v: %v", - bestHeader.BlockHash(), err) + best.Hash, err) return nil, makeError(ErrSerializeHeader, str) } diff --git a/internal/mining/mining_test.go b/internal/mining/mining_test.go index acf96605a2..e4e0ee051f 100644 --- a/internal/mining/mining_test.go +++ b/internal/mining/mining_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 The Decred developers +// Copyright (c) 2020-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -315,15 +315,15 @@ func TestNewBlockTemplateAutoRevocations(t *testing.T) { vote.MsgTx().TxIn[0].ValueIn = 0 // Get the best header bytes. - bestHeader, err := harness.chain.HeaderByHash(&harness.chain.bestState.Hash) + bestStateHash := harness.chain.bestState.Hash + bestHeader, err := harness.chain.HeaderByHash(&bestStateHash) if err != nil { - t.Fatalf("unable to get tip block header %v: %v", - harness.chain.bestState.Hash, err) + t.Fatalf("unable to get tip block header %v: %v", bestStateHash, err) } bestHeaderBytes, err := bestHeader.Bytes() if err != nil { - t.Fatalf("failed to serialize header for block %v: %v", - bestHeader.BlockHash(), err) + t.Fatalf("failed to serialize header for block %v: %v", bestStateHash, + err) } // Add revocations for tickets that were previously missed. diff --git a/internal/rpcserver/interface.go b/internal/rpcserver/interface.go index 109b706458..17bafa419b 100644 --- a/internal/rpcserver/interface.go +++ b/internal/rpcserver/interface.go @@ -420,6 +420,11 @@ type Chain interface { // block with the most cumulative proof of work that is valid becomes the // tip of the main chain. ReconsiderBlock(*chainhash.Hash) error + + // IsBlake3PowAgendaActive returns whether or not the agenda to change the + // proof of work hash function to blake3, as defined in DCP0011, has passed + // and is now active for the block AFTER the given block. + IsBlake3PowAgendaActive(*chainhash.Hash) (bool, error) } // Clock represents a clock for use with the RPC server. The purpose of this diff --git a/internal/rpcserver/rpcserver.go b/internal/rpcserver/rpcserver.go index 9df32554f2..8de7bb7167 100644 --- a/internal/rpcserver/rpcserver.go +++ b/internal/rpcserver/rpcserver.go @@ -42,6 +42,7 @@ import ( "github.com/decred/dcrd/blockchain/standalone/v2" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/chaincfg/v3" + "github.com/decred/dcrd/crypto/blake256" "github.com/decred/dcrd/database/v3" "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" "github.com/decred/dcrd/dcrjson/v4" @@ -78,19 +79,30 @@ const ( // 256-bit integer. uint256Size = 32 - // getworkDataLen is the length of the data field of the getwork RPC. - // It consists of the serialized block header plus the internal blake256 - // padding. The internal blake256 padding consists of a single 1 bit - // followed by zeros and a final 1 bit in order to pad the message out - // to 56 bytes followed by length of the message in bits encoded as a - // big-endian uint64 (8 bytes). Thus, the resulting length is a - // multiple of the blake256 block size (64 bytes). Given the padding - // requires at least a 1 bit and 64 bits for the padding, the following - // converts the block header length and hash block size to bits in order - // to ensure the correct number of hash blocks are calculated and then - // multiplies the result by the block hash block size in bytes. - getworkDataLen = (1 + ((wire.MaxBlockHeaderPayload*8 + 65) / - (chainhash.HashBlockSize * 8))) * chainhash.HashBlockSize + // getworkDataLenBlake256 is the length of the data field of the getwork + // RPC when providing work for blake256. It consists of the serialized + // block header plus the internal blake256 padding. The internal blake256 + // padding consists of a single 1 bit followed by zeros and a final 1 bit in + // order to pad the message out to 56 bytes followed by length of the + // message in bits encoded as a big-endian uint64 (8 bytes). Thus, the + // resulting length is a multiple of the blake256 block size (64 bytes). + // Given the padding requires at least a 1 bit and 64 bits for the padding, + // the following converts the block header length and hash block size to + // bits in order to ensure the correct number of hash blocks are calculated + // and then multiplies the result by the block hash block size in bytes. + getworkDataLenBlake256 = (1 + ((wire.MaxBlockHeaderPayload*8 + 65) / + (blake256.BlockSize * 8))) * blake256.BlockSize + + // blake3BlkSize is the internal block size of the blake3 hashing algorithm. + blake3BlkSize = 64 + + // getworkDataLenBlake3 is the length of the data field of the getwork RPC + // when providing work for blake3. It consists of the serialized block + // header plus the internal blake3 padding. The internal blake3 padding + // consists of enough zeros to pad the message out to a multiple of the + // blake3 block size (64 bytes). + getworkDataLenBlake3 = ((wire.MaxBlockHeaderPayload + (blake3BlkSize - 1)) / + blake3BlkSize) * blake3BlkSize // getworkExpirationDiff is the number of blocks below the current // best block in height to begin pruning out old block work from @@ -122,6 +134,10 @@ var ( // calculation. blake256Pad []byte + // blake3Pad is the extra blake3 internal padding needed for the data of the + // getwork RPC. It consists of all zeros. + blake3Pad = make([]byte, getworkDataLenBlake3-wire.MaxBlockHeaderPayload) + // JSON 2.0 batched request prefix batchedRequestPrefix = []byte("[") @@ -1755,7 +1771,7 @@ func handleGenerate(ctx context.Context, s *Server, cmd interface{}) (interface{ c := cmd.(*types.GenerateCmd) - // Respond with an error if the client is requesting 0 blocks to be generated. + // Respond with an error when no blocks are requested. if c.NumBlocks == 0 { return nil, rpcInternalError("Invalid number of blocks", "Configuration") @@ -3690,6 +3706,46 @@ func getWorkTemplateKey(header *wire.BlockHeader) [merkleRootPairSize]byte { return merkleRootPair } +// serializeGetWorkData returns serialized data that represents work to be +// solved and is used in the getwork RPC and notifywork WebSocket notification. +// It consists of the serialized block header along with any internal padding +// that makes the data ready for callers to make use of only the final chunk +// along with the midstate for the rest when solving the block. +func serializeGetWorkData(header *wire.BlockHeader, isBlake3PowActive bool) ([]byte, error) { + // Choose the full buffer data length as well as internal padding to apply + // based on the result of the vote for the blake3 proof of work agenda. + getworkDataLen := getworkDataLenBlake256 + internalHashFuncPad := blake256Pad + if isBlake3PowActive { + getworkDataLen = getworkDataLenBlake3 + internalHashFuncPad = blake3Pad + } + + // Serialize the block header into a buffer large enough to hold the block + // header and any internal padding that is added and returned as part of the + // data below. + // + // For reference (0-index based, end value is exclusive): + // data[115:119] --> Bits + // data[135:139] --> Timestamp + // data[139:143] --> Nonce + // data[143:151] --> ExtraNonce + data := make([]byte, 0, getworkDataLen) + buf := bytes.NewBuffer(data) + err := header.Serialize(buf) + if err != nil { + context := "Failed to serialize data" + return nil, rpcInternalError(err.Error(), context) + } + + // Expand the data slice to include the full data buffer and apply internal + // padding for the hash function. This makes the data ready for callers to + // make use of only the final chunk along with the midstate for the rest. + data = data[:getworkDataLen] + copy(data[wire.MaxBlockHeaderPayload:], internalHashFuncPad) + return data, nil +} + // handleGetWorkRequest is a helper for handleGetWork which deals with // generating and returning work to the caller. func handleGetWorkRequest(ctx context.Context, s *Server) (interface{}, error) { @@ -3786,21 +3842,17 @@ func handleGetWorkRequest(ctx context.Context, s *Server) (interface{}, error) { headerCopy := template.Block.Header bt.UpdateBlockTime(&headerCopy) - // Serialize the block header into a buffer large enough to hold the - // the block header and the internal blake256 padding that is added and - // retuned as part of the data below. - // - // For reference (0-index based, end value is exclusive): - // data[116:120] --> Bits - // data[136:140] --> Timestamp - // data[140:144] --> Nonce - // data[144:152] --> ExtraNonce - data := make([]byte, 0, getworkDataLen) - buf := bytes.NewBuffer(data) - err := headerCopy.Serialize(buf) + // Serialize the data that represents work to be solved as well as any + // internal padding that makes the data ready for callers to make use of + // only the final chunk along with the midstate for the rest when solving + // the block. + isBlake3PowActive, err := s.isBlake3PowAgendaActive(&headerCopy.PrevBlock) if err != nil { - context := "Failed to serialize data" - return nil, rpcInternalError(err.Error(), context) + return nil, err + } + data, err := serializeGetWorkData(&headerCopy, isBlake3PowActive) + if err != nil { + return nil, err } // Add the template to the template pool. Since the key is a combination @@ -3811,13 +3863,6 @@ func handleGetWorkRequest(ctx context.Context, s *Server) (interface{}, error) { state.templatePool[templateKey] = template.Block state.Unlock() - // Expand the data slice to include the full data buffer and apply the - // internal blake256 padding. This makes the data ready for callers to - // make use of only the final chunk along with the midstate for the - // rest. - data = data[:getworkDataLen] - copy(data[wire.MaxBlockHeaderPayload:], blake256Pad) - // The target is in big endian, but it is treated as a uint256 and byte // swapped to little endian in the final result. Even though there is // really no reason for it to be swapped, it is a holdover from legacy code @@ -3830,10 +3875,43 @@ func handleGetWorkRequest(ctx context.Context, s *Server) (interface{}, error) { return reply, nil } +// minInt is a helper function to return the minimum of two ints. This avoids +// the need to cast to floats. +func minInt(a, b int) int { + if a < b { + return a + } + return b +} + +// maxInt is a helper function to return the maximum of two ints. This avoids +// the need to cast to floats. +func maxInt(a, b int) int { + if a > b { + return a + } + return b +} + // handleGetWorkSubmission is a helper for handleGetWork which deals with // the calling submitting work to be verified and processed. func handleGetWorkSubmission(_ context.Context, s *Server, hexData string) (interface{}, error) { // Ensure the provided data is sane. + minDataLen := minInt(getworkDataLenBlake256, getworkDataLenBlake3) + maxDataLen := maxInt(getworkDataLenBlake256, getworkDataLenBlake3) + paddedHexDataLen := len(hexData) + len(hexData)%2 + if paddedHexDataLen < minDataLen*2 || paddedHexDataLen > maxDataLen*2 { + if minDataLen == maxDataLen { + return nil, rpcInvalidError("Argument must be a hexadecimal string "+ + "with length %d (not %d)", minDataLen*2, len(hexData)) + } + return nil, rpcInvalidError("Argument must be a hexadecimal string "+ + "with a length between %d and %d (not %d)", minDataLen*2, + maxDataLen*2, len(hexData)) + } + + // Decode the provided hex data while padding the front with a 0 for + // odd-length strings since the decoder requires even-length strings. if len(hexData)%2 != 0 { hexData = "0" + hexData } @@ -3841,10 +3919,6 @@ func handleGetWorkSubmission(_ context.Context, s *Server, hexData string) (inte if err != nil { return false, rpcDecodeHexError(hexData) } - if len(data) != getworkDataLen { - return nil, rpcInvalidError("Argument must be %d bytes (not "+ - "%d)", getworkDataLen, len(data)) - } // Deserialize the block header from the data. var submittedHeader wire.BlockHeader @@ -3854,9 +3928,31 @@ func handleGetWorkSubmission(_ context.Context, s *Server, hexData string) (inte return false, rpcInvalidError("Invalid block header: %v", err) } - // Ensure the submitted block hash is less than the target difficulty. - blockHash := submittedHeader.BlockHash() - err = standalone.CheckProofOfWork(&blockHash, submittedHeader.Bits, + // Reject orphan blocks. This is done here to provide nicer feedback about + // why the block was rejected since attempting to determine the state of a + // voting agenda requires all previous blocks to be known. + prevBlkHash := &submittedHeader.PrevBlock + if _, err := s.cfg.Chain.HeaderByHash(prevBlkHash); err != nil { + log.Infof("Block submitted via getwork rejected: orphan building on "+ + "parent %v", prevBlkHash) + return false, nil + } + + // Choose the proof of work mining algorithm based on the result of the vote + // for the blake3 proof of work agenda. + isBlake3PowActive, err := s.isBlake3PowAgendaActive(prevBlkHash) + if err != nil { + return false, err + } + powHashFn := submittedHeader.PowHashV1 + if isBlake3PowActive { + powHashFn = submittedHeader.PowHashV2 + } + + // Ensure the submitted proof of work hash is less than the target + // difficulty. + powHash := powHashFn() + err = standalone.CheckProofOfWork(&powHash, submittedHeader.Bits, s.cfg.ChainParams.PowLimit) if err != nil { // Anything other than a rule violation is an unexpected error, so @@ -3897,12 +3993,6 @@ func handleGetWorkSubmission(_ context.Context, s *Server, hexData string) (inte // nodes. This will in turn relay it to the network like normal. err = s.cfg.SyncMgr.SubmitBlock(block) if err != nil { - if errors.Is(err, blockchain.ErrMissingParent) { - log.Infof("Block submitted via getwork rejected: orphan building "+ - "on parent %v", block.MsgBlock().Header.PrevBlock) - return false, nil - } - // Anything other than a rule violation is an unexpected error, // so return that error as an internal error. var rErr blockchain.RuleError @@ -3916,8 +4006,13 @@ func handleGetWorkSubmission(_ context.Context, s *Server, hexData string) (inte } // The block was accepted. - log.Infof("Block submitted via getwork accepted: %s (height %d)", - block.Hash(), msgBlock.Header.Height) + var powHashStr string + blockHash := block.Hash() + if *blockHash != powHash { + powHashStr = ", pow hash " + powHash.String() + } + log.Infof("Block submitted via getwork accepted: %s (height %d%s)", + blockHash, msgBlock.Header.Height, powHashStr) return true, nil } @@ -4976,6 +5071,20 @@ func (s *Server) isSubsidySplitAgendaActive(prevBlkHash *chainhash.Hash) (bool, return isSubsidySplitEnabled, nil } +// isBlake3PowAgendaActive returns whether or not the agenda to change the proof +// of work hash function to blake3 is active or not for the block AFTER the +// provided block hash. +func (s *Server) isBlake3PowAgendaActive(prevBlkHash *chainhash.Hash) (bool, error) { + chain := s.cfg.Chain + isActive, err := chain.IsBlake3PowAgendaActive(prevBlkHash) + if err != nil { + context := fmt.Sprintf("Could not obtain blake3 proof of work "+ + "agenda status for block %s", prevBlkHash) + return false, rpcInternalError(err.Error(), context) + } + return isActive, nil +} + // isSubsidySplitR2AgendaActive returns if the modified subsidy split round 2 // agenda is active or not for the block AFTER the provided block hash. func (s *Server) isSubsidySplitR2AgendaActive(prevBlkHash *chainhash.Hash) (bool, error) { @@ -5929,7 +6038,7 @@ func init() { // length is a multiple of the blake256 block size (64 bytes). Since // the block header is a fixed size, it only needs to be calculated // once. - blake256Pad = make([]byte, getworkDataLen-wire.MaxBlockHeaderPayload) + blake256Pad = make([]byte, getworkDataLenBlake256-wire.MaxBlockHeaderPayload) blake256Pad[0] = 0x80 blake256Pad[len(blake256Pad)-9] |= 0x01 binary.BigEndian.PutUint64(blake256Pad[len(blake256Pad)-8:], diff --git a/internal/rpcserver/rpcserverhandlers_test.go b/internal/rpcserver/rpcserverhandlers_test.go index 9fdd99a263..7e7ac83068 100644 --- a/internal/rpcserver/rpcserverhandlers_test.go +++ b/internal/rpcserver/rpcserverhandlers_test.go @@ -197,6 +197,8 @@ type testRPCChain struct { treasuryActiveErr error subsidySplitActive bool subsidySplitActiveErr error + blake3PowActive bool + blake3PowActiveErr error subsidySplitR2Active bool subsidySplitR2ActiveErr error } @@ -438,6 +440,12 @@ func (c *testRPCChain) IsSubsidySplitAgendaActive(*chainhash.Hash) (bool, error) return c.subsidySplitActive, c.subsidySplitActiveErr } +// IsBlake3PowAgendaActive returns a mocked bool representing whether or not the +// blake3 proof of work agenda is active. +func (c *testRPCChain) IsBlake3PowAgendaActive(*chainhash.Hash) (bool, error) { + return c.blake3PowActive, c.blake3PowActiveErr +} + // IsSubsidySplitR2AgendaActive returns a mocked bool representing whether or // not the modified subsidy split round 2 agenda is active. func (c *testRPCChain) IsSubsidySplitR2AgendaActive(*chainhash.Hash) (bool, error) { @@ -5660,18 +5668,15 @@ func TestHandleGetWork(t *testing.T) { mockPowLimitBig := mockPowLimit.ToBig() mockPowLimitBits := standalone.BigToCompact(mockPowLimitBig) - serializeGetWorkData := func(header *wire.BlockHeader) []byte { - data := make([]byte, 0, getworkDataLen) - buf := bytes.NewBuffer(data) - err := header.Serialize(buf) + serializeGetWorkDataBlake256 := func(header *wire.BlockHeader) []byte { + const isBlake3PowActive = false + data, err := serializeGetWorkData(header, isBlake3PowActive) if err != nil { t.Fatalf("unexpected serialize error: %v", err) } - data = data[:getworkDataLen] - copy(data[wire.MaxBlockHeaderPayload:], blake256Pad) return data } - data := serializeGetWorkData(&block432100.Header) + data := serializeGetWorkDataBlake256(&block432100.Header) submissionB := make([]byte, hex.EncodedLen(len(data))) hex.Encode(submissionB, data) @@ -5682,7 +5687,7 @@ func TestHandleGetWork(t *testing.T) { var buf bytes.Buffer buf.Write(submissionB[:10]) buf.WriteRune('g') - buf.Write(submissionB[10:]) + buf.Write(submissionB[10 : len(submissionB)-2]) invalidHexSub := buf.String() buf.Reset() @@ -5691,6 +5696,23 @@ func TestHandleGetWork(t *testing.T) { buf.Write(submissionB[240:]) invalidPOWSub := buf.String() + // Create a mock block solved by blake3 based on the existing test block. + solvedBlake3Block := func() *wire.MsgBlock { + isSolved := func(header *wire.BlockHeader) bool { + powHash := header.PowHashV2() + err := standalone.CheckProofOfWork(&powHash, header.Bits, + mockPowLimitBig) + return err == nil + } + blk := block432100 + header := &blk.Header + header.Bits = mockPowLimitBits + for !isSolved(header) { + header.Nonce++ + } + return &blk + }() + testRPCServerHandler(t, []rpcTest{{ name: "handleGetWork: CPU IsMining enabled", handler: handleGetWork, @@ -5831,7 +5853,7 @@ func TestHandleGetWork(t *testing.T) { // Create an orphan block by mutating the previous block field // and solving the block. isSolved := func(header *wire.BlockHeader) bool { - powHash := header.BlockHash() + powHash := header.PowHashV1() err := standalone.CheckProofOfWork(&powHash, header.Bits, mockPowLimitBig) return err == nil @@ -5843,7 +5865,8 @@ func TestHandleGetWork(t *testing.T) { header.Nonce++ } - encoded := hex.EncodeToString(serializeGetWorkData(&header)) + data := serializeGetWorkDataBlake256(&header) + encoded := hex.EncodeToString(data) return &encoded }(), }, @@ -5854,10 +5877,10 @@ func TestHandleGetWork(t *testing.T) { return params }(), mockMiningState: defaultMockMiningState(), - mockSyncManager: func() *testSyncManager { - syncManager := defaultMockSyncManager() - syncManager.submitBlockErr = blockchain.ErrMissingParent - return syncManager + mockChain: func() *testRPCChain { + chain := defaultMockRPCChain() + chain.headerByHashErr = blockchain.ErrUnknownBlock + return chain }(), result: false, }, { @@ -5882,6 +5905,66 @@ func TestHandleGetWork(t *testing.T) { }, mockMiningState: defaultMockMiningState(), result: true, + }, { + name: "handleGetWork: blake3 submission ok", + handler: handleGetWork, + cmd: &types.GetWorkCmd{ + Data: func() *string { + const isBlake3PowActive = true + data, err := serializeGetWorkData(&solvedBlake3Block.Header, + isBlake3PowActive) + if err != nil { + t.Fatalf("unexpected serialize error: %v", err) + } + encoded := hex.EncodeToString(data) + return &encoded + }(), + }, + mockChainParams: func() *chaincfg.Params { + params := cloneParams(defaultChainParams) + params.PowLimit = mockPowLimitBig + params.PowLimitBits = mockPowLimitBits + return params + }(), + mockMiningState: func() *testMiningState { + mockMiningState := defaultMockMiningState() + tmplKey := getWorkTemplateKey(&solvedBlake3Block.Header) + workState := newWorkState() + workState.templatePool[tmplKey] = solvedBlake3Block + workState.prevBestHash = &solvedBlake3Block.Header.PrevBlock + mockMiningState.workState = workState + return mockMiningState + }(), + mockChain: func() *testRPCChain { + chain := defaultMockRPCChain() + chain.blake3PowActive = true + return chain + }(), + result: true, + }, { + name: "handleGetWork: submission hex too short", + handler: handleGetWork, + cmd: &types.GetWorkCmd{ + Data: func() *string { + submissionHex := string(submissionB[:getworkDataLenBlake256-1]) + return &submissionHex + }(), + }, + mockMiningState: defaultMockMiningState(), + wantErr: true, + errCode: dcrjson.ErrRPCInvalidParameter, + }, { + name: "handleGetWork: submission hex too long", + handler: handleGetWork, + cmd: &types.GetWorkCmd{ + Data: func() *string { + submissionHex := string(submissionB) + "a" + return &submissionHex + }(), + }, + mockMiningState: defaultMockMiningState(), + wantErr: true, + errCode: dcrjson.ErrRPCInvalidParameter, }, { name: "handleGetWork: invalid submission hex", handler: handleGetWork, @@ -5891,6 +5974,20 @@ func TestHandleGetWork(t *testing.T) { mockMiningState: defaultMockMiningState(), wantErr: true, errCode: dcrjson.ErrRPCDecodeHexString, + }, { + name: "handleGetWork: unable to obtain blake3 pow agenda status", + handler: handleGetWork, + cmd: &types.GetWorkCmd{ + Data: &submission, + }, + mockMiningState: defaultMockMiningState(), + mockChain: func() *testRPCChain { + chain := defaultMockRPCChain() + chain.blake3PowActiveErr = blockchain.ErrUnknownBlock + return chain + }(), + wantErr: true, + errCode: dcrjson.ErrRPCInternal.Code, }, { name: "handleGetWork: invalid proof of work", handler: handleGetWork, diff --git a/internal/rpcserver/rpcwebsocket.go b/internal/rpcserver/rpcwebsocket.go index b30c82db86..229dc81a97 100644 --- a/internal/rpcserver/rpcwebsocket.go +++ b/internal/rpcserver/rpcwebsocket.go @@ -834,41 +834,26 @@ func (m *wsNotificationManager) notifyWork(clients map[chan struct{}]*wsClient, return } - // Serialize the block header into a buffer large enough to hold the - // the block header and the internal blake256 padding that is added and - // retuned as part of the data below. - // - // For reference (0-index based, end value is exclusive): - // data[115:119] --> Bits - // data[135:139] --> Timestamp - // data[139:143] --> Nonce - // data[143:151] --> ExtraNonce + // Serialize the data that represents work to be solved as well as any + // internal padding that makes the data ready for callers to make use of + // only the final chunk along with the midstate for the rest when solving + // the block. header := &templateNtfn.Template.Block.Header - data := make([]byte, 0, getworkDataLen) - buf := bytes.NewBuffer(data) - err := header.Serialize(buf) + isBlake3PowActive, err := m.server.isBlake3PowAgendaActive(&header.PrevBlock) + if err != nil { + log.Errorf("Could not obtain blake3 agenda status: %v", err) + return + } + data, err := serializeGetWorkData(header, isBlake3PowActive) if err != nil { log.Errorf("Failed to serialize data: %v", err) return } - // Expand the data slice to include the full data buffer and apply the - // internal blake256 padding. This makes the data ready for callers to - // make use of only the final chunk along with the midstate for the - // rest. - data = data[:getworkDataLen] - copy(data[wire.MaxBlockHeaderPayload:], blake256Pad) - - // The final result reverses each of the fields to little endian. In - // particular, the data, hash1, and midstate fields are treated as - // arrays of uint32s (per the internal sha256 hashing state) which are - // in big endian, and thus each 4 bytes is byte swapped. The target is - // also in big endian, but it is treated as a uint256 and byte swapped - // to little endian accordingly. - // - // The fact the fields are reversed in this way is rather odd and likey - // an artifact of some legacy internal state in the reference - // implementation, but it is required for compatibility. + // The target is in big endian, but it is treated as a uint256 and byte + // swapped to little endian in the final result. Even though there is + // really no reason for it to be swapped, it is a holdover from legacy code + // and is now required for compatibility. target := bigToLEUint256(standalone.CompactToBig(header.Bits)) ntfn := types.WorkNtfn{ Data: hex.EncodeToString(data), diff --git a/internal/staging/primitives/pow.go b/internal/staging/primitives/pow.go index a13a4cb2e6..8d67088a50 100644 --- a/internal/staging/primitives/pow.go +++ b/internal/staging/primitives/pow.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 The Decred developers +// Copyright (c) 2021-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -217,7 +217,7 @@ func checkProofOfWorkRange(diffBits uint32, powLimit *uint256.Uint256) (uint256. } if overflows { str := fmt.Sprintf("target difficulty bits %08x is higher than the "+ - "max limit %x", diffBits, powLimit) + "max limit %064x", diffBits, powLimit) return uint256.Uint256{}, ruleError(ErrUnexpectedDifficulty, str) } if target.IsZero() { @@ -227,7 +227,7 @@ func checkProofOfWorkRange(diffBits uint32, powLimit *uint256.Uint256) (uint256. // The target difficulty must not exceed the maximum allowed. if target.Gt(powLimit) { - str := fmt.Sprintf("target difficulty of %x is higher than max of %x", + str := fmt.Sprintf("target difficulty %064x is higher than max %064x", target, powLimit) return uint256.Uint256{}, ruleError(ErrUnexpectedDifficulty, str) } @@ -243,20 +243,20 @@ func CheckProofOfWorkRange(diffBits uint32, powLimit *uint256.Uint256) error { return err } -// CheckProofOfWork ensures the provided block hash is less than the target -// difficulty represented by given header bits and that said difficulty is in -// min/max range per the provided proof-of-work limit. -func CheckProofOfWork(blockHash *chainhash.Hash, diffBits uint32, powLimit *uint256.Uint256) error { +// CheckProofOfWork ensures the provided hash is less than the target difficulty +// represented by given header bits and that said difficulty is in min/max range +// per the provided proof-of-work limit. +func CheckProofOfWork(powHash *chainhash.Hash, diffBits uint32, powLimit *uint256.Uint256) error { target, err := checkProofOfWorkRange(diffBits, powLimit) if err != nil { return err } // The block hash must be less than the target difficulty. - hashNum := HashToUint256(blockHash) + hashNum := HashToUint256(powHash) if hashNum.Gt(&target) { - str := fmt.Sprintf("block hash of %x is higher than expected max of %x", - hashNum, target) + str := fmt.Sprintf("proof of work hash %064x is higher than expected "+ + "max of %064x", hashNum, target) return ruleError(ErrHighHash, str) } diff --git a/server.go b/server.go index a3a8ea6743..c475ab682e 100644 --- a/server.go +++ b/server.go @@ -3679,6 +3679,7 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, ConnectedCount: s.ConnectedCount, IsCurrent: s.syncManager.IsCurrent, IsKnownInvalidBlock: s.chain.IsKnownInvalidBlock, + IsBlake3PowAgendaActive: s.chain.IsBlake3PowAgendaActive, }) } diff --git a/wire/blockheader.go b/wire/blockheader.go index ddbde4c102..2893525d03 100644 --- a/wire/blockheader.go +++ b/wire/blockheader.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2015-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -11,6 +11,7 @@ import ( "time" "github.com/decred/dcrd/chaincfg/chainhash" + "lukechampine.com/blake3" ) // MaxBlockHeaderPayload is the maximum number of bytes a block header can be. @@ -89,16 +90,36 @@ const blockHeaderLen = 180 // BlockHash computes the block identifier hash for the given block header. func (h *BlockHeader) BlockHash() chainhash.Hash { - // Encode the header and hash256 everything prior to the number of - // transactions. Ignore the error returns since there is no way the - // encode could fail except being out of memory which would cause a - // run-time panic. + // Encode the header and hash everything prior to the number of + // transactions. Ignore the error returns since there is no way the encode + // could fail except being out of memory which would cause a run-time panic. buf := bytes.NewBuffer(make([]byte, 0, MaxBlockHeaderPayload)) _ = writeBlockHeader(buf, 0, h) return chainhash.HashH(buf.Bytes()) } +// PowHashV1 calculates and returns the version 1 proof of work hash for the +// block header. +// +// NOTE: This is the original proof of work hash function used at Decred launch +// and applies to all blocks prior to the activation of DCP0011. +func (h *BlockHeader) PowHashV1() chainhash.Hash { + return h.BlockHash() +} + +// PowHashV2 calculates and returns the version 2 proof of work hash as defined +// in DCP0011 for the block header. +func (h *BlockHeader) PowHashV2() chainhash.Hash { + // Encode the header and hash everything prior to the number of + // transactions. Ignore the error returns since there is no way the encode + // could fail except being out of memory which would cause a run-time panic. + buf := bytes.NewBuffer(make([]byte, 0, MaxBlockHeaderPayload)) + _ = writeBlockHeader(buf, 0, h) + + return blake3.Sum256(buf.Bytes()) +} + // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. // See Deserialize for decoding block headers stored to disk, such as in a diff --git a/wire/go.mod b/wire/go.mod index 373fe2e83d..ac0c89fd6a 100644 --- a/wire/go.mod +++ b/wire/go.mod @@ -5,6 +5,10 @@ go 1.17 require ( github.com/davecgh/go-spew v1.1.1 github.com/decred/dcrd/chaincfg/chainhash v1.0.2 + lukechampine.com/blake3 v1.2.1 ) -require github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect +require ( + github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect +) diff --git a/wire/go.sum b/wire/go.sum index 1fb8a4c466..0a2bccca17 100644 --- a/wire/go.sum +++ b/wire/go.sum @@ -4,3 +4,7 @@ github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyL github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= diff --git a/wire/msgblock.go b/wire/msgblock.go index 5952452813..113b140e4e 100644 --- a/wire/msgblock.go +++ b/wire/msgblock.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -359,6 +359,21 @@ func (msg *MsgBlock) BlockHash() chainhash.Hash { return msg.Header.BlockHash() } +// PowHashV1 calculates and returns the version 1 proof of work hash for the +// block. +// +// NOTE: This is the original proof of work hash that was used at Decred launch +// and applies to all blocks prior to the activation of DCP0011. +func (msg *MsgBlock) PowHashV1() chainhash.Hash { + return msg.Header.PowHashV1() +} + +// PowHashV2 calculates and returns the version 2 proof of work hash as defined +// in DCP0011 for the block. +func (msg *MsgBlock) PowHashV2() chainhash.Hash { + return msg.Header.PowHashV2() +} + // TxHashes returns a slice of hashes of all of transactions in this block. func (msg *MsgBlock) TxHashes() []chainhash.Hash { hashList := make([]chainhash.Hash, 0, len(msg.Transactions))