diff --git a/src/miner.h b/src/miner.h --- a/src/miner.h +++ b/src/miner.h @@ -32,6 +32,7 @@ //!< Cached total number of SigOps uint64_t txSigOps; + CBlockTemplateEntry() {} CBlockTemplateEntry(CTransactionRef _tx, Amount _fees, uint64_t _size, int64_t _sigOps) : tx(_tx), txFee(_fees), txSize(_size), txSigOps(_sigOps) {} @@ -128,6 +129,11 @@ CTxMemPool::txiter iter; }; +enum BlockAssemblerFlags { + NONE = 0, + EMPTY_BLOCK_ON_FAILURE = 1, +}; + /** Generate a new block, without valid proof-of-work */ class BlockAssembler { private: @@ -162,7 +168,8 @@ BlockAssembler(const Config &_config, const CTxMemPool &mempool); /** Construct a new block template with coinbase to scriptPubKeyIn */ std::unique_ptr - CreateNewBlock(const CScript &scriptPubKeyIn); + CreateNewBlock(const CScript &scriptPubKeyIn, + const BlockAssemblerFlags &flags = EMPTY_BLOCK_ON_FAILURE); uint64_t GetMaxGeneratedBlockSize() const { return nMaxGeneratedBlockSize; } diff --git a/src/miner.cpp b/src/miner.cpp --- a/src/miner.cpp +++ b/src/miner.cpp @@ -120,7 +120,8 @@ } std::unique_ptr -BlockAssembler::CreateNewBlock(const CScript &scriptPubKeyIn) { +BlockAssembler::CreateNewBlock(const CScript &scriptPubKeyIn, + const BlockAssemblerFlags &flags) { int64_t nTimeStart = GetTimeMicros(); resetBlock(); @@ -229,9 +230,46 @@ BlockValidationOptions validationOptions(false, false); if (!TestBlockValidity(*config, state, *pblock, pindexPrev, validationOptions)) { - throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", - __func__, - FormatStateMessage(state))); + if (flags & BlockAssemblerFlags::EMPTY_BLOCK_ON_FAILURE) { + LogPrintf("%s: WARNING: TestBlockValidity failed: %s\nUsing empty " + "block as a fallback.\n", + __func__, FormatStateMessage(state)); + + // To prevent mining on useless hashes, generate an empty block. + coinbaseTx.vout[0].nValue = + GetBlockSubsidy(nHeight, chainparams.GetConsensus()); + coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0; + + // Make sure the coinbase is big enough. + uint64_t coinbaseSize = + ::GetSerializeSize(coinbaseTx, SER_NETWORK, PROTOCOL_VERSION); + if (coinbaseSize < MIN_TX_SIZE) { + coinbaseTx.vin[0].scriptSig + << std::vector(MIN_TX_SIZE - coinbaseSize - 1); + } + + pblocktemplate->entries.resize(1, pblocktemplate->entries[0]); + pblocktemplate->entries[0].tx = MakeTransactionRef(coinbaseTx); + pblocktemplate->entries[0].txFee = Amount::zero(); + + pblock->vtx.resize(0); + pblock->vtx.push_back(pblocktemplate->entries[0].tx); + + // Only fail if the empty block also fails validity checks. + CValidationState fallbackState; + if (!TestBlockValidity(*config, fallbackState, *pblock, pindexPrev, + validationOptions)) { + throw std::runtime_error(strprintf( + "%s: TestBlockValidity failed: %s\nTestBlockValidity also " + "failed for fallback empty block: %s", + __func__, FormatStateMessage(state), + FormatStateMessage(fallbackState))); + } + } else { + throw std::runtime_error( + strprintf("%s: TestBlockValidity failed: %s", __func__, + FormatStateMessage(state))); + } } int64_t nTime2 = GetTimeMicros(); diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -151,6 +151,7 @@ pblocktemplate = BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey); // Verify that the free tx and the low fee tx didn't get selected. + BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 4); for (const auto &txn : pblocktemplate->block.vtx) { BOOST_CHECK(txn->GetId() != freeTxId); BOOST_CHECK(txn->GetId() != lowFeeTxId); @@ -194,6 +195,7 @@ BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey); // Verify that this tx isn't selected. + BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 6); for (const auto &txn : pblocktemplate->block.vtx) { BOOST_CHECK(txn->GetId() != freeTxId2); BOOST_CHECK(txn->GetId() != lowFeeTxId2); @@ -267,7 +269,8 @@ // Simple block creation, nothing special yet: BOOST_CHECK( pblocktemplate = - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey)); + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE)); // We can't make transactions until we have inputs. Therefore, load 100 // blocks :) @@ -308,7 +311,8 @@ // Just to make sure we can still make simple blocks. BOOST_CHECK( pblocktemplate = - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey)); + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE)); const Amount BLOCKSUBSIDY = 50 * COIN; const Amount LOWFEE = CENT; @@ -337,8 +341,16 @@ tx.vin[0].prevout = COutPoint(txid, 0); } BOOST_CHECK_THROW( - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey), + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE), std::runtime_error); + // Test empty block fallback, which should create a valid empty block + BOOST_CHECK( + pblocktemplate = + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, + BlockAssemblerFlags::EMPTY_BLOCK_ON_FAILURE)); + BOOST_CHECK(pblocktemplate->block.vtx.size() == 1); g_mempool.clear(); tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0); @@ -360,6 +372,7 @@ BOOST_CHECK( pblocktemplate = BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey)); + BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 249); g_mempool.clear(); // block size > limit @@ -387,14 +400,25 @@ BOOST_CHECK( pblocktemplate = BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey)); + BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 129); g_mempool.clear(); // Orphan in mempool, template creation fails. TxId txid = tx.GetId(); g_mempool.addUnchecked(txid, entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx)); BOOST_CHECK_THROW( - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey), + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE), std::runtime_error); + // Add back the unused fees + tx.vout[0].nValue += 1001 * CENT; + // Test empty block fallback, which should create a valid empty block + BOOST_CHECK( + pblocktemplate = + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, + BlockAssemblerFlags::EMPTY_BLOCK_ON_FAILURE)); + BOOST_CHECK(pblocktemplate->block.vtx.size() == 1); g_mempool.clear(); // Child with higher priority than parent. @@ -418,6 +442,7 @@ BOOST_CHECK( pblocktemplate = BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey)); + BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 3); g_mempool.clear(); // Coinbase in mempool, template creation fails. @@ -431,8 +456,16 @@ txid, entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); BOOST_CHECK_THROW( - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey), + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE), std::runtime_error); + // Test empty block fallback, which should create a valid empty block + BOOST_CHECK( + pblocktemplate = + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, + BlockAssemblerFlags::EMPTY_BLOCK_ON_FAILURE)); + BOOST_CHECK(pblocktemplate->block.vtx.size() == 1); g_mempool.clear(); // Double spend txn pair in mempool, template creation fails. @@ -450,8 +483,16 @@ txid, entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK_THROW( - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey), + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE), std::runtime_error); + // Test empty block fallback, which should create a valid empty block + BOOST_CHECK( + pblocktemplate = + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, + BlockAssemblerFlags::EMPTY_BLOCK_ON_FAILURE)); + BOOST_CHECK(pblocktemplate->block.vtx.size() == 1); g_mempool.clear(); // Subsidy changing. @@ -504,8 +545,16 @@ txid, entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); BOOST_CHECK_THROW( - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey), + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE), std::runtime_error); + // Test empty block fallback, which should create a valid empty block + BOOST_CHECK( + pblocktemplate = + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, + BlockAssemblerFlags::EMPTY_BLOCK_ON_FAILURE)); + BOOST_CHECK(pblocktemplate->block.vtx.size() == 1); g_mempool.clear(); // Delete the dummy blocks again.