From 4133596ddbc80d00c8ea469b984f6747633542b7 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Tue, 6 Aug 2019 08:42:04 -0500 Subject: [PATCH] blockchain: Use standalone module for work funcs. This updates blockchain to make use of the new work functions in the standalone module. Finally, it also deprecates the related exported functions in the blockchain module so they can be removed in the next major version and rewrites them in terms of the standalone module in the mean time. --- blockchain/blockindex.go | 3 +- blockchain/chainio_test.go | 5 +- blockchain/difficulty.go | 108 ++++++++----------------------------- blockchain/process.go | 5 +- blockchain/upgrade.go | 3 +- blockchain/validate.go | 65 ++++++++++++---------- 6 files changed, 68 insertions(+), 121 deletions(-) diff --git a/blockchain/blockindex.go b/blockchain/blockindex.go index 45ec24c7..716153cc 100644 --- a/blockchain/blockindex.go +++ b/blockchain/blockindex.go @@ -13,6 +13,7 @@ import ( "time" "github.com/decred/dcrd/blockchain/stake" + "github.com/decred/dcrd/blockchain/standalone" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/database" "github.com/decred/dcrd/wire" @@ -137,7 +138,7 @@ type blockNode struct { func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parent *blockNode) { *node = blockNode{ hash: blockHeader.BlockHash(), - workSum: CalcWork(blockHeader.Bits), + workSum: standalone.CalcWork(blockHeader.Bits), height: int64(blockHeader.Height), blockVersion: blockHeader.Version, voteBits: blockHeader.VoteBits, diff --git a/blockchain/chainio_test.go b/blockchain/chainio_test.go index c70602c2..aa1de01d 100644 --- a/blockchain/chainio_test.go +++ b/blockchain/chainio_test.go @@ -15,6 +15,7 @@ import ( "time" "github.com/decred/dcrd/blockchain/stake" + "github.com/decred/dcrd/blockchain/standalone" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/database" "github.com/decred/dcrd/wire" @@ -1385,7 +1386,7 @@ func TestBestChainStateSerialization(t *testing.T) { totalTxns: 1, totalSubsidy: 0, workSum: func() *big.Int { - workSum.Add(workSum, CalcWork(486604799)) + workSum.Add(workSum, standalone.CalcWork(486604799)) return new(big.Int).Set(workSum) }(), // 0x0100010001 }, @@ -1399,7 +1400,7 @@ func TestBestChainStateSerialization(t *testing.T) { totalTxns: 2, totalSubsidy: 123456789, workSum: func() *big.Int { - workSum.Add(workSum, CalcWork(486604799)) + workSum.Add(workSum, standalone.CalcWork(486604799)) return new(big.Int).Set(workSum) }(), // 0x0200020002, }, diff --git a/blockchain/difficulty.go b/blockchain/difficulty.go index cb9cd338..1a99dc29 100644 --- a/blockchain/difficulty.go +++ b/blockchain/difficulty.go @@ -10,6 +10,7 @@ import ( "math/big" "time" + "github.com/decred/dcrd/blockchain/standalone" "github.com/decred/dcrd/chaincfg" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/wire" @@ -31,16 +32,10 @@ var ( // HashToBig converts a chainhash.Hash into a big.Int that can be used to // perform math comparisons. +// +// Deprecated: Use standalone.HashToBig instead. func HashToBig(hash *chainhash.Hash) *big.Int { - // A Hash is in little-endian, but the big package wants the bytes in - // big-endian, so reverse them. - buf := *hash - blen := len(buf) - for i := 0; i < blen/2; i++ { - buf[i], buf[blen-1-i] = buf[blen-1-i], buf[i] - } - - return new(big.Int).SetBytes(buf[:]) + return standalone.HashToBig(hash) } // CompactToBig converts a compact representation of a whole number N to an @@ -66,74 +61,20 @@ func HashToBig(hash *chainhash.Hash) *big.Int { // 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. +// +// Deprecated: Use standalone.CompactToBig instead. 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 + return standalone.CompactToBig(compact) } // 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. +// +// Deprecated: Use standalone.BigToCompact instead. 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 + return standalone.BigToCompact(n) } // CalcWork calculates a work value from difficulty bits. Decred increases @@ -146,18 +87,10 @@ func BigToCompact(n *big.Int) uint32 { // accumulated must be the inverse of the difficulty. Also, in order to avoid // potential division by zero and really small floating point numbers, the // result adds 1 to the denominator and multiplies the numerator by 2^256. +// +// Deprecated: Use standalone.CalcWork instead. func CalcWork(bits uint32) *big.Int { - // Return a work value of zero if the passed difficulty bits represent - // a negative number. Note this should not happen in practice with valid - // blocks, but an invalid block could trigger it. - difficultyNum := CompactToBig(bits) - if difficultyNum.Sign() <= 0 { - return big.NewInt(0) - } - - // (1 << 256) / (difficultyNum + 1) - denominator := new(big.Int).Add(difficultyNum, bigOne) - return new(big.Int).Div(oneLsh256, denominator) + return standalone.CalcWork(bits) } // calcEasiestDifficulty calculates the easiest possible difficulty that a block @@ -183,7 +116,7 @@ func (b *BlockChain) calcEasiestDifficulty(bits uint32, duration time.Duration) // difficulty for a given duration is the largest value possible given // the number of retargets for the duration and starting difficulty // multiplied by the max adjustment factor. - newTarget := CompactToBig(bits) + newTarget := standalone.CompactToBig(bits) for durationVal > 0 && newTarget.Cmp(b.chainParams.PowLimit) < 0 { newTarget.Mul(newTarget, adjustmentFactor) durationVal -= maxRetargetTimespan @@ -194,7 +127,7 @@ func (b *BlockChain) calcEasiestDifficulty(bits uint32, duration time.Duration) newTarget.Set(b.chainParams.PowLimit) } - return BigToCompact(newTarget) + return standalone.BigToCompact(newTarget) } // findPrevTestNetDifficulty returns the difficulty of the previous block which @@ -231,7 +164,7 @@ func (b *BlockChain) calcNextRequiredDifficulty(curNode *blockNode, newBlockTime // Get the old difficulty; if we aren't at a block height where it changes, // just return this. oldDiff := curNode.bits - oldDiffBig := CompactToBig(curNode.bits) + oldDiffBig := standalone.CompactToBig(curNode.bits) // We're not at a retarget point, return the oldDiff. if (curNode.height+1)%b.chainParams.WorkDiffWindowSize != 0 { @@ -259,9 +192,9 @@ func (b *BlockChain) calcNextRequiredDifficulty(curNode *blockNode, newBlockTime // Declare some useful variables. RAFBig := big.NewInt(b.chainParams.RetargetAdjustmentFactor) - nextDiffBigMin := CompactToBig(curNode.bits) + nextDiffBigMin := standalone.CompactToBig(curNode.bits) nextDiffBigMin.Div(nextDiffBigMin, RAFBig) - nextDiffBigMax := CompactToBig(curNode.bits) + nextDiffBigMax := standalone.CompactToBig(curNode.bits) nextDiffBigMax.Mul(nextDiffBigMax, RAFBig) alpha := b.chainParams.WorkDiffAlpha @@ -367,10 +300,11 @@ func (b *BlockChain) calcNextRequiredDifficulty(curNode *blockNode, newBlockTime // intentionally converting the bits back to a number instead of using // newTarget since conversion to the compact representation loses // precision. - nextDiffBits := BigToCompact(nextDiffBig) + nextDiffBits := standalone.BigToCompact(nextDiffBig) log.Debugf("Difficulty retarget at block height %d", curNode.height+1) log.Debugf("Old target %08x (%064x)", curNode.bits, oldDiffBig) - log.Debugf("New target %08x (%064x)", nextDiffBits, CompactToBig(nextDiffBits)) + log.Debugf("New target %08x (%064x)", nextDiffBits, standalone.CompactToBig( + nextDiffBits)) return nextDiffBits, nil } diff --git a/blockchain/process.go b/blockchain/process.go index 693b4b8f..4a8ddccd 100644 --- a/blockchain/process.go +++ b/blockchain/process.go @@ -9,6 +9,7 @@ import ( "fmt" "time" + "github.com/decred/dcrd/blockchain/standalone" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/dcrutil" ) @@ -167,9 +168,9 @@ func (b *BlockChain) ProcessBlock(block *dcrutil.Block, flags BehaviorFlags) (in // expected based on elapsed time since the last checkpoint and // maximum adjustment allowed by the retarget rules. duration := blockHeader.Timestamp.Sub(checkpointTime) - requiredTarget := CompactToBig(b.calcEasiestDifficulty( + requiredTarget := standalone.CompactToBig(b.calcEasiestDifficulty( checkpointNode.bits, duration)) - currentTarget := CompactToBig(blockHeader.Bits) + currentTarget := standalone.CompactToBig(blockHeader.Bits) if currentTarget.Cmp(requiredTarget) > 0 { str := fmt.Sprintf("block target difficulty of %064x "+ "is too low when compared to the previous "+ diff --git a/blockchain/upgrade.go b/blockchain/upgrade.go index d117466b..2663e5c2 100644 --- a/blockchain/upgrade.go +++ b/blockchain/upgrade.go @@ -14,6 +14,7 @@ import ( "github.com/decred/dcrd/blockchain/internal/progresslog" "github.com/decred/dcrd/blockchain/stake" + "github.com/decred/dcrd/blockchain/standalone" "github.com/decred/dcrd/chaincfg" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/database" @@ -609,7 +610,7 @@ func upgradeToVersion5(db database.DB, chainParams *chaincfg.Params, dbInfo *dat height: 0, totalTxns: numTxns, totalSubsidy: 0, - workSum: CalcWork(genesisBlock.Header.Bits), + workSum: standalone.CalcWork(genesisBlock.Header.Bits), }) err = meta.Put(chainStateKeyName, serializedData) if err != nil { diff --git a/blockchain/validate.go b/blockchain/validate.go index a2ed1b9d..10c00805 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -433,6 +433,25 @@ func CheckProofOfStake(block *dcrutil.Block, posLimit int64) error { return checkProofOfStake(block, posLimit) } +// standaloneToChainRuleError attempts to convert the passed error from a +// standalone.RuleError to a blockchain.RuleError with the equivalent code. The +// error is simply passed through without modification if it is not a +// standalone.RuleError, not one of the specifically recognized error codes, or +// nil. +func standaloneToChainRuleError(err error) error { + // Convert standalone package rule errors to blockchain rule errors. + if rErr, ok := err.(standalone.RuleError); ok { + switch rErr.ErrorCode { + case standalone.ErrUnexpectedDifficulty: + return ruleError(ErrUnexpectedDifficulty, rErr.Description) + case standalone.ErrHighHash: + return ruleError(ErrHighHash, rErr.Description) + } + } + + 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. @@ -441,40 +460,30 @@ func CheckProofOfStake(block *dcrutil.Block, posLimit int64) error { // - 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 { - // The target difficulty must be larger than zero. - target := CompactToBig(header.Bits) - if target.Sign() <= 0 { - str := fmt.Sprintf("block target difficulty of %064x is too "+ - "low", target) - return ruleError(ErrUnexpectedDifficulty, str) + // 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 { + err := standalone.CheckProofOfWorkRange(header.Bits, powLimit) + return standaloneToChainRuleError(err) } - // The target difficulty must be less than the maximum allowed. - if target.Cmp(powLimit) > 0 { - str := fmt.Sprintf("block target difficulty of %064x is "+ - "higher than max of %064x", target, powLimit) - return ruleError(ErrUnexpectedDifficulty, str) - } - - // The block hash must be less than the claimed target unless the flag - // to avoid proof of work checks is set. - if flags&BFNoPoWCheck != BFNoPoWCheck { - // The block hash must be less than the claimed target. - hash := header.BlockHash() - hashNum := HashToBig(&hash) - 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) - } - } - - return nil + // Perform all proof of work checks when the flag is not set: + // + // - 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) + return standaloneToChainRuleError(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. +// target difficulty as claimed. This is equivalent to the function in the +// standalone package with the exception that that any error is converted to a +// RuleError. +// +// Deprecated: Use standalone.CheckProofOfWork instead. func CheckProofOfWork(header *wire.BlockHeader, powLimit *big.Int) error { return checkProofOfWork(header, powLimit, BFNone) }