Fix bit-rotted code in miner tests.

Signed-off-by: Daira Hopwood <daira@jacaranda.org>
This commit is contained in:
Daira Emma Hopwood 2023-03-08 16:01:57 +00:00
parent 33d9ff3d24
commit dd246587a3
6 changed files with 175 additions and 167 deletions

View File

@ -127,7 +127,7 @@ void EhIndexToArray(const eh_index i, unsigned char* array)
memcpy(array, &bei, sizeof(eh_index));
}
std::vector<unsigned char> GetMinimalFromIndices(std::vector<eh_index> indices,
std::vector<unsigned char> GetMinimalFromIndices(const std::vector<eh_index>& indices,
size_t cBitLen)
{
assert(((cBitLen+1)+7)/8 <= sizeof(eh_index));

View File

@ -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<unsigned char> GetMinimalFromIndices(std::vector<eh_index> indices,
std::vector<unsigned char> GetMinimalFromIndices(const std::vector<eh_index>& indices,
size_t cBitLen);
void CompressArray(const unsigned char* in, size_t in_len,
unsigned char* out, size_t out_len,

View File

@ -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<void()> incrementRuns = [&]() { ehSolverRuns.increment(); };
std::function<bool(size_t s, const std::vector<uint32_t>&)> checkSolution = [&](size_t s, const std::vector<uint32_t>& index_vector) {
LogPrint("pow", "Checking solution %d\n", s+1);
std::vector<eh_index> index_vector(PROOFSIZE);
for (size_t i = 0; i < PROOFSIZE; i++) {
index_vector[i] = eq.sols[s][i];
}
std::vector<unsigned char> 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

View File

@ -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<blake2b::State>& 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<blake2b::State>& 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<b ? -1 : a==b ? 0 : +1;
}
bool duped(proof prf) {
proof sortprf;
memcpy(sortprf, prf, sizeof(proof));
qsort(sortprf, PROOFSIZE, sizeof(u32), &compu32);
for (u32 i=1; i<PROOFSIZE; i++)
if (sortprf[i] <= sortprf[i-1])
return true;
return false;
}
void genhash(const rust::Box<blake2b::State>& ctx, u32 idx, uchar *hash);
int verifyrec(const rust::Box<blake2b::State>& 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<blake2b::State> 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<blake2b::State> ctx);
bool equihash_solve(
const rust::Box<blake2b::State>& curr_state,
std::function<void()>& incrementRuns,
std::function<bool(size_t s, const std::vector<uint32_t>&)>& checkSolution);
#endif // ZCASH_POW_TROMP_EQUI_H

View File

@ -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<blake2b::State>& 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<blake2b::State>& 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<b ? -1 : a==b ? 0 : +1;
}
bool duped(proof prf) {
proof sortprf;
memcpy(sortprf, prf, sizeof(proof));
qsort(sortprf, PROOFSIZE, sizeof(u32), &compu32);
for (u32 i=1; i<PROOFSIZE; i++)
if (sortprf[i] <= sortprf[i-1])
return true;
return false;
}
// verify Wagner conditions
int verify(u32 indices[PROOFSIZE], const rust::Box<blake2b::State> ctx) {
if (duped(indices))
return POW_DUPLICATE;
uchar hash[WN/8];
return verifyrec(ctx, indices, hash, WK);
}
bool equihash_solve(
const rust::Box<blake2b::State>& curr_state,
std::function<void()>& incrementRuns,
std::function<bool(size_t s, const std::vector<uint32_t>&)>& 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<uint32_t> 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

View File

@ -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 <rust/equihash.h>
#include <boost/test/unit_test.hpp>
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::vector<CTransaction*>txFirst;
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<CTransaction*> 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<void()> incrementRuns = [&]() {};
std::function<bool(size_t, const std::vector<uint32_t>&)> checkSolution = [&](size_t s, const std::vector<uint32_t>& 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<std::vector<unsigned char>> solns;
for (size_t s = 0; s < std::min(MAXSOLS, eq.nsols); s++) {
LogPrint("pow", "Checking solution %d\n", s+1);
std::vector<eh_index> index_vector(PROOFSIZE);
for (size_t i = 0; i < PROOFSIZE; i++) {
index_vector[i] = eq.sols[s][i];
}
std::vector<unsigned char> 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();