Browse Source

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.
pull/145/head
Jack Grigg 8 years ago
parent
commit
fdda3c5085
  1. 10
      src/chain.h
  2. 4
      src/chainparams.cpp
  3. 4
      src/chainparams.h
  4. 8
      src/main.cpp
  5. 114
      src/miner.cpp
  6. 29
      src/pow.cpp
  7. 4
      src/pow.h
  8. 4
      src/primitives/block.cpp
  9. 35
      src/primitives/block.h
  10. 7
      src/rpcblockchain.cpp
  11. 43
      src/rpcmining.cpp
  12. 3
      src/test/miner_tests.cpp
  13. 1
      src/txdb.cpp

10
src/chain.h

@ -143,7 +143,8 @@ public:
uint256 hashMerkleRoot;
unsigned int nTime;
unsigned int nBits;
unsigned int nNonce;
uint256 nNonce;
std::vector<uint32_t> 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();
}

4
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;

4
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<CDNSSeedData> vSeeds;
std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];
std::string strNetworkID;

8
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"),

114
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 <boost/thread.hpp>
#include <boost/tuple/tuple.hpp>
@ -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<CBlockTemplate> 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;
// 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) {
// Check if something found
if (ScanHash(pblock, nNonce, &hash))
// Find valid nonce
while (true)
{
if (UintToArith256(hash) <= hashTarget)
{
// Found a solution
pblock->nNonce = nNonce;
assert(hash == pblock->GetHash());
// 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<std::vector<unsigned int>> 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;
}
}

29
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;

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

4
src/primitives/block.cpp

@ -112,12 +112,12 @@ uint256 CBlock::CheckMerkleBranch(uint256 hash, const std::vector<uint256>& 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++)
{

35
src/primitives/block.h

@ -27,7 +27,8 @@ public:
uint256 hashMerkleRoot;
uint32_t nTime;
uint32_t nBits;
uint32_t nNonce;
uint256 nNonce;
std::vector<uint32_t> 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 <typename Stream, typename Operation>
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.

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

43
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<CBlockTemplate> 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<std::vector<unsigned int>> 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");

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

1
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;

Loading…
Cancel
Save