diff --git a/src/init.cpp b/src/init.cpp index c1a87a57e6..8b3712459e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -637,6 +637,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-whitelistrelay", strprintf("Add 'relay' permission to whitelisted peers with default permissions. This will accept relayed transactions even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + argsman.AddArg("-blockmaxsize=", strprintf("Set maximum block size in bytes (default: %d)", DEFAULT_BLOCK_MAX_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); argsman.AddArg("-blockmaxweight=", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_BLOCK_MAX_WEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); argsman.AddArg("-blockmintxfee=", strprintf("Set lowest fee rate (in %s/kvB) for transactions to be included in block creation. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); argsman.AddArg("-blockversion=", "Override block version to test forking scenarios", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::BLOCK_CREATION); diff --git a/src/node/miner.cpp b/src/node/miner.cpp index caa2991819..06dc6d651f 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -61,6 +61,9 @@ static BlockAssembler::Options ClampOptions(BlockAssembler::Options options) { // Limit weight to between 4K and DEFAULT_BLOCK_MAX_WEIGHT for sanity: options.nBlockMaxWeight = std::clamp(options.nBlockMaxWeight, 4000, DEFAULT_BLOCK_MAX_WEIGHT); + // Limit size to between 1K and MAX_BLOCK_SERIALIZED_SIZE-1K for sanity: + options.nBlockMaxSize = std::clamp(options.nBlockMaxSize, 1000, MAX_BLOCK_SERIALIZED_SIZE - 1000); + // Whether we need to account for byte usage (in addition to weight usage) return options; } @@ -70,12 +73,27 @@ BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool m_chainstate{chainstate}, m_options{ClampOptions(options)} { + fNeedSizeAccounting = (options.nBlockMaxSize < MAX_BLOCK_SERIALIZED_SIZE - 1000); } void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& options) { // Block resource limits - options.nBlockMaxWeight = args.GetIntArg("-blockmaxweight", options.nBlockMaxWeight); + // If neither -blockmaxsize or -blockmaxweight is given, limit to DEFAULT_BLOCK_MAX_* + // If only one is given, only restrict the specified resource. + // If both are given, restrict both. + bool fWeightSet = false; + if (args.IsArgSet("-blockmaxweight")) { + options.nBlockMaxWeight = args.GetIntArg("-blockmaxweight", DEFAULT_BLOCK_MAX_WEIGHT); + options.nBlockMaxSize = MAX_BLOCK_SERIALIZED_SIZE; + fWeightSet = true; + } + if (args.IsArgSet("-blockmaxsize")) { + options.nBlockMaxSize = args.GetIntArg("-blockmaxsize", DEFAULT_BLOCK_MAX_SIZE); + if (!fWeightSet) { + options.nBlockMaxWeight = options.nBlockMaxSize * WITNESS_SCALE_FACTOR; + } + } if (const auto blockmintxfee{args.GetArg("-blockmintxfee")}) { if (const auto parsed{ParseMoney(*blockmintxfee)}) options.blockMinFeeRate = CFeeRate{*parsed}; } @@ -95,6 +113,7 @@ void BlockAssembler::resetBlock() inBlock.clear(); // Reserve space for coinbase tx + nBlockSize = 1000; nBlockWeight = 4000; nBlockSigOpsCost = 400; @@ -147,6 +166,11 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc m_last_block_num_txs = nBlockTx; m_last_block_weight = nBlockWeight; + if (fNeedSizeAccounting) { + m_last_block_size = nBlockSize; + } else { + m_last_block_size = std::nullopt; + } // Create coinbase transaction. CMutableTransaction coinbaseTx; @@ -160,7 +184,8 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vchCoinbaseCommitment = m_chainstate.m_chainman.GenerateCoinbaseCommitment(*pblock, pindexPrev); pblocktemplate->vTxFees[0] = -nFees; - LogPrintf("CreateNewBlock(): block weight: %u txs: %u fees: %ld sigops %d\n", GetBlockWeight(*pblock), nBlockTx, nFees, nBlockSigOpsCost); + uint64_t nSerializeSize = GetSerializeSize(*pblock, PROTOCOL_VERSION); + LogPrintf("CreateNewBlock(): total size: %u block weight: %u txs: %u fees: %ld sigops %d\n", nSerializeSize, GetBlockWeight(*pblock), nBlockTx, nFees, nBlockSigOpsCost); // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash(); @@ -210,12 +235,21 @@ bool BlockAssembler::TestPackage(uint64_t packageSize, int64_t packageSigOpsCost // Perform transaction-level checks before adding to block: // - transaction finality (locktime) +// - serialized size (in case -blockmaxsize is in use) bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& package) const { + uint64_t nPotentialBlockSize = nBlockSize; // only used with fNeedSizeAccounting for (CTxMemPool::txiter it : package) { if (!IsFinalTx(it->GetTx(), nHeight, m_lock_time_cutoff)) { return false; } + if (fNeedSizeAccounting) { + uint64_t nTxSize = ::GetSerializeSize(it->GetTx(), PROTOCOL_VERSION); + if (nPotentialBlockSize + nTxSize >= m_options.nBlockMaxSize) { + return false; + } + nPotentialBlockSize += nTxSize; + } } return true; } @@ -225,6 +259,9 @@ void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) pblocktemplate->block.vtx.emplace_back(iter->GetSharedTx()); pblocktemplate->vTxFees.push_back(iter->GetFee()); pblocktemplate->vTxSigOpsCost.push_back(iter->GetSigOpCost()); + if (fNeedSizeAccounting) { + nBlockSize += ::GetSerializeSize(iter->GetTx(), PROTOCOL_VERSION); + } nBlockWeight += iter->GetTxWeight(); ++nBlockTx; nBlockSigOpsCost += iter->GetSigOpCost(); @@ -406,6 +443,15 @@ void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSele mapModifiedTx.get().erase(modit); failedTx.insert(iter); } + + if (fNeedSizeAccounting) { + ++nConsecutiveFailed; + + if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockSize > m_options.nBlockMaxSize - 1000) { + // Give up if we're close to full and haven't succeeded in a while + break; + } + } continue; } diff --git a/src/node/miner.h b/src/node/miner.h index 4173521585..88e0fddc16 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -137,8 +137,11 @@ private: // The constructed block template std::unique_ptr pblocktemplate; + bool fNeedSizeAccounting; + // Information on the current status of the block uint64_t nBlockWeight; + uint64_t nBlockSize; uint64_t nBlockTx; uint64_t nBlockSigOpsCost; CAmount nFees; @@ -156,6 +159,7 @@ public: struct Options { // Configuration parameters for the block size size_t nBlockMaxWeight{DEFAULT_BLOCK_MAX_WEIGHT}; + size_t nBlockMaxSize{DEFAULT_BLOCK_MAX_SIZE}; CFeeRate blockMinFeeRate{DEFAULT_BLOCK_MIN_TX_FEE}; // Whether to call TestBlockValidity() at the end of CreateNewBlock(). bool test_block_validity{true}; @@ -169,6 +173,7 @@ public: inline static std::optional m_last_block_num_txs{}; inline static std::optional m_last_block_weight{}; + inline static std::optional m_last_block_size{}; private: const Options m_options; diff --git a/src/policy/policy.h b/src/policy/policy.h index d1c8148800..9f68033c0e 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -19,6 +19,8 @@ class CCoinsViewCache; class CFeeRate; class CScript; +/** Default for -blockmaxsize, which controls the maximum size of block the mining code will create **/ +static const unsigned int DEFAULT_BLOCK_MAX_SIZE = MAX_BLOCK_SERIALIZED_SIZE; /** Default for -blockmaxweight, which controls the range of block weights the mining code will create **/ static constexpr unsigned int DEFAULT_BLOCK_MAX_WEIGHT{MAX_BLOCK_WEIGHT - 4000}; /** Default for -blockmintxfee, which sets the minimum feerate for a transaction in blocks created by mining code **/ diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 9f3a26a123..e9ca09876a 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -416,6 +416,7 @@ static RPCHelpMan getmininginfo() RPCResult::Type::OBJ, "", "", { {RPCResult::Type::NUM, "blocks", "The current block"}, + {RPCResult::Type::NUM, "currentblocksize", /*optional=*/true, "The block size of the last assembled block (only present if a block was ever assembled, and blockmaxsize is configured)"}, {RPCResult::Type::NUM, "currentblockweight", /*optional=*/true, "The block weight of the last assembled block (only present if a block was ever assembled)"}, {RPCResult::Type::NUM, "currentblocktx", /*optional=*/true, "The number of block transactions of the last assembled block (only present if a block was ever assembled)"}, {RPCResult::Type::NUM, "difficulty", "The current difficulty"}, @@ -438,6 +439,7 @@ static RPCHelpMan getmininginfo() UniValue obj(UniValue::VOBJ); obj.pushKV("blocks", active_chain.Height()); + if (BlockAssembler::m_last_block_size) obj.pushKV("currentblocksize", *BlockAssembler::m_last_block_size); if (BlockAssembler::m_last_block_weight) obj.pushKV("currentblockweight", *BlockAssembler::m_last_block_weight); if (BlockAssembler::m_last_block_num_txs) obj.pushKV("currentblocktx", *BlockAssembler::m_last_block_num_txs); obj.pushKV("difficulty", (double)GetDifficulty(active_chain.Tip())); diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index b4c7cac223..91feedc7da 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -63,6 +63,7 @@ BlockAssembler MinerTestingSetup::AssemblerForTest(CTxMemPool& tx_mempool) BlockAssembler::Options options; options.nBlockMaxWeight = MAX_BLOCK_WEIGHT; + options.nBlockMaxSize = MAX_BLOCK_SERIALIZED_SIZE; options.blockMinFeeRate = blockMinFeeRate; return BlockAssembler{m_node.chainman->ActiveChainstate(), &tx_mempool, options}; } diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index da796d3f70..75a0037f23 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -65,6 +65,7 @@ class MiningTest(BitcoinTestFramework): assert_equal(mining_info['blocks'], 200) assert_equal(mining_info['currentblocktx'], 0) assert_equal(mining_info['currentblockweight'], 4000) + assert 'currentblocksize' not in mining_info self.log.info('test blockversion') self.restart_node(0, extra_args=[f'-mocktime={t}', '-blockversion=1337']) @@ -132,6 +133,7 @@ class MiningTest(BitcoinTestFramework): assert_equal(mining_info['chain'], self.chain) assert 'currentblocktx' not in mining_info assert 'currentblockweight' not in mining_info + assert 'currentblocksize' not in mining_info assert_equal(mining_info['difficulty'], Decimal('4.656542373906925E-10')) assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334')) assert_equal(mining_info['pooledtx'], 0)