Skip to content

[0.17] Mandatory coinbase feature #430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,11 @@ class CCustomParams : public CRegTestParams {
consensus.nMinimumChainWork = uint256S(args.GetArg("-con_nminimumchainwork", "0x0"));
consensus.defaultAssumeValid = uint256S(args.GetArg("-con_defaultassumevalid", "0x00"));

// All non-zero coinbase outputs must go to this scriptPubKey
std::vector<unsigned char> man_bytes = ParseHex(gArgs.GetArg("-con_mandatorycoinbase", ""));
consensus.mandatory_coinbase_destination = CScript(man_bytes.begin(), man_bytes.end()); // Blank script allows any coinbase destination


nPruneAfterHeight = (uint64_t)args.GetArg("-npruneafterheight", nPruneAfterHeight);
fDefaultConsistencyChecks = args.GetBoolArg("-fdefaultconsistencychecks", fDefaultConsistencyChecks);
fMineBlocksOnDemand = args.GetBoolArg("-fmineblocksondemand", fMineBlocksOnDemand);
Expand Down
1 change: 1 addition & 0 deletions src/chainparamsbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ void SetupChainParamsBaseOptions()
"This is intended for regression testing tools and app development.", true, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-testnet", "Use the test chain", false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest or custom only)", true, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-con_mandatorycoinbase", "All non-zero valued coinbase outputs must go to this scriptPubKey, if set.", false, OptionsCategory::CHAINPARAMS);
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);
}

Expand Down
5 changes: 5 additions & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include <map>
#include <string>

#include <script/script.h> // mandatory_coinbase_destination
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this dependency giving you problems? I would swear I tried the same with #433

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hopefully not, we do the same thing for elements-0.14.1 IIRC!


namespace Consensus {

enum DeploymentPos
Expand Down Expand Up @@ -75,6 +77,9 @@ struct Params {
int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; }
uint256 nMinimumChainWork;
uint256 defaultAssumeValid;

// Elements-specific chainparams
CScript mandatory_coinbase_destination;
};
} // namespace Consensus

Expand Down
11 changes: 11 additions & 0 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1850,6 +1850,17 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl

nBlocksTotal++;

// Check that all non-zero coinbase outputs pay to the required destination
const CScript& mandatory_coinbase_destination = chainparams.GetConsensus().mandatory_coinbase_destination;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this local variable worth it? small nit, feel free to ignore.

if (mandatory_coinbase_destination != CScript()) {
for (auto& txout : block.vtx[0]->vout) {
if (txout.scriptPubKey != mandatory_coinbase_destination && txout.nValue != 0) {
return state.DoS(100, error("ConnectBlock(): Coinbase outputs didnt match required scriptPubKey"),
REJECT_INVALID, "bad-coinbase-txos");
}
}
}

bool fScriptChecks = true;
if (!hashAssumeValid.IsNull()) {
// We've been configured with the hash of a block which has been externally verified to have a valid history.
Expand Down
80 changes: 80 additions & 0 deletions test/functional/mandatory_coinbase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2018 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test mandatory coinbase feature"""

from binascii import b2a_hex

from test_framework.blocktools import create_coinbase
from test_framework.messages import CBlock
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error

mandatory_privkey = "cNaQCDwmmh4dS9LzCgVtyy1e1xjCJ21GUDHe9K98nzb689JvinGV"
mandatory_address = "n3NkSZqoPMCQN5FENxUBw4qVATbytH6FDK"
mandatory_pubkey = "02fcba7ecf41bc7e1be4ee122d9d22e3333671eb0a3a87b5cdf099d59874e1940f"
mandatory_script = "76a914efc58b838b3153174bf3d1677b7213353a4dccfd88ac"

def b2x(b):
return b2a_hex(b).decode('ascii')

def assert_template(node, block, expect, rehash=True):
if rehash:
block.hashMerkleRoot = block.calc_merkle_root()
rsp = node.getblocktemplate({'data': b2x(block.serialize()), 'mode': 'proposal'})
assert_equal(rsp, expect)

class MandatoryCoinbaseTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
# Non-zero coinbase outputs *must* match this. Not setting it means anything is allowed
self.extra_args = [["-con_mandatorycoinbase="+mandatory_script], []]

def run_test(self):
node0 = self.nodes[0]
node1 = self.nodes[1]

node0.importprivkey(mandatory_privkey)

self.log.info("generatetoaddress: Making blocks of various kinds, checking for rejection")

# Create valid blocks to get out of IBD and get some funds (subsidy goes to permitted addr)
node0.generatetoaddress(101, mandatory_address)

# Generating for another address will not work
assert_raises_rpc_error(-1, "CreateNewBlock: TestBlockValidity failed: bad-coinbase-txos", node0.generatetoaddress, 1, node0.getnewaddress())

# Have non-mandatory node make a template
self.sync_all()
tmpl = node1.getblocktemplate()

# We make a block with OP_TRUE coinbase output that will fail on node0
coinbase_tx = create_coinbase(height=int(tmpl["height"]) + 1)
# sequence numbers must not be max for nLockTime to have effect
coinbase_tx.vin[0].nSequence = 2 ** 32 - 2
coinbase_tx.rehash()

block = CBlock()
block.nVersion = tmpl["version"]
block.hashPrevBlock = int(tmpl["previousblockhash"], 16)
block.nTime = tmpl["curtime"]
block.nBits = int(tmpl["bits"], 16)
block.nNonce = 0
block.vtx = [coinbase_tx]

self.log.info("getblocktemplate: Test block on both nodes")
assert_equal(node0.submitblock(b2x(block.serialize())), 'invalid')
assert_template(node1, block, None)

self.log.info("getblocktemplate: Test non-subsidy block on both nodes")
# Without block reward anything goes, this allows commitment outputs like segwit
coinbase_tx.vout[0].nValue = 0
coinbase_tx.rehash()
block.vtx = [coinbase_tx]
assert_template(node0, block, None)
assert_template(node1, block, None)

if __name__ == '__main__':
MandatoryCoinbaseTest().main()