Merge #14811: Mining: Enforce that segwit option must be set in GBT

d2ce315fbf [docs] add release note for change to GBT (John Newbery)
0025c9eae4 [mining] segwit option must be set in GBT (John Newbery)

Pull request description:

  Calling getblocktemplate without the segwit rule specified is most
  likely a client error, since it results in lower fees for the miner.
  Prevent this client error by failing getblocktemplate if called without
  the segwit rule specified.

  Of the previous 1000 blocks (measured at block [551591 (hash 0x...173c811)](https://blockstream.info/block/000000000000000000173c811e79858808abc3216af607035973f002bef60a7a)), 991 included segwit transactions.

Tree-SHA512: 7933b073d72683c9ab9318db46a085ec19a56a14937945c73f783ac7656887619a86b74db0bdfcb8121df44f63a1d6a6fb19e98505b2a26a6a8a6e768e442fee
This commit is contained in:
MarcoFalke 2018-12-21 13:45:55 +01:00
commit feda41e0a7
No known key found for this signature in database
GPG key ID: D2EA4850E7528B25
11 changed files with 39 additions and 62 deletions

View file

@ -65,6 +65,13 @@ platform.
Notable changes Notable changes
=============== ===============
Mining
------
- Calls to `getblocktemplate` will fail if the segwit rule is not specified.
Calling `getblocktemplate` without segwit specified is almost certainly
a misconfiguration since doing so results in lower rewards for the miner.
Command line option changes Command line option changes
--------------------------- ---------------------------
@ -182,6 +189,8 @@ in the Low-level Changes section below.
P2SH-P2WPKH, and P2SH-P2WSH. Requests for P2WSH and P2SH-P2WSH accept P2SH-P2WPKH, and P2SH-P2WSH. Requests for P2WSH and P2SH-P2WSH accept
an additional `witnessscript` parameter. an additional `witnessscript` parameter.
- See the [Mining](#mining) section for changes to `getblocktemplate`.
Low-level changes Low-level changes
================= =================

View file

@ -27,7 +27,7 @@ static std::shared_ptr<CBlock> PrepareBlock(const CScript& coinbase_scriptPubKey
{ {
auto block = std::make_shared<CBlock>( auto block = std::make_shared<CBlock>(
BlockAssembler{Params()} BlockAssembler{Params()}
.CreateNewBlock(coinbase_scriptPubKey, /* fMineWitnessTx */ true) .CreateNewBlock(coinbase_scriptPubKey)
->block); ->block);
block->nTime = ::chainActive.Tip()->GetMedianTimePast() + 1; block->nTime = ::chainActive.Tip()->GetMedianTimePast() + 1;

View file

@ -95,7 +95,7 @@ void BlockAssembler::resetBlock()
nFees = 0; nFees = 0;
} }
std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx) std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn)
{ {
int64_t nTimeStart = GetTimeMicros(); int64_t nTimeStart = GetTimeMicros();
@ -139,7 +139,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
// not activated. // not activated.
// TODO: replace this with a call to main to assess validity of a mempool // TODO: replace this with a call to main to assess validity of a mempool
// transaction (which in most cases can be a no-op). // transaction (which in most cases can be a no-op).
fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()) && fMineWitnessTx; fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus());
int nPackagesSelected = 0; int nPackagesSelected = 0;
int nDescendantsUpdated = 0; int nDescendantsUpdated = 0;

View file

@ -157,7 +157,7 @@ public:
BlockAssembler(const CChainParams& params, const Options& options); BlockAssembler(const CChainParams& params, const Options& options);
/** Construct a new block template with coinbase to scriptPubKeyIn */ /** Construct a new block template with coinbase to scriptPubKeyIn */
std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx=true); std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn);
private: private:
// utility functions // utility functions

View file

@ -311,7 +311,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
" https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki#getblocktemplate_changes\n" " https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki#getblocktemplate_changes\n"
" https://github.com/bitcoin/bips/blob/master/bip-0145.mediawiki\n", " https://github.com/bitcoin/bips/blob/master/bip-0145.mediawiki\n",
{ {
{"template_request", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "A json object in the following spec", {"template_request", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "A json object in the following spec",
{ {
{"mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"}, {"mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"},
{"capabilities", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "", "A list of strings", {"capabilities", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "", "A list of strings",
@ -319,7 +319,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
{"support", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "client side supported feature, 'longpoll', 'coinbasetxn', 'coinbasevalue', 'proposal', 'serverlist', 'workid'"}, {"support", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "client side supported feature, 'longpoll', 'coinbasetxn', 'coinbasevalue', 'proposal', 'serverlist', 'workid'"},
}, },
}, },
{"rules", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "", "A list of strings", {"rules", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A list of strings",
{ {
{"support", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "client side supported softfork deployment"}, {"support", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "client side supported softfork deployment"},
}, },
@ -503,21 +503,17 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
} }
const struct VBDeploymentInfo& segwit_info = VersionBitsDeploymentInfo[Consensus::DEPLOYMENT_SEGWIT]; const struct VBDeploymentInfo& segwit_info = VersionBitsDeploymentInfo[Consensus::DEPLOYMENT_SEGWIT];
// If the caller is indicating segwit support, then allow CreateNewBlock() // GBT must be called with 'segwit' set in the rules
// to select witness transactions, after segwit activates (otherwise if (setClientRules.count(segwit_info.name) != 1) {
// don't). throw JSONRPCError(RPC_INVALID_PARAMETER, "getblocktemplate must be called with the segwit rule set (call with {\"rules\": [\"segwit\"]})");
bool fSupportsSegwit = setClientRules.find(segwit_info.name) != setClientRules.end(); }
// Update block // Update block
static CBlockIndex* pindexPrev; static CBlockIndex* pindexPrev;
static int64_t nStart; static int64_t nStart;
static std::unique_ptr<CBlockTemplate> pblocktemplate; static std::unique_ptr<CBlockTemplate> pblocktemplate;
// Cache whether the last invocation was with segwit support, to avoid returning
// a segwit-block to a non-segwit caller.
static bool fLastTemplateSupportsSegwit = true;
if (pindexPrev != chainActive.Tip() || if (pindexPrev != chainActive.Tip() ||
(mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5) || (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5))
fLastTemplateSupportsSegwit != fSupportsSegwit)
{ {
// 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;
@ -526,11 +522,10 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); nTransactionsUpdatedLast = mempool.GetTransactionsUpdated();
CBlockIndex* pindexPrevNew = chainActive.Tip(); CBlockIndex* pindexPrevNew = chainActive.Tip();
nStart = GetTime(); nStart = GetTime();
fLastTemplateSupportsSegwit = fSupportsSegwit;
// Create new block // Create new block
CScript scriptDummy = CScript() << OP_TRUE; CScript scriptDummy = CScript() << OP_TRUE;
pblocktemplate = BlockAssembler(Params()).CreateNewBlock(scriptDummy, fSupportsSegwit); pblocktemplate = BlockAssembler(Params()).CreateNewBlock(scriptDummy);
if (!pblocktemplate) if (!pblocktemplate)
throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory");
@ -682,7 +677,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
result.pushKV("bits", strprintf("%08x", pblock->nBits)); result.pushKV("bits", strprintf("%08x", pblock->nBits));
result.pushKV("height", (int64_t)(pindexPrev->nHeight+1)); result.pushKV("height", (int64_t)(pindexPrev->nHeight+1));
if (!pblocktemplate->vchCoinbaseCommitment.empty() && fSupportsSegwit) { if (!pblocktemplate->vchCoinbaseCommitment.empty()) {
result.pushKV("default_witness_commitment", HexStr(pblocktemplate->vchCoinbaseCommitment.begin(), pblocktemplate->vchCoinbaseCommitment.end())); result.pushKV("default_witness_commitment", HexStr(pblocktemplate->vchCoinbaseCommitment.begin(), pblocktemplate->vchCoinbaseCommitment.end()));
} }

View file

@ -54,7 +54,7 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash)
CScript pubKey; CScript pubKey;
pubKey << i++ << OP_TRUE; pubKey << i++ << OP_TRUE;
auto ptemplate = BlockAssembler(Params()).CreateNewBlock(pubKey, false); auto ptemplate = BlockAssembler(Params()).CreateNewBlock(pubKey);
auto pblock = std::make_shared<CBlock>(ptemplate->block); auto pblock = std::make_shared<CBlock>(ptemplate->block);
pblock->hashPrevBlock = prev_hash; pblock->hashPrevBlock = prev_hash;
pblock->nTime = ++time; pblock->nTime = ++time;

View file

@ -90,7 +90,7 @@ class SegWitTest(BitcoinTestFramework):
self.log.info("Verify sigops are counted in GBT with pre-BIP141 rules before the fork") self.log.info("Verify sigops are counted in GBT with pre-BIP141 rules before the fork")
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
tmpl = self.nodes[0].getblocktemplate({}) tmpl = self.nodes[0].getblocktemplate({'rules': ['segwit']})
assert(tmpl['sizelimit'] == 1000000) assert(tmpl['sizelimit'] == 1000000)
assert('weightlimit' not in tmpl) assert('weightlimit' not in tmpl)
assert(tmpl['sigoplimit'] == 20000) assert(tmpl['sigoplimit'] == 20000)
@ -232,15 +232,7 @@ class SegWitTest(BitcoinTestFramework):
assert(tx.wit.is_null()) assert(tx.wit.is_null())
assert(txid3 in self.nodes[0].getrawmempool()) assert(txid3 in self.nodes[0].getrawmempool())
# Now try calling getblocktemplate() without segwit support. # Check that getblocktemplate includes all transactions.
template = self.nodes[0].getblocktemplate()
# Check that tx1 is the only transaction of the 3 in the template.
template_txids = [t['txid'] for t in template['transactions']]
assert(txid2 not in template_txids and txid3 not in template_txids)
assert(txid1 in template_txids)
# Check that running with segwit support results in all 3 being included.
template = self.nodes[0].getblocktemplate({"rules": ["segwit"]}) template = self.nodes[0].getblocktemplate({"rules": ["segwit"]})
template_txids = [t['txid'] for t in template['transactions']] template_txids = [t['txid'] for t in template['transactions']]
assert(txid1 in template_txids) assert(txid1 in template_txids)

View file

@ -30,7 +30,7 @@ from test_framework.script import CScriptNum
def assert_template(node, block, expect, rehash=True): def assert_template(node, block, expect, rehash=True):
if rehash: if rehash:
block.hashMerkleRoot = block.calc_merkle_root() block.hashMerkleRoot = block.calc_merkle_root()
rsp = node.getblocktemplate(template_request={'data': b2x(block.serialize()), 'mode': 'proposal'}) rsp = node.getblocktemplate(template_request={'data': b2x(block.serialize()), 'mode': 'proposal', 'rules': ['segwit']})
assert_equal(rsp, expect) assert_equal(rsp, expect)
@ -60,7 +60,7 @@ class MiningTest(BitcoinTestFramework):
# Mine a block to leave initial block download # Mine a block to leave initial block download
node.generatetoaddress(1, node.get_deterministic_priv_key().address) node.generatetoaddress(1, node.get_deterministic_priv_key().address)
tmpl = node.getblocktemplate() tmpl = node.getblocktemplate({'rules': ['segwit']})
self.log.info("getblocktemplate: Test capability advertised") self.log.info("getblocktemplate: Test capability advertised")
assert 'proposal' in tmpl['capabilities'] assert 'proposal' in tmpl['capabilities']
assert 'coinbasetxn' not in tmpl assert 'coinbasetxn' not in tmpl
@ -86,6 +86,9 @@ class MiningTest(BitcoinTestFramework):
block.nNonce = 0 block.nNonce = 0
block.vtx = [coinbase_tx] block.vtx = [coinbase_tx]
self.log.info("getblocktemplate: segwit rule must be set")
assert_raises_rpc_error(-8, "getblocktemplate must be called with the segwit rule set", node.getblocktemplate)
self.log.info("getblocktemplate: Test valid block") self.log.info("getblocktemplate: Test valid block")
assert_template(node, block, None) assert_template(node, block, None)
@ -102,7 +105,7 @@ class MiningTest(BitcoinTestFramework):
assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, b2x(bad_block.serialize())) assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, b2x(bad_block.serialize()))
self.log.info("getblocktemplate: Test truncated final transaction") self.log.info("getblocktemplate: Test truncated final transaction")
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(block.serialize()[:-1]), 'mode': 'proposal'}) assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(block.serialize()[:-1]), 'mode': 'proposal', 'rules': ['segwit']})
self.log.info("getblocktemplate: Test duplicate transaction") self.log.info("getblocktemplate: Test duplicate transaction")
bad_block = copy.deepcopy(block) bad_block = copy.deepcopy(block)
@ -132,7 +135,7 @@ class MiningTest(BitcoinTestFramework):
bad_block_sn = bytearray(block.serialize()) bad_block_sn = bytearray(block.serialize())
assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1) assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1)
bad_block_sn[TX_COUNT_OFFSET] += 1 bad_block_sn[TX_COUNT_OFFSET] += 1
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(bad_block_sn), 'mode': 'proposal'}) assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(bad_block_sn), 'mode': 'proposal', 'rules': ['segwit']})
self.log.info("getblocktemplate: Test bad bits") self.log.info("getblocktemplate: Test bad bits")
bad_block = copy.deepcopy(block) bad_block = copy.deepcopy(block)

View file

@ -15,14 +15,14 @@ class LongpollThread(threading.Thread):
def __init__(self, node): def __init__(self, node):
threading.Thread.__init__(self) threading.Thread.__init__(self)
# query current longpollid # query current longpollid
template = node.getblocktemplate() template = node.getblocktemplate({'rules': ['segwit']})
self.longpollid = template['longpollid'] self.longpollid = template['longpollid']
# create a new connection to the node, we can't use the same # create a new connection to the node, we can't use the same
# connection from two threads # connection from two threads
self.node = get_rpc_proxy(node.url, 1, timeout=600, coveragedir=node.coverage_dir) self.node = get_rpc_proxy(node.url, 1, timeout=600, coveragedir=node.coverage_dir)
def run(self): def run(self):
self.node.getblocktemplate({'longpollid':self.longpollid}) self.node.getblocktemplate({'longpollid': self.longpollid, 'rules': ['segwit']})
class GetBlockTemplateLPTest(BitcoinTestFramework): class GetBlockTemplateLPTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
@ -34,10 +34,10 @@ class GetBlockTemplateLPTest(BitcoinTestFramework):
def run_test(self): def run_test(self):
self.log.info("Warning: this test will take about 70 seconds in the best case. Be patient.") self.log.info("Warning: this test will take about 70 seconds in the best case. Be patient.")
self.nodes[0].generate(10) self.nodes[0].generate(10)
template = self.nodes[0].getblocktemplate() template = self.nodes[0].getblocktemplate({'rules': ['segwit']})
longpollid = template['longpollid'] longpollid = template['longpollid']
# longpollid should not change between successive invocations if nothing else happens # longpollid should not change between successive invocations if nothing else happens
template2 = self.nodes[0].getblocktemplate() template2 = self.nodes[0].getblocktemplate({'rules': ['segwit']})
assert(template2['longpollid'] == longpollid) assert(template2['longpollid'] == longpollid)
# Test 1: test that the longpolling wait if we do nothing # Test 1: test that the longpolling wait if we do nothing

View file

@ -142,10 +142,10 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
# getblocktemplate to (eventually) return a new block. # getblocktemplate to (eventually) return a new block.
mock_time = int(time.time()) mock_time = int(time.time())
self.nodes[0].setmocktime(mock_time) self.nodes[0].setmocktime(mock_time)
template = self.nodes[0].getblocktemplate() template = self.nodes[0].getblocktemplate({'rules': ['segwit']})
self.nodes[0].prioritisetransaction(txid=tx_id, fee_delta=-int(self.relayfee*COIN)) self.nodes[0].prioritisetransaction(txid=tx_id, fee_delta=-int(self.relayfee*COIN))
self.nodes[0].setmocktime(mock_time+10) self.nodes[0].setmocktime(mock_time+10)
new_template = self.nodes[0].getblocktemplate() new_template = self.nodes[0].getblocktemplate({'rules': ['segwit']})
assert(template != new_template) assert(template != new_template)

View file

@ -545,31 +545,13 @@ class SegWitTest(BitcoinTestFramework):
@subtest @subtest
def test_getblocktemplate_before_lockin(self): def test_getblocktemplate_before_lockin(self):
# Node0 is segwit aware, node2 is not.
for node in [self.nodes[0], self.nodes[2]]:
gbt_results = node.getblocktemplate()
block_version = gbt_results['version']
# If we're not indicating segwit support, we will still be
# signalling for segwit activation.
assert_equal((block_version & (1 << VB_WITNESS_BIT) != 0), node == self.nodes[0])
# If we don't specify the segwit rule, then we won't get a default
# commitment.
assert('default_witness_commitment' not in gbt_results)
# Workaround:
# Can either change the tip, or change the mempool and wait 5 seconds
# to trigger a recomputation of getblocktemplate.
txid = int(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1), 16) txid = int(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1), 16)
# Using mocktime lets us avoid sleep()
sync_mempools(self.nodes)
self.nodes[0].setmocktime(int(time.time()) + 10)
self.nodes[2].setmocktime(int(time.time()) + 10)
for node in [self.nodes[0], self.nodes[2]]: for node in [self.nodes[0], self.nodes[2]]:
gbt_results = node.getblocktemplate({"rules": ["segwit"]}) gbt_results = node.getblocktemplate({"rules": ["segwit"]})
block_version = gbt_results['version'] block_version = gbt_results['version']
if node == self.nodes[2]: if node == self.nodes[2]:
# If this is a non-segwit node, we should still not get a witness # If this is a non-segwit node, we should not get a witness
# commitment, nor a version bit signalling segwit. # commitment, nor a version bit signalling segwit.
assert_equal(block_version & (1 << VB_WITNESS_BIT), 0) assert_equal(block_version & (1 << VB_WITNESS_BIT), 0)
assert('default_witness_commitment' not in gbt_results) assert('default_witness_commitment' not in gbt_results)
@ -586,10 +568,6 @@ class SegWitTest(BitcoinTestFramework):
script = get_witness_script(witness_root, 0) script = get_witness_script(witness_root, 0)
assert_equal(witness_commitment, bytes_to_hex_str(script)) assert_equal(witness_commitment, bytes_to_hex_str(script))
# undo mocktime
self.nodes[0].setmocktime(0)
self.nodes[2].setmocktime(0)
@subtest @subtest
def advance_to_segwit_lockin(self): def advance_to_segwit_lockin(self):
"""Mine enough blocks to lock in segwit, but don't activate.""" """Mine enough blocks to lock in segwit, but don't activate."""