Skip to content

Commit 101ec23

Browse files
committed
Merge #430: [0.17] Mandatory coinbase feature
0d20b55 add mandatory coinbase functional test (Gregory Sanders) edb0532 add consensus.mandatory_coinbase_destination (Gregory Sanders) Pull request description: Includes functional test Tree-SHA512: e7fdf4584c5a61516778dff272c0f4c9964fee515b705c4fd3ade842954bcc7426bdaf5273a64e4b2472fedc940ead7552c0ec80b4608aac381f91a8f2356d95
2 parents de842cb + 0d20b55 commit 101ec23

File tree

5 files changed

+102
-0
lines changed

5 files changed

+102
-0
lines changed

src/chainparams.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,11 @@ class CCustomParams : public CRegTestParams {
420420
consensus.nMinimumChainWork = uint256S(args.GetArg("-con_nminimumchainwork", "0x0"));
421421
consensus.defaultAssumeValid = uint256S(args.GetArg("-con_defaultassumevalid", "0x00"));
422422

423+
// All non-zero coinbase outputs must go to this scriptPubKey
424+
std::vector<unsigned char> man_bytes = ParseHex(gArgs.GetArg("-con_mandatorycoinbase", ""));
425+
consensus.mandatory_coinbase_destination = CScript(man_bytes.begin(), man_bytes.end()); // Blank script allows any coinbase destination
426+
427+
423428
nPruneAfterHeight = (uint64_t)args.GetArg("-npruneafterheight", nPruneAfterHeight);
424429
fDefaultConsistencyChecks = args.GetBoolArg("-fdefaultconsistencychecks", fDefaultConsistencyChecks);
425430
fMineBlocksOnDemand = args.GetBoolArg("-fmineblocksondemand", fMineBlocksOnDemand);

src/chainparamsbase.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ void SetupChainParamsBaseOptions()
2222
"This is intended for regression testing tools and app development.", true, OptionsCategory::CHAINPARAMS);
2323
gArgs.AddArg("-testnet", "Use the test chain", false, OptionsCategory::CHAINPARAMS);
2424
gArgs.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest or custom only)", true, OptionsCategory::CHAINPARAMS);
25+
gArgs.AddArg("-con_mandatorycoinbase", "All non-zero valued coinbase outputs must go to this scriptPubKey, if set.", false, OptionsCategory::CHAINPARAMS);
2526
gArgs.AddArg("-seednode=<ip>", "Use specified node as seed node. This option can be specified multiple times to connect to multiple nodes. (custom only)", true, OptionsCategory::CHAINPARAMS);
2627
}
2728

src/consensus/params.h

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include <map>
1212
#include <string>
1313

14+
#include <script/script.h> // mandatory_coinbase_destination
15+
1416
namespace Consensus {
1517

1618
enum DeploymentPos
@@ -75,6 +77,9 @@ struct Params {
7577
int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; }
7678
uint256 nMinimumChainWork;
7779
uint256 defaultAssumeValid;
80+
81+
// Elements-specific chainparams
82+
CScript mandatory_coinbase_destination;
7883
};
7984
} // namespace Consensus
8085

src/validation.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -1850,6 +1850,17 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
18501850

18511851
nBlocksTotal++;
18521852

1853+
// Check that all non-zero coinbase outputs pay to the required destination
1854+
const CScript& mandatory_coinbase_destination = chainparams.GetConsensus().mandatory_coinbase_destination;
1855+
if (mandatory_coinbase_destination != CScript()) {
1856+
for (auto& txout : block.vtx[0]->vout) {
1857+
if (txout.scriptPubKey != mandatory_coinbase_destination && txout.nValue != 0) {
1858+
return state.DoS(100, error("ConnectBlock(): Coinbase outputs didnt match required scriptPubKey"),
1859+
REJECT_INVALID, "bad-coinbase-txos");
1860+
}
1861+
}
1862+
}
1863+
18531864
bool fScriptChecks = true;
18541865
if (!hashAssumeValid.IsNull()) {
18551866
// We've been configured with the hash of a block which has been externally verified to have a valid history.

test/functional/mandatory_coinbase.py

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2014-2018 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""Test mandatory coinbase feature"""
6+
7+
from binascii import b2a_hex
8+
9+
from test_framework.blocktools import create_coinbase
10+
from test_framework.messages import CBlock
11+
from test_framework.test_framework import BitcoinTestFramework
12+
from test_framework.util import assert_equal, assert_raises_rpc_error
13+
14+
mandatory_privkey = "cNaQCDwmmh4dS9LzCgVtyy1e1xjCJ21GUDHe9K98nzb689JvinGV"
15+
mandatory_address = "n3NkSZqoPMCQN5FENxUBw4qVATbytH6FDK"
16+
mandatory_pubkey = "02fcba7ecf41bc7e1be4ee122d9d22e3333671eb0a3a87b5cdf099d59874e1940f"
17+
mandatory_script = "76a914efc58b838b3153174bf3d1677b7213353a4dccfd88ac"
18+
19+
def b2x(b):
20+
return b2a_hex(b).decode('ascii')
21+
22+
def assert_template(node, block, expect, rehash=True):
23+
if rehash:
24+
block.hashMerkleRoot = block.calc_merkle_root()
25+
rsp = node.getblocktemplate({'data': b2x(block.serialize()), 'mode': 'proposal'})
26+
assert_equal(rsp, expect)
27+
28+
class MandatoryCoinbaseTest(BitcoinTestFramework):
29+
def set_test_params(self):
30+
self.num_nodes = 2
31+
self.setup_clean_chain = True
32+
# Non-zero coinbase outputs *must* match this. Not setting it means anything is allowed
33+
self.extra_args = [["-con_mandatorycoinbase="+mandatory_script], []]
34+
35+
def run_test(self):
36+
node0 = self.nodes[0]
37+
node1 = self.nodes[1]
38+
39+
node0.importprivkey(mandatory_privkey)
40+
41+
self.log.info("generatetoaddress: Making blocks of various kinds, checking for rejection")
42+
43+
# Create valid blocks to get out of IBD and get some funds (subsidy goes to permitted addr)
44+
node0.generatetoaddress(101, mandatory_address)
45+
46+
# Generating for another address will not work
47+
assert_raises_rpc_error(-1, "CreateNewBlock: TestBlockValidity failed: bad-coinbase-txos", node0.generatetoaddress, 1, node0.getnewaddress())
48+
49+
# Have non-mandatory node make a template
50+
self.sync_all()
51+
tmpl = node1.getblocktemplate()
52+
53+
# We make a block with OP_TRUE coinbase output that will fail on node0
54+
coinbase_tx = create_coinbase(height=int(tmpl["height"]) + 1)
55+
# sequence numbers must not be max for nLockTime to have effect
56+
coinbase_tx.vin[0].nSequence = 2 ** 32 - 2
57+
coinbase_tx.rehash()
58+
59+
block = CBlock()
60+
block.nVersion = tmpl["version"]
61+
block.hashPrevBlock = int(tmpl["previousblockhash"], 16)
62+
block.nTime = tmpl["curtime"]
63+
block.nBits = int(tmpl["bits"], 16)
64+
block.nNonce = 0
65+
block.vtx = [coinbase_tx]
66+
67+
self.log.info("getblocktemplate: Test block on both nodes")
68+
assert_equal(node0.submitblock(b2x(block.serialize())), 'invalid')
69+
assert_template(node1, block, None)
70+
71+
self.log.info("getblocktemplate: Test non-subsidy block on both nodes")
72+
# Without block reward anything goes, this allows commitment outputs like segwit
73+
coinbase_tx.vout[0].nValue = 0
74+
coinbase_tx.rehash()
75+
block.vtx = [coinbase_tx]
76+
assert_template(node0, block, None)
77+
assert_template(node1, block, None)
78+
79+
if __name__ == '__main__':
80+
MandatoryCoinbaseTest().main()

0 commit comments

Comments
 (0)