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;
/**
* 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] options options for creating the block
* @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> createNewBlock2(const CScript& script_pub_key, const node::BlockCreateOptions& assemble_options) = 0;
/**
* Processes new block. A valid new block is automatically relayed to peers.

View File

@ -939,6 +939,11 @@ public:
{
BlockAssembler::Options assemble_options{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);
}

View File

@ -65,11 +65,15 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman)
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);
Assert(options.coinbase_max_additional_weight <= DEFAULT_BLOCK_MAX_WEIGHT);
Assert(options.coinbase_output_max_additional_sigops <= MAX_BLOCK_SIGOPS_COST);
BlockAssembler::Options options = *this;
constexpr size_t theoretical_min_gentx_sz = 1+4+1+36+1+1+4+1+4;
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:
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:
@ -82,7 +86,7 @@ BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool
: chainparams{chainstate.m_chainman.GetParams()},
m_mempool{options.use_mempool ? mempool : nullptr},
m_chainstate{chainstate},
m_options{ClampOptions(options)}
m_options{options.Clamped()}
{
// Whether we need to account for byte usage (in addition to weight usage)
fNeedSizeAccounting = (options.nBlockMaxSize < MAX_BLOCK_SERIALIZED_SIZE - 1000);

View File

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

View File

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

View File

@ -34,6 +34,7 @@
#include <script/signingprovider.h>
#include <txmempool.h>
#include <univalue.h>
#include <util/check.h>
#include <util/signalinterrupt.h>
#include <util/strencodings.h>
#include <util/string.h>
@ -580,6 +581,8 @@ static std::string gbt_vb_name(const Consensus::DeploymentPos pos) {
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()
{
return RPCHelpMan{"getblocktemplate",
@ -594,9 +597,14 @@ static RPCHelpMan getblocktemplate()
{"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"},
{"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",
{
{"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",
{
@ -604,6 +612,7 @@ static RPCHelpMan getblocktemplate()
{"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"},
{"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\""},
},
},
@ -680,6 +689,14 @@ static RPCHelpMan getblocktemplate()
LOCK(cs_main);
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";
UniValue lpval = NullUniValue;
std::set<std::string> setClientRules;
@ -733,6 +750,39 @@ static RPCHelpMan getblocktemplate()
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")
@ -817,8 +867,20 @@ static RPCHelpMan getblocktemplate()
static int64_t time_start;
static std::unique_ptr<CBlockTemplate> pblocktemplate;
if (!pindexPrev || pindexPrev->GetBlockHash() != tip ||
bypass_cache ||
(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
pindexPrev = nullptr;
@ -844,6 +906,14 @@ static RPCHelpMan getblocktemplate()
UpdateTime(pblock, consensusParams, pindexPrev);
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
const bool fPreSegWit = !DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT);
@ -911,6 +981,7 @@ static RPCHelpMan getblocktemplate()
aRules.push_back("!signet");
}
auto block_version = pblock->nVersion;
UniValue vbavailable(UniValue::VOBJ);
for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) {
Consensus::DeploymentPos pos = Consensus::DeploymentPos(j);
@ -922,7 +993,7 @@ static RPCHelpMan getblocktemplate()
break;
case ThresholdState::LOCKED_IN:
// Ensure bit is set in block version
pblock->nVersion |= chainman.m_versionbitscache.Mask(consensusParams, pos);
block_version |= chainman.m_versionbitscache.Mask(consensusParams, pos);
[[fallthrough]];
case ThresholdState::STARTED:
{
@ -931,7 +1002,7 @@ static RPCHelpMan getblocktemplate()
if (setClientRules.find(vbinfo.name) == setClientRules.end()) {
if (!vbinfo.gbt_force) {
// 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;
@ -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("vbavailable", std::move(vbavailable));
result.pushKV("vbrequired", int(0));
@ -960,7 +1031,7 @@ static RPCHelpMan getblocktemplate()
result.pushKV("transactions", std::move(transactions));
result.pushKV("coinbaseaux", std::move(aux));
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("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1);
result.pushKV("mutable", std::move(aMutable));
@ -991,8 +1062,6 @@ static RPCHelpMan getblocktemplate()
}
return result;
},
};
}
class submitblock_StateCatcher final : public CValidationInterface

View File

@ -16,6 +16,7 @@ from test_framework.blocktools import (
get_witness_script,
NORMAL_GBT_REQUEST_PARAMS,
TIME_GENESIS_BLOCK,
WITNESS_SCALE_FACTOR,
)
from test_framework.messages import (
BLOCK_HEADER_SIZE,
@ -32,15 +33,18 @@ from test_framework.util import (
assert_raises_rpc_error,
get_fee,
)
from test_framework.wallet import MiniWallet
from test_framework.wallet import MiniWallet, MiniWalletMode
DIFFICULTY_ADJUSTMENT_INTERVAL = 144
MAX_FUTURE_BLOCK_TIME = 2 * 3600
MAX_TIMEWARP = 600
ASSUMED_BLOCK_OVERHEAD_SIZE = 1000
ASSUMED_BLOCK_OVERHEAD_WEIGHT = ASSUMED_BLOCK_OVERHEAD_SIZE * WITNESS_SCALE_FACTOR
VERSIONBITS_TOP_BITS = 0x20000000
VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28
DEFAULT_BLOCK_MIN_TX_FEE = 1000 # default `-blockmintxfee` setting [sat/kvB]
MAX_SIGOP_COST = 80000
def assert_template(node, block, expect, rehash=True):
@ -81,15 +85,21 @@ class MiningTest(BitcoinTestFramework):
self.restart_node(0)
self.connect_nodes(0, 1)
def test_blockmintxfee_parameter(self):
self.log.info("Test -blockmintxfee setting")
def test_blockmintxfee_parameter(self, *, use_rpc=False):
if not use_rpc:
self.log.info("Test -blockmintxfee setting")
self.restart_node(0, extra_args=['-minrelaytxfee=0', '-persistmempool=0'])
node = self.nodes[0]
# 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):
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)...")
else:
blockmintxfee_parameter = f"-blockmintxfee={blockmintxfee_btc_kvb:.8f}"
@ -109,16 +119,124 @@ class MiningTest(BitcoinTestFramework):
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
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']]
self.generate(self.wallet, 1, sync_fun=self.no_op)
block = node.getblock(node.getbestblockhash(), verbosity=2)
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_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):
self.log.info("Test timewarp attack mitigation (BIP94)")
@ -171,7 +289,7 @@ class MiningTest(BitcoinTestFramework):
def run_test(self):
node = self.nodes[0]
self.wallet = MiniWallet(node)
self.wallet = MiniWallet(node, mode=MiniWalletMode.RAW_P2PK)
self.mine_chain()
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
self.test_blockmintxfee_parameter()
self.test_rpc_params()
self.test_timewarp()