mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
Fix checkParentHeadersExist and cover pruning_violation_proof_of_work_and_difficulty.go with tests (#1418)
* Fix checkParentHeadersExist and cover pruning_violation_proof_of_work_and_difficulty.go with tests * Remove unused variable * Change consensus violation * Change condition order * Get rid of irrelevant error codes in extractRejectCode * Fix wrong test db names * Fix checkParentHeadersExist
This commit is contained in:
parent
67be4d82bf
commit
dd57e6abe6
@ -1,70 +0,0 @@
|
||||
package blockvalidator_test
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/pow"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/mining"
|
||||
"github.com/kaspanet/kaspad/util/difficulty"
|
||||
"math"
|
||||
"math/rand"
|
||||
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// TestPOW tests the validation of the block's POW.
|
||||
func TestPOW(t *testing.T) {
|
||||
// We set the flag "skip pow" to be false (second argument in the function) for not skipping the check of POW and validate its correctness.
|
||||
testutils.ForAllNets(t, false, func(t *testing.T, params *dagconfig.Params) {
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestPOW")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
// Builds and checks block with invalid POW.
|
||||
invalidBlockWrongPOW, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
invalidBlockWrongPOW = solveBlockWithWrongPOW(invalidBlockWrongPOW)
|
||||
_, err = tc.ValidateAndInsertBlock(invalidBlockWrongPOW)
|
||||
if !errors.Is(err, ruleerrors.ErrInvalidPoW) {
|
||||
t.Fatalf("Expected block to be invalid with err: %v, instead found: %v", ruleerrors.ErrInvalidPoW, err)
|
||||
}
|
||||
|
||||
// test on a valid block.
|
||||
validBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
random := rand.New(rand.NewSource(0))
|
||||
mining.SolveBlock(validBlock, random)
|
||||
_, err = tc.ValidateAndInsertBlock(validBlock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// solveBlockWithWrongPOW increments the given block's nonce until it gets wrong POW (for test!).
|
||||
func solveBlockWithWrongPOW(block *externalapi.DomainBlock) *externalapi.DomainBlock {
|
||||
targetDifficulty := difficulty.CompactToBig(block.Header.Bits())
|
||||
headerForMining := block.Header.ToMutable()
|
||||
initialNonce := uint64(0)
|
||||
for i := initialNonce; i < math.MaxUint64; i++ {
|
||||
headerForMining.SetNonce(i)
|
||||
if !pow.CheckProofOfWorkWithTarget(headerForMining, targetDifficulty) {
|
||||
block.Header = headerForMining.ToImmutable()
|
||||
return block
|
||||
}
|
||||
}
|
||||
|
||||
panic("Failed to solve block! cannot find a invalid POW for the test")
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/pow"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/util/difficulty"
|
||||
"github.com/pkg/errors"
|
||||
@ -83,13 +84,13 @@ func (v *blockValidator) checkProofOfWork(header externalapi.BlockHeader) error
|
||||
// The target difficulty must be larger than zero.
|
||||
target := difficulty.CompactToBig(header.Bits())
|
||||
if target.Sign() <= 0 {
|
||||
return errors.Wrapf(ruleerrors.ErrUnexpectedDifficulty, "block target difficulty of %064x is too low",
|
||||
return errors.Wrapf(ruleerrors.ErrNegativeTarget, "block target difficulty of %064x is too low",
|
||||
target)
|
||||
}
|
||||
|
||||
// The target difficulty must be less than the maximum allowed.
|
||||
if target.Cmp(v.powMax) > 0 {
|
||||
return errors.Wrapf(ruleerrors.ErrUnexpectedDifficulty, "block target difficulty of %064x is "+
|
||||
return errors.Wrapf(ruleerrors.ErrTargetTooHigh, "block target difficulty of %064x is "+
|
||||
"higher than max of %064x", target, v.powMax)
|
||||
}
|
||||
|
||||
@ -111,18 +112,18 @@ func (v *blockValidator) checkParentHeadersExist(header externalapi.BlockHeader)
|
||||
return err
|
||||
}
|
||||
if !parentHeaderExists {
|
||||
parentStatus, err := v.blockStatusStore.Get(v.databaseContext, parent)
|
||||
if err != nil {
|
||||
if !database.IsNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
} else if parentStatus == externalapi.StatusInvalid {
|
||||
return errors.Wrapf(ruleerrors.ErrInvalidAncestorBlock, "parent %s is invalid", parent)
|
||||
}
|
||||
|
||||
missingParentHashes = append(missingParentHashes, parent)
|
||||
continue
|
||||
}
|
||||
|
||||
parentStatus, err := v.blockStatusStore.Get(v.databaseContext, parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if parentStatus == externalapi.StatusInvalid {
|
||||
return errors.Wrapf(ruleerrors.ErrInvalidAncestorBlock, "parent %s is invalid", parent)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingParentHashes) > 0 {
|
@ -0,0 +1,243 @@
|
||||
package blockvalidator_test
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/pow"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/merkle"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/mining"
|
||||
"github.com/kaspanet/kaspad/util/difficulty"
|
||||
"math"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// TestPOW tests the validation of the block's POW.
|
||||
func TestPOW(t *testing.T) {
|
||||
// We set the flag "skip pow" to be false (second argument in the function) for not skipping the check of POW and validate its correctness.
|
||||
testutils.ForAllNets(t, false, func(t *testing.T, params *dagconfig.Params) {
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestPOW")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
// Builds and checks block with invalid POW.
|
||||
invalidBlockWrongPOW, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
invalidBlockWrongPOW = solveBlockWithWrongPOW(invalidBlockWrongPOW)
|
||||
_, err = tc.ValidateAndInsertBlock(invalidBlockWrongPOW)
|
||||
if !errors.Is(err, ruleerrors.ErrInvalidPoW) {
|
||||
t.Fatalf("Expected block to be invalid with err: %v, instead found: %v", ruleerrors.ErrInvalidPoW, err)
|
||||
}
|
||||
|
||||
abovePowMaxBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
abovePowMaxTarget := big.NewInt(0).Add(big.NewInt(1), params.PowMax)
|
||||
abovePowMaxBlock.Header = blockheader.NewImmutableBlockHeader(
|
||||
abovePowMaxBlock.Header.Version(),
|
||||
abovePowMaxBlock.Header.ParentHashes(),
|
||||
abovePowMaxBlock.Header.HashMerkleRoot(),
|
||||
abovePowMaxBlock.Header.AcceptedIDMerkleRoot(),
|
||||
abovePowMaxBlock.Header.UTXOCommitment(),
|
||||
abovePowMaxBlock.Header.TimeInMilliseconds(),
|
||||
difficulty.BigToCompact(abovePowMaxTarget),
|
||||
abovePowMaxBlock.Header.Nonce(),
|
||||
)
|
||||
|
||||
_, err = tc.ValidateAndInsertBlock(abovePowMaxBlock)
|
||||
if !errors.Is(err, ruleerrors.ErrTargetTooHigh) {
|
||||
t.Fatalf("Unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
negativeTargetBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
negativeTargetBlock.Header = blockheader.NewImmutableBlockHeader(
|
||||
negativeTargetBlock.Header.Version(),
|
||||
negativeTargetBlock.Header.ParentHashes(),
|
||||
negativeTargetBlock.Header.HashMerkleRoot(),
|
||||
negativeTargetBlock.Header.AcceptedIDMerkleRoot(),
|
||||
negativeTargetBlock.Header.UTXOCommitment(),
|
||||
negativeTargetBlock.Header.TimeInMilliseconds(),
|
||||
0x00800000,
|
||||
negativeTargetBlock.Header.Nonce(),
|
||||
)
|
||||
|
||||
_, err = tc.ValidateAndInsertBlock(negativeTargetBlock)
|
||||
if !errors.Is(err, ruleerrors.ErrNegativeTarget) {
|
||||
t.Fatalf("Unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
// test on a valid block.
|
||||
validBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
random := rand.New(rand.NewSource(0))
|
||||
mining.SolveBlock(validBlock, random)
|
||||
_, err = tc.ValidateAndInsertBlock(validBlock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// solveBlockWithWrongPOW increments the given block's nonce until it gets wrong POW (for test!).
|
||||
func solveBlockWithWrongPOW(block *externalapi.DomainBlock) *externalapi.DomainBlock {
|
||||
targetDifficulty := difficulty.CompactToBig(block.Header.Bits())
|
||||
headerForMining := block.Header.ToMutable()
|
||||
initialNonce := uint64(0)
|
||||
for i := initialNonce; i < math.MaxUint64; i++ {
|
||||
headerForMining.SetNonce(i)
|
||||
if !pow.CheckProofOfWorkWithTarget(headerForMining, targetDifficulty) {
|
||||
block.Header = headerForMining.ToImmutable()
|
||||
return block
|
||||
}
|
||||
}
|
||||
|
||||
panic("Failed to solve block! cannot find a invalid POW for the test")
|
||||
}
|
||||
|
||||
func TestCheckParentHeadersExist(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
factory := consensus.NewFactory()
|
||||
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckParentHeadersExist")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
orphanBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
parentHash := externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{}) // Non existing parent hash
|
||||
orphanBlock.Header = blockheader.NewImmutableBlockHeader(
|
||||
orphanBlock.Header.Version(),
|
||||
[]*externalapi.DomainHash{
|
||||
parentHash,
|
||||
},
|
||||
orphanBlock.Header.HashMerkleRoot(),
|
||||
orphanBlock.Header.AcceptedIDMerkleRoot(),
|
||||
orphanBlock.Header.UTXOCommitment(),
|
||||
orphanBlock.Header.TimeInMilliseconds(),
|
||||
orphanBlock.Header.Bits(),
|
||||
orphanBlock.Header.Nonce(),
|
||||
)
|
||||
|
||||
_, err = tc.ValidateAndInsertBlock(orphanBlock)
|
||||
errMissingParents := &ruleerrors.ErrMissingParents{}
|
||||
if !errors.As(err, errMissingParents) {
|
||||
t.Fatalf("Unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
if !externalapi.HashesEqual(errMissingParents.MissingParentHashes, []*externalapi.DomainHash{parentHash}) {
|
||||
t.Fatalf("unexpected missing parents %s", errMissingParents.MissingParentHashes)
|
||||
}
|
||||
|
||||
invalidBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
invalidBlock.Transactions[0].Version = constants.MaxTransactionVersion + 1 // This should invalidate the block
|
||||
invalidBlock.Header = blockheader.NewImmutableBlockHeader(
|
||||
invalidBlock.Header.Version(),
|
||||
invalidBlock.Header.ParentHashes(),
|
||||
merkle.CalculateHashMerkleRoot(invalidBlock.Transactions),
|
||||
orphanBlock.Header.AcceptedIDMerkleRoot(),
|
||||
orphanBlock.Header.UTXOCommitment(),
|
||||
orphanBlock.Header.TimeInMilliseconds(),
|
||||
orphanBlock.Header.Bits(),
|
||||
orphanBlock.Header.Nonce(),
|
||||
)
|
||||
|
||||
_, err = tc.ValidateAndInsertBlock(invalidBlock)
|
||||
if !errors.Is(err, ruleerrors.ErrTransactionVersionIsUnknown) {
|
||||
t.Fatalf("Unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
invalidBlockHash := consensushashing.BlockHash(invalidBlock)
|
||||
|
||||
invalidBlockChild, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
invalidBlockChild.Header = blockheader.NewImmutableBlockHeader(
|
||||
invalidBlockChild.Header.Version(),
|
||||
[]*externalapi.DomainHash{invalidBlockHash},
|
||||
invalidBlockChild.Header.HashMerkleRoot(),
|
||||
invalidBlockChild.Header.AcceptedIDMerkleRoot(),
|
||||
invalidBlockChild.Header.UTXOCommitment(),
|
||||
invalidBlockChild.Header.TimeInMilliseconds(),
|
||||
invalidBlockChild.Header.Bits(),
|
||||
invalidBlockChild.Header.Nonce(),
|
||||
)
|
||||
|
||||
_, err = tc.ValidateAndInsertBlock(invalidBlockChild)
|
||||
if !errors.Is(err, ruleerrors.ErrInvalidAncestorBlock) {
|
||||
t.Fatalf("Unexpected error: %+v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckPruningPointViolation(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
// This is done to reduce the pruning depth to 6 blocks
|
||||
params.FinalityDuration = 2 * params.TargetTimePerBlock
|
||||
params.K = 0
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckPruningPointViolation")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
// Add blocks until the pruning point changes
|
||||
tipHash := params.GenesisHash
|
||||
for {
|
||||
tipHash, _, err = tc.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
|
||||
pruningPoint, err := tc.PruningPoint()
|
||||
if err != nil {
|
||||
t.Fatalf("PruningPoint: %+v", err)
|
||||
}
|
||||
|
||||
if !pruningPoint.Equal(params.GenesisHash) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_, _, err = tc.AddUTXOInvalidBlock([]*externalapi.DomainHash{params.GenesisHash})
|
||||
if !errors.Is(err, ruleerrors.ErrPruningPointViolation) {
|
||||
t.Fatalf("Unexpected error: %+v", err)
|
||||
}
|
||||
})
|
||||
}
|
@ -29,16 +29,20 @@ var (
|
||||
// ErrNoParents indicates that the block is missing parents
|
||||
ErrNoParents = newRuleError("ErrNoParents")
|
||||
|
||||
// ErrDifficultyTooLow indicates the difficulty for the block is lower
|
||||
// than the difficulty required.
|
||||
ErrDifficultyTooLow = newRuleError("ErrDifficultyTooLow")
|
||||
|
||||
// ErrUnexpectedDifficulty indicates specified bits do not align with
|
||||
// the expected value either because it doesn't match the calculated
|
||||
// valued based on difficulty regarted rules or it is out of the valid
|
||||
// range.
|
||||
// valued based on difficulty regarted rules.
|
||||
ErrUnexpectedDifficulty = newRuleError("ErrUnexpectedDifficulty")
|
||||
|
||||
// ErrTargetTooHigh indicates specified bits do not align with
|
||||
// the expected value either because it is above the valid
|
||||
// range.
|
||||
ErrTargetTooHigh = newRuleError("ErrTargetTooHigh")
|
||||
|
||||
// ErrUnexpectedDifficulty indicates specified bits do not align with
|
||||
// the expected value either because it is negative.
|
||||
ErrNegativeTarget = newRuleError("ErrNegativeTarget")
|
||||
|
||||
// ErrInvalidPoW indicates that the block proof-of-work is invalid.
|
||||
ErrInvalidPoW = newRuleError("ErrInvalidPoW")
|
||||
|
||||
|
@ -1,153 +0,0 @@
|
||||
package math
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var (
|
||||
// bigOne is 1 represented as a big.Int. It is defined here to avoid
|
||||
// the overhead of creating it multiple times.
|
||||
bigOne = big.NewInt(1)
|
||||
|
||||
// oneLsh256 is 1 shifted left 256 bits. It is defined here to avoid
|
||||
// the overhead of creating it multiple times.
|
||||
oneLsh256 = new(big.Int).Lsh(bigOne, 256)
|
||||
|
||||
// log2FloorMasks defines the masks to use when quickly calculating
|
||||
// floor(log2(x)) in a constant log2(64) = 6 steps, where x is a uint64, using
|
||||
// shifts. They are derived from (2^(2^x) - 1) * (2^(2^x)), for x in 5..0.
|
||||
log2FloorMasks = []uint64{0xffffffff00000000, 0xffff0000, 0xff00, 0xf0, 0xc, 0x2}
|
||||
)
|
||||
|
||||
// FastLog2Floor calculates and returns floor(log2(x)) in a constant 5 steps.
|
||||
func FastLog2Floor(n uint64) uint8 {
|
||||
rv := uint8(0)
|
||||
exponent := uint8(32)
|
||||
for i := 0; i < 6; i++ {
|
||||
if n&log2FloorMasks[i] != 0 {
|
||||
rv += exponent
|
||||
n >>= exponent
|
||||
}
|
||||
exponent >>= 1
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// 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:
|
||||
//
|
||||
// * the most significant 8 bits represent the unsigned base 256 exponent
|
||||
// * bit 23 (the 24th bit) represents the sign bit
|
||||
// * the least significant 23 bits represent the mantissa
|
||||
//
|
||||
// -------------------------------------------------
|
||||
// | 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)
|
||||
func CompactToBig(compact uint32) *big.Int {
|
||||
destination := big.NewInt(0)
|
||||
CompactToBigWithDestination(compact, destination)
|
||||
return destination
|
||||
}
|
||||
|
||||
// CompactToBigWithDestination is a version of CompactToBig that
|
||||
// takes a destination parameter. This is useful for saving memory,
|
||||
// as then the destination big.Int can be reused.
|
||||
// See CompactToBig for further details.
|
||||
func CompactToBigWithDestination(compact uint32, destination *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)
|
||||
if exponent <= 3 {
|
||||
mantissa >>= 8 * (3 - exponent)
|
||||
destination.SetInt64(int64(mantissa))
|
||||
} else {
|
||||
destination.SetInt64(int64(mantissa))
|
||||
destination.Lsh(destination, 8*(exponent-3))
|
||||
}
|
||||
|
||||
// Make it negative if the sign bit is set.
|
||||
if isNegative {
|
||||
destination.Neg(destination)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// CalcWork calculates a work value from difficulty bits. Kaspa increases
|
||||
// the difficulty for generating a block by decreasing the value which the
|
||||
// generated hash must be less than. This difficulty target is stored in each
|
||||
// block header using a compact representation as described in the documentation
|
||||
// for CompactToBig. Since a lower target difficulty value equates to higher
|
||||
// actual difficulty, the work value which will be 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.
|
||||
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)
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
package math
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFastLog2Floor(t *testing.T) {
|
||||
tests := []struct {
|
||||
n uint64
|
||||
expectedResult uint8
|
||||
}{
|
||||
{1, 0},
|
||||
{2, 1},
|
||||
{3, 1},
|
||||
{4, 2},
|
||||
{5, 2},
|
||||
{16, 4},
|
||||
{31, 4},
|
||||
{1684234, 20},
|
||||
{4294967295, 31}, // math.MaxUint32 (2^32 - 1)
|
||||
{4294967296, 32}, // 2^32
|
||||
{4294967297, 32}, // 2^32 + 1
|
||||
{4611686018427387904, 62},
|
||||
{9223372036854775808, 63}, // 2^63
|
||||
{18446744073709551615, 63}, // math.MaxUint64 (2^64 - 1).
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
actualResult := FastLog2Floor(test.n)
|
||||
|
||||
if test.expectedResult != actualResult {
|
||||
t.Errorf("TestFastLog2Floor: %d: expected result: %d but got: %d", test.n, test.expectedResult, actualResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBigToCompact ensures BigToCompact converts big integers to the expected
|
||||
// compact representation.
|
||||
func TestBigToCompact(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
out uint32
|
||||
}{
|
||||
{"0000000000000000000000000000000000000000000000000000000000000000", 0},
|
||||
{"-1", 25231360},
|
||||
{"9223372036854775807", 142606335},
|
||||
{"922337203685477580712312312123487", 237861256},
|
||||
{"128", 0x02008000},
|
||||
}
|
||||
|
||||
for x, test := range tests {
|
||||
n := new(big.Int)
|
||||
n.SetString(test.in, 10)
|
||||
r := BigToCompact(n)
|
||||
if r != test.out {
|
||||
t.Errorf("TestBigToCompact test #%d failed: got %d want %d\n",
|
||||
x, r, test.out)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompactToBig ensures CompactToBig converts numbers using the compact
|
||||
// representation to the expected big integers.
|
||||
func TestCompactToBig(t *testing.T) {
|
||||
tests := []struct {
|
||||
before uint32
|
||||
intHex string
|
||||
after uint32
|
||||
}{
|
||||
{math.MaxUint32, "-7fffff000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000", math.MaxUint32},
|
||||
{0x00000000, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x0989680, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x87fffff, "0000000000000000000000000000000000000000000000007fffff0000000000", 0x87fffff},
|
||||
{0x1810000, "-000000000000000000000000000000000000000000000000000000000000001", 0x1810000},
|
||||
{0xe2d7988, "0000000000000000000000000000000000002d79880000000000000000000000", 0xe2d7988},
|
||||
{0x00123456, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x01003456, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x02000056, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x03000000, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x04000000, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x00923456, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x01803456, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x02800056, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x03800000, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x04800000, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x01123456, "0000000000000000000000000000000000000000000000000000000000000012", 0x01120000},
|
||||
{0x02008000, "0000000000000000000000000000000000000000000000000000000000000080", 0x02008000},
|
||||
{0x01fedcba, "-00000000000000000000000000000000000000000000000000000000000007e", 0x01fe0000},
|
||||
{0x02123456, "0000000000000000000000000000000000000000000000000000000000001234", 0x02123400},
|
||||
{0x03123456, "0000000000000000000000000000000000000000000000000000000000123456", 0x03123456},
|
||||
{0x04123456, "0000000000000000000000000000000000000000000000000000000012345600", 0x04123456},
|
||||
{0x04923456, "-000000000000000000000000000000000000000000000000000000012345600", 0x04923456},
|
||||
{0x05009234, "0000000000000000000000000000000000000000000000000000000092340000", 0x05009234},
|
||||
{0x20123456, "1234560000000000000000000000000000000000000000000000000000000000", 0x20123456},
|
||||
{0xff123456, "123456000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 0xff123456},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
n := CompactToBig(test.before)
|
||||
convertBack := BigToCompact(n)
|
||||
got := fmt.Sprintf("%064x", n)
|
||||
if got != test.intHex {
|
||||
t.Errorf("TestCompactToBig test #%d failed: got %s want %s, input: 0x%08x",
|
||||
i, got, test.intHex, test.before)
|
||||
}
|
||||
if convertBack != test.after {
|
||||
t.Errorf("TestCompactToBig test #%d failed: got: 0x%08x want 0x%08x input: 0x%08x", i, convertBack, test.after, test.before)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,17 +2,17 @@ package mining
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/pow"
|
||||
"github.com/kaspanet/kaspad/util/difficulty"
|
||||
"math"
|
||||
"math/rand"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
utilsMath "github.com/kaspanet/kaspad/domain/consensus/utils/math"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SolveBlock increments the given block's nonce until it matches the difficulty requirements in its bits field
|
||||
func SolveBlock(block *externalapi.DomainBlock, rd *rand.Rand) {
|
||||
targetDifficulty := utilsMath.CompactToBig(block.Header.Bits())
|
||||
targetDifficulty := difficulty.CompactToBig(block.Header.Bits())
|
||||
headerForMining := block.Header.ToMutable()
|
||||
for i := rd.Uint64(); i < math.MaxUint64; i++ {
|
||||
headerForMining.SetNonce(i)
|
||||
|
@ -7,8 +7,6 @@ package mempool
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -115,41 +113,10 @@ func extractRejectCode(err error) (RejectCode, bool) {
|
||||
err = ruleErr.Err
|
||||
}
|
||||
|
||||
var dagRuleErr ruleerrors.RuleError
|
||||
if errors.As(err, &dagRuleErr) {
|
||||
// Convert the DAG error to a reject code.
|
||||
var code RejectCode
|
||||
switch dagRuleErr {
|
||||
// Rejected due to duplicate.
|
||||
case ruleerrors.ErrDuplicateBlock:
|
||||
code = RejectDuplicate
|
||||
|
||||
// Rejected due to obsolete version.
|
||||
case ruleerrors.ErrBlockVersionTooOld:
|
||||
code = RejectObsolete
|
||||
|
||||
// Rejected due to being earlier than the last finality point.
|
||||
case ruleerrors.ErrFinalityPointTimeTooOld:
|
||||
code = RejectFinality
|
||||
case ruleerrors.ErrDifficultyTooLow:
|
||||
code = RejectDifficulty
|
||||
|
||||
// Everything else is due to the block or transaction being invalid.
|
||||
default:
|
||||
code = RejectInvalid
|
||||
}
|
||||
|
||||
return code, true
|
||||
}
|
||||
|
||||
var trErr TxRuleError
|
||||
if errors.As(err, &trErr) {
|
||||
return trErr.RejectCode, true
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
return RejectInvalid, false
|
||||
}
|
||||
|
||||
return RejectInvalid, false
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/math"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/difficulty"
|
||||
"github.com/pkg/errors"
|
||||
"math/big"
|
||||
"os"
|
||||
@ -165,7 +165,7 @@ func (networkFlags *NetworkFlags) overrideDAGParams() error {
|
||||
return errors.Errorf("couldn't convert %s to big int", *config.PowMax)
|
||||
}
|
||||
|
||||
genesisTarget := math.CompactToBig(networkFlags.ActiveNetParams.GenesisBlock.Header.Bits())
|
||||
genesisTarget := difficulty.CompactToBig(networkFlags.ActiveNetParams.GenesisBlock.Header.Bits())
|
||||
if powMax.Cmp(genesisTarget) > 0 {
|
||||
return errors.Errorf("powMax (%s) is smaller than genesis's target (%s)", powMax.Text(16),
|
||||
genesisTarget.Text(16))
|
||||
|
@ -1,9 +1,12 @@
|
||||
package difficulty_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/difficulty"
|
||||
"math"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -22,3 +25,88 @@ func TestGetHashrateString(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestBigToCompact ensures BigToCompact converts big integers to the expected
|
||||
// compact representation.
|
||||
func TestBigToCompact(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
out uint32
|
||||
}{
|
||||
{"0000000000000000000000000000000000000000000000000000000000000000", 0},
|
||||
{"-1", 25231360},
|
||||
{"9223372036854775807", 142606335},
|
||||
{"922337203685477580712312312123487", 237861256},
|
||||
{"128", 0x02008000},
|
||||
}
|
||||
|
||||
for x, test := range tests {
|
||||
n := new(big.Int)
|
||||
n.SetString(test.in, 10)
|
||||
r := difficulty.BigToCompact(n)
|
||||
if r != test.out {
|
||||
t.Errorf("TestBigToCompact test #%d failed: got %d want %d\n",
|
||||
x, r, test.out)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompactToBig ensures CompactToBig converts numbers using the compact
|
||||
// representation to the expected big integers.
|
||||
func TestCompactToBig(t *testing.T) {
|
||||
tests := []struct {
|
||||
before uint32
|
||||
intHex string
|
||||
after uint32
|
||||
}{
|
||||
{math.MaxUint32, "-7fffff000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000", math.MaxUint32},
|
||||
{0x00000000, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x0989680, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x87fffff, "0000000000000000000000000000000000000000000000007fffff0000000000", 0x87fffff},
|
||||
{0x1810000, "-000000000000000000000000000000000000000000000000000000000000001", 0x1810000},
|
||||
{0xe2d7988, "0000000000000000000000000000000000002d79880000000000000000000000", 0xe2d7988},
|
||||
{0x00123456, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x01003456, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x02000056, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x03000000, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x04000000, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x00923456, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x01803456, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x02800056, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x03800000, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x04800000, "0000000000000000000000000000000000000000000000000000000000000000", 0x00000000},
|
||||
{0x01123456, "0000000000000000000000000000000000000000000000000000000000000012", 0x01120000},
|
||||
{0x02008000, "0000000000000000000000000000000000000000000000000000000000000080", 0x02008000},
|
||||
{0x01fedcba, "-00000000000000000000000000000000000000000000000000000000000007e", 0x01fe0000},
|
||||
{0x02123456, "0000000000000000000000000000000000000000000000000000000000001234", 0x02123400},
|
||||
{0x03123456, "0000000000000000000000000000000000000000000000000000000000123456", 0x03123456},
|
||||
{0x04123456, "0000000000000000000000000000000000000000000000000000000012345600", 0x04123456},
|
||||
{0x04923456, "-000000000000000000000000000000000000000000000000000000012345600", 0x04923456},
|
||||
{0x05009234, "0000000000000000000000000000000000000000000000000000000092340000", 0x05009234},
|
||||
{0x20123456, "1234560000000000000000000000000000000000000000000000000000000000", 0x20123456},
|
||||
{0xff123456, "123456000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 0xff123456},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
n := difficulty.CompactToBig(test.before)
|
||||
convertBack := difficulty.BigToCompact(n)
|
||||
got := fmt.Sprintf("%064x", n)
|
||||
if got != test.intHex {
|
||||
t.Errorf("TestCompactToBig test #%d failed: got %s want %s, input: 0x%08x",
|
||||
i, got, test.intHex, test.before)
|
||||
}
|
||||
if convertBack != test.after {
|
||||
t.Errorf("TestCompactToBig test #%d failed: got: 0x%08x want 0x%08x input: 0x%08x", i, convertBack, test.after, test.before)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user