Auto merge of #4448 - str4d:equihash-validator-rust-nu, r=ebfull
consensus: From Heartwood activation, use Rust Equihash validator The C++ and Rust Equihash validators are intended to have an identical set of valid Equihash solutions, so this should merely be an implementation detail. However, deploying the Rust validator at the same time as a network upgrade reduces the risk of an unintentional consensus divergence due to undocumented behaviour in either implementation. Once Heartwood has activated on mainnet, we can verify that all pre-Heartwood blocks satisfy the Rust validator, and then remove the C++ validator and make Equihash-checking non-contextual again. Replaces zcash/zcash#2851.
This commit is contained in:
commit
b5dda4ecc5
28
src/main.cpp
28
src/main.cpp
|
@ -1740,7 +1740,7 @@ bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHea
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus::Params& consensusParams)
|
||||
bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, int nHeight, const Consensus::Params& consensusParams)
|
||||
{
|
||||
block.SetNull();
|
||||
|
||||
|
@ -1758,7 +1758,7 @@ bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus:
|
|||
}
|
||||
|
||||
// Check the header
|
||||
if (!(CheckEquihashSolution(&block, consensusParams) &&
|
||||
if (!(CheckEquihashSolution(&block, nHeight, consensusParams) &&
|
||||
CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)))
|
||||
return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString());
|
||||
|
||||
|
@ -1767,7 +1767,7 @@ bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus:
|
|||
|
||||
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams)
|
||||
{
|
||||
if (!ReadBlockFromDisk(block, pindex->GetBlockPos(), consensusParams))
|
||||
if (!ReadBlockFromDisk(block, pindex->GetBlockPos(), pindex->nHeight, consensusParams))
|
||||
return false;
|
||||
if (block.GetHash() != pindex->GetBlockHash())
|
||||
return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s",
|
||||
|
@ -3855,13 +3855,19 @@ bool CheckBlockHeader(
|
|||
const CChainParams& chainparams,
|
||||
bool fCheckPOW)
|
||||
{
|
||||
auto consensusParams = chainparams.GetConsensus();
|
||||
|
||||
// Check block version
|
||||
if (block.nVersion < MIN_BLOCK_VERSION)
|
||||
return state.DoS(100, error("CheckBlockHeader(): block version too low"),
|
||||
REJECT_INVALID, "version-too-low");
|
||||
|
||||
// Check Equihash solution is valid
|
||||
if (fCheckPOW && !CheckEquihashSolution(&block, chainparams.GetConsensus()))
|
||||
// Check Equihash solution is valid. The main check is in ContextualCheckBlockHeader,
|
||||
// because we currently need to know the block height. That function skips the genesis
|
||||
// block because it has no previous block, so we check it specifically here.
|
||||
if (fCheckPOW &&
|
||||
block.GetHash() == consensusParams.hashGenesisBlock &&
|
||||
!CheckEquihashSolution(&block, 0, consensusParams))
|
||||
return state.DoS(100, error("CheckBlockHeader(): Equihash solution invalid"),
|
||||
REJECT_INVALID, "invalid-solution");
|
||||
|
||||
|
@ -3938,7 +3944,8 @@ bool CheckBlock(const CBlock& block, CValidationState& state,
|
|||
|
||||
bool ContextualCheckBlockHeader(
|
||||
const CBlockHeader& block, CValidationState& state,
|
||||
const CChainParams& chainParams, CBlockIndex * const pindexPrev)
|
||||
const CChainParams& chainParams, CBlockIndex * const pindexPrev,
|
||||
bool fCheckPOW)
|
||||
{
|
||||
const Consensus::Params& consensusParams = chainParams.GetConsensus();
|
||||
uint256 hash = block.GetHash();
|
||||
|
@ -3950,6 +3957,11 @@ bool ContextualCheckBlockHeader(
|
|||
|
||||
int nHeight = pindexPrev->nHeight+1;
|
||||
|
||||
// Check Equihash solution is valid
|
||||
if (fCheckPOW && !CheckEquihashSolution(&block, nHeight, consensusParams))
|
||||
return state.DoS(100, error("CheckBlockHeader(): Equihash solution invalid"),
|
||||
REJECT_INVALID, "invalid-solution");
|
||||
|
||||
// Check proof of work
|
||||
if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) {
|
||||
return state.DoS(100, error("%s: incorrect proof of work", __func__),
|
||||
|
@ -4239,7 +4251,7 @@ bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams,
|
|||
auto verifier = libzcash::ProofVerifier::Disabled();
|
||||
|
||||
// NOTE: CheckBlockHeader is called by CheckBlock
|
||||
if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev))
|
||||
if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, fCheckPOW))
|
||||
return false;
|
||||
if (!CheckBlock(block, state, chainparams, verifier, fCheckPOW, fCheckMerkleRoot))
|
||||
return false;
|
||||
|
@ -5031,7 +5043,7 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, CDiskB
|
|||
std::pair<std::multimap<uint256, CDiskBlockPos>::iterator, std::multimap<uint256, CDiskBlockPos>::iterator> range = mapBlocksUnknownParent.equal_range(head);
|
||||
while (range.first != range.second) {
|
||||
std::multimap<uint256, CDiskBlockPos>::iterator it = range.first;
|
||||
if (ReadBlockFromDisk(block, it->second, chainparams.GetConsensus()))
|
||||
if (ReadBlockFromDisk(block, it->second, mapBlockIndex[head]->nHeight, chainparams.GetConsensus()))
|
||||
{
|
||||
LogPrintf("%s: Processing out of order child %s of %s\n", __func__, block.GetHash().ToString(),
|
||||
head.ToString());
|
||||
|
|
|
@ -436,7 +436,7 @@ bool GetTimestampIndex(unsigned int high, unsigned int low, bool fActiveOnly,
|
|||
|
||||
/** Functions for disk access for blocks */
|
||||
bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart);
|
||||
bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus::Params& consensusParams);
|
||||
bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, int nHeight, const Consensus::Params& consensusParams);
|
||||
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams);
|
||||
|
||||
/** Functions for validating blocks and updating the block tree */
|
||||
|
@ -454,7 +454,8 @@ bool CheckBlock(const CBlock& block, CValidationState& state,
|
|||
* By "context", we mean only the previous block headers, but not the UTXO
|
||||
* set; UTXO-related validity checks are done in ConnectBlock(). */
|
||||
bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& state,
|
||||
const CChainParams& chainparams, CBlockIndex *pindexPrev);
|
||||
const CChainParams& chainparams, CBlockIndex *pindexPrev,
|
||||
bool fCheckPOW = true);
|
||||
bool ContextualCheckBlock(const CBlock& block, CValidationState& state,
|
||||
const CChainParams& chainparams, CBlockIndex *pindexPrev);
|
||||
|
||||
|
|
14
src/pow.cpp
14
src/pow.cpp
|
@ -13,6 +13,7 @@
|
|||
#include "streams.h"
|
||||
#include "uint256.h"
|
||||
|
||||
#include <librustzcash.h>
|
||||
#include "sodium.h"
|
||||
|
||||
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params)
|
||||
|
@ -92,7 +93,7 @@ unsigned int CalculateNextWorkRequired(arith_uint256 bnAvg,
|
|||
return bnNew.GetCompact();
|
||||
}
|
||||
|
||||
bool CheckEquihashSolution(const CBlockHeader *pblock, const Consensus::Params& params)
|
||||
bool CheckEquihashSolution(const CBlockHeader *pblock, int nHeight, const Consensus::Params& params)
|
||||
{
|
||||
unsigned int n = params.nEquihashN;
|
||||
unsigned int k = params.nEquihashK;
|
||||
|
@ -106,6 +107,17 @@ bool CheckEquihashSolution(const CBlockHeader *pblock, const Consensus::Params&
|
|||
// I||V
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << I;
|
||||
|
||||
// From Heartwood activation, check with the Rust validator
|
||||
if (params.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_HEARTWOOD)) {
|
||||
return librustzcash_eh_isvalid(
|
||||
n, k,
|
||||
(unsigned char*)&ss[0], ss.size(),
|
||||
pblock->nNonce.begin(), pblock->nNonce.size(),
|
||||
pblock->nSolution.data(), pblock->nSolution.size());
|
||||
}
|
||||
|
||||
// Before Heartwood activation, check with the C++ validator
|
||||
ss << pblock->nNonce;
|
||||
|
||||
// H(I||V||...
|
||||
|
|
|
@ -23,7 +23,7 @@ unsigned int CalculateNextWorkRequired(arith_uint256 bnAvg,
|
|||
int nextHeight);
|
||||
|
||||
/** Check whether the Equihash solution in a block header is valid */
|
||||
bool CheckEquihashSolution(const CBlockHeader *pblock, const Consensus::Params&);
|
||||
bool CheckEquihashSolution(const CBlockHeader *pblock, int nHeight, const Consensus::Params&);
|
||||
|
||||
/** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */
|
||||
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&);
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
#include "sodium.h"
|
||||
|
||||
#include "librustzcash.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
@ -87,6 +89,9 @@ void TestEquihashSolvers(unsigned int n, unsigned int k, const std::string &I, c
|
|||
|
||||
void TestEquihashValidator(unsigned int n, unsigned int k, const std::string &I, const arith_uint256 &nonce, std::vector<uint32_t> soln, bool expected) {
|
||||
size_t cBitLen { n/(k+1) };
|
||||
auto minimal = GetMinimalFromIndices(soln, cBitLen);
|
||||
|
||||
// First test the C++ validator
|
||||
crypto_generichash_blake2b_state state;
|
||||
EhInitialiseState(n, k, state);
|
||||
uint256 V = ArithToUint256(nonce);
|
||||
|
@ -97,7 +102,15 @@ void TestEquihashValidator(unsigned int n, unsigned int k, const std::string &I,
|
|||
PrintSolution(strm, soln);
|
||||
BOOST_TEST_MESSAGE(strm.str());
|
||||
bool isValid;
|
||||
EhIsValidSolution(n, k, state, GetMinimalFromIndices(soln, cBitLen), isValid);
|
||||
EhIsValidSolution(n, k, state, minimal, isValid);
|
||||
BOOST_CHECK(isValid == expected);
|
||||
|
||||
// The Rust validator should have the exact same result
|
||||
isValid = librustzcash_eh_isvalid(
|
||||
n, k,
|
||||
(unsigned char*)&I[0], I.size(),
|
||||
V.begin(), V.size(),
|
||||
minimal.data(), minimal.size());
|
||||
BOOST_CHECK(isValid == expected);
|
||||
}
|
||||
|
||||
|
@ -219,6 +232,11 @@ BOOST_AUTO_TEST_CASE(validator_allbitsmatter) {
|
|||
bool isValid;
|
||||
EhIsValidSolution(n, k, state, sol_char, isValid);
|
||||
BOOST_CHECK(isValid == true);
|
||||
BOOST_CHECK(librustzcash_eh_isvalid(
|
||||
n, k,
|
||||
(unsigned char*)&I[0], I.size(),
|
||||
V.begin(), V.size(),
|
||||
sol_char.data(), sol_char.size()));
|
||||
|
||||
// Changing any single bit of the encoded solution should make it invalid.
|
||||
for (size_t i = 0; i < sol_char.size() * 8; i++) {
|
||||
|
@ -226,6 +244,11 @@ BOOST_AUTO_TEST_CASE(validator_allbitsmatter) {
|
|||
mutated.at(i/8) ^= (1 << (i % 8));
|
||||
EhIsValidSolution(n, k, state, mutated, isValid);
|
||||
BOOST_CHECK(isValid == false);
|
||||
BOOST_CHECK(!librustzcash_eh_isvalid(
|
||||
n, k,
|
||||
(unsigned char*)&I[0], I.size(),
|
||||
V.begin(), V.size(),
|
||||
mutated.data(), mutated.size()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -204,7 +204,7 @@ double benchmark_verify_equihash()
|
|||
CBlockHeader genesis_header = genesis.GetBlockHeader();
|
||||
struct timeval tv_start;
|
||||
timer_start(tv_start);
|
||||
CheckEquihashSolution(&genesis_header, params.GetConsensus());
|
||||
assert(CheckEquihashSolution(&genesis_header, 1, params.GetConsensus()));
|
||||
return timer_stop(tv_start);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue