From dd246587a359fdca8b36a5973c506a10e3e3ebbf Mon Sep 17 00:00:00 2001 From: Daira Emma Hopwood Date: Wed, 8 Mar 2023 16:01:57 +0000 Subject: [PATCH] Fix bit-rotted code in miner tests. Signed-off-by: Daira Hopwood --- src/crypto/equihash.cpp | 2 +- src/crypto/equihash.h | 2 +- src/miner.cpp | 35 ++-------- src/pow/tromp/equi.h | 69 ++++--------------- src/pow/tromp/equi_miner.h | 98 +++++++++++++++++++++++++- src/test/miner_tests.cpp | 136 ++++++++++++++++--------------------- 6 files changed, 175 insertions(+), 167 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index 5f3c324ac..fbfb5d902 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -127,7 +127,7 @@ void EhIndexToArray(const eh_index i, unsigned char* array) memcpy(array, &bei, sizeof(eh_index)); } -std::vector GetMinimalFromIndices(std::vector indices, +std::vector GetMinimalFromIndices(const std::vector& indices, size_t cBitLen) { assert(((cBitLen+1)+7)/8 <= sizeof(eh_index)); diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index a57bc0f83..586068d0a 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -16,7 +16,7 @@ inline constexpr size_t equihash_solution_size(unsigned int N, unsigned int K) { typedef uint32_t eh_index; typedef uint8_t eh_trunc; -std::vector GetMinimalFromIndices(std::vector indices, +std::vector GetMinimalFromIndices(const std::vector& indices, size_t cBitLen); void CompressArray(const unsigned char* in, size_t in_len, unsigned char* out, size_t out_len, diff --git a/src/miner.cpp b/src/miner.cpp index f8602cc38..ac1b54a61 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -1053,37 +1053,12 @@ void static BitcoinMiner(const CChainParams& chainparams) // TODO: factor this out into a function with the same API for each solver. if (solver == "tromp") { - // Create solver and initialize it. - equi eq(1); - eq.setstate(curr_state.inner); - - // Initialization done, start algo driver. - eq.digit0(0); - eq.xfull = eq.bfull = eq.hfull = 0; - eq.showbsizes(0); - for (u32 r = 1; r < WK; r++) { - (r&1) ? eq.digitodd(r, 0) : eq.digiteven(r, 0); - eq.xfull = eq.bfull = eq.hfull = 0; - eq.showbsizes(r); - } - eq.digitK(0); - ehSolverRuns.increment(); - - // Convert solution indices to byte array (decompress) and pass it to validBlock method. - for (size_t s = 0; s < std::min(MAXSOLS, eq.nsols); s++) { + std::function incrementRuns = [&]() { ehSolverRuns.increment(); }; + std::function&)> checkSolution = [&](size_t s, const std::vector& index_vector) { LogPrint("pow", "Checking solution %d\n", s+1); - std::vector index_vector(PROOFSIZE); - for (size_t i = 0; i < PROOFSIZE; i++) { - index_vector[i] = eq.sols[s][i]; - } - std::vector sol_char = GetMinimalFromIndices(index_vector, DIGITBITS); - - if (validBlock(sol_char)) { - // If we find a POW solution, do not try other solutions - // because they become invalid as we created a new block in blockchain. - break; - } - } + return validBlock(GetMinimalFromIndices(index_vector, DIGITBITS)); + }; + equihash_solve(curr_state.inner, incrementRuns, checkSolution); } else { try { // If we find a valid block, we rebuild diff --git a/src/pow/tromp/equi.h b/src/pow/tromp/equi.h index 8df0f42ef..0f829f132 100644 --- a/src/pow/tromp/equi.h +++ b/src/pow/tromp/equi.h @@ -1,5 +1,5 @@ // Equihash solver -// Copyright (c) 2016-2016 John Tromp, The Zcash developers +// Copyright (c) 2016-2023 John Tromp, The Zcash developers #ifndef ZCASH_POW_TROMP_EQUI_H #define ZCASH_POW_TROMP_EQUI_H @@ -43,64 +43,19 @@ typedef u32 proof[PROOFSIZE]; enum verify_code { POW_OK, POW_DUPLICATE, POW_OUT_OF_ORDER, POW_NONZERO_XOR }; -const char *errstr[] = { "OK", "duplicate index", "indices out of order", "nonzero xor" }; +extern const char *errstr[4]; -void genhash(const rust::Box& ctx, u32 idx, uchar *hash) { - auto state = ctx->box_clone(); - u32 leb = htole32(idx / HASHESPERBLAKE); - state->update({(const uchar *)&leb, sizeof(u32)}); - uchar blakehash[HASHOUT]; - state->finalize({blakehash, HASHOUT}); - memcpy(hash, blakehash + (idx % HASHESPERBLAKE) * WN/8, WN/8); -} - -int verifyrec(const rust::Box& ctx, u32 *indices, uchar *hash, int r) { - if (r == 0) { - genhash(ctx, *indices, hash); - return POW_OK; - } - u32 *indices1 = indices + (1 << (r-1)); - if (*indices >= *indices1) - return POW_OUT_OF_ORDER; - uchar hash0[WN/8], hash1[WN/8]; - int vrf0 = verifyrec(ctx, indices, hash0, r-1); - if (vrf0 != POW_OK) - return vrf0; - int vrf1 = verifyrec(ctx, indices1, hash1, r-1); - if (vrf1 != POW_OK) - return vrf1; - for (int i=0; i < WN/8; i++) - hash[i] = hash0[i] ^ hash1[i]; - int i, b = r * DIGITBITS; - for (i = 0; i < b/8; i++) - if (hash[i]) - return POW_NONZERO_XOR; - if ((b%8) && hash[i] >> (8-(b%8))) - return POW_NONZERO_XOR; - return POW_OK; -} - -int compu32(const void *pa, const void *pb) { - u32 a = *(u32 *)pa, b = *(u32 *)pb; - return a& ctx, u32 idx, uchar *hash); +int verifyrec(const rust::Box& ctx, u32 *indices, uchar *hash, int r); +int compu32(const void *pa, const void *pb); +bool duped(proof prf); // verify Wagner conditions -int verify(u32 indices[PROOFSIZE], const rust::Box ctx) { - if (duped(indices)) - return POW_DUPLICATE; - uchar hash[WN/8]; - return verifyrec(ctx, indices, hash, WK); -} +int verify(u32 indices[PROOFSIZE], const rust::Box ctx); + +bool equihash_solve( + const rust::Box& curr_state, + std::function& incrementRuns, + std::function&)>& checkSolution); #endif // ZCASH_POW_TROMP_EQUI_H diff --git a/src/pow/tromp/equi_miner.h b/src/pow/tromp/equi_miner.h index fe4df3cb1..448b4ba02 100644 --- a/src/pow/tromp/equi_miner.h +++ b/src/pow/tromp/equi_miner.h @@ -1,5 +1,5 @@ // Equihash solver -// Copyright (c) 2016 John Tromp, The Zcash developers +// Copyright (c) 2016-2023 John Tromp, The Zcash developers // Fix N, K, such that n = N/(k+1) is integer // Fix M = 2^{n+1} hashes each of length N bits, @@ -75,6 +75,8 @@ static const u32 NBLOCKS = (NHASHES+HASHESPERBLAKE-1)/HASHESPERBLAKE; // nothing larger found in 100000 runs static const u32 MAXSOLS = 8; +const char *errstr[] = { "OK", "duplicate index", "indices out of order", "nonzero xor" }; + // tree node identifying its children as two different slots in // a bucket on previous layer with the same rest bits (x-tra hash) struct tree { @@ -646,4 +648,98 @@ void *worker(void *vp) { return 0; } +void genhash(const rust::Box& ctx, u32 idx, uchar *hash) { + auto state = ctx->box_clone(); + u32 leb = htole32(idx / HASHESPERBLAKE); + state->update({(const uchar *)&leb, sizeof(u32)}); + uchar blakehash[HASHOUT]; + state->finalize({blakehash, HASHOUT}); + memcpy(hash, blakehash + (idx % HASHESPERBLAKE) * WN/8, WN/8); +} + +int verifyrec(const rust::Box& ctx, u32 *indices, uchar *hash, int r) { + if (r == 0) { + genhash(ctx, *indices, hash); + return POW_OK; + } + u32 *indices1 = indices + (1 << (r-1)); + if (*indices >= *indices1) + return POW_OUT_OF_ORDER; + uchar hash0[WN/8], hash1[WN/8]; + int vrf0 = verifyrec(ctx, indices, hash0, r-1); + if (vrf0 != POW_OK) + return vrf0; + int vrf1 = verifyrec(ctx, indices1, hash1, r-1); + if (vrf1 != POW_OK) + return vrf1; + for (int i=0; i < WN/8; i++) + hash[i] = hash0[i] ^ hash1[i]; + int i, b = r * DIGITBITS; + for (i = 0; i < b/8; i++) + if (hash[i]) + return POW_NONZERO_XOR; + if ((b%8) && hash[i] >> (8-(b%8))) + return POW_NONZERO_XOR; + return POW_OK; +} + +int compu32(const void *pa, const void *pb) { + u32 a = *(u32 *)pa, b = *(u32 *)pb; + return a ctx) { + if (duped(indices)) + return POW_DUPLICATE; + uchar hash[WN/8]; + return verifyrec(ctx, indices, hash, WK); +} + +bool equihash_solve( + const rust::Box& curr_state, + std::function& incrementRuns, + std::function&)>& checkSolution) +{ + // Create solver and initialize it. + equi eq(1); + eq.setstate(curr_state); + + // Initialization done, start algo driver. + eq.digit0(0); + eq.xfull = eq.bfull = eq.hfull = 0; + eq.showbsizes(0); + for (u32 r = 1; r < WK; r++) { + (r&1) ? eq.digitodd(r, 0) : eq.digiteven(r, 0); + eq.xfull = eq.bfull = eq.hfull = 0; + eq.showbsizes(r); + } + eq.digitK(0); + incrementRuns(); + + // Decompress solution indices and pass to checkSolution. + for (size_t s = 0; s < std::min(MAXSOLS, eq.nsols); s++) { + std::vector index_vector(PROOFSIZE); + for (size_t i = 0; i < PROOFSIZE; i++) { + index_vector[i] = eq.sols[s][i]; + } + if (checkSolution(s, index_vector)) { + // If we find a POW solution, do not try other solutions + // because they become invalid as we created a new block in blockchain. + return true; + } + } + return false; +} + #endif // ZCASH_POW_TROMP_EQUI_MINER_H diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 92ca9b93e..fab302b63 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -12,10 +12,11 @@ #include "uint256.h" #include "util/system.h" #include "crypto/equihash.h" -//#include "pow/tromp/equi_miner.h" - +#include "pow/tromp/equi.h" #include "test/test_bitcoin.h" +#include + #include BOOST_FIXTURE_TEST_SUITE(miner_tests, TestingSetup) @@ -170,11 +171,18 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) // Simple block creation, nothing special yet: BOOST_CHECK(pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey)); - // We can't make transactions until we have inputs - // Therefore, load 100 blocks :) - std::vectortxFirst; - for (unsigned int i = 0; i < sizeof(blockinfo)/sizeof(*blockinfo); ++i) + // We can't make transactions until we have inputs. Therefore, load 110 blocks. + const int nblocks = 110; + assert(nblocks < chainparams.GetConsensus().SubsidySlowStartShift()); + + auto MinerSubsidy = [](int height) { return height*50000; }; + auto FoundersReward = [](int height) { return height*12500; }; + + std::vector txFirst; + + for (unsigned int i = 0; i < nblocks; ++i) { + int height = i+1; CBlock *pblock = &pblocktemplate->block; // pointer for convenience pblock->nVersion = 4; // Fake the blocks taking at least nPowTargetSpacing to be mined. @@ -182,99 +190,70 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) // of the next block must be six spacings ahead of that to be at least // one spacing ahead of the tip. Within 11 blocks of genesis, the median // will be closer to the tip, and blocks will appear slower. - pblock->nTime = chainActive.Tip()->GetMedianTimePast()+6*Params().GetConsensus().PoWTargetSpacing(i); + pblock->nTime = chainActive.Tip()->GetMedianTimePast() + 6*chainparams.GetConsensus().PoWTargetSpacing(i); pblock->nBits = GetNextWorkRequired(chainActive.Tip(), pblock, chainparams.GetConsensus()); CMutableTransaction txCoinbase(pblock->vtx[0]); txCoinbase.nVersion = 1; txCoinbase.vin[0].scriptSig = CScript() << (chainActive.Height()+1) << OP_0; txCoinbase.vout[0].scriptPubKey = CScript(); - txCoinbase.vout[0].nValue = 50000 * (i + 1); - txCoinbase.vout[1].nValue = 12500 * (i + 1); + txCoinbase.vout[0].nValue = MinerSubsidy(height); + txCoinbase.vout[1].nValue = FoundersReward(height); pblock->vtx[0] = CTransaction(txCoinbase); if (txFirst.size() < 2) txFirst.push_back(new CTransaction(pblock->vtx[0])); pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); - pblock->nNonce = uint256S(blockinfo[i].nonce_hex); - pblock->nSolution = ParseHex(blockinfo[i].solution_hex); -/* - { - arith_uint256 try_nonce(0); - unsigned int n = Params().GetConsensus().nEquihashN; - unsigned int k = Params().GetConsensus().nEquihashK; + if (i < sizeof(blockinfo)/sizeof(*blockinfo)) { + pblock->nNonce = uint256S(blockinfo[i].nonce_hex); + pblock->nSolution = ParseHex(blockinfo[i].solution_hex); + } else { + // If you need to mine more blocks than are currently in blockinfo, this code will do so. + // We leave it compiling so that it doesn't bitrot. + arith_uint256 try_nonce(0); + unsigned int n = chainparams.GetConsensus().nEquihashN; + unsigned int k = chainparams.GetConsensus().nEquihashK; - // Hash state - eh_HashState eh_state = EhInitialiseState(n, k); + // Hash state + eh_HashState eh_state = EhInitialiseState(n, k); - // I = the block header minus nonce and solution. - CEquihashInput I{*pblock}; - CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); - ss << I; + // I = the block header minus nonce and solution. + CEquihashInput I{*pblock}; + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << I; - // H(I||... - eh_state.Update((unsigned char*)&ss[0], ss.size()); + // H(I||... + eh_state.Update((unsigned char*)&ss[0], ss.size()); - while (true) { - pblock->nNonce = ArithToUint256(try_nonce); + while (true) { + pblock->nNonce = ArithToUint256(try_nonce); - // H(I||V||... - eh_HashState curr_state(eh_state); - curr_state.Update(pblock->nNonce.begin(), pblock->nNonce.size()); + // H(I||V||... + eh_HashState curr_state(eh_state); + curr_state.Update(pblock->nNonce.begin(), pblock->nNonce.size()); - // Create solver and initialize it. - equi eq(1); - eq.setstate(curr_state.state); + std::function incrementRuns = [&]() {}; + std::function&)> checkSolution = [&](size_t s, const std::vector& index_vector) { + auto soln = GetMinimalFromIndices(index_vector, DIGITBITS); + pblock->nSolution = soln; + if (!equihash::is_valid( + n, k, + {(const unsigned char*)ss.data(), ss.size()}, + {pblock->nNonce.begin(), pblock->nNonce.size()}, + {soln.data(), soln.size()})) { + return false; + } + CValidationState state; + return ProcessNewBlock(state, chainparams, NULL, pblock, true, NULL) && state.IsValid(); + }; + if (equihash_solve(curr_state.inner, incrementRuns, checkSolution)) + break; - // Initialization done, start algo driver. - eq.digit0(0); - eq.xfull = eq.bfull = eq.hfull = 0; - eq.showbsizes(0); - for (u32 r = 1; r < WK; r++) { - (r&1) ? eq.digitodd(r, 0) : eq.digiteven(r, 0); - eq.xfull = eq.bfull = eq.hfull = 0; - eq.showbsizes(r); + try_nonce += 1; } - eq.digitK(0); - - // Convert solution indices to byte array (decompress) and pass it to validBlock method. - std::set> solns; - for (size_t s = 0; s < std::min(MAXSOLS, eq.nsols); s++) { - LogPrint("pow", "Checking solution %d\n", s+1); - std::vector index_vector(PROOFSIZE); - for (size_t i = 0; i < PROOFSIZE; i++) { - index_vector[i] = eq.sols[s][i]; - } - std::vector sol_char = GetMinimalFromIndices(index_vector, DIGITBITS); - solns.insert(sol_char); - } - - for (auto soln : solns) { - if (!equihash::is_valid( - n, k, - {(const unsigned char*)ss.data(), ss.size()}, - {pblock->nNonce.begin(), pblock->nNonce.size()}, - {soln.data(), soln.size()})) continue; - pblock->nSolution = soln; - - CValidationState state; - - if (ProcessNewBlock(state, NULL, pblock, true, NULL) && state.IsValid()) { - goto foundit; - } - - //std::cout << state.GetRejectReason() << std::endl; - } - - try_nonce += 1; - } - foundit: - std::cout << " {\"" << pblock->nNonce.GetHex() << "\", \""; std::cout << HexStr(pblock->nSolution.begin(), pblock->nSolution.end()); std::cout << "\"}," << std::endl; - } -*/ // These tests assume null hashBlockCommitments (before Sapling) pblock->hashBlockCommitments = uint256(); @@ -285,6 +264,9 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) pblock->hashPrevBlock = pblock->GetHash(); } + // Stop here if we needed to mine more blocks. + assert(nblocks == sizeof(blockinfo)/sizeof(*blockinfo)); + // Set the clock to be just ahead of the last "mined" block, to ensure we satisfy the // future timestamp soft fork rule. auto curTime = GetTime();