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:
Homu 2020-04-15 02:03:06 +00:00
commit b5dda4ecc5
6 changed files with 62 additions and 14 deletions

View File

@ -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());

View File

@ -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);

View File

@ -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||...

View File

@ -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&);

View File

@ -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()));
}
}

View File

@ -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);
}