diff --git a/src/main.cpp b/src/main.cpp index b12f3a037..c593fbd93 100644 --- a/src/main.cpp +++ b/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::iterator, std::multimap::iterator> range = mapBlocksUnknownParent.equal_range(head); while (range.first != range.second) { std::multimap::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()); diff --git a/src/main.h b/src/main.h index 421f74ffb..193d06c56 100644 --- a/src/main.h +++ b/src/main.h @@ -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); diff --git a/src/pow.cpp b/src/pow.cpp index 7e95df8e0..0477a59cb 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -13,6 +13,7 @@ #include "streams.h" #include "uint256.h" +#include #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||... diff --git a/src/pow.h b/src/pow.h index eb05c6e34..4a6f86b48 100644 --- a/src/pow.h +++ b/src/pow.h @@ -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&); diff --git a/src/test/equihash_tests.cpp b/src/test/equihash_tests.cpp index e573f8e29..8e202fc68 100644 --- a/src/test/equihash_tests.cpp +++ b/src/test/equihash_tests.cpp @@ -15,6 +15,8 @@ #include "sodium.h" +#include "librustzcash.h" + #include #include #include @@ -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 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())); } } diff --git a/src/zcbenchmarks.cpp b/src/zcbenchmarks.cpp index 5db123594..cd728eceb 100644 --- a/src/zcbenchmarks.cpp +++ b/src/zcbenchmarks.cpp @@ -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); }