Merge gbt_rpc_options-28+knots

This commit is contained in:
Luke Dashjr 2025-03-05 03:27:08 +00:00
commit dc8fc35fc0
7 changed files with 237 additions and 31 deletions

View File

@ -40,13 +40,14 @@ public:
virtual std::optional<uint256> getTipHash() = 0; virtual std::optional<uint256> getTipHash() = 0;
/** /**
* Construct a new block template * Construct a new block template. For the createNewBlock variant, subclass options (if any) are silently lost and overridden by any config args. For createNewBlock2, the options are assumed to be complete.
* *
* @param[in] script_pub_key the coinbase output * @param[in] script_pub_key the coinbase output
* @param[in] options options for creating the block * @param[in] options options for creating the block
* @returns a block template * @returns a block template
*/ */
virtual std::unique_ptr<node::CBlockTemplate> createNewBlock(const CScript& script_pub_key, const node::BlockCreateOptions& options={}) = 0; virtual std::unique_ptr<node::CBlockTemplate> createNewBlock(const CScript& script_pub_key, const node::BlockCreateOptions& options={}) = 0;
virtual std::unique_ptr<node::CBlockTemplate> createNewBlock2(const CScript& script_pub_key, const node::BlockCreateOptions& assemble_options) = 0;
/** /**
* Processes new block. A valid new block is automatically relayed to peers. * Processes new block. A valid new block is automatically relayed to peers.

View File

@ -939,6 +939,11 @@ public:
{ {
BlockAssembler::Options assemble_options{options}; BlockAssembler::Options assemble_options{options};
ApplyArgsManOptions(*Assert(m_node.args), assemble_options); ApplyArgsManOptions(*Assert(m_node.args), assemble_options);
return createNewBlock2(script_pub_key, assemble_options);
}
std::unique_ptr<CBlockTemplate> createNewBlock2(const CScript& script_pub_key, const BlockCreateOptions& assemble_options) override
{
return BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(script_pub_key); return BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(script_pub_key);
} }

View File

@ -65,11 +65,15 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman)
block.hashMerkleRoot = BlockMerkleRoot(block); block.hashMerkleRoot = BlockMerkleRoot(block);
} }
static BlockAssembler::Options ClampOptions(BlockAssembler::Options options) BlockCreateOptions BlockCreateOptions::Clamped() const
{ {
Assert(options.coinbase_max_additional_size <= MAX_BLOCK_SERIALIZED_SIZE - 1000); BlockAssembler::Options options = *this;
Assert(options.coinbase_max_additional_weight <= DEFAULT_BLOCK_MAX_WEIGHT); constexpr size_t theoretical_min_gentx_sz = 1+4+1+36+1+1+4+1+4;
Assert(options.coinbase_output_max_additional_sigops <= MAX_BLOCK_SIGOPS_COST); constexpr size_t theoretical_min_gentx_weight = theoretical_min_gentx_sz * WITNESS_SCALE_FACTOR;
CHECK_NONFATAL(options.coinbase_max_additional_size <= MAX_BLOCK_SERIALIZED_SIZE - 1000);
CHECK_NONFATAL(options.coinbase_max_additional_weight <= DEFAULT_BLOCK_MAX_WEIGHT);
CHECK_NONFATAL(options.coinbase_max_additional_weight >= theoretical_min_gentx_weight);
CHECK_NONFATAL(options.coinbase_output_max_additional_sigops <= MAX_BLOCK_SIGOPS_COST);
// Limit size to between coinbase_max_additional_size and MAX_BLOCK_SERIALIZED_SIZE-1K for sanity: // Limit size to between coinbase_max_additional_size and MAX_BLOCK_SERIALIZED_SIZE-1K for sanity:
options.nBlockMaxSize = std::clamp<size_t>(options.nBlockMaxSize, options.coinbase_max_additional_size, MAX_BLOCK_SERIALIZED_SIZE - 1000); options.nBlockMaxSize = std::clamp<size_t>(options.nBlockMaxSize, options.coinbase_max_additional_size, MAX_BLOCK_SERIALIZED_SIZE - 1000);
// Limit weight to between coinbase_max_additional_weight and DEFAULT_BLOCK_MAX_WEIGHT for sanity: // Limit weight to between coinbase_max_additional_weight and DEFAULT_BLOCK_MAX_WEIGHT for sanity:
@ -82,7 +86,7 @@ BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool
: chainparams{chainstate.m_chainman.GetParams()}, : chainparams{chainstate.m_chainman.GetParams()},
m_mempool{options.use_mempool ? mempool : nullptr}, m_mempool{options.use_mempool ? mempool : nullptr},
m_chainstate{chainstate}, m_chainstate{chainstate},
m_options{ClampOptions(options)} m_options{options.Clamped()}
{ {
// Whether we need to account for byte usage (in addition to weight usage) // Whether we need to account for byte usage (in addition to weight usage)
fNeedSizeAccounting = (options.nBlockMaxSize < MAX_BLOCK_SERIALIZED_SIZE - 1000); fNeedSizeAccounting = (options.nBlockMaxSize < MAX_BLOCK_SERIALIZED_SIZE - 1000);

View File

@ -31,7 +31,6 @@ class ChainstateManager;
namespace Consensus { struct Params; }; namespace Consensus { struct Params; };
namespace node { namespace node {
static const bool DEFAULT_PRINT_MODIFIED_FEE = false;
struct CBlockTemplate struct CBlockTemplate
{ {
@ -166,15 +165,7 @@ private:
bool blockFinished; bool blockFinished;
public: public:
struct Options : BlockCreateOptions { using Options = BlockCreateOptions;
// 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};
bool print_modified_fee{DEFAULT_PRINT_MODIFIED_FEE};
};
explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options); explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options);

View File

@ -13,6 +13,9 @@
#ifndef BITCOIN_NODE_TYPES_H #ifndef BITCOIN_NODE_TYPES_H
#define BITCOIN_NODE_TYPES_H #define BITCOIN_NODE_TYPES_H
#include <policy/feerate.h>
#include <policy/policy.h>
#include <cstddef> #include <cstddef>
namespace node { namespace node {
@ -27,6 +30,8 @@ enum class TransactionError {
INVALID_PACKAGE, INVALID_PACKAGE,
}; };
static const bool DEFAULT_PRINT_MODIFIED_FEE = false;
struct BlockCreateOptions { struct BlockCreateOptions {
/** /**
* Set false to omit mempool transactions * Set false to omit mempool transactions
@ -49,6 +54,18 @@ struct BlockCreateOptions {
* transaction outputs. * transaction outputs.
*/ */
size_t coinbase_output_max_additional_sigops{400}; size_t coinbase_output_max_additional_sigops{400};
// 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};
bool print_modified_fee{DEFAULT_PRINT_MODIFIED_FEE};
BlockCreateOptions Clamped() const;
friend bool operator==(const BlockCreateOptions& a, const BlockCreateOptions& b) noexcept = default;
}; };
} // namespace node } // namespace node

View File

@ -34,6 +34,7 @@
#include <script/signingprovider.h> #include <script/signingprovider.h>
#include <txmempool.h> #include <txmempool.h>
#include <univalue.h> #include <univalue.h>
#include <util/check.h>
#include <util/signalinterrupt.h> #include <util/signalinterrupt.h>
#include <util/strencodings.h> #include <util/strencodings.h>
#include <util/string.h> #include <util/string.h>
@ -580,6 +581,8 @@ static std::string gbt_vb_name(const Consensus::DeploymentPos pos) {
return s; return s;
} }
static UniValue TemplateToJSON(const Consensus::Params&, const ChainstateManager&, const CBlockTemplate*, const CBlockIndex*, const std::set<std::string>& setClientRules, unsigned int nTransactionsUpdatedLast);
static RPCHelpMan getblocktemplate() static RPCHelpMan getblocktemplate()
{ {
return RPCHelpMan{"getblocktemplate", return RPCHelpMan{"getblocktemplate",
@ -594,9 +597,14 @@ static RPCHelpMan getblocktemplate()
{"template_request", RPCArg::Type::OBJ, RPCArg::Optional::NO, "Format of the template", {"template_request", RPCArg::Type::OBJ, RPCArg::Optional::NO, "Format of the template",
{ {
{"mode", RPCArg::Type::STR, /* treat as named arg */ RPCArg::Optional::OMITTED, "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"}, {"mode", RPCArg::Type::STR, /* treat as named arg */ RPCArg::Optional::OMITTED, "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"},
{"blockmaxsize", RPCArg::Type::NUM, RPCArg::DefaultHint{"set by -blockmaxsize"}, "limit returned block to specified size (disables template cache)"},
{"blockmaxweight", RPCArg::Type::NUM, RPCArg::DefaultHint{"set by -blockmaxweight"}, "limit returned block to specified weight (disables template cache)"},
{"blockreservedsigops", RPCArg::Type::NUM, RPCArg::Default{node::BlockCreateOptions{}.coinbase_output_max_additional_sigops}, "reserve specified number of sigops in returned block for generation transaction (disables template cache)"},
{"blockreservedsize", RPCArg::Type::NUM, RPCArg::Default{node::BlockCreateOptions{}.coinbase_max_additional_size}, "reserve specified size in returned block for generation transaction (disables template cache)"},
{"blockreservedweight", RPCArg::Type::NUM, RPCArg::Default{node::BlockCreateOptions{}.coinbase_max_additional_weight}, "reserve specified weight in returned block for generation transaction (disables template cache)"},
{"capabilities", RPCArg::Type::ARR, /* treat as named arg */ RPCArg::Optional::OMITTED, "A list of strings", {"capabilities", RPCArg::Type::ARR, /* treat as named arg */ RPCArg::Optional::OMITTED, "A list of strings",
{ {
{"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "client side supported feature, 'longpoll', 'coinbasevalue', 'proposal', 'serverlist', 'workid'"}, {"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "client side supported feature, 'longpoll', 'coinbasevalue', 'proposal', 'skip_validity_test', 'serverlist', 'workid'"},
}}, }},
{"rules", RPCArg::Type::ARR, RPCArg::Optional::NO, "A list of strings", {"rules", RPCArg::Type::ARR, RPCArg::Optional::NO, "A list of strings",
{ {
@ -604,6 +612,7 @@ static RPCHelpMan getblocktemplate()
{"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "other client side supported softfork deployment"}, {"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "other client side supported softfork deployment"},
}}, }},
{"longpollid", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "delay processing request until the result would vary significantly from the \"longpollid\" of a prior template"}, {"longpollid", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "delay processing request until the result would vary significantly from the \"longpollid\" of a prior template"},
{"minfeerate", RPCArg::Type::NUM, RPCArg::DefaultHint{"set by -blockmintxfee"}, "only include transactions with a minimum sats/vbyte (disables template cache)"},
{"data", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "proposed block data to check, encoded in hexadecimal; valid only for mode=\"proposal\""}, {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "proposed block data to check, encoded in hexadecimal; valid only for mode=\"proposal\""},
}, },
}, },
@ -680,6 +689,14 @@ static RPCHelpMan getblocktemplate()
LOCK(cs_main); LOCK(cs_main);
uint256 tip{CHECK_NONFATAL(miner.getTipHash()).value()}; uint256 tip{CHECK_NONFATAL(miner.getTipHash()).value()};
BlockAssembler::Options options;
{
const ArgsManager& args{EnsureAnyArgsman(request.context)};
ApplyArgsManOptions(args, options);
}
const BlockAssembler::Options options_def{options.Clamped()};
bool bypass_cache{false};
std::string strMode = "template"; std::string strMode = "template";
UniValue lpval = NullUniValue; UniValue lpval = NullUniValue;
std::set<std::string> setClientRules; std::set<std::string> setClientRules;
@ -733,6 +750,39 @@ static RPCHelpMan getblocktemplate()
setClientRules.insert(v.get_str()); setClientRules.insert(v.get_str());
} }
} }
if (!oparam["blockmaxsize"].isNull()) {
options.nBlockMaxSize = oparam["blockmaxsize"].getInt<size_t>();
}
if (!oparam["blockmaxweight"].isNull()) {
options.nBlockMaxWeight = oparam["blockmaxweight"].getInt<size_t>();
}
if (!oparam["blockreservedsize"].isNull()) {
options.coinbase_max_additional_size = oparam["blockreservedsize"].getInt<size_t>();
}
if (!oparam["blockreservedweight"].isNull()) {
options.coinbase_max_additional_weight = oparam["blockreservedweight"].getInt<size_t>();
}
if (!oparam["blockreservedsigops"].isNull()) {
options.coinbase_output_max_additional_sigops = oparam["blockreservedsigops"].getInt<size_t>();
}
if (!oparam["minfeerate"].isNull()) {
options.blockMinFeeRate = CFeeRate{AmountFromValue(oparam["minfeerate"]), COIN /* sat/vB */};
}
options = options.Clamped();
bypass_cache |= !(options == options_def);
// NOTE: Intentionally not setting bypass_cache for skip_validity_test since _using_ the cache is fine
const UniValue& client_caps = oparam.find_value("capabilities");
if (client_caps.isArray()) {
for (unsigned int i = 0; i < client_caps.size(); ++i) {
const UniValue& v = client_caps[i];
if (!v.isStr()) continue;
if (v.get_str() == "skip_validity_test") {
options.test_block_validity = false;
}
}
}
} }
if (strMode != "template") if (strMode != "template")
@ -817,8 +867,20 @@ static RPCHelpMan getblocktemplate()
static int64_t time_start; static int64_t time_start;
static std::unique_ptr<CBlockTemplate> pblocktemplate; static std::unique_ptr<CBlockTemplate> pblocktemplate;
if (!pindexPrev || pindexPrev->GetBlockHash() != tip || if (!pindexPrev || pindexPrev->GetBlockHash() != tip ||
bypass_cache ||
(miner.getTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - time_start > 5)) (miner.getTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - time_start > 5))
{ {
if (bypass_cache || !options.test_block_validity) {
// Create one-off template unrelated to cache
const auto tx_update_counter = miner.getTransactionsUpdated();
CBlockIndex* const local_pindexPrev = chainman.m_blockman.LookupBlockIndex(tip);
const CScript dummy_script{CScript() << OP_TRUE};
auto tmpl = miner.createNewBlock2(dummy_script, options);
if (!tmpl) throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory");
return TemplateToJSON(consensusParams, chainman, &*tmpl, local_pindexPrev, setClientRules, tx_update_counter);
}
CHECK_NONFATAL(options == options_def);
// Clear pindexPrev so future calls make a new block, despite any failures from here on // Clear pindexPrev so future calls make a new block, despite any failures from here on
pindexPrev = nullptr; pindexPrev = nullptr;
@ -844,6 +906,14 @@ static RPCHelpMan getblocktemplate()
UpdateTime(pblock, consensusParams, pindexPrev); UpdateTime(pblock, consensusParams, pindexPrev);
pblock->nNonce = 0; pblock->nNonce = 0;
return TemplateToJSON(consensusParams, chainman, &*pblocktemplate, pindexPrev, setClientRules, nTransactionsUpdatedLast);
},
};
}
static UniValue TemplateToJSON(const Consensus::Params& consensusParams, const ChainstateManager& chainman, const CBlockTemplate* const pblocktemplate, const CBlockIndex* const pindexPrev, const std::set<std::string>& setClientRules, const unsigned int nTransactionsUpdatedLast) {
const CBlock* const pblock = &pblocktemplate->block;
// NOTE: If at some point we support pre-segwit miners post-segwit-activation, this needs to take segwit support into consideration // NOTE: If at some point we support pre-segwit miners post-segwit-activation, this needs to take segwit support into consideration
const bool fPreSegWit = !DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT); const bool fPreSegWit = !DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT);
@ -911,6 +981,7 @@ static RPCHelpMan getblocktemplate()
aRules.push_back("!signet"); aRules.push_back("!signet");
} }
auto block_version = pblock->nVersion;
UniValue vbavailable(UniValue::VOBJ); UniValue vbavailable(UniValue::VOBJ);
for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) {
Consensus::DeploymentPos pos = Consensus::DeploymentPos(j); Consensus::DeploymentPos pos = Consensus::DeploymentPos(j);
@ -922,7 +993,7 @@ static RPCHelpMan getblocktemplate()
break; break;
case ThresholdState::LOCKED_IN: case ThresholdState::LOCKED_IN:
// Ensure bit is set in block version // Ensure bit is set in block version
pblock->nVersion |= chainman.m_versionbitscache.Mask(consensusParams, pos); block_version |= chainman.m_versionbitscache.Mask(consensusParams, pos);
[[fallthrough]]; [[fallthrough]];
case ThresholdState::STARTED: case ThresholdState::STARTED:
{ {
@ -931,7 +1002,7 @@ static RPCHelpMan getblocktemplate()
if (setClientRules.find(vbinfo.name) == setClientRules.end()) { if (setClientRules.find(vbinfo.name) == setClientRules.end()) {
if (!vbinfo.gbt_force) { if (!vbinfo.gbt_force) {
// If the client doesn't support this, don't indicate it in the [default] version // If the client doesn't support this, don't indicate it in the [default] version
pblock->nVersion &= ~chainman.m_versionbitscache.Mask(consensusParams, pos); block_version &= ~chainman.m_versionbitscache.Mask(consensusParams, pos);
} }
} }
break; break;
@ -951,7 +1022,7 @@ static RPCHelpMan getblocktemplate()
} }
} }
} }
result.pushKV("version", pblock->nVersion); result.pushKV("version", block_version);
result.pushKV("rules", std::move(aRules)); result.pushKV("rules", std::move(aRules));
result.pushKV("vbavailable", std::move(vbavailable)); result.pushKV("vbavailable", std::move(vbavailable));
result.pushKV("vbrequired", int(0)); result.pushKV("vbrequired", int(0));
@ -960,7 +1031,7 @@ static RPCHelpMan getblocktemplate()
result.pushKV("transactions", std::move(transactions)); result.pushKV("transactions", std::move(transactions));
result.pushKV("coinbaseaux", std::move(aux)); result.pushKV("coinbaseaux", std::move(aux));
result.pushKV("coinbasevalue", (int64_t)pblock->vtx[0]->vout[0].nValue); result.pushKV("coinbasevalue", (int64_t)pblock->vtx[0]->vout[0].nValue);
result.pushKV("longpollid", tip.GetHex() + ToString(nTransactionsUpdatedLast)); result.pushKV("longpollid", pindexPrev->GetBlockHash().GetHex() + ToString(nTransactionsUpdatedLast));
result.pushKV("target", hashTarget.GetHex()); result.pushKV("target", hashTarget.GetHex());
result.pushKV("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1); result.pushKV("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1);
result.pushKV("mutable", std::move(aMutable)); result.pushKV("mutable", std::move(aMutable));
@ -991,8 +1062,6 @@ static RPCHelpMan getblocktemplate()
} }
return result; return result;
},
};
} }
class submitblock_StateCatcher final : public CValidationInterface class submitblock_StateCatcher final : public CValidationInterface

View File

@ -16,6 +16,7 @@ from test_framework.blocktools import (
get_witness_script, get_witness_script,
NORMAL_GBT_REQUEST_PARAMS, NORMAL_GBT_REQUEST_PARAMS,
TIME_GENESIS_BLOCK, TIME_GENESIS_BLOCK,
WITNESS_SCALE_FACTOR,
) )
from test_framework.messages import ( from test_framework.messages import (
BLOCK_HEADER_SIZE, BLOCK_HEADER_SIZE,
@ -32,15 +33,18 @@ from test_framework.util import (
assert_raises_rpc_error, assert_raises_rpc_error,
get_fee, get_fee,
) )
from test_framework.wallet import MiniWallet from test_framework.wallet import MiniWallet, MiniWalletMode
DIFFICULTY_ADJUSTMENT_INTERVAL = 144 DIFFICULTY_ADJUSTMENT_INTERVAL = 144
MAX_FUTURE_BLOCK_TIME = 2 * 3600 MAX_FUTURE_BLOCK_TIME = 2 * 3600
MAX_TIMEWARP = 600 MAX_TIMEWARP = 600
ASSUMED_BLOCK_OVERHEAD_SIZE = 1000
ASSUMED_BLOCK_OVERHEAD_WEIGHT = ASSUMED_BLOCK_OVERHEAD_SIZE * WITNESS_SCALE_FACTOR
VERSIONBITS_TOP_BITS = 0x20000000 VERSIONBITS_TOP_BITS = 0x20000000
VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28 VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28
DEFAULT_BLOCK_MIN_TX_FEE = 1000 # default `-blockmintxfee` setting [sat/kvB] DEFAULT_BLOCK_MIN_TX_FEE = 1000 # default `-blockmintxfee` setting [sat/kvB]
MAX_SIGOP_COST = 80000
def assert_template(node, block, expect, rehash=True): def assert_template(node, block, expect, rehash=True):
@ -81,15 +85,21 @@ class MiningTest(BitcoinTestFramework):
self.restart_node(0) self.restart_node(0)
self.connect_nodes(0, 1) self.connect_nodes(0, 1)
def test_blockmintxfee_parameter(self): def test_blockmintxfee_parameter(self, *, use_rpc=False):
self.log.info("Test -blockmintxfee setting") if not use_rpc:
self.log.info("Test -blockmintxfee setting")
self.restart_node(0, extra_args=['-minrelaytxfee=0', '-persistmempool=0']) self.restart_node(0, extra_args=['-minrelaytxfee=0', '-persistmempool=0'])
node = self.nodes[0] node = self.nodes[0]
# test default (no parameter), zero and a bunch of arbitrary blockmintxfee rates [sat/kvB] # test default (no parameter), zero and a bunch of arbitrary blockmintxfee rates [sat/kvB]
for blockmintxfee_sat_kvb in (DEFAULT_BLOCK_MIN_TX_FEE, 0, 50, 100, 500, 2500, 5000, 21000, 333333, 2500000): for blockmintxfee_sat_kvb in (DEFAULT_BLOCK_MIN_TX_FEE, 0, 50, 100, 500, 2500, 5000, 21000, 333333, 2500000):
blockmintxfee_btc_kvb = blockmintxfee_sat_kvb / Decimal(COIN) blockmintxfee_btc_kvb = blockmintxfee_sat_kvb / Decimal(COIN)
if blockmintxfee_sat_kvb == DEFAULT_BLOCK_MIN_TX_FEE: if use_rpc:
blockmintxfee_sat_vb = blockmintxfee_sat_kvb / 1000
self.log.info(f"-> Test RPC param minfeerate={blockmintxfee_sat_vb} ({blockmintxfee_sat_kvb} sat/kvB)...")
self.restart_node(0, extra_args=['-minrelaytxfee=0', '-persistmempool=0'])
self.wallet.rescan_utxos() # to avoid spending outputs of txs that are not in mempool anymore after restart
elif blockmintxfee_sat_kvb == DEFAULT_BLOCK_MIN_TX_FEE:
self.log.info(f"-> Default -blockmintxfee setting ({blockmintxfee_sat_kvb} sat/kvB)...") self.log.info(f"-> Default -blockmintxfee setting ({blockmintxfee_sat_kvb} sat/kvB)...")
else: else:
blockmintxfee_parameter = f"-blockmintxfee={blockmintxfee_btc_kvb:.8f}" blockmintxfee_parameter = f"-blockmintxfee={blockmintxfee_btc_kvb:.8f}"
@ -109,16 +119,124 @@ class MiningTest(BitcoinTestFramework):
node.prioritisetransaction(tx_below_min_feerate["txid"], 0, -1) node.prioritisetransaction(tx_below_min_feerate["txid"], 0, -1)
# check that tx below specified fee-rate is neither in template nor in the actual block # check that tx below specified fee-rate is neither in template nor in the actual block
block_template = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) req = NORMAL_GBT_REQUEST_PARAMS
if use_rpc:
req = copy.deepcopy(req)
req['minfeerate'] = blockmintxfee_sat_vb
block_template = node.getblocktemplate(req)
block_template_txids = [tx['txid'] for tx in block_template['transactions']] block_template_txids = [tx['txid'] for tx in block_template['transactions']]
self.generate(self.wallet, 1, sync_fun=self.no_op) self.generate(self.wallet, 1, sync_fun=self.no_op)
block = node.getblock(node.getbestblockhash(), verbosity=2) block = node.getblock(node.getbestblockhash(), verbosity=2)
block_txids = [tx['txid'] for tx in block['tx']] block_txids = [tx['txid'] for tx in block['tx']]
assert tx_with_min_feerate['txid'] in block_template_txids assert tx_with_min_feerate['txid'] in block_template_txids
assert tx_with_min_feerate['txid'] in block_txids
assert tx_below_min_feerate['txid'] not in block_template_txids assert tx_below_min_feerate['txid'] not in block_template_txids
assert tx_below_min_feerate['txid'] not in block_txids
if not use_rpc:
assert tx_with_min_feerate['txid'] in block_txids
assert tx_below_min_feerate['txid'] not in block_txids
def test_rpc_params(self):
self.log.info("Test minfeerate RPC param")
self.test_blockmintxfee_parameter(use_rpc=True)
node = self.nodes[0]
self.log.info("Preparing mempool")
self.restart_node(0, extra_args=['-limitancestorcount=1000', '-limitancestorsize=7000', '-limitdescendantcount=1000', '-limitdescendantsize=7000'])
# Fill the mempool
target_mempool_size = 200000
last_tx_size = 0
utxo = self.wallet.get_utxo() # save for small coins
while node.getmempoolinfo()['bytes'] < target_mempool_size - last_tx_size:
tx = self.wallet.send_self_transfer_multi(
from_node=self.nodes[0],
num_outputs=1000,
)
last_tx_size = len(tx['hex']) / 2
while node.getmempoolinfo()['bytes'] < 200000:
tx = self.wallet.send_self_transfer_multi(
utxos_to_spend=[utxo],
from_node=node,
num_outputs=1,
)
utxo = tx['new_utxos'][0]
self.log.info("Test blockmaxsize RPC param")
req = copy.deepcopy(NORMAL_GBT_REQUEST_PARAMS)
normal_size = ASSUMED_BLOCK_OVERHEAD_SIZE + (sum(len(tx['data']) for tx in self.nodes[0].getblocktemplate(req)['transactions']) // 2)
last_size = ASSUMED_BLOCK_OVERHEAD_SIZE
for target_size in (50000, 100000, 150000):
self.log.info(f"-> Test RPC param blockmaxsize={target_size}...")
req['blockmaxsize'] = target_size
tmpl = self.nodes[0].getblocktemplate(req)
blk_size = ASSUMED_BLOCK_OVERHEAD_SIZE + (sum(len(tx['data']) for tx in tmpl['transactions']) // 2)
assert blk_size < normal_size
assert blk_size < target_size
assert blk_size > last_size
last_size = blk_size
self.log.info("Test blockreservedsize RPC param")
req = copy.deepcopy(NORMAL_GBT_REQUEST_PARAMS)
req['blockmaxsize'] = 150000
normal_size = (sum(len(tx['data']) for tx in self.nodes[0].getblocktemplate(req)['transactions']) // 2)
last_size = 0
for reserved_size in (100000, 10000, 100):
self.log.info(f"-> Test RPC param blockreservedsize={reserved_size}...")
req['blockreservedsize'] = reserved_size
tmpl = self.nodes[0].getblocktemplate(req)
blk_size = (sum(len(tx['data']) for tx in tmpl['transactions']) // 2)
assert blk_size < normal_size if reserved_size > 1000 else blk_size > normal_size
assert blk_size + reserved_size <= req['blockmaxsize']
assert blk_size > last_size
last_size = blk_size
self.log.info("Test blockmaxweight RPC param")
req = copy.deepcopy(NORMAL_GBT_REQUEST_PARAMS)
normal_weight = ASSUMED_BLOCK_OVERHEAD_WEIGHT + sum(tx['weight'] for tx in self.nodes[0].getblocktemplate(req)['transactions'])
last_weight = ASSUMED_BLOCK_OVERHEAD_WEIGHT
for target_weight in (200000, 400000, 600000):
self.log.info(f"-> Test RPC param blockmaxweight={target_weight}...")
req['blockmaxweight'] = target_weight
tmpl = self.nodes[0].getblocktemplate(req)
blk_weight = ASSUMED_BLOCK_OVERHEAD_WEIGHT + sum(tx['weight'] for tx in tmpl['transactions'])
assert blk_weight < normal_weight
assert blk_weight < target_weight
assert blk_weight > last_weight
last_weight = blk_weight
self.log.info("Test blockreservedweight RPC param")
req = copy.deepcopy(NORMAL_GBT_REQUEST_PARAMS)
req['blockmaxweight'] = 600000
normal_weight = sum(tx['weight'] for tx in self.nodes[0].getblocktemplate(req)['transactions'])
last_weight = 0
for reserved_weight in (400000, 40000, 400):
self.log.info(f"-> Test RPC param blockreservedweight={reserved_weight}...")
req['blockreservedweight'] = reserved_weight
tmpl = self.nodes[0].getblocktemplate(req)
blk_weight = sum(tx['weight'] for tx in tmpl['transactions'])
assert blk_weight < normal_weight if reserved_weight > 4000 else blk_weight > normal_weight
assert blk_weight + reserved_weight <= req['blockmaxweight']
assert blk_weight > last_weight
last_weight = blk_weight
self.log.info("Test blockreservedsigops RPC param")
req = copy.deepcopy(NORMAL_GBT_REQUEST_PARAMS)
normal_sigops = sum(tx['sigops'] for tx in self.nodes[0].getblocktemplate(req)['transactions'])
assert normal_sigops
last_sigops = 0
baseline_sigops = MAX_SIGOP_COST - normal_sigops
for reserved_sigops in (800, 400, 100):
reserved_sigops += baseline_sigops
self.log.info(f"-> Test RPC param blockreservedsigops={reserved_sigops}...")
req['blockreservedsigops'] = reserved_sigops
tmpl = self.nodes[0].getblocktemplate(req)
blk_sigops = sum(tx['sigops'] for tx in tmpl['transactions'])
assert blk_sigops < normal_sigops if reserved_sigops > 400 else blk_sigops > normal_sigops
assert blk_sigops + reserved_sigops <= MAX_SIGOP_COST
assert blk_sigops > last_sigops
last_sigops = blk_sigops
def test_timewarp(self): def test_timewarp(self):
self.log.info("Test timewarp attack mitigation (BIP94)") self.log.info("Test timewarp attack mitigation (BIP94)")
@ -171,7 +289,7 @@ class MiningTest(BitcoinTestFramework):
def run_test(self): def run_test(self):
node = self.nodes[0] node = self.nodes[0]
self.wallet = MiniWallet(node) self.wallet = MiniWallet(node, mode=MiniWalletMode.RAW_P2PK)
self.mine_chain() self.mine_chain()
def assert_submitblock(block, result_str_1, result_str_2=None): def assert_submitblock(block, result_str_1, result_str_2=None):
@ -377,6 +495,7 @@ class MiningTest(BitcoinTestFramework):
assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid
self.test_blockmintxfee_parameter() self.test_blockmintxfee_parameter()
self.test_rpc_params()
self.test_timewarp() self.test_timewarp()