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 @@ -149,7 +149,8 @@ TxId lowFeeTxId = tx.GetId(); g_mempool.addUnchecked(lowFeeTxId, entry.Fee(feeToUse).FromTx(tx)); pblocktemplate = - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey); + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE); // Verify that the free tx and the low fee tx didn't get selected. for (const auto &txn : pblocktemplate->block.vtx) { BOOST_CHECK(txn->GetId() != freeTxId); @@ -191,7 +192,8 @@ g_mempool.addUnchecked( lowFeeTxId2, entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); pblocktemplate = - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey); + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE); // Verify that this tx isn't selected. for (const auto &txn : pblocktemplate->block.vtx) { @@ -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->entries.size() == 1); g_mempool.clear(); tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0); @@ -359,7 +371,8 @@ } BOOST_CHECK( pblocktemplate = - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey)); + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE)); g_mempool.clear(); // block size > limit @@ -386,15 +399,26 @@ } BOOST_CHECK( pblocktemplate = - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey)); + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE)); 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->entries.size() == 1); g_mempool.clear(); // Child with higher priority than parent. @@ -417,7 +441,8 @@ entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK( pblocktemplate = - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey)); + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE)); 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->entries.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->entries.size() == 1); g_mempool.clear(); // Subsidy changing. @@ -469,7 +510,8 @@ } BOOST_CHECK( pblocktemplate = - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey)); + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE)); // Extend to a 210000-long block chain. while (chainActive.Tip()->nHeight < 210000) { CBlockIndex *prev = chainActive.Tip(); @@ -483,7 +525,8 @@ } BOOST_CHECK( pblocktemplate = - BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey)); + BlockAssembler(config, g_mempool) + .CreateNewBlock(scriptPubKey, BlockAssemblerFlags::NONE)); // Invalid p2sh txn in mempool, template creation fails tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0); @@ -504,8 +547,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->entries.size() == 1); g_mempool.clear(); // Delete the dummy blocks again.