diff --git a/cmd/kaspaminer/mineloop.go b/cmd/kaspaminer/mineloop.go index 67f23a14c..0ecf3cf94 100644 --- a/cmd/kaspaminer/mineloop.go +++ b/cmd/kaspaminer/mineloop.go @@ -13,7 +13,6 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/utils/pow" "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router" "github.com/kaspanet/kaspad/util" - "github.com/kaspanet/kaspad/util/difficulty" "github.com/pkg/errors" ) @@ -143,20 +142,20 @@ func mineNextBlock(mineWhenNotSynced bool) *externalapi.DomainBlock { // In the rare case where the nonce space is exhausted for a specific // block, it'll keep looping the nonce until a new block template // is discovered. - block := getBlockForMining(mineWhenNotSynced) - targetDifficulty := difficulty.CompactToBig(block.Header.Bits()) - headerForMining := block.Header.ToMutable() - headerForMining.SetNonce(nonce) + block, state := getBlockForMining(mineWhenNotSynced) + state.Nonce = nonce atomic.AddUint64(&hashesTried, 1) - if pow.CheckProofOfWorkWithTarget(headerForMining, targetDifficulty) { - block.Header = headerForMining.ToImmutable() + if state.CheckProofOfWork() { + mutHeader := block.Header.ToMutable() + mutHeader.SetNonce(nonce) + block.Header = mutHeader.ToImmutable() log.Infof("Found block %s with parents %s", consensushashing.BlockHash(block), block.Header.DirectParents()) return block } } } -func getBlockForMining(mineWhenNotSynced bool) *externalapi.DomainBlock { +func getBlockForMining(mineWhenNotSynced bool) (*externalapi.DomainBlock, *pow.State) { tryCount := 0 const sleepTime = 500 * time.Millisecond @@ -166,7 +165,7 @@ func getBlockForMining(mineWhenNotSynced bool) *externalapi.DomainBlock { tryCount++ shouldLog := (tryCount-1)%10 == 0 - template, isSynced := templatemanager.Get() + template, state, isSynced := templatemanager.Get() if template == nil { if shouldLog { log.Info("Waiting for the initial template") @@ -182,7 +181,7 @@ func getBlockForMining(mineWhenNotSynced bool) *externalapi.DomainBlock { continue } - return template + return template, state } } diff --git a/cmd/kaspaminer/templatemanager/templatemanager.go b/cmd/kaspaminer/templatemanager/templatemanager.go index ab1027727..dc24f979e 100644 --- a/cmd/kaspaminer/templatemanager/templatemanager.go +++ b/cmd/kaspaminer/templatemanager/templatemanager.go @@ -3,23 +3,26 @@ package templatemanager import ( "github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/pow" "sync" ) var currentTemplate *externalapi.DomainBlock +var currentState *pow.State var isSynced bool var lock = &sync.Mutex{} // Get returns the template to work on -func Get() (*externalapi.DomainBlock, bool) { +func Get() (*externalapi.DomainBlock, *pow.State, bool) { lock.Lock() defer lock.Unlock() // Shallow copy the block so when the user replaces the header it won't affect the template here. if currentTemplate == nil { - return nil, false + return nil, nil, false } block := *currentTemplate - return &block, isSynced + state := *currentState + return &block, &state, isSynced } // Set sets the current template to work on @@ -31,6 +34,7 @@ func Set(template *appmessage.GetBlockTemplateResponseMessage) error { lock.Lock() defer lock.Unlock() currentTemplate = block + currentState = pow.NewState(block.Header.ToMutable()) isSynced = template.IsSynced return nil } diff --git a/domain/consensus/processes/blockvalidator/pruning_violation_proof_of_work_and_difficulty.go b/domain/consensus/processes/blockvalidator/pruning_violation_proof_of_work_and_difficulty.go index 57c4be91f..c511e80d7 100644 --- a/domain/consensus/processes/blockvalidator/pruning_violation_proof_of_work_and_difficulty.go +++ b/domain/consensus/processes/blockvalidator/pruning_violation_proof_of_work_and_difficulty.go @@ -8,7 +8,6 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/utils/virtual" "github.com/kaspanet/kaspad/infrastructure/db/database" "github.com/kaspanet/kaspad/infrastructure/logger" - "github.com/kaspanet/kaspad/util/difficulty" "github.com/pkg/errors" ) @@ -149,7 +148,8 @@ func (v *blockValidator) validateDifficulty(stagingArea *model.StagingArea, // difficulty is not performed. func (v *blockValidator) checkProofOfWork(header externalapi.BlockHeader) error { // The target difficulty must be larger than zero. - target := difficulty.CompactToBig(header.Bits()) + state := pow.NewState(header.ToMutable()) + target := &state.Target if target.Sign() <= 0 { return errors.Wrapf(ruleerrors.ErrNegativeTarget, "block target difficulty of %064x is too low", target) @@ -163,7 +163,7 @@ func (v *blockValidator) checkProofOfWork(header externalapi.BlockHeader) error // The block pow must be valid unless the flag to avoid proof of work checks is set. if !v.skipPoW { - valid := pow.CheckProofOfWorkWithTarget(header.ToMutable(), target) + valid := state.CheckProofOfWork() if !valid { return errors.Wrap(ruleerrors.ErrInvalidPoW, "block has invalid proof of work") } diff --git a/domain/consensus/processes/blockvalidator/pruning_violation_proof_of_work_and_difficulty_test.go b/domain/consensus/processes/blockvalidator/pruning_violation_proof_of_work_and_difficulty_test.go index a732fe592..83b525bb4 100644 --- a/domain/consensus/processes/blockvalidator/pruning_violation_proof_of_work_and_difficulty_test.go +++ b/domain/consensus/processes/blockvalidator/pruning_violation_proof_of_work_and_difficulty_test.go @@ -111,13 +111,13 @@ func TestPOW(t *testing.T) { // 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() + header := block.Header.ToMutable() + state := pow.NewState(header) + for i := uint64(0); i < math.MaxUint64; i++ { + state.Nonce = i + if !state.CheckProofOfWork() { + header.SetNonce(state.Nonce) + block.Header = header.ToImmutable() return block } } diff --git a/domain/consensus/utils/mining/solve.go b/domain/consensus/utils/mining/solve.go index 84b606047..12d3ea8c7 100644 --- a/domain/consensus/utils/mining/solve.go +++ b/domain/consensus/utils/mining/solve.go @@ -6,18 +6,17 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/utils/pow" - "github.com/kaspanet/kaspad/util/difficulty" "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 := difficulty.CompactToBig(block.Header.Bits()) - headerForMining := block.Header.ToMutable() - for i := rd.Uint64(); i < math.MaxUint64; i++ { - headerForMining.SetNonce(i) - if pow.CheckProofOfWorkWithTarget(headerForMining, targetDifficulty) { - block.Header = headerForMining.ToImmutable() + header := block.Header.ToMutable() + state := pow.NewState(header) + for state.Nonce = rd.Uint64(); state.Nonce < math.MaxUint64; state.Nonce++ { + if state.CheckProofOfWork() { + header.SetNonce(state.Nonce) + block.Header = header.ToImmutable() return } } diff --git a/domain/consensus/utils/pow/pow.go b/domain/consensus/utils/pow/pow.go index 2201b2fde..8009c9f52 100644 --- a/domain/consensus/utils/pow/pow.go +++ b/domain/consensus/utils/pow/pow.go @@ -12,52 +12,77 @@ import ( "math/big" ) -// CheckProofOfWorkWithTarget check's if the block has a valid PoW according to the provided target -// it does not check if the difficulty itself is valid or less than the maximum for the appropriate network -func CheckProofOfWorkWithTarget(header externalapi.MutableBlockHeader, target *big.Int) bool { - // The block pow must be less than the claimed target - powNum := CalculateProofOfWorkValue(header) - - // The block hash must be less or equal than the claimed target. - return powNum.Cmp(target) <= 0 +// State is an intermediate data structure with pre-computed values to speed up mining. +type State struct { + mat matrix + Timestamp int64 + Nonce uint64 + Target big.Int + prePowHash externalapi.DomainHash } -// CheckProofOfWorkByBits check's if the block has a valid PoW according to its Bits field -// it does not check if the difficulty itself is valid or less than the maximum for the appropriate network -func CheckProofOfWorkByBits(header externalapi.MutableBlockHeader) bool { - return CheckProofOfWorkWithTarget(header, difficulty.CompactToBig(header.Bits())) -} - -// CalculateProofOfWorkValue hashes the given header and returns its big.Int value -func CalculateProofOfWorkValue(header externalapi.MutableBlockHeader) *big.Int { +// NewState creates a new state with pre-computed values to speed up mining +// It takes the target from the Bits field +func NewState(header externalapi.MutableBlockHeader) *State { + target := difficulty.CompactToBig(header.Bits()) // Zero out the time and nonce. timestamp, nonce := header.TimeInMilliseconds(), header.Nonce() header.SetTimeInMilliseconds(0) header.SetNonce(0) - prePowHash := consensushashing.HeaderHash(header) - matrix := generateMatrix(prePowHash) header.SetTimeInMilliseconds(timestamp) header.SetNonce(nonce) + return &State{ + Target: *target, + prePowHash: *prePowHash, + mat: *generateMatrix(prePowHash), + Timestamp: timestamp, + Nonce: nonce, + } +} + +// CalculateProofOfWorkValue hashes the internal header and returns its big.Int value +func (state *State) CalculateProofOfWorkValue() *big.Int { // PRE_POW_HASH || TIME || 32 zero byte padding || NONCE writer := hashes.NewPoWHashWriter() - writer.InfallibleWrite(prePowHash.ByteSlice()) - err := serialization.WriteElement(writer, timestamp) + writer.InfallibleWrite(state.prePowHash.ByteSlice()) + err := serialization.WriteElement(writer, state.Timestamp) if err != nil { panic(errors.Wrap(err, "this should never happen. Hash digest should never return an error")) } zeroes := [32]byte{} writer.InfallibleWrite(zeroes[:]) - err = serialization.WriteElement(writer, nonce) + err = serialization.WriteElement(writer, state.Nonce) if err != nil { panic(errors.Wrap(err, "this should never happen. Hash digest should never return an error")) } powHash := writer.Finalize() - heavyHash := matrix.HeavyHash(powHash) + heavyHash := state.mat.HeavyHash(powHash) return toBig(heavyHash) } +// IncrementNonce the nonce in State by 1 +func (state *State) IncrementNonce() { + state.Nonce++ +} + +// CheckProofOfWork check's if the block has a valid PoW according to the provided target +// it does not check if the difficulty itself is valid or less than the maximum for the appropriate network +func (state *State) CheckProofOfWork() bool { + // The block pow must be less than the claimed target + powNum := state.CalculateProofOfWorkValue() + + // The block hash must be less or equal than the claimed target. + return powNum.Cmp(&state.Target) <= 0 +} + +// CheckProofOfWorkByBits check's if the block has a valid PoW according to its Bits field +// it does not check if the difficulty itself is valid or less than the maximum for the appropriate network +func CheckProofOfWorkByBits(header externalapi.MutableBlockHeader) bool { + return NewState(header).CheckProofOfWork() +} + // ToBig converts a externalapi.DomainHash into a big.Int treated as a little endian string. func toBig(hash *externalapi.DomainHash) *big.Int { // We treat the Hash as little-endian for PoW purposes, but the big package wants the bytes in big-endian, so reverse them. @@ -78,7 +103,7 @@ func BlockLevel(header externalapi.BlockHeader) int { return constants.MaxBlockLevel } - proofOfWorkValue := CalculateProofOfWorkValue(header.ToMutable()) + proofOfWorkValue := NewState(header.ToMutable()).CalculateProofOfWorkValue() for blockLevel := 0; ; blockLevel++ { if blockLevel == constants.MaxBlockLevel || proofOfWorkValue.Bit(blockLevel+1) != 0 { return blockLevel diff --git a/stability-tests/daa/daa_test.go b/stability-tests/daa/daa_test.go index e48669921..003c87767 100644 --- a/stability-tests/daa/daa_test.go +++ b/stability-tests/daa/daa_test.go @@ -7,9 +7,7 @@ import ( "github.com/kaspanet/kaspad/domain/dagconfig" "github.com/kaspanet/kaspad/infrastructure/network/rpcclient" "github.com/kaspanet/kaspad/stability-tests/common" - "github.com/kaspanet/kaspad/util/difficulty" "math" - "math/big" "math/rand" "os" "testing" @@ -162,17 +160,15 @@ func measureMachineHashNanoseconds(t *testing.T) int64 { defer t.Logf("Finished measuring machine hash rate") genesisBlock := dagconfig.DevnetParams.GenesisBlock - targetDifficulty := difficulty.CompactToBig(genesisBlock.Header.Bits()) - headerForMining := genesisBlock.Header.ToMutable() + state := pow.NewState(genesisBlock.Header.ToMutable()) machineHashesPerSecondMeasurementDuration := 10 * time.Second hashes := int64(0) - nonce := rand.Uint64() + state.Nonce = rand.Uint64() loopForDuration(machineHashesPerSecondMeasurementDuration, func(isFinished *bool) { - headerForMining.SetNonce(nonce) - pow.CheckProofOfWorkWithTarget(headerForMining, targetDifficulty) + state.CheckProofOfWork() hashes++ - nonce++ + state.IncrementNonce() }) return machineHashesPerSecondMeasurementDuration.Nanoseconds() / hashes @@ -202,17 +198,18 @@ func runDAATest(t *testing.T, testName string, runDuration time.Duration, startTime := time.Now() loopForDuration(runDuration, func(isFinished *bool) { templateBlock := fetchBlockForMining(t, rpcClient) - targetDifficulty := difficulty.CompactToBig(templateBlock.Header.Bits()) headerForMining := templateBlock.Header.ToMutable() + minerState := pow.NewState(headerForMining) // Try hashes until we find a valid block miningStartTime := time.Now() - nonce := rand.Uint64() + minerState.Nonce = rand.Uint64() for { hashStartTime := time.Now() - blockFound := tryNonceForMiningAndIncrementNonce(headerForMining, &nonce, targetDifficulty, templateBlock) - if blockFound { + if minerState.CheckProofOfWork() { + headerForMining.SetNonce(minerState.Nonce) + templateBlock.Header = headerForMining.ToImmutable() break } @@ -227,6 +224,7 @@ func runDAATest(t *testing.T, testName string, runDuration time.Duration, if *isFinished { return } + minerState.IncrementNonce() } // Collect stats about block rate @@ -265,19 +263,6 @@ func fetchBlockForMining(t *testing.T, rpcClient *rpcclient.RPCClient) *external return templateBlock } -func tryNonceForMiningAndIncrementNonce(headerForMining externalapi.MutableBlockHeader, nonce *uint64, - targetDifficulty *big.Int, templateBlock *externalapi.DomainBlock) bool { - - headerForMining.SetNonce(*nonce) - if pow.CheckProofOfWorkWithTarget(headerForMining, targetDifficulty) { - templateBlock.Header = headerForMining.ToImmutable() - return true - } - - *nonce++ - return false -} - func waitUntilTargetHashDurationHadElapsed(startTime time.Time, hashStartTime time.Time, targetHashNanosecondsFunction func(totalElapsedDuration time.Duration) int64) { diff --git a/stability-tests/many-tips/main.go b/stability-tests/many-tips/main.go index ba02de1fd..e553f121e 100644 --- a/stability-tests/many-tips/main.go +++ b/stability-tests/many-tips/main.go @@ -4,11 +4,9 @@ import ( "fmt" "github.com/kaspanet/go-secp256k1" "github.com/kaspanet/kaspad/app/appmessage" - "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" - "github.com/kaspanet/kaspad/domain/consensus/utils/pow" + "github.com/kaspanet/kaspad/domain/consensus/utils/mining" "github.com/kaspanet/kaspad/util" - "github.com/kaspanet/kaspad/util/difficulty" - "math" + "math/rand" "os" "os/exec" "strings" @@ -78,8 +76,8 @@ func realMain() error { genesisTimestamp := activeConfig().NetParams().GenesisBlock.Header.TimeInMilliseconds() mutableHeader.SetTimeInMilliseconds(genesisTimestamp + 1000) block.Header = mutableHeader.ToImmutable() - solvedBlock := solveBlock(block) - _, err = rpcClient.SubmitBlock(solvedBlock) + mining.SolveBlock(block, rand.New(rand.NewSource(time.Now().UnixNano()))) + _, err = rpcClient.SubmitBlock(block) if err != nil { return err } @@ -183,8 +181,8 @@ func mineBlock(rpcClient *rpc.Client, miningAddress util.Address) error { if err != nil { return err } - solvedBlock := solveBlock(block) - _, err = rpcClient.SubmitBlock(solvedBlock) + mining.SolveBlock(block, rand.New(rand.NewSource(time.Now().UnixNano()))) + _, err = rpcClient.SubmitBlock(block) if err != nil { return err } @@ -200,9 +198,10 @@ func mineTips(numOfTips int, miningAddress util.Address, rpcClient *rpc.Client) if err != nil { return err } + rd := rand.New(rand.NewSource(time.Now().UnixNano())) for i := 0; i < numOfTips; i++ { - solvedBlock := solveBlock(block) - _, err = rpcClient.SubmitBlock(solvedBlock) + mining.SolveBlock(block, rd) + _, err = rpcClient.SubmitBlock(block) if err != nil { return err } @@ -296,23 +295,6 @@ func getCurrentTipsLength(rpcClient *rpc.Client) (int, error) { return len(dagInfo.TipHashes), nil } -var latestNonce uint64 = 0 // Use to make the nonce unique. -// The nonce is unique for each block in this function. -func solveBlock(block *externalapi.DomainBlock) *externalapi.DomainBlock { - targetDifficulty := difficulty.CompactToBig(block.Header.Bits()) - headerForMining := block.Header.ToMutable() - maxUInt64 := uint64(math.MaxUint64) - for i := latestNonce; i < maxUInt64; i++ { - headerForMining.SetNonce(i) - if pow.CheckProofOfWorkWithTarget(headerForMining, targetDifficulty) { - block.Header = headerForMining.ToImmutable() - latestNonce = i + 1 - return block - } - } - panic("Failed to solve block!") -} - func killWithSigterm(cmd *exec.Cmd, commandName string) { err := cmd.Process.Signal(syscall.SIGTERM) if err != nil { diff --git a/testing/integration/ibd_test.go b/testing/integration/ibd_test.go index 33c47b254..6e66fec12 100644 --- a/testing/integration/ibd_test.go +++ b/testing/integration/ibd_test.go @@ -3,6 +3,8 @@ package integration import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/kaspanet/kaspad/domain/consensus/utils/mining" + "math/rand" "reflect" "sync" "testing" @@ -117,7 +119,7 @@ func TestIBDWithPruning(t *testing.T) { } // This should trigger resolving the syncee virtual - syncerTip := mineNextBlockWithMockTimestamps(t, syncer) + syncerTip := mineNextBlockWithMockTimestamps(t, syncer, rand.New(rand.NewSource(time.Now().UnixNano()))) time.Sleep(time.Second) synceeSelectedTip, err := syncee.rpcClient.GetSelectedTipHash() if err != nil { @@ -184,12 +186,13 @@ func TestIBDWithPruning(t *testing.T) { // iteration to find the highest shared chain // block. const synceeOnlyBlocks = 2 + rd := rand.New(rand.NewSource(time.Now().UnixNano())) for i := 0; i < synceeOnlyBlocks; i++ { - mineNextBlockWithMockTimestamps(t, syncee1) + mineNextBlockWithMockTimestamps(t, syncee1, rd) } for i := 0; i < numBlocks-1; i++ { - mineNextBlockWithMockTimestamps(t, syncer) + mineNextBlockWithMockTimestamps(t, syncer, rd) } testSync(syncer, syncee1) @@ -203,7 +206,7 @@ var currentMockTimestamp int64 = 0 // mineNextBlockWithMockTimestamps mines blocks with large timestamp differences // between every two blocks. This is done to avoid the timestamp threshold validation // of ibd-with-headers-proof -func mineNextBlockWithMockTimestamps(t *testing.T, harness *appHarness) *externalapi.DomainBlock { +func mineNextBlockWithMockTimestamps(t *testing.T, harness *appHarness, rd *rand.Rand) *externalapi.DomainBlock { blockTemplate, err := harness.rpcClient.GetBlockTemplate(harness.miningAddress) if err != nil { t.Fatalf("Error getting block template: %+v", err) @@ -223,7 +226,7 @@ func mineNextBlockWithMockTimestamps(t *testing.T, harness *appHarness) *externa mutableHeader.SetTimeInMilliseconds(currentMockTimestamp) block.Header = mutableHeader.ToImmutable() - solveBlock(block) + mining.SolveBlock(block, rd) _, err = harness.rpcClient.SubmitBlock(block) if err != nil { diff --git a/testing/integration/mining_test.go b/testing/integration/mining_test.go index 304f872e0..b101c0995 100644 --- a/testing/integration/mining_test.go +++ b/testing/integration/mining_test.go @@ -3,28 +3,13 @@ package integration import ( "math/rand" "testing" + "time" "github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" - "github.com/kaspanet/kaspad/domain/consensus/utils/pow" - "github.com/kaspanet/kaspad/util/difficulty" + "github.com/kaspanet/kaspad/domain/consensus/utils/mining" ) -func solveBlock(block *externalapi.DomainBlock) *externalapi.DomainBlock { - targetDifficulty := difficulty.CompactToBig(block.Header.Bits()) - headerForMining := block.Header.ToMutable() - initialNonce := rand.Uint64() - for i := initialNonce; i != initialNonce-1; i++ { - headerForMining.SetNonce(i) - if pow.CheckProofOfWorkWithTarget(headerForMining, targetDifficulty) { - block.Header = headerForMining.ToImmutable() - return block - } - } - - panic("Failed to solve block! This should never happen") -} - func mineNextBlock(t *testing.T, harness *appHarness) *externalapi.DomainBlock { blockTemplate, err := harness.rpcClient.GetBlockTemplate(harness.miningAddress) if err != nil { @@ -36,7 +21,8 @@ func mineNextBlock(t *testing.T, harness *appHarness) *externalapi.DomainBlock { t.Fatalf("Error converting block: %s", err) } - solveBlock(block) + rd := rand.New(rand.NewSource(time.Now().UnixNano())) + mining.SolveBlock(block, rd) _, err = harness.rpcClient.SubmitBlock(block) if err != nil {