Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions src/crypto/progpow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@

#include "progpow.h"

#include <chainparams.h>
#include <crypto/progpow/helpers.hpp>
#include <crypto/progpow/lib/ethash/endianness.hpp>
#include <crypto/progpow/include/ethash/ethash.hpp>
#include <hash.h>
#include <primitives/block.h>

#include <sstream>
#include <cassert>
#include <stdexcept>

static inline ethash::hash256 U256ToH256(const uint256& in) {

Expand Down Expand Up @@ -40,15 +38,23 @@ static inline uint256 H256ToU256(const ethash::hash256& in) {

uint256 progpow_hash_full(const CProgPowHeader& header, uint256& mix_hash)
{
static ethash::epoch_context_ptr epochContext{nullptr,nullptr};
if (!epochContext || epochContext->epoch_number != ethash::get_epoch_number(header.nHeight))
{
epochContext.reset();
epochContext = ethash::create_epoch_context(ethash::get_epoch_number(header.nHeight));
}
return progpow_hash_full(progpow_header_hash(header), header.nHeight, header.nNonce64, mix_hash);
}

const auto header_h256{U256ToH256(SerializeHash(header))};
const auto result = progpow::hash(*epochContext, header.nHeight, header_h256, header.nNonce64);
ethash::hash256 progpow_header_hash(const CProgPowHeader& header)
{
return U256ToH256(SerializeHash(header));
}

uint256 progpow_hash_full(const ethash::hash256& header_hash, uint32_t height, uint64_t nonce, uint256& mix_hash)
{
// The managed cache keeps this context alive in the calling thread, including across
// epoch changes in other threads. Hashing only reads the light cache.
const auto* epochContext = ethash_get_global_epoch_context(ethash::get_epoch_number(height));
if (!epochContext)
throw std::runtime_error("Unable to allocate FiroPoW epoch context");

const auto result = progpow::hash(*epochContext, height, header_hash, nonce);
mix_hash = H256ToU256(result.mix_hash);
return H256ToU256(result.final_hash);
}
Expand All @@ -63,4 +69,4 @@ uint256 progpow_hash_light(const CProgPowHeader& header)
const auto seed_h256{progpow::hash_seed(header_h256, header.nNonce64)};
const auto final_h256{progpow::hash_final(seed_h256, mix_h256)};
return H256ToU256(final_h256);
}
}
8 changes: 7 additions & 1 deletion src/crypto/progpow.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ class CProgPowHeader {
/* Performs a full progpow hash (DAG loops implied) provided header already hash nHeight valued */
uint256 progpow_hash_full(const CProgPowHeader& header, uint256& mix_hash);

/** Precompute the nonce-independent input. Refresh after changing any serialized header field. */
ethash::hash256 progpow_header_hash(const CProgPowHeader& header);

/** Hash one nonce using the managed light cache. Height must match the precomputed header. */
uint256 progpow_hash_full(const ethash::hash256& header_hash, uint32_t height, uint64_t nonce, uint256& mix_hash);

/* Performs a light progpow hash (DAG loops excluded) provided header has mix_hash */
uint256 progpow_hash_light(const CProgPowHeader& header);

#endif // FIRO_PROGPOW_H
#endif // FIRO_PROGPOW_H
74 changes: 51 additions & 23 deletions src/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@
#include "crypto/MerkleTreeProof/mtp.h"
#include "crypto/Lyra2Z/Lyra2Z.h"
#include "crypto/Lyra2Z/Lyra2.h"
#include "crypto/progpow.h"
#include "evo/spork.h"
#include <algorithm>
#include <boost/thread.hpp>
#include <boost/tuple/tuple.hpp>
#include <chrono>
#include <queue>
#include <unistd.h>

Expand Down Expand Up @@ -1083,6 +1085,7 @@ void static FiroMiner(const CChainParams &chainparams) {
}

while (true) {
boost::this_thread::interruption_point();
if (chainparams.MiningRequiresPeers()) {
// Busy-wait for the network to come online so we don't waste time mining
// on an obsolete chain. In regtest mode we expect to fly solo.
Expand All @@ -1109,10 +1112,6 @@ void static FiroMiner(const CChainParams &chainparams) {
// Create new block
//
unsigned int nTransactionsUpdatedLast = mempool.GetTransactionsUpdated();
CBlockIndex *pindexPrev = chainActive.Tip();
if (pindexPrev) {
LogPrintf("loop pindexPrev->nHeight=%d\n", pindexPrev->nHeight);
}
LogPrintf("BEFORE: pblocktemplate\n");
std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(Params()).CreateNewBlock(coinbaseScript->reserveScript, {});
LogPrintf("AFTER: pblocktemplate\n");
Expand All @@ -1121,7 +1120,14 @@ void static FiroMiner(const CChainParams &chainparams) {
return;
}
CBlock *pblock = &pblocktemplate->block;
IncrementExtraNonce(pblock, pindexPrev, nExtraNonce);
CBlockIndex* pindexPrev;
{
LOCK(cs_main);
pindexPrev = chainActive.Tip();
if (pblock->hashPrevBlock != pindexPrev->GetBlockHash())
continue;
IncrementExtraNonce(pblock, pindexPrev, nExtraNonce);
}

LogPrintf("Running FiroMiner with %u transactions in block (%u bytes)\n", pblock->vtx.size(),
::GetSerializeSize(*pblock, SER_NETWORK, PROTOCOL_VERSION));
Expand All @@ -1145,10 +1151,15 @@ void static FiroMiner(const CChainParams &chainparams) {
// Check if something found
uint256 thash;
uint256 mix_hash;
bool found = false;
const bool fProgPow = pblock->IsProgPow();
const auto headerHash = fProgPow ? progpow_header_hash(pblock->GetProgPowHeader()) : ethash::hash256{};
// Bound stale work by elapsed time as well as the nonce count.
const auto batchEnd = std::chrono::steady_clock::now() + std::chrono::seconds(1);

while (true) {
if (pblock->IsProgPow()) {
thash = pblock->GetProgPowHashFull(mix_hash);
if (fProgPow) {
thash = progpow_hash_full(headerHash, pblock->nHeight, pblock->nNonce64, mix_hash);
} else if (pblock->IsMTP()) {
thash = mtp::hash(*pblock, Params().GetConsensus().powLimit);
pblock->mtpHashValue = thash;
Expand Down Expand Up @@ -1194,27 +1205,32 @@ void static FiroMiner(const CChainParams &chainparams) {
// In regression test mode, stop mining after a block is found.
if (chainparams.MineBlocksOnDemand())
throw boost::thread_interrupted();
found = true;
break;
}
pblock->nNonce += 1;
pblock->nNonce64 += 1;
if ((pblock->nNonce & 0xFF) == 0)
if ((pblock->nNonce & 0xFF) == 0 || std::chrono::steady_clock::now() >= batchEnd)
break;
}
// Rebuild after any solution, including a stale or rejected block.
if (found)
break;
// Regtest mode doesn't require peers
if (g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0 && chainparams.MiningRequiresPeers())
if (chainparams.MiningRequiresPeers() && g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0)
break;
if (pblock->nNonce >= 0xffff0000)
break;
if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 60)
break;
if (pindexPrev != chainActive.Tip())
break;
{
LOCK(cs_main);
if (pindexPrev != chainActive.Tip())
break;

// Update nTime every few seconds
if (UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev) < 0)
break; // Recreate the block if the clock has run backwards,
// so that we can use the correct time.
if (UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev) < 0)
break; // Recreate the block if the clock has run backwards.
}
if (chainparams.GetConsensus().fPowAllowMinDifficultyBlocks) {
// Changing pblock->nTime can change work required on testnet:
hashTarget.SetCompact(pblock->nBits);
Expand All @@ -1234,30 +1250,42 @@ void static FiroMiner(const CChainParams &chainparams) {

void GenerateBitcoins(bool fGenerate, int nThreads, const CChainParams& chainparams)
{
static boost::thread_group* minerThreads = NULL;
// Workers need cs_main while stopping; callers must not hold it across join_all().
AssertLockNotHeld(cs_main);
static CCriticalSection cs_miner;
LOCK(cs_miner);
boost::this_thread::disable_interruption noInterrupt;
static std::unique_ptr<boost::thread_group> minerThreads;

if (nThreads < 0)
nThreads = GetNumCores();

if (minerThreads != NULL)
if (minerThreads)
{
minerThreads->interrupt_all();
delete minerThreads;
minerThreads = NULL;
minerThreads->join_all();
minerThreads.reset();
}

if (nThreads == 0 || !fGenerate)
return;

minerThreads = new boost::thread_group();
for (int i = 0; i < nThreads; i++)
minerThreads->create_thread(boost::bind(&FiroMiner, boost::cref(chainparams)));
minerThreads = std::make_unique<boost::thread_group>();
try {
for (int i = 0; i < nThreads; ++i)
minerThreads->create_thread(boost::bind(&FiroMiner, boost::cref(chainparams)));
} catch (...) {
minerThreads->interrupt_all();
minerThreads->join_all();
minerThreads.reset();
throw;
}
}

void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce)
{
// Update nExtraNonce
static uint256 hashPrevBlock;
static thread_local uint256 hashPrevBlock;
if (hashPrevBlock != pblock->hashPrevBlock)
{
nExtraNonce = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/miner.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ class BlockAssembler
/** Modify the extranonce in a block */
void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce);
int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev);
/** Run the miner threads */
/** Run the miner threads. Must not be called with cs_main held. */
void GenerateBitcoins(bool fGenerate, int nThreads, const CChainParams& chainparams);

#endif // BITCOIN_MINER_H
8 changes: 6 additions & 2 deletions src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,10 @@ UniValue generateBlocks(boost::shared_ptr<CReserveScript> coinbaseScript, int nG
CBlock *pblock = &pblocktemplate->block;
{
LOCK(cs_main);
IncrementExtraNonce(pblock, chainActive.Tip(), nExtraNonce);
const CBlockIndex* pindexPrev = chainActive.Tip();
if (pblock->hashPrevBlock != pindexPrev->GetBlockHash())
continue;
IncrementExtraNonce(pblock, pindexPrev, nExtraNonce);
}

/**
Expand All @@ -149,9 +152,10 @@ UniValue generateBlocks(boost::shared_ptr<CReserveScript> coinbaseScript, int nG
*/

if (pblock->IsProgPow()) {
const auto header_hash = progpow_header_hash(pblock->GetProgPowHeader());
while (nMaxTries > 0 && pblock->nNonce64 < nInnerLoopCount) {
uint256 mix_hash;
auto final_hash{progpow_hash_full(pblock->GetProgPowHeader(), mix_hash)};
auto final_hash{progpow_hash_full(header_hash, pblock->nHeight, pblock->nNonce64, mix_hash)};
if (CheckProofOfWork(final_hash, pblock->nBits, Params().GetConsensus()))
{
pblock->mix_hash = mix_hash;
Expand Down
1 change: 1 addition & 0 deletions src/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ add_executable(test_firo
${CMAKE_CURRENT_SOURCE_DIR}/mbstring_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mempool_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/merkle_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/miner_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mtp_halving_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mtp_tests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mtp_trans_tests.cpp
Expand Down
77 changes: 77 additions & 0 deletions src/test/firopow_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@
#include "test/fixtures.h"

#include <boost/test/unit_test.hpp>
#include <crypto/progpow.h>
#include <crypto/progpow/firopow_test_vectors.hpp>
#include <crypto/progpow/lib/ethash/ethash-internal.hpp>
#include <crypto/progpow/include/ethash/progpow.hpp>
#include <crypto/progpow/helpers.hpp>
#include <hash.h>

#include <array>
#include <future>
#include <latch>

BOOST_FIXTURE_TEST_SUITE(firpow_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(firopow_hash_and_verify) {
Expand Down Expand Up @@ -39,4 +45,75 @@ BOOST_AUTO_TEST_CASE(firopow_hash_and_verify) {
}
}

BOOST_AUTO_TEST_CASE(firopow_prepared_header)
{
const CProgPowHeader original{1, uint256S("12"), uint256S("34"), 123456, 0x207fffff, 1, 42, uint256()};
std::array<CProgPowHeader, 9> headers;
headers.fill(original);
++headers[1].nVersion;
++headers[2].hashPrevBlock.begin()[0];
++headers[3].hashMerkleRoot.begin()[0];
++headers[4].nTime;
++headers[5].nBits;
++headers[6].nHeight;
++headers[7].nNonce64;
headers[8].mix_hash = uint256S("56");

auto context = ethash::create_epoch_context(0);
BOOST_REQUIRE(context);
const auto original_hash = progpow_header_hash(original);
for (size_t i = 0; i < headers.size(); ++i) {
const auto& header = headers[i];
// Encode the consensus header independently of CProgPowHeader's serializer.
std::array<unsigned char, 80> bytes{};
WriteLE32(bytes.data(), header.nVersion);
std::copy(header.hashPrevBlock.begin(), header.hashPrevBlock.end(), bytes.begin() + 4);
std::copy(header.hashMerkleRoot.begin(), header.hashMerkleRoot.end(), bytes.begin() + 36);
WriteLE32(bytes.data() + 68, header.nTime);
WriteLE32(bytes.data() + 72, header.nBits);
WriteLE32(bytes.data() + 76, header.nHeight);
uint256 serialized_hash;
CHash256().Write(bytes.data(), bytes.size()).Finalize(serialized_hash.begin());
const auto reference_header = to_hash256(serialized_hash.GetHex());
const auto prepared_header = progpow_header_hash(header);
BOOST_CHECK(ethash::is_equal(prepared_header, reference_header));
BOOST_CHECK_EQUAL(ethash::is_equal(prepared_header, original_hash), i == 0 || i >= 7);

const auto reference = progpow::hash(*context, header.nHeight, reference_header, header.nNonce64);
uint256 prepared_mix, wrapper_mix;
const auto prepared = progpow_hash_full(prepared_header, header.nHeight, header.nNonce64, prepared_mix);
const auto wrapped = progpow_hash_full(header, wrapper_mix);
BOOST_CHECK_EQUAL(prepared.GetHex(), to_hex(reference.final_hash));
BOOST_CHECK_EQUAL(prepared_mix.GetHex(), to_hex(reference.mix_hash));
BOOST_CHECK(prepared == wrapped);
BOOST_CHECK(prepared_mix == wrapper_mix);
}
}

BOOST_AUTO_TEST_CASE(firopow_concurrent_epochs)
{
const std::array<const firopow_hash_test_case*, 2> cases{
&firopow_hash_test_cases[4], &firopow_hash_test_cases[5]};
BOOST_REQUIRE_EQUAL(cases[0]->block_number, ethash::epoch_length - 1);
BOOST_REQUIRE_EQUAL(cases[1]->block_number, ethash::epoch_length);
std::array<std::future<bool>, 4> workers;
std::latch start{workers.size()};
for (size_t worker = 0; worker < workers.size(); ++worker) {
workers[worker] = std::async(std::launch::async, [&, worker] {
start.arrive_and_wait();
for (size_t offset = 0; offset < cases.size(); ++offset) {
const auto& t = *cases[(worker + offset) % cases.size()];
uint256 mix;
const auto result = progpow_hash_full(to_hash256(t.header_hash_hex), t.block_number,
std::stoull(t.nonce_hex, nullptr, 16), mix);
if (result.GetHex() != t.final_hash_hex || mix.GetHex() != t.mix_hash_hex)
return false;
}
return true;
});
}
for (auto& worker : workers)
BOOST_CHECK(worker.get());
}

BOOST_AUTO_TEST_SUITE_END()
Loading
Loading