Merge pull request #6589 from daira/zip317-blocktemplate-followup
This commit is contained in:
commit
3143ad5944
17
src/random.h
17
src/random.h
|
@ -16,17 +16,22 @@
|
||||||
|
|
||||||
#include "int128.h"
|
#include "int128.h"
|
||||||
|
|
||||||
/**
|
/** @file
|
||||||
* Functions to gather random data via the rand_core OsRng
|
* Functions to gather random data via the rand_core OsRng
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/** Fill the buffer buf of length num with random unformly distributed bytes, via the rand_core OsRng. */
|
||||||
void GetRandBytes(unsigned char* buf, size_t num);
|
void GetRandBytes(unsigned char* buf, size_t num);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a random value of type I uniformly distributed on [0, nMax), unless nMax is 0 in which case return 0.
|
||||||
|
* I must be an unsigned numeric type.
|
||||||
|
*/
|
||||||
template <typename I>
|
template <typename I>
|
||||||
I GetRandGeneric(I nMax)
|
I GetRandGeneric(I nMax)
|
||||||
{
|
{
|
||||||
// I must be an unsigned numeric type.
|
|
||||||
static_assert(std::numeric_limits<I>::min() == 0);
|
static_assert(std::numeric_limits<I>::min() == 0);
|
||||||
// I() for primitive signed integer types returns 0.
|
// I() for primitive integer types returns 0.
|
||||||
if (nMax == I())
|
if (nMax == I())
|
||||||
return I();
|
return I();
|
||||||
|
|
||||||
|
@ -40,11 +45,17 @@ I GetRandGeneric(I nMax)
|
||||||
return (nRand % nMax);
|
return (nRand % nMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Return a random uint128_t uniformly distributed on [0, nMax), unless nMax is 0 in which case return 0. */
|
||||||
uint128_t GetRandUInt128(uint128_t nMax);
|
uint128_t GetRandUInt128(uint128_t nMax);
|
||||||
|
/** Return a random int128_t uniformly distributed on [0, nMax), unless nMax is 0 in which case return 0. nMax must be >= 0. */
|
||||||
int128_t GetRandInt128(int128_t nMax);
|
int128_t GetRandInt128(int128_t nMax);
|
||||||
|
/** Return a random uint64_t uniformly distributed on [0, nMax), unless nMax is 0 in which case return 0. */
|
||||||
uint64_t GetRand(uint64_t nMax);
|
uint64_t GetRand(uint64_t nMax);
|
||||||
|
/** Return a random int64_t uniformly distributed on [0, nMax), unless nMax is 0 in which case return 0. nMax must be >= 0. */
|
||||||
int64_t GetRandInt64(int64_t nMax);
|
int64_t GetRandInt64(int64_t nMax);
|
||||||
|
/** Return a random int uniformly distributed on [0, nMax), unless nMax is 0 in which case return 0. nMax must be >= 0. */
|
||||||
int GetRandInt(int nMax);
|
int GetRandInt(int nMax);
|
||||||
|
/** Return a random uniformly distributed uint256. */
|
||||||
uint256 GetRandHash();
|
uint256 GetRandHash();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -188,7 +188,7 @@ impl Block {
|
||||||
let mut inner_hasher = Sha256::new();
|
let mut inner_hasher = Sha256::new();
|
||||||
inner_hasher.update(merkle_tree[j + i]);
|
inner_hasher.update(merkle_tree[j + i]);
|
||||||
inner_hasher.update(merkle_tree[j + i2]);
|
inner_hasher.update(merkle_tree[j + i2]);
|
||||||
merkle_tree.push(Sha256::digest(&inner_hasher.finalize()));
|
merkle_tree.push(Sha256::digest(inner_hasher.finalize()));
|
||||||
i += 2;
|
i += 2;
|
||||||
}
|
}
|
||||||
j += size;
|
j += size;
|
||||||
|
|
|
@ -29,6 +29,7 @@ use crate::{
|
||||||
wallet_scanner::{init_batch_scanner, BatchResult, BatchScanner},
|
wallet_scanner::{init_batch_scanner, BatchResult, BatchScanner},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
#[cxx::bridge]
|
#[cxx::bridge]
|
||||||
pub(crate) mod ffi {
|
pub(crate) mod ffi {
|
||||||
extern "C++" {
|
extern "C++" {
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_SUITE(miner_tests, TestingSetup)
|
BOOST_FIXTURE_TEST_SUITE(miner_tests, TestingSetup)
|
||||||
|
|
||||||
// Precomputed nonces and Equihash solutions. These were CPU-mined by the code in `CreateNewBlock_validity`.
|
// Precomputed nonces and Equihash solutions. These were CPU-mined by the code in MineBlockForTest.
|
||||||
// CreateNewBlock_validity will recreate blocks from these via ProcessNewBlock, which would fail if they were incorrect.
|
// CreateNewBlock_validity will recreate blocks from these via ProcessNewBlock, which would fail if they were incorrect.
|
||||||
static
|
static
|
||||||
struct {
|
struct {
|
||||||
|
@ -243,6 +243,52 @@ class MinerAddressScript : public CReserveScript
|
||||||
void KeepScript() {}
|
void KeepScript() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This is only useful if the test changes to require more blocks, but we keep it
|
||||||
|
// compiling to avoid bitrot.
|
||||||
|
void MineBlockForTest(const CChainParams& chainparams, CBlock* pblock) {
|
||||||
|
arith_uint256 try_nonce(0);
|
||||||
|
unsigned int n = chainparams.GetConsensus().nEquihashN;
|
||||||
|
unsigned int k = chainparams.GetConsensus().nEquihashK;
|
||||||
|
|
||||||
|
// Hash state
|
||||||
|
eh_HashState eh_state = EhInitialiseState(n, k);
|
||||||
|
|
||||||
|
// I = the block header minus nonce and solution.
|
||||||
|
CEquihashInput I{*pblock};
|
||||||
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
ss << I;
|
||||||
|
|
||||||
|
// H(I||...
|
||||||
|
eh_state.Update((unsigned char*)&ss[0], ss.size());
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
pblock->nNonce = ArithToUint256(try_nonce);
|
||||||
|
|
||||||
|
// H(I||V||...
|
||||||
|
eh_HashState curr_state(eh_state);
|
||||||
|
curr_state.Update(pblock->nNonce.begin(), pblock->nNonce.size());
|
||||||
|
|
||||||
|
std::function<void()> incrementRuns = [&]() {};
|
||||||
|
std::function<bool(size_t, const std::vector<uint32_t>&)> checkSolution = [&](size_t s, const std::vector<uint32_t>& index_vector) {
|
||||||
|
auto soln = GetMinimalFromIndices(index_vector, DIGITBITS);
|
||||||
|
pblock->nSolution = soln;
|
||||||
|
if (!equihash::is_valid(
|
||||||
|
n, k,
|
||||||
|
{(const unsigned char*)ss.data(), ss.size()},
|
||||||
|
{pblock->nNonce.begin(), pblock->nNonce.size()},
|
||||||
|
{soln.data(), soln.size()})) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CValidationState state;
|
||||||
|
return ProcessNewBlock(state, chainparams, NULL, pblock, true, NULL) && state.IsValid();
|
||||||
|
};
|
||||||
|
if (equihash_solve(curr_state.inner, incrementRuns, checkSolution))
|
||||||
|
break;
|
||||||
|
|
||||||
|
try_nonce += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: These tests rely on CreateNewBlock doing its own self-validation!
|
// NOTE: These tests rely on CreateNewBlock doing its own self-validation!
|
||||||
BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||||
{
|
{
|
||||||
|
@ -300,49 +346,9 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||||
pblock->nNonce = uint256S(blockinfo[i].nonce_hex);
|
pblock->nNonce = uint256S(blockinfo[i].nonce_hex);
|
||||||
pblock->nSolution = ParseHex(blockinfo[i].solution_hex);
|
pblock->nSolution = ParseHex(blockinfo[i].solution_hex);
|
||||||
} else {
|
} else {
|
||||||
// If you need to mine more blocks than are currently in blockinfo, this code will do so.
|
// If you need to mine more blocks than are currently in blockinfo, increase
|
||||||
// We leave it compiling so that it doesn't bitrot.
|
// nblocks and then this code will do so.
|
||||||
arith_uint256 try_nonce(0);
|
MineBlockForTest(chainparams, pblock);
|
||||||
unsigned int n = chainparams.GetConsensus().nEquihashN;
|
|
||||||
unsigned int k = chainparams.GetConsensus().nEquihashK;
|
|
||||||
|
|
||||||
// Hash state
|
|
||||||
eh_HashState eh_state = EhInitialiseState(n, k);
|
|
||||||
|
|
||||||
// I = the block header minus nonce and solution.
|
|
||||||
CEquihashInput I{*pblock};
|
|
||||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
|
||||||
ss << I;
|
|
||||||
|
|
||||||
// H(I||...
|
|
||||||
eh_state.Update((unsigned char*)&ss[0], ss.size());
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
pblock->nNonce = ArithToUint256(try_nonce);
|
|
||||||
|
|
||||||
// H(I||V||...
|
|
||||||
eh_HashState curr_state(eh_state);
|
|
||||||
curr_state.Update(pblock->nNonce.begin(), pblock->nNonce.size());
|
|
||||||
|
|
||||||
std::function<void()> incrementRuns = [&]() {};
|
|
||||||
std::function<bool(size_t, const std::vector<uint32_t>&)> checkSolution = [&](size_t s, const std::vector<uint32_t>& index_vector) {
|
|
||||||
auto soln = GetMinimalFromIndices(index_vector, DIGITBITS);
|
|
||||||
pblock->nSolution = soln;
|
|
||||||
if (!equihash::is_valid(
|
|
||||||
n, k,
|
|
||||||
{(const unsigned char*)ss.data(), ss.size()},
|
|
||||||
{pblock->nNonce.begin(), pblock->nNonce.size()},
|
|
||||||
{soln.data(), soln.size()})) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
CValidationState state;
|
|
||||||
return ProcessNewBlock(state, chainparams, NULL, pblock, true, NULL) && state.IsValid();
|
|
||||||
};
|
|
||||||
if (equihash_solve(curr_state.inner, incrementRuns, checkSolution))
|
|
||||||
break;
|
|
||||||
|
|
||||||
try_nonce += 1;
|
|
||||||
}
|
|
||||||
std::cout << " {\"" << pblock->nNonce.GetHex() << "\", \"";
|
std::cout << " {\"" << pblock->nNonce.GetHex() << "\", \"";
|
||||||
std::cout << HexStr(pblock->nSolution.begin(), pblock->nSolution.end());
|
std::cout << HexStr(pblock->nSolution.begin(), pblock->nSolution.end());
|
||||||
std::cout << "\"}," << std::endl;
|
std::cout << "\"}," << std::endl;
|
||||||
|
@ -384,7 +390,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
auto PrepareTemplate = [&](size_t numTransactions, CAmount fee, std::function<void(size_t i, TestMemPoolEntryHelper& entry, CMutableTransaction& tx)> addTx) {
|
auto PrepareMempool = [&](size_t numTransactions, CAmount fee, std::function<void(size_t i, TestMemPoolEntryHelper& entry, CMutableTransaction& tx)> addTx) {
|
||||||
// This relies on tx.{vin,vout} having been prepared for the specific check.
|
// This relies on tx.{vin,vout} having been prepared for the specific check.
|
||||||
assert(fee >= 0);
|
assert(fee >= 0);
|
||||||
int coinbaseHeight = 20 - 1;
|
int coinbaseHeight = 20 - 1;
|
||||||
|
@ -413,14 +419,14 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||||
tx.vout.resize(1);
|
tx.vout.resize(1);
|
||||||
|
|
||||||
// If we don't set the # of sigops in the CTxMemPoolEntry, template creation fails.
|
// If we don't set the # of sigops in the CTxMemPoolEntry, template creation fails.
|
||||||
PrepareTemplate(1000, MINIMUM_FEE, [&](size_t i, auto& entry, auto& tx) {
|
PrepareMempool(1000, MINIMUM_FEE, [&](size_t i, auto& entry, auto& tx) {
|
||||||
mempool.addUnchecked(tx.GetHash(), entry.FromTx(tx));
|
mempool.addUnchecked(tx.GetHash(), entry.FromTx(tx));
|
||||||
});
|
});
|
||||||
BOOST_CHECK_EXCEPTION(BlockAssembler(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, err_is("bad-blk-sigops"));
|
BOOST_CHECK_EXCEPTION(BlockAssembler(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, err_is("bad-blk-sigops"));
|
||||||
mempool.clear();
|
mempool.clear();
|
||||||
|
|
||||||
// If we do set the # of sigops in the CTxMemPoolEntry, template creation passes.
|
// If we do set the # of sigops in the CTxMemPoolEntry, template creation passes.
|
||||||
PrepareTemplate(1000, MINIMUM_FEE, [&](size_t i, auto& entry, auto& tx) {
|
PrepareMempool(1000, MINIMUM_FEE, [&](size_t i, auto& entry, auto& tx) {
|
||||||
mempool.addUnchecked(tx.GetHash(), entry.SigOps(20).FromTx(tx));
|
mempool.addUnchecked(tx.GetHash(), entry.SigOps(20).FromTx(tx));
|
||||||
});
|
});
|
||||||
BOOST_CHECK(pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey));
|
BOOST_CHECK(pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey));
|
||||||
|
@ -441,7 +447,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||||
tx.vin[0].scriptSig << OP_1;
|
tx.vin[0].scriptSig << OP_1;
|
||||||
|
|
||||||
// Test block size just under the 2000000-byte limit.
|
// Test block size just under the 2000000-byte limit.
|
||||||
PrepareTemplate(210, CalculateConventionalFee(64), [&](size_t i, auto& entry, auto& tx) {
|
PrepareMempool(210, CalculateConventionalFee(64), [&](size_t i, auto& entry, auto& tx) {
|
||||||
mempool.addUnchecked(tx.GetHash(), entry.FromTx(tx));
|
mempool.addUnchecked(tx.GetHash(), entry.FromTx(tx));
|
||||||
});
|
});
|
||||||
BOOST_CHECK(pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey));
|
BOOST_CHECK(pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey));
|
||||||
|
@ -451,12 +457,14 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||||
mempool.clear();
|
mempool.clear();
|
||||||
|
|
||||||
// ... and just over the limit.
|
// ... and just over the limit.
|
||||||
PrepareTemplate(211, CalculateConventionalFee(64), [&](size_t i, auto& entry, auto& tx) {
|
PrepareMempool(211, CalculateConventionalFee(64), [&](size_t i, auto& entry, auto& tx) {
|
||||||
mempool.addUnchecked(tx.GetHash(), entry.FromTx(tx));
|
mempool.addUnchecked(tx.GetHash(), entry.FromTx(tx));
|
||||||
});
|
});
|
||||||
BOOST_CHECK(pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey));
|
BOOST_CHECK(pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey));
|
||||||
// only 211 transactions including coinbase, not 212
|
// only 211 transactions including coinbase, not 212
|
||||||
BOOST_CHECK_EQUAL(211, pblocktemplate->block.vtx.size());
|
BOOST_CHECK_EQUAL(211, pblocktemplate->block.vtx.size());
|
||||||
|
// We can't check the exact block size because which tx will be excluded is nondeterministic,
|
||||||
|
// and they are different sizes.
|
||||||
BOOST_CHECK_GE(2000000, ::GetSerializeSize(pblocktemplate->block, SER_NETWORK, PROTOCOL_VERSION));
|
BOOST_CHECK_GE(2000000, ::GetSerializeSize(pblocktemplate->block, SER_NETWORK, PROTOCOL_VERSION));
|
||||||
delete pblocktemplate;
|
delete pblocktemplate;
|
||||||
mempool.clear();
|
mempool.clear();
|
||||||
|
@ -465,36 +473,12 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||||
|
|
||||||
// Test with an orphan tx in the mempool. Note that this is testing a case that should never occur,
|
// Test with an orphan tx in the mempool. Note that this is testing a case that should never occur,
|
||||||
// because AcceptToMemoryPool would reject the orphan. Using addUnchecked bypasses that rejection.
|
// because AcceptToMemoryPool would reject the orphan. Using addUnchecked bypasses that rejection.
|
||||||
PrepareTemplate(2, MINIMUM_FEE, [&](size_t i, auto& entry, auto& tx) {
|
PrepareMempool(2, MINIMUM_FEE, [&](size_t i, auto& entry, auto& tx) {
|
||||||
if (i != 0) mempool.addUnchecked(tx.GetHash(), entry.SigOps(1).FromTx(tx));
|
if (i != 0) mempool.addUnchecked(tx.GetHash(), entry.SigOps(1).FromTx(tx));
|
||||||
});
|
});
|
||||||
BOOST_CHECK_EXCEPTION(BlockAssembler(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, err_is("bad-txns-inputs-missingorspent"));
|
BOOST_CHECK_EXCEPTION(BlockAssembler(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, err_is("bad-txns-inputs-missingorspent"));
|
||||||
mempool.clear();
|
mempool.clear();
|
||||||
|
|
||||||
// child with higher priority than parent
|
|
||||||
// TODO: re-enable this when we have implemented priority logic for ZIP 317 block construction.
|
|
||||||
|
|
||||||
// const CAmount HIGHFEE = MINIMUM_FEE*2;
|
|
||||||
// const CAmount HIGHERFEE = MINIMUM_FEE*3;
|
|
||||||
//
|
|
||||||
// tx.vin[0].scriptSig = CScript() << OP_1;
|
|
||||||
// tx.vin[0].prevout.hash = CoinbaseTx(1)->GetHash();
|
|
||||||
// tx.vin[0].prevout.n = 0;
|
|
||||||
// tx.vout[0].nValue = MinerSubsidy(1) - HIGHFEE;
|
|
||||||
// hash = tx.GetHash();
|
|
||||||
// mempool.addUnchecked(hash, entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
|
|
||||||
// tx.vin[0].prevout.hash = hash;
|
|
||||||
// tx.vin.resize(2);
|
|
||||||
// tx.vin[1].scriptSig = CScript() << OP_1;
|
|
||||||
// tx.vin[1].prevout.hash = CoinbaseTx(2)->GetHash();
|
|
||||||
// tx.vin[1].prevout.n = 0;
|
|
||||||
// tx.vout[0].nValue = tx.vout[0].nValue + MinerSubsidy(2) - HIGHERFEE; // First txn output + fresh coinbase - new txn fee
|
|
||||||
// hash = tx.GetHash();
|
|
||||||
// mempool.addUnchecked(hash, entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
|
|
||||||
// BOOST_CHECK(pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey));
|
|
||||||
// delete pblocktemplate;
|
|
||||||
// mempool.clear();
|
|
||||||
|
|
||||||
// coinbase in mempool, template creation fails
|
// coinbase in mempool, template creation fails
|
||||||
tx.vin.resize(1);
|
tx.vin.resize(1);
|
||||||
tx.vin[0].prevout.SetNull();
|
tx.vin[0].prevout.SetNull();
|
||||||
|
@ -588,13 +572,15 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||||
|
|
||||||
// However if we advance height and time by one, both will.
|
// However if we advance height and time by one, both will.
|
||||||
chainActive.Tip()->nHeight++;
|
chainActive.Tip()->nHeight++;
|
||||||
FixedClock::Instance()->Set(std::chrono::seconds(chainActive.Tip()->GetMedianTimePast()+2));
|
int64_t newTime = chainActive.Tip()->GetMedianTimePast()+2;
|
||||||
|
|
||||||
// FIXME: we should *actually* create a new block so the following test
|
// We can't test this by directly calling CheckFinalTx(tx*, LOCKTIME_MEDIAN_TIME_PAST)
|
||||||
// works; CheckFinalTx() isn't fooled by monkey-patching nHeight.
|
// because that would use chainActive.Height() which isn't fooled by monkey-patching nHeight.
|
||||||
//BOOST_CHECK(CheckFinalTx(tx));
|
// However, this should be equivalent:
|
||||||
//BOOST_CHECK(CheckFinalTx(tx2));
|
BOOST_CHECK(IsFinalTx(tx, chainActive.Tip()->nHeight+1, newTime));
|
||||||
|
BOOST_CHECK(IsFinalTx(tx2, chainActive.Tip()->nHeight+1, newTime));
|
||||||
|
|
||||||
|
FixedClock::Instance()->Set(std::chrono::seconds(newTime));
|
||||||
BOOST_CHECK(pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey));
|
BOOST_CHECK(pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey));
|
||||||
BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 2);
|
BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 2);
|
||||||
delete pblocktemplate;
|
delete pblocktemplate;
|
||||||
|
|
Loading…
Reference in New Issue