Browse Source

Avoid checking for difficulty when doing PoW test.

cl-refactor
Gav Wood 10 years ago
parent
commit
717c9c06df
  1. 35
      libethcore/BlockInfo.cpp
  2. 21
      libethcore/BlockInfo.h
  3. 30
      libethereum/Client.cpp
  4. 14
      libethereum/Client.h
  5. 65
      libethereum/State.cpp
  6. 30
      libethereum/State.h
  7. 2
      libweb3jsonrpc/WebThreeStubServerBase.cpp
  8. 13
      test/blockchain.cpp
  9. 2
      test/dagger.cpp

35
libethcore/BlockInfo.cpp

@ -35,9 +35,9 @@ BlockInfo::BlockInfo(): timestamp(Invalid256)
{ {
} }
BlockInfo::BlockInfo(bytesConstRef _block, bool _checkNonce) BlockInfo::BlockInfo(bytesConstRef _block, Strictness _s)
{ {
populate(_block, _checkNonce); populate(_block, _s);
} }
void BlockInfo::setEmpty() void BlockInfo::setEmpty()
@ -61,10 +61,10 @@ void BlockInfo::setEmpty()
hash = headerHash(WithNonce); hash = headerHash(WithNonce);
} }
BlockInfo BlockInfo::fromHeader(bytesConstRef _block) BlockInfo BlockInfo::fromHeader(bytesConstRef _block, Strictness _s)
{ {
BlockInfo ret; BlockInfo ret;
ret.populateFromHeader(RLP(_block)); ret.populateFromHeader(RLP(_block), _s);
return ret; return ret;
} }
@ -89,7 +89,7 @@ h256 BlockInfo::headerHash(bytesConstRef _block)
return sha3(RLP(_block)[0].data()); return sha3(RLP(_block)[0].data());
} }
void BlockInfo::populateFromHeader(RLP const& _header, bool _checkNonce) void BlockInfo::populateFromHeader(RLP const& _header, Strictness _s)
{ {
hash = dev::sha3(_header.data()); hash = dev::sha3(_header.data());
@ -121,30 +121,33 @@ void BlockInfo::populateFromHeader(RLP const& _header, bool _checkNonce)
} }
// check it hashes according to proof of work or that it's the genesis block. // check it hashes according to proof of work or that it's the genesis block.
if (_checkNonce && parentHash && !ProofOfWork::verify(*this)) if (_s == CheckEverything && parentHash && !ProofOfWork::verify(*this))
BOOST_THROW_EXCEPTION(InvalidBlockNonce() << errinfo_hash256(headerHash(WithoutNonce)) << errinfo_nonce(nonce) << errinfo_difficulty(difficulty)); BOOST_THROW_EXCEPTION(InvalidBlockNonce() << errinfo_hash256(headerHash(WithoutNonce)) << errinfo_nonce(nonce) << errinfo_difficulty(difficulty));
if (gasUsed > gasLimit) if (_s != CheckNothing)
BOOST_THROW_EXCEPTION(TooMuchGasUsed() << RequirementError(bigint(gasLimit), bigint(gasUsed)) ); {
if (gasUsed > gasLimit)
BOOST_THROW_EXCEPTION(TooMuchGasUsed() << RequirementError(bigint(gasLimit), bigint(gasUsed)) );
if (difficulty < c_minimumDifficulty) if (difficulty < c_minimumDifficulty)
BOOST_THROW_EXCEPTION(InvalidDifficulty() << RequirementError(bigint(c_minimumDifficulty), bigint(difficulty)) ); BOOST_THROW_EXCEPTION(InvalidDifficulty() << RequirementError(bigint(c_minimumDifficulty), bigint(difficulty)) );
if (gasLimit < c_minGasLimit) if (gasLimit < c_minGasLimit)
BOOST_THROW_EXCEPTION(InvalidGasLimit() << RequirementError(bigint(c_minGasLimit), bigint(gasLimit)) ); BOOST_THROW_EXCEPTION(InvalidGasLimit() << RequirementError(bigint(c_minGasLimit), bigint(gasLimit)) );
if (number && extraData.size() > c_maximumExtraDataSize) if (number && extraData.size() > c_maximumExtraDataSize)
BOOST_THROW_EXCEPTION(ExtraDataTooBig() << RequirementError(bigint(c_maximumExtraDataSize), bigint(extraData.size()))); BOOST_THROW_EXCEPTION(ExtraDataTooBig() << RequirementError(bigint(c_maximumExtraDataSize), bigint(extraData.size())));
}
} }
void BlockInfo::populate(bytesConstRef _block, bool _checkNonce) void BlockInfo::populate(bytesConstRef _block, Strictness _s)
{ {
RLP root(_block); RLP root(_block);
RLP header = root[0]; RLP header = root[0];
if (!header.isList()) if (!header.isList())
BOOST_THROW_EXCEPTION(InvalidBlockFormat() << errinfo_comment("block header needs to be a list") << BadFieldError(0, header.data().toString())); BOOST_THROW_EXCEPTION(InvalidBlockFormat() << errinfo_comment("block header needs to be a list") << BadFieldError(0, header.data().toString()));
populateFromHeader(header, _checkNonce); populateFromHeader(header, _s);
if (!root[1].isList()) if (!root[1].isList())
BOOST_THROW_EXCEPTION(InvalidBlockFormat() << errinfo_comment("block transactions need to be a list") << BadFieldError(1, root[1].data().toString())); BOOST_THROW_EXCEPTION(InvalidBlockFormat() << errinfo_comment("block transactions need to be a list") << BadFieldError(1, root[1].data().toString()));

21
libethcore/BlockInfo.h

@ -36,6 +36,13 @@ enum IncludeNonce
WithNonce = 1 WithNonce = 1
}; };
enum Strictness
{
CheckEverything,
IgnoreNonce,
CheckNothing
};
/** @brief Encapsulation of a block header. /** @brief Encapsulation of a block header.
* Class to contain all of a block header's data. It is able to parse a block header and populate * Class to contain all of a block header's data. It is able to parse a block header and populate
* from some given RLP block serialisation with the static fromHeader(), through the method * from some given RLP block serialisation with the static fromHeader(), through the method
@ -79,14 +86,14 @@ public:
Nonce nonce; Nonce nonce;
BlockInfo(); BlockInfo();
explicit BlockInfo(bytes const& _block): BlockInfo(&_block) {} explicit BlockInfo(bytes const& _block, Strictness _s = CheckEverything): BlockInfo(&_block, _s) {}
explicit BlockInfo(bytesConstRef _block, bool _checkNonce = true); explicit BlockInfo(bytesConstRef _block, Strictness _s = CheckEverything);
static h256 headerHash(bytes const& _block) { return headerHash(&_block); } static h256 headerHash(bytes const& _block) { return headerHash(&_block); }
static h256 headerHash(bytesConstRef _block); static h256 headerHash(bytesConstRef _block);
static BlockInfo fromHeader(bytes const& _block) { return fromHeader(bytesConstRef(&_block)); } static BlockInfo fromHeader(bytes const& _block, Strictness _s = CheckEverything) { return fromHeader(bytesConstRef(&_block), _s); }
static BlockInfo fromHeader(bytesConstRef _block); static BlockInfo fromHeader(bytesConstRef _block, Strictness _s = CheckEverything);
explicit operator bool() const { return timestamp != Invalid256; } explicit operator bool() const { return timestamp != Invalid256; }
@ -113,9 +120,9 @@ public:
void setEmpty(); void setEmpty();
void populateFromHeader(RLP const& _header, bool _checkNonce = true); void populateFromHeader(RLP const& _header, Strictness _s = CheckEverything);
void populate(bytesConstRef _block, bool _checkNonce = true); void populate(bytesConstRef _block, Strictness _s = CheckEverything);
void populate(bytes const& _block, bool _checkNonce = true) { populate(&_block, _checkNonce); } void populate(bytes const& _block, Strictness _s = CheckEverything) { populate(&_block, _s); }
void verifyInternals(bytesConstRef _block) const; void verifyInternals(bytesConstRef _block) const;
void verifyParent(BlockInfo const& _parent) const; void verifyParent(BlockInfo const& _parent) const;
void populateFromParent(BlockInfo const& parent); void populateFromParent(BlockInfo const& parent);

30
libethereum/Client.cpp

@ -64,10 +64,38 @@ Client::Client(p2p::Host* _extNet, std::string const& _dbPath, bool _forceClean,
Worker("eth"), Worker("eth"),
m_vc(_dbPath), m_vc(_dbPath),
m_bc(_dbPath, !m_vc.ok() || _forceClean), m_bc(_dbPath, !m_vc.ok() || _forceClean),
m_gp(u256("60000000000000")),
m_stateDB(State::openDB(_dbPath, !m_vc.ok() || _forceClean)), m_stateDB(State::openDB(_dbPath, !m_vc.ok() || _forceClean)),
m_preMine(Address(), m_stateDB), m_preMine(Address(), m_stateDB),
m_postMine(Address(), m_stateDB) m_postMine(Address(), m_stateDB)
{ {
m_gp.updateQuartiles(m_bc);
m_host = _extNet->registerCapability(new EthereumHost(m_bc, m_tq, m_bq, _networkId));
if (_miners > -1)
setMiningThreads(_miners);
else
setMiningThreads();
if (_dbPath.size())
Defaults::setDBPath(_dbPath);
m_vc.setOk();
doWork();
startWorking();
}
Client::Client(p2p::Host* _extNet, u256 weiPerCent, std::string const& _dbPath, bool _forceClean, u256 _networkId, int _miners):
Worker("eth"),
m_vc(_dbPath),
m_bc(_dbPath, !m_vc.ok() || _forceClean),
m_gp(weiPerCent),
m_stateDB(State::openDB(_dbPath, !m_vc.ok() || _forceClean)),
m_preMine(Address(), m_stateDB),
m_postMine(Address(), m_stateDB)
{
m_gp.updateQuartiles(m_bc);
m_host = _extNet->registerCapability(new EthereumHost(m_bc, m_tq, m_bq, _networkId)); m_host = _extNet->registerCapability(new EthereumHost(m_bc, m_tq, m_bq, _networkId));
if (_miners > -1) if (_miners > -1)
@ -592,7 +620,7 @@ void Client::doWork()
// returns h256s as blooms, once for each transaction. // returns h256s as blooms, once for each transaction.
cwork << "postSTATE <== TQ"; cwork << "postSTATE <== TQ";
TransactionReceipts newPendingReceipts = m_postMine.sync(m_bc, m_tq); TransactionReceipts newPendingReceipts = m_postMine.sync(m_bc, m_tq, m_gp);
if (newPendingReceipts.size()) if (newPendingReceipts.size())
{ {
for (size_t i = 0; i < newPendingReceipts.size(); i++) for (size_t i = 0; i < newPendingReceipts.size(); i++)

14
libethereum/Client.h

@ -177,6 +177,15 @@ public:
int _miners = -1 int _miners = -1
); );
explicit Client(
p2p::Host* _host,
u256 _weiPerCent,
std::string const& _dbPath = std::string(),
bool _forceClean = false,
u256 _networkId = 0,
int _miners = -1
);
/// Destructor. /// Destructor.
virtual ~Client(); virtual ~Client();
@ -352,6 +361,7 @@ private:
CanonBlockChain m_bc; ///< Maintains block database. CanonBlockChain m_bc; ///< Maintains block database.
TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain. TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain.
BlockQueue m_bq; ///< Maintains a list of incoming blocks not yet on the blockchain (to be imported). BlockQueue m_bq; ///< Maintains a list of incoming blocks not yet on the blockchain (to be imported).
GasPricer m_gp; ///< The gas pricer.
mutable SharedMutex x_stateDB; ///< Lock on the state DB, effectively a lock on m_postMine. mutable SharedMutex x_stateDB; ///< Lock on the state DB, effectively a lock on m_postMine.
OverlayDB m_stateDB; ///< Acts as the central point for the state database, so multiple States can share it. OverlayDB m_stateDB; ///< Acts as the central point for the state database, so multiple States can share it.
@ -368,7 +378,9 @@ private:
bool m_paranoia = false; ///< Should we be paranoid about our state? bool m_paranoia = false; ///< Should we be paranoid about our state?
bool m_turboMining = false; ///< Don't squander all of our time mining actually just sleeping. bool m_turboMining = false; ///< Don't squander all of our time mining actually just sleeping.
bool m_forceMining = false; ///< Mine even when there are no transactions pending? bool m_forceMining = false; ///< Mine even when there are no transactions pending?
bool m_verifyOwnBlocks = true; ///< Shoudl be verify blocks that we mined? bool m_verifyOwnBlocks = true; ///< Should be verify blocks that we mined?
mutable Mutex m_filterLock; mutable Mutex m_filterLock;
std::map<h256, InstalledFilter> m_filters; std::map<h256, InstalledFilter> m_filters;

65
libethereum/State.cpp

@ -400,7 +400,7 @@ bool State::cull(TransactionQueue& _tq) const
return ret; return ret;
} }
TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, bool* o_transactionQueueChanged) TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, GasPricer const& _gp, bool* o_transactionQueueChanged)
{ {
// TRANSACTIONS // TRANSACTIONS
TransactionReceipts ret; TransactionReceipts ret;
@ -414,16 +414,20 @@ TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, bo
for (auto const& i: ts) for (auto const& i: ts)
if (!m_transactionSet.count(i.first)) if (!m_transactionSet.count(i.first))
{ {
// don't have it yet! Execute it now.
try try
{ {
uncommitToMine(); Transaction t(i.second, CheckSignature::Sender);
// boost::timer t; if (t.gasPrice() >= _gp.ask(*this))
execute(lh, i.second); {
ret.push_back(m_receipts.back()); // don't have it yet! Execute it now.
_tq.noteGood(i); uncommitToMine();
++goodTxs; // boost::timer t;
// cnote << "TX took:" << t.elapsed() * 1000; execute(lh, i.second);
ret.push_back(m_receipts.back());
_tq.noteGood(i);
++goodTxs;
// cnote << "TX took:" << t.elapsed() * 1000;
}
} }
catch (InvalidNonce const& in) catch (InvalidNonce const& in)
{ {
@ -460,12 +464,51 @@ TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, bo
return ret; return ret;
} }
void GasPricer::updateQuartiles(BlockChain const& _bc)
{
unsigned c = 0;
h256 p = _bc.currentHash();
map<u256, unsigned> dist;
unsigned total;
while (c < 1000 && p)
{
BlockInfo bi = _bc.info(p);
if (bi.transactionsRoot != EmptyTrie)
{
auto bb = _bc.block(p);
RLP r(bb);
BlockReceipts brs(_bc.receipts(bi.hash));
for (unsigned i = 0; i < r[1].size(); ++i)
{
auto gu = brs.receipts[i].gasUsed();
dist[Transaction(r[1][i].data(), CheckSignature::None).gasPrice()] += (unsigned)brs.receipts[i].gasUsed();
total += (unsigned)gu;
}
}
p = bi.parentHash;
++c;
}
if (total > 0)
{
unsigned t = 0;
unsigned q = 1;
for (auto const& i: dist)
{
for (; t <= total * q / 4 && t + i.second > total * q / 4; ++q)
m_quartiles[q - 1] = i.first;
if (q > 3)
break;
}
}
}
u256 State::enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce) u256 State::enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce)
{ {
// m_currentBlock is assumed to be prepopulated and reset. // m_currentBlock is assumed to be prepopulated and reset.
#if !ETH_RELEASE #if !ETH_RELEASE
BlockInfo bi(_block, _checkNonce); BlockInfo bi(_block, _checkNonce ? CheckEverything : IgnoreNonce);
assert(m_previousBlock.hash == bi.parentHash); assert(m_previousBlock.hash == bi.parentHash);
assert(m_currentBlock.parentHash == bi.parentHash); assert(m_currentBlock.parentHash == bi.parentHash);
assert(rootHash() == m_previousBlock.stateRoot); assert(rootHash() == m_previousBlock.stateRoot);
@ -475,7 +518,7 @@ u256 State::enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce)
BOOST_THROW_EXCEPTION(InvalidParentHash()); BOOST_THROW_EXCEPTION(InvalidParentHash());
// Populate m_currentBlock with the correct values. // Populate m_currentBlock with the correct values.
m_currentBlock.populate(_block, _checkNonce); m_currentBlock.populate(_block, _checkNonce ? CheckEverything : IgnoreNonce);
m_currentBlock.verifyInternals(_block); m_currentBlock.verifyInternals(_block);
// cnote << "playback begins:" << m_state.root(); // cnote << "playback begins:" << m_state.root();

30
libethereum/State.h

@ -55,6 +55,8 @@ struct StateSafeExceptions: public LogChannel { static const char* name() { retu
enum class BaseState { Empty, CanonGenesis }; enum class BaseState { Empty, CanonGenesis };
class GasPricer;
/** /**
* @brief Model of the current state of the ledger. * @brief Model of the current state of the ledger.
* Maintains current ledger (m_current) as a fast hash-map. This is hashed only when required (i.e. to create or verify a block). * Maintains current ledger (m_current) as a fast hash-map. This is hashed only when required (i.e. to create or verify a block).
@ -147,7 +149,7 @@ public:
/// @returns a list of receipts one for each transaction placed from the queue into the state. /// @returns a list of receipts one for each transaction placed from the queue into the state.
/// @a o_transactionQueueChanged boolean pointer, the value of which will be set to true if the transaction queue /// @a o_transactionQueueChanged boolean pointer, the value of which will be set to true if the transaction queue
/// changed and the pointer is non-null /// changed and the pointer is non-null
TransactionReceipts sync(BlockChain const& _bc, TransactionQueue& _tq, bool* o_transactionQueueChanged = nullptr); TransactionReceipts sync(BlockChain const& _bc, TransactionQueue& _tq, GasPricer const& _gp, bool* o_transactionQueueChanged = nullptr);
/// Like sync but only operate on _tq, killing the invalid/old ones. /// Like sync but only operate on _tq, killing the invalid/old ones.
bool cull(TransactionQueue& _tq) const; bool cull(TransactionQueue& _tq) const;
@ -368,6 +370,32 @@ void commit(std::map<Address, Account> const& _cache, DB& _db, SecureTrieDB<Addr
} }
} }
enum class TransactionPriority
{
Low = 0,
Medium = 1,
High = 2
};
class GasPricer
{
public:
explicit GasPricer(u256 _weiPerCent): m_weiPerCent(_weiPerCent) {}
u256 ask(State const&) const { return m_weiPerCent * m_centsPerBlock / m_gasPerBlock; }
u256 bid(TransactionPriority _p = TransactionPriority::Medium) const { return m_quartiles[(int)_p]; }
void updateRefPrice(u256 _weiPerCent) { m_weiPerCent = _weiPerCent; }
void updateRefRequirement(u256 _centsPerBlock) { m_centsPerBlock = _centsPerBlock; }
void updateQuartiles(BlockChain const& _bc);
private:
u256 m_weiPerCent;
u256 m_centsPerBlock = 15;
u256 m_gasPerBlock = 1000000;
std::array<u256, 100> m_quartiles;
};
} }
} }

2
libweb3jsonrpc/WebThreeStubServerBase.cpp

@ -710,7 +710,7 @@ std::string WebThreeStubServerBase::eth_transact(Json::Value const& _json)
if (t.creation) if (t.creation)
ret = toJS(right160(sha3(rlpList(t.from, client()->countAt(t.from)))));; ret = toJS(right160(sha3(rlpList(t.from, client()->countAt(t.from)))));;
if (!t.gasPrice) if (!t.gasPrice)
t.gasPrice = 10 * dev::eth::szabo; t.gasPrice = 10 * dev::eth::szabo; // TODO: should be determined by user somehow.
if (!t.gas) if (!t.gas)
t.gas = min<u256>(client()->gasLimitRemaining(), client()->balanceAt(t.from) / t.gasPrice); t.gas = min<u256>(client()->gasLimitRemaining(), client()->balanceAt(t.from) / t.gasPrice);

13
test/blockchain.cpp

@ -87,6 +87,7 @@ void doBlockchainTests(json_spirit::mValue& _v, bool _fillin)
// get txs // get txs
TransactionQueue txs; TransactionQueue txs;
GasPricer gp(10000);
BOOST_REQUIRE(blObj.count("transactions")); BOOST_REQUIRE(blObj.count("transactions"));
for (auto const& txObj: blObj["transactions"].get_array()) for (auto const& txObj: blObj["transactions"].get_array())
{ {
@ -131,7 +132,7 @@ void doBlockchainTests(json_spirit::mValue& _v, bool _fillin)
try try
{ {
state.sync(bc); state.sync(bc);
state.sync(bc,txs); state.sync(bc, txs, gp);
state.commitToMine(bc); state.commitToMine(bc);
MineInfo info; MineInfo info;
for (info.completed = false; !info.completed; info = state.mine()) {} for (info.completed = false; !info.completed; info = state.mine()) {}
@ -281,7 +282,7 @@ void doBlockchainTests(json_spirit::mValue& _v, bool _fillin)
BlockInfo blockHeaderFromFields; BlockInfo blockHeaderFromFields;
const bytes c_rlpBytesBlockHeader = createBlockRLPFromFields(tObj); const bytes c_rlpBytesBlockHeader = createBlockRLPFromFields(tObj);
const RLP c_blockHeaderRLP(c_rlpBytesBlockHeader); const RLP c_blockHeaderRLP(c_rlpBytesBlockHeader);
blockHeaderFromFields.populateFromHeader(c_blockHeaderRLP, false); blockHeaderFromFields.populateFromHeader(c_blockHeaderRLP, IgnoreNonce);
BlockInfo blockFromRlp = bc.info(); BlockInfo blockFromRlp = bc.info();
@ -381,7 +382,7 @@ void doBlockchainTests(json_spirit::mValue& _v, bool _fillin)
BlockInfo uncleBlockHeader; BlockInfo uncleBlockHeader;
try try
{ {
uncleBlockHeader.populateFromHeader(c_uRLP, true); uncleBlockHeader.populateFromHeader(c_uRLP);
} }
catch(...) catch(...)
{ {
@ -395,7 +396,7 @@ void doBlockchainTests(json_spirit::mValue& _v, bool _fillin)
for (auto const& uRLP: root[2]) for (auto const& uRLP: root[2])
{ {
BlockInfo uBl; BlockInfo uBl;
uBl.populateFromHeader(uRLP, true); uBl.populateFromHeader(uRLP);
uBlHsFromRlp.push_back(uBl); uBlHsFromRlp.push_back(uBl);
} }
@ -538,7 +539,7 @@ void overwriteBlockHeader(BlockInfo& _current_BlockHeader, mObject& _blObj)
// take the blockheader as is // take the blockheader as is
const bytes c_blockRLP = createBlockRLPFromFields(_blObj["blockHeader"].get_obj()); const bytes c_blockRLP = createBlockRLPFromFields(_blObj["blockHeader"].get_obj());
const RLP c_bRLP(c_blockRLP); const RLP c_bRLP(c_blockRLP);
_current_BlockHeader.populateFromHeader(c_bRLP, false); _current_BlockHeader.populateFromHeader(c_bRLP, IgnoreNonce);
} }
} }
@ -551,7 +552,7 @@ BlockInfo constructBlock(mObject& _o)
// construct genesis block // construct genesis block
const bytes c_blockRLP = createBlockRLPFromFields(_o); const bytes c_blockRLP = createBlockRLPFromFields(_o);
const RLP c_bRLP(c_blockRLP); const RLP c_bRLP(c_blockRLP);
ret.populateFromHeader(c_bRLP, false); ret.populateFromHeader(c_bRLP, IgnoreNonce);
} }
catch (Exception const& _e) catch (Exception const& _e)
{ {

2
test/dagger.cpp

@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(basic_test)
cnote << i.first; cnote << i.first;
js::mObject& o = i.second.get_obj(); js::mObject& o = i.second.get_obj();
vector<pair<string, string>> ss; vector<pair<string, string>> ss;
BlockInfo header = BlockInfo::fromHeader(fromHex(o["header"].get_str())); BlockInfo header = BlockInfo::fromHeader(fromHex(o["header"].get_str()), CheckNothing);
h256 headerHash(o["header_hash"].get_str()); h256 headerHash(o["header_hash"].get_str());
Nonce nonce(o["nonce"].get_str()); Nonce nonce(o["nonce"].get_str());
BOOST_REQUIRE_EQUAL(headerHash, header.headerHash(WithoutNonce)); BOOST_REQUIRE_EQUAL(headerHash, header.headerHash(WithoutNonce));

Loading…
Cancel
Save