diff --git a/src/chain.h b/src/chain.h index 01be2d6e5..05b030267 100644 --- a/src/chain.h +++ b/src/chain.h @@ -143,7 +143,8 @@ public: uint256 hashMerkleRoot; unsigned int nTime; unsigned int nBits; - unsigned int nNonce; + uint256 nNonce; + std::vector nSolution; //! (memory only) Sequential id assigned to distinguish order in which blocks are received. uint32_t nSequenceId; @@ -167,7 +168,8 @@ public: hashMerkleRoot = uint256(); nTime = 0; nBits = 0; - nNonce = 0; + nNonce = uint256(); + nSolution.clear(); } CBlockIndex() @@ -184,6 +186,7 @@ public: nTime = block.nTime; nBits = block.nBits; nNonce = block.nNonce; + nSolution = block.nSolution; } CDiskBlockPos GetBlockPos() const { @@ -214,6 +217,7 @@ public: block.nTime = nTime; block.nBits = nBits; block.nNonce = nNonce; + block.nSolution = nSolution; return block; } @@ -320,6 +324,7 @@ public: READWRITE(nTime); READWRITE(nBits); READWRITE(nNonce); + READWRITE(nSolution); } uint256 GetBlockHash() const @@ -331,6 +336,7 @@ public: block.nTime = nTime; block.nBits = nBits; block.nNonce = nNonce; + block.nSolution = nSolution; return block.GetHash(); } diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 8ac1e0092..a820ef2e1 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -53,6 +53,8 @@ public: nMinerThreads = 0; nMaxTipAge = 24 * 60 * 60; nPruneAfterHeight = 100000; + nEquihashN = 96; + nEquihashK = 5; /** * Build the genesis block. Note that the output of its generation @@ -212,6 +214,8 @@ public: pchMessageStart[3] = 0xda; nMinerThreads = 1; nMaxTipAge = 24 * 60 * 60; + nEquihashN = 48; + nEquihashK = 5; genesis.nTime = 1296688602; genesis.nBits = 0x207fffff; genesis.nNonce = 2; diff --git a/src/chainparams.h b/src/chainparams.h index f61e3db13..7169c3241 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -62,6 +62,8 @@ public: bool RequireStandard() const { return fRequireStandard; } int64_t MaxTipAge() const { return nMaxTipAge; } int64_t PruneAfterHeight() const { return nPruneAfterHeight; } + unsigned int EquihashN() const { return nEquihashN; } + unsigned int EquihashK() const { return nEquihashK; } /** Make miner stop after a block is found. In RPC, don't return until nGenProcLimit blocks are generated */ bool MineBlocksOnDemand() const { return fMineBlocksOnDemand; } /** In the future use NetworkIDString() for RPC fields */ @@ -83,6 +85,8 @@ protected: int nMinerThreads; long nMaxTipAge; uint64_t nPruneAfterHeight; + unsigned int nEquihashN; + unsigned int nEquihashK; std::vector vSeeds; std::vector base58Prefixes[MAX_BASE58_TYPES]; std::string strNetworkID; diff --git a/src/main.cpp b/src/main.cpp index 646a513ce..525504090 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1295,7 +1295,8 @@ bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos) } // Check the header - if (!CheckProofOfWork(block.GetHash(), block.nBits, Params().GetConsensus())) + if (!(CheckEquihashSolution(&block, Params()) && + CheckProofOfWork(block.GetHash(), block.nBits, Params().GetConsensus()))) return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString()); return true; @@ -2851,6 +2852,11 @@ bool FindUndoPos(CValidationState &state, int nFile, CDiskBlockPos &pos, unsigne bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, bool fCheckPOW) { + // Check Equihash solution is valid + if (fCheckPOW && !CheckEquihashSolution(&block, Params())) + return state.DoS(100, error("CheckBlockHeader(): Equihash solution invalid"), + REJECT_INVALID, "invalid-solution"); + // Check proof of work matches claimed amount if (fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits, Params().GetConsensus())) return state.DoS(50, error("CheckBlockHeader(): proof of work failed"), diff --git a/src/miner.cpp b/src/miner.cpp index 5972f7421..2f92150bd 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -18,9 +18,12 @@ #include "util.h" #include "utilmoneystr.h" #ifdef ENABLE_WALLET +#include "crypto/equihash.h" #include "wallet/wallet.h" #endif +#include "sodium.h" + #include #include @@ -338,7 +341,8 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) pblock->hashPrevBlock = pindexPrev->GetBlockHash(); UpdateTime(pblock, Params().GetConsensus(), pindexPrev); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus()); - pblock->nNonce = 0; + pblock->nNonce = uint256(); + pblock->nSolution.clear(); pblocktemplate->vTxSigOps[0] = GetLegacySigOpCount(pblock->vtx[0]); CValidationState state; @@ -374,39 +378,6 @@ void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& // Internal miner // -// -// ScanHash scans nonces looking for a hash with at least some zero bits. -// The nonce is usually preserved between calls, but periodically or if the -// nonce is 0xffff0000 or above, the block is rebuilt and nNonce starts over at -// zero. -// -bool static ScanHash(const CBlockHeader *pblock, uint32_t& nNonce, uint256 *phash) -{ - // Write the first 76 bytes of the block header to a double-SHA256 state. - CHash256 hasher; - CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); - ss << *pblock; - assert(ss.size() == 80); - hasher.Write((unsigned char*)&ss[0], 76); - - while (true) { - nNonce++; - - // Write the last 4 bytes of the block header (the nonce) to a copy of - // the double-SHA256 state, and compute the result. - CHash256(hasher).Write((unsigned char*)&nNonce, 4).Finalize((unsigned char*)phash); - - // Return the nonce if the hash has at least some zero bits, - // caller will check if it has enough to reach the target - if (((uint16_t*)phash)[15] == 0) - return true; - - // If nothing found after trying for a while, return -1 - if ((nNonce & 0xfff) == 0) - return false; - } -} - CBlockTemplate* CreateNewBlockWithKey(CReserveKey& reservekey) { CPubKey pubkey; @@ -426,7 +397,7 @@ static bool ProcessBlockFound(CBlock* pblock, CWallet& wallet, CReserveKey& rese { LOCK(cs_main); if (pblock->hashPrevBlock != chainActive.Tip()->GetBlockHash()) - return error("BitcoinMiner: generated block is stale"); + return error("ZcashMiner: generated block is stale"); } // Remove key from key pool @@ -441,22 +412,24 @@ static bool ProcessBlockFound(CBlock* pblock, CWallet& wallet, CReserveKey& rese // Process this block the same as if we had received it from another node CValidationState state; if (!ProcessNewBlock(state, NULL, pblock, true, NULL)) - return error("BitcoinMiner: ProcessNewBlock, block not accepted"); + return error("ZcashMiner: ProcessNewBlock, block not accepted"); return true; } void static BitcoinMiner(CWallet *pwallet) { - LogPrintf("BitcoinMiner started\n"); + LogPrintf("ZcashMiner started\n"); SetThreadPriority(THREAD_PRIORITY_LOWEST); - RenameThread("bitcoin-miner"); + RenameThread("zcash-miner"); const CChainParams& chainparams = Params(); // Each thread has its own key and counter CReserveKey reservekey(pwallet); unsigned int nExtraNonce = 0; + Equihash eh {chainparams.EquihashN(), chainparams.EquihashK()}; + try { while (true) { if (chainparams.MiningRequiresPeers()) { @@ -483,13 +456,13 @@ void static BitcoinMiner(CWallet *pwallet) auto_ptr pblocktemplate(CreateNewBlockWithKey(reservekey)); if (!pblocktemplate.get()) { - LogPrintf("Error in BitcoinMiner: Keypool ran out, please call keypoolrefill before restarting the mining thread\n"); + LogPrintf("Error in ZcashMiner: Keypool ran out, please call keypoolrefill before restarting the mining thread\n"); return; } CBlock *pblock = &pblocktemplate->block; IncrementExtraNonce(pblock, pindexPrev, nExtraNonce); - LogPrintf("Running BitcoinMiner with %u transactions in block (%u bytes)\n", pblock->vtx.size(), + LogPrintf("Running ZcashMiner with %u transactions in block (%u bytes)\n", pblock->vtx.size(), ::GetSerializeSize(*pblock, SER_NETWORK, PROTOCOL_VERSION)); // @@ -497,21 +470,48 @@ void static BitcoinMiner(CWallet *pwallet) // int64_t nStart = GetTime(); arith_uint256 hashTarget = arith_uint256().SetCompact(pblock->nBits); - uint256 hash; - uint32_t nNonce = 0; - while (true) { - // Check if something found - if (ScanHash(pblock, nNonce, &hash)) - { - if (UintToArith256(hash) <= hashTarget) - { - // Found a solution - pblock->nNonce = nNonce; - assert(hash == pblock->GetHash()); + // Hash state + crypto_generichash_blake2b_state state; + eh.InitialiseState(state); + + // I = the block header minus nonce and solution. + CEquihashInput I{*pblock}; + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << I; + + // H(I||... + crypto_generichash_blake2b_update(&state, (unsigned char*)&ss[0], ss.size()); + + while (true) { + // Find valid nonce + while (true) + { + // H(I||V||... + crypto_generichash_blake2b_state curr_state; + curr_state = state; + crypto_generichash_blake2b_update(&curr_state, + pblock->nNonce.begin(), + pblock->nNonce.size()); + + // (x_1, x_2, ...) = A(I, V, n, k) + LogPrint("pow", "Running Equihash solver with nNonce = %s\n", + pblock->nNonce.ToString()); + std::set> solns = eh.BasicSolve(curr_state); + LogPrint("pow", "Solutions: %d\n", solns.size()); + + // Write the solution to the hash and compute the result. + for (auto soln : solns) { + pblock->nSolution = soln; + + if (UintToArith256(pblock->GetHash()) > hashTarget) { + continue; + } + + // Found a solution SetThreadPriority(THREAD_PRIORITY_NORMAL); - LogPrintf("BitcoinMiner:\n"); - LogPrintf("proof-of-work found \n hash: %s \ntarget: %s\n", hash.GetHex(), hashTarget.GetHex()); + LogPrintf("ZcashMiner:\n"); + LogPrintf("proof-of-work found \n hash: %s \ntarget: %s\n", pblock->GetHash().GetHex(), hashTarget.GetHex()); ProcessBlockFound(pblock, *pwallet, reservekey); SetThreadPriority(THREAD_PRIORITY_LOWEST); @@ -519,16 +519,20 @@ void static BitcoinMiner(CWallet *pwallet) if (chainparams.MineBlocksOnDemand()) throw boost::thread_interrupted(); - break; + goto updateblock; } + pblock->nNonce = ArithToUint256(UintToArith256(pblock->nNonce) + 1); + if ((UintToArith256(pblock->nNonce) & 0x1) == 0) + break; } +updateblock: // Check for stop or if block needs to be rebuilt boost::this_thread::interruption_point(); // Regtest mode doesn't require peers if (vNodes.empty() && chainparams.MiningRequiresPeers()) break; - if (nNonce >= 0xffff0000) + if (UintToArith256(pblock->nNonce) >= 0xffff) break; if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 60) break; @@ -547,12 +551,12 @@ void static BitcoinMiner(CWallet *pwallet) } catch (const boost::thread_interrupted&) { - LogPrintf("BitcoinMiner terminated\n"); + LogPrintf("ZcashMiner terminated\n"); throw; } catch (const std::runtime_error &e) { - LogPrintf("BitcoinMiner runtime error: %s\n", e.what()); + LogPrintf("ZcashMiner runtime error: %s\n", e.what()); return; } } diff --git a/src/pow.cpp b/src/pow.cpp index bb53ad204..1b5a87890 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -7,10 +7,15 @@ #include "arith_uint256.h" #include "chain.h" +#include "chainparams.h" +#include "crypto/equihash.h" #include "primitives/block.h" +#include "streams.h" #include "uint256.h" #include "util.h" +#include "sodium.h" + unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params) { unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact(); @@ -81,6 +86,30 @@ unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nF return bnNew.GetCompact(); } +bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams& params) +{ + Equihash eh {params.EquihashN(), params.EquihashK()}; + + // Hash state + crypto_generichash_blake2b_state state; + eh.InitialiseState(state); + + // I = the block header minus nonce and solution. + CEquihashInput I{*pblock}; + // I||V + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << I; + ss << pblock->nNonce; + + // H(I||V||... + crypto_generichash_blake2b_update(&state, (unsigned char*)&ss[0], ss.size()); + + if (!eh.IsValidSolution(state, pblock->nSolution)) + return error("CheckEquihashSolution(): invalid solution"); + + return true; +} + bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params) { bool fNegative; diff --git a/src/pow.h b/src/pow.h index e864a474c..864783132 100644 --- a/src/pow.h +++ b/src/pow.h @@ -12,12 +12,16 @@ class CBlockHeader; class CBlockIndex; +class CChainParams; class uint256; class arith_uint256; unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params&); unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params&); +/** Check whether the Equihash solution in a block header is valid */ +bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams&); + /** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */ bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&); arith_uint256 GetBlockProof(const CBlockIndex& block); diff --git a/src/primitives/block.cpp b/src/primitives/block.cpp index 5b9c13d87..b79859d21 100644 --- a/src/primitives/block.cpp +++ b/src/primitives/block.cpp @@ -112,12 +112,12 @@ uint256 CBlock::CheckMerkleBranch(uint256 hash, const std::vector& vMer std::string CBlock::ToString() const { std::stringstream s; - s << strprintf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%u)\n", + s << strprintf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%s, vtx=%u)\n", GetHash().ToString(), nVersion, hashPrevBlock.ToString(), hashMerkleRoot.ToString(), - nTime, nBits, nNonce, + nTime, nBits, nNonce.ToString(), vtx.size()); for (unsigned int i = 0; i < vtx.size(); i++) { diff --git a/src/primitives/block.h b/src/primitives/block.h index 90b8904ab..9886bfe8b 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -27,7 +27,8 @@ public: uint256 hashMerkleRoot; uint32_t nTime; uint32_t nBits; - uint32_t nNonce; + uint256 nNonce; + std::vector nSolution; CBlockHeader() { @@ -45,6 +46,7 @@ public: READWRITE(nTime); READWRITE(nBits); READWRITE(nNonce); + READWRITE(nSolution); } void SetNull() @@ -54,7 +56,8 @@ public: hashMerkleRoot.SetNull(); nTime = 0; nBits = 0; - nNonce = 0; + nNonce = uint256(); + nSolution.clear(); } bool IsNull() const @@ -115,6 +118,7 @@ public: block.nTime = nTime; block.nBits = nBits; block.nNonce = nNonce; + block.nSolution = nSolution; return block; } @@ -130,6 +134,33 @@ public: }; +/** + * Custom serializer for CBlockHeader that omits the nonce and solution, for use + * as input to Equihash. + */ +class CEquihashInput : private CBlockHeader +{ +public: + CEquihashInput(const CBlockHeader &header) + { + CBlockHeader::SetNull(); + *((CBlockHeader*)this) = header; + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(this->nVersion); + nVersion = this->nVersion; + READWRITE(hashPrevBlock); + READWRITE(hashMerkleRoot); + READWRITE(nTime); + READWRITE(nBits); + } +}; + + /** Describes a place in the block chain to another node such that if the * other node doesn't have the same branch, it can find a recent common trunk. * The further back it is, the further before the fork it may be. diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 1bab3b66a..4d46a1370 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -80,7 +80,12 @@ Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDe } result.push_back(Pair("tx", txs)); result.push_back(Pair("time", block.GetBlockTime())); - result.push_back(Pair("nonce", (uint64_t)block.nNonce)); + result.push_back(Pair("nonce", block.nNonce.GetHex())); + Array equihash_solution; + BOOST_FOREACH(uint32_t solution_index, block.nSolution) { + equihash_solution.push_back((size_t)(solution_index)); + } + result.push_back(Pair("solution", equihash_solution)); result.push_back(Pair("bits", strprintf("%08x", block.nBits))); result.push_back(Pair("difficulty", GetDifficulty(blockindex))); result.push_back(Pair("chainwork", blockindex->nChainWork.GetHex())); diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index 98133b0f5..0d5966a35 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -8,6 +8,7 @@ #include "consensus/consensus.h" #include "consensus/validation.h" #include "core_io.h" +#include "crypto/equihash.h" #include "init.h" #include "main.h" #include "miner.h" @@ -149,6 +150,7 @@ Value generate(const Array& params, bool fHelp) } unsigned int nExtraNonce = 0; Array blockHashes; + Equihash eh {Params().EquihashN(), Params().EquihashK()}; while (nHeight < nHeightEnd) { auto_ptr pblocktemplate(CreateNewBlockWithKey(reservekey)); @@ -159,11 +161,44 @@ Value generate(const Array& params, bool fHelp) LOCK(cs_main); IncrementExtraNonce(pblock, chainActive.Tip(), nExtraNonce); } - while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) { + + // Hash state + crypto_generichash_blake2b_state eh_state; + eh.InitialiseState(eh_state); + + // I = the block header minus nonce and solution. + CEquihashInput I{*pblock}; + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << I; + + // H(I||... + crypto_generichash_blake2b_update(&eh_state, (unsigned char*)&ss[0], ss.size()); + + while (true) { // Yes, there is a chance every nonce could fail to satisfy the -regtest - // target -- 1 in 2^(2^32). That ain't gonna happen. - ++pblock->nNonce; + // target -- 1 in 2^(2^256). That ain't gonna happen + pblock->nNonce = ArithToUint256(UintToArith256(pblock->nNonce) + 1); + + // H(I||V||... + crypto_generichash_blake2b_state curr_state; + curr_state = eh_state; + crypto_generichash_blake2b_update(&curr_state, + pblock->nNonce.begin(), + pblock->nNonce.size()); + + // (x_1, x_2, ...) = A(I, V, n, k) + std::set> solns = eh.BasicSolve(curr_state); + + for (auto soln : solns) { + assert(eh.IsValidSolution(curr_state, soln)); + pblock->nSolution = soln; + + if (CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) { + goto endloop; + } + } } +endloop: CValidationState state; if (!ProcessNewBlock(state, NULL, pblock, true, NULL)) throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); @@ -519,7 +554,7 @@ Value getblocktemplate(const Array& params, bool fHelp) // Update nTime UpdateTime(pblock, Params().GetConsensus(), pindexPrev); - pblock->nNonce = 0; + pblock->nNonce = uint256(); static const Array aCaps = boost::assign::list_of("proposal"); diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index e419d61e7..131e440c9 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "arith_uint256.h" #include "consensus/validation.h" #include "main.h" #include "miner.h" @@ -82,7 +83,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) if (txFirst.size() < 2) txFirst.push_back(new CTransaction(pblock->vtx[0])); pblock->hashMerkleRoot = pblock->BuildMerkleTree(); - pblock->nNonce = blockinfo[i].nonce; + pblock->nNonce = ArithToUint256(blockinfo[i].nonce); CValidationState state; BOOST_CHECK(ProcessNewBlock(state, NULL, pblock, true, NULL)); BOOST_CHECK(state.IsValid()); diff --git a/src/txdb.cpp b/src/txdb.cpp index 26687e3fc..ca335fb78 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -308,6 +308,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts() pindexNew->nTime = diskindex.nTime; pindexNew->nBits = diskindex.nBits; pindexNew->nNonce = diskindex.nNonce; + pindexNew->nSolution = diskindex.nSolution; pindexNew->nStatus = diskindex.nStatus; pindexNew->nTx = diskindex.nTx;