From fdda3c5085199d2c2170887aa064fc42afdb0360 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sun, 28 Feb 2016 20:19:07 +0000 Subject: [PATCH] Use Equihash for Proof-of-Work The main and test networks are configured to use parameters that are currently low-memory but usable with the basic solver; they will be increased once the solver is optimised. The regtest network is configured to have extremely low memory usage for speed. Note that Bitcoin's double-hasher is used for the difficulty check. This does not match the paper, but is simpler than changing the block header serialization. Single hashing is kept for the EquiHash solver because there is no requirement on execution time there, only on memory usage. --- src/chain.h | 10 +++- src/chainparams.cpp | 4 ++ src/chainparams.h | 4 ++ src/main.cpp | 8 ++- src/miner.cpp | 118 ++++++++++++++++++++------------------- src/pow.cpp | 29 ++++++++++ src/pow.h | 4 ++ src/primitives/block.cpp | 4 +- src/primitives/block.h | 35 +++++++++++- src/rpcblockchain.cpp | 7 ++- src/rpcmining.cpp | 43 ++++++++++++-- src/test/miner_tests.cpp | 3 +- src/txdb.cpp | 1 + 13 files changed, 200 insertions(+), 70 deletions(-) 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;