Auto merge of #1017 - ebfull:coinbase-must-be-protected, r=ebfull
Enforce that coinbases must be protected This PR forces miners to place their funds in the private value transfer system, making "transparent coins" opt-in for users, and increasing privacy for all participants on a systemic level. Closes #101.
This commit is contained in:
commit
6793168a2e
|
@ -31,6 +31,7 @@ class CMainParams : public CChainParams {
|
||||||
public:
|
public:
|
||||||
CMainParams() {
|
CMainParams() {
|
||||||
strNetworkID = "main";
|
strNetworkID = "main";
|
||||||
|
consensus.fCoinbaseMustBeProtected = true;
|
||||||
consensus.nSubsidySlowStartInterval = 20000;
|
consensus.nSubsidySlowStartInterval = 20000;
|
||||||
consensus.nSubsidyHalvingInterval = 840000;
|
consensus.nSubsidyHalvingInterval = 840000;
|
||||||
consensus.nMajorityEnforceBlockUpgrade = 750;
|
consensus.nMajorityEnforceBlockUpgrade = 750;
|
||||||
|
@ -210,6 +211,7 @@ class CRegTestParams : public CTestNetParams {
|
||||||
public:
|
public:
|
||||||
CRegTestParams() {
|
CRegTestParams() {
|
||||||
strNetworkID = "regtest";
|
strNetworkID = "regtest";
|
||||||
|
consensus.fCoinbaseMustBeProtected = false;
|
||||||
consensus.nSubsidySlowStartInterval = 0;
|
consensus.nSubsidySlowStartInterval = 0;
|
||||||
consensus.nSubsidyHalvingInterval = 150;
|
consensus.nSubsidyHalvingInterval = 150;
|
||||||
consensus.nMajorityEnforceBlockUpgrade = 750;
|
consensus.nMajorityEnforceBlockUpgrade = 750;
|
||||||
|
|
|
@ -14,6 +14,9 @@ namespace Consensus {
|
||||||
*/
|
*/
|
||||||
struct Params {
|
struct Params {
|
||||||
uint256 hashGenesisBlock;
|
uint256 hashGenesisBlock;
|
||||||
|
|
||||||
|
bool fCoinbaseMustBeProtected;
|
||||||
|
|
||||||
/** Needs to evenly divide MAX_SUBSIDY to avoid rounding errors. */
|
/** Needs to evenly divide MAX_SUBSIDY to avoid rounding errors. */
|
||||||
int nSubsidySlowStartInterval;
|
int nSubsidySlowStartInterval;
|
||||||
/**
|
/**
|
||||||
|
|
59
src/main.cpp
59
src/main.cpp
|
@ -62,6 +62,7 @@ bool fPruneMode = false;
|
||||||
bool fIsBareMultisigStd = true;
|
bool fIsBareMultisigStd = true;
|
||||||
bool fCheckBlockIndex = false;
|
bool fCheckBlockIndex = false;
|
||||||
bool fCheckpointsEnabled = true;
|
bool fCheckpointsEnabled = true;
|
||||||
|
bool fCoinbaseEnforcedProtectionEnabled = true;
|
||||||
size_t nCoinCacheUsage = 5000 * 300;
|
size_t nCoinCacheUsage = 5000 * 300;
|
||||||
uint64_t nPruneTarget = 0;
|
uint64_t nPruneTarget = 0;
|
||||||
bool fAlerts = DEFAULT_ALERTS;
|
bool fAlerts = DEFAULT_ALERTS;
|
||||||
|
@ -1219,7 +1220,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
|
||||||
|
|
||||||
// Check against previous transactions
|
// Check against previous transactions
|
||||||
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
|
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
|
||||||
if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true))
|
if (!ContextualCheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true, Params().GetConsensus()))
|
||||||
{
|
{
|
||||||
return error("AcceptToMemoryPool: ConnectInputs failed %s", hash.ToString());
|
return error("AcceptToMemoryPool: ConnectInputs failed %s", hash.ToString());
|
||||||
}
|
}
|
||||||
|
@ -1233,7 +1234,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
|
||||||
// There is a similar check in CreateNewBlock() to prevent creating
|
// There is a similar check in CreateNewBlock() to prevent creating
|
||||||
// invalid blocks, however allowing such transactions into the mempool
|
// invalid blocks, however allowing such transactions into the mempool
|
||||||
// can be exploited as a DoS attack.
|
// can be exploited as a DoS attack.
|
||||||
if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true))
|
if (!ContextualCheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, Params().GetConsensus()))
|
||||||
{
|
{
|
||||||
return error("AcceptToMemoryPool: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s", hash.ToString());
|
return error("AcceptToMemoryPool: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s", hash.ToString());
|
||||||
}
|
}
|
||||||
|
@ -1604,7 +1605,7 @@ bool CScriptCheck::operator()() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector<CScriptCheck> *pvChecks)
|
bool NonContextualCheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, const Consensus::Params& consensusParams, std::vector<CScriptCheck> *pvChecks)
|
||||||
{
|
{
|
||||||
if (!tx.IsCoinBase())
|
if (!tx.IsCoinBase())
|
||||||
{
|
{
|
||||||
|
@ -1620,10 +1621,6 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
|
||||||
if (!inputs.HavePourRequirements(tx))
|
if (!inputs.HavePourRequirements(tx))
|
||||||
return state.Invalid(error("CheckInputs(): %s pour requirements not met", tx.GetHash().ToString()));
|
return state.Invalid(error("CheckInputs(): %s pour requirements not met", tx.GetHash().ToString()));
|
||||||
|
|
||||||
// While checking, GetBestBlock() refers to the parent block.
|
|
||||||
// This is also true for mempool checks.
|
|
||||||
CBlockIndex *pindexPrev = mapBlockIndex.find(inputs.GetBestBlock())->second;
|
|
||||||
int nSpendHeight = pindexPrev->nHeight + 1;
|
|
||||||
CAmount nValueIn = 0;
|
CAmount nValueIn = 0;
|
||||||
CAmount nFees = 0;
|
CAmount nFees = 0;
|
||||||
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
||||||
|
@ -1632,12 +1629,16 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
|
||||||
const CCoins *coins = inputs.AccessCoins(prevout.hash);
|
const CCoins *coins = inputs.AccessCoins(prevout.hash);
|
||||||
assert(coins);
|
assert(coins);
|
||||||
|
|
||||||
// If prev is coinbase, check that it's matured
|
|
||||||
if (coins->IsCoinBase()) {
|
if (coins->IsCoinBase()) {
|
||||||
if (nSpendHeight - coins->nHeight < COINBASE_MATURITY)
|
// Ensure that coinbases cannot be spent to transparent outputs
|
||||||
|
// Disabled on regtest
|
||||||
|
if (fCoinbaseEnforcedProtectionEnabled &&
|
||||||
|
consensusParams.fCoinbaseMustBeProtected &&
|
||||||
|
!tx.vout.empty()) {
|
||||||
return state.Invalid(
|
return state.Invalid(
|
||||||
error("CheckInputs(): tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight),
|
error("CheckInputs(): tried to spend coinbase with transparent outputs"),
|
||||||
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase");
|
REJECT_INVALID, "bad-txns-coinbase-spend-has-transparent-outputs");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for negative or overflow input values
|
// Check for negative or overflow input values
|
||||||
|
@ -1715,6 +1716,40 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ContextualCheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, const Consensus::Params& consensusParams, std::vector<CScriptCheck> *pvChecks)
|
||||||
|
{
|
||||||
|
if (!NonContextualCheckInputs(tx, state, inputs, fScriptChecks, flags, cacheStore, consensusParams, pvChecks)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tx.IsCoinBase())
|
||||||
|
{
|
||||||
|
// While checking, GetBestBlock() refers to the parent block.
|
||||||
|
// This is also true for mempool checks.
|
||||||
|
CBlockIndex *pindexPrev = mapBlockIndex.find(inputs.GetBestBlock())->second;
|
||||||
|
int nSpendHeight = pindexPrev->nHeight + 1;
|
||||||
|
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
||||||
|
{
|
||||||
|
const COutPoint &prevout = tx.vin[i].prevout;
|
||||||
|
const CCoins *coins = inputs.AccessCoins(prevout.hash);
|
||||||
|
// Assertion is okay because NonContextualCheckInputs ensures the inputs
|
||||||
|
// are available.
|
||||||
|
assert(coins);
|
||||||
|
|
||||||
|
// If prev is coinbase, check that it's matured
|
||||||
|
if (coins->IsCoinBase()) {
|
||||||
|
if (nSpendHeight - coins->nHeight < COINBASE_MATURITY) {
|
||||||
|
return state.Invalid(
|
||||||
|
error("CheckInputs(): tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight),
|
||||||
|
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
bool UndoWriteToDisk(const CBlockUndo& blockundo, CDiskBlockPos& pos, const uint256& hashBlock, const CMessageHeader::MessageStartChars& messageStart)
|
bool UndoWriteToDisk(const CBlockUndo& blockundo, CDiskBlockPos& pos, const uint256& hashBlock, const CMessageHeader::MessageStartChars& messageStart)
|
||||||
|
@ -2106,7 +2141,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||||
nFees += view.GetValueIn(tx)-tx.GetValueOut();
|
nFees += view.GetValueIn(tx)-tx.GetValueOut();
|
||||||
|
|
||||||
std::vector<CScriptCheck> vChecks;
|
std::vector<CScriptCheck> vChecks;
|
||||||
if (!CheckInputs(tx, state, view, fScriptChecks, flags, false, nScriptCheckThreads ? &vChecks : NULL))
|
if (!ContextualCheckInputs(tx, state, view, fScriptChecks, flags, false, chainparams.GetConsensus(), nScriptCheckThreads ? &vChecks : NULL))
|
||||||
return false;
|
return false;
|
||||||
control.Add(vChecks);
|
control.Add(vChecks);
|
||||||
}
|
}
|
||||||
|
|
12
src/main.h
12
src/main.h
|
@ -116,6 +116,9 @@ extern bool fTxIndex;
|
||||||
extern bool fIsBareMultisigStd;
|
extern bool fIsBareMultisigStd;
|
||||||
extern bool fCheckBlockIndex;
|
extern bool fCheckBlockIndex;
|
||||||
extern bool fCheckpointsEnabled;
|
extern bool fCheckpointsEnabled;
|
||||||
|
// TODO: remove this flag by structuring our code such that
|
||||||
|
// it is unneeded for testing
|
||||||
|
extern bool fCoinbaseEnforcedProtectionEnabled;
|
||||||
extern size_t nCoinCacheUsage;
|
extern size_t nCoinCacheUsage;
|
||||||
extern CFeeRate minRelayTxFee;
|
extern CFeeRate minRelayTxFee;
|
||||||
extern bool fAlerts;
|
extern bool fAlerts;
|
||||||
|
@ -317,8 +320,13 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& ma
|
||||||
* This does not modify the UTXO set. If pvChecks is not NULL, script checks are pushed onto it
|
* This does not modify the UTXO set. If pvChecks is not NULL, script checks are pushed onto it
|
||||||
* instead of being performed inline.
|
* instead of being performed inline.
|
||||||
*/
|
*/
|
||||||
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &view, bool fScriptChecks,
|
bool ContextualCheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &view, bool fScriptChecks,
|
||||||
unsigned int flags, bool cacheStore, std::vector<CScriptCheck> *pvChecks = NULL);
|
unsigned int flags, bool cacheStore, const Consensus::Params& consensusParams,
|
||||||
|
std::vector<CScriptCheck> *pvChecks = NULL);
|
||||||
|
|
||||||
|
bool NonContextualCheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &view, bool fScriptChecks,
|
||||||
|
unsigned int flags, bool cacheStore, const Consensus::Params& consensusParams,
|
||||||
|
std::vector<CScriptCheck> *pvChecks = NULL);
|
||||||
|
|
||||||
/** Apply the effects of this transaction on the UTXO set represented by view */
|
/** Apply the effects of this transaction on the UTXO set represented by view */
|
||||||
void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCache &inputs, int nHeight);
|
void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCache &inputs, int nHeight);
|
||||||
|
|
|
@ -282,7 +282,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
|
||||||
// policy here, but we still have to ensure that the block we
|
// policy here, but we still have to ensure that the block we
|
||||||
// create only contains transactions that are valid in new blocks.
|
// create only contains transactions that are valid in new blocks.
|
||||||
CValidationState state;
|
CValidationState state;
|
||||||
if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true))
|
if (!ContextualCheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, Params().GetConsensus()))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
UpdateCoins(tx, state, view, nHeight);
|
UpdateCoins(tx, state, view, nHeight);
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "uint256.h"
|
#include "uint256.h"
|
||||||
#include "test/test_bitcoin.h"
|
#include "test/test_bitcoin.h"
|
||||||
|
#include "consensus/validation.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "undo.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
@ -475,4 +478,48 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
|
||||||
BOOST_CHECK(missed_an_entry);
|
BOOST_CHECK(missed_an_entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(coins_coinbase_spends)
|
||||||
|
{
|
||||||
|
CCoinsViewTest base;
|
||||||
|
CCoinsViewCacheTest cache(&base);
|
||||||
|
|
||||||
|
// Create coinbase transaction
|
||||||
|
CMutableTransaction mtx;
|
||||||
|
mtx.vin.resize(1);
|
||||||
|
mtx.vin[0].scriptSig = CScript() << OP_1;
|
||||||
|
mtx.vin[0].nSequence = 0;
|
||||||
|
mtx.vout.resize(1);
|
||||||
|
mtx.vout[0].nValue = 500;
|
||||||
|
mtx.vout[0].scriptPubKey = CScript() << OP_1;
|
||||||
|
|
||||||
|
CTransaction tx(mtx);
|
||||||
|
|
||||||
|
BOOST_CHECK(tx.IsCoinBase());
|
||||||
|
|
||||||
|
CValidationState state;
|
||||||
|
UpdateCoins(tx, state, cache, 100);
|
||||||
|
|
||||||
|
// Create coinbase spend
|
||||||
|
CMutableTransaction mtx2;
|
||||||
|
mtx2.vin.resize(1);
|
||||||
|
mtx2.vin[0].prevout = COutPoint(tx.GetHash(), 0);
|
||||||
|
mtx2.vin[0].scriptSig = CScript() << OP_1;
|
||||||
|
mtx2.vin[0].nSequence = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
CTransaction tx2(mtx2);
|
||||||
|
BOOST_CHECK(NonContextualCheckInputs(tx2, state, cache, false, SCRIPT_VERIFY_NONE, false, Params().GetConsensus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
mtx2.vout.resize(1);
|
||||||
|
mtx2.vout[0].nValue = 500;
|
||||||
|
mtx2.vout[0].scriptPubKey = CScript() << OP_1;
|
||||||
|
|
||||||
|
{
|
||||||
|
CTransaction tx2(mtx2);
|
||||||
|
BOOST_CHECK(!NonContextualCheckInputs(tx2, state, cache, false, SCRIPT_VERIFY_NONE, false, Params().GetConsensus()));
|
||||||
|
BOOST_CHECK(state.GetRejectReason() == "bad-txns-coinbase-spend-has-transparent-outputs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
|
@ -147,6 +147,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||||
|
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
fCheckpointsEnabled = false;
|
fCheckpointsEnabled = false;
|
||||||
|
fCoinbaseEnforcedProtectionEnabled = false;
|
||||||
|
|
||||||
// We can't make transactions until we have inputs
|
// We can't make transactions until we have inputs
|
||||||
// Therefore, load 100 blocks :)
|
// Therefore, load 100 blocks :)
|
||||||
|
@ -415,6 +416,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||||
delete tx;
|
delete tx;
|
||||||
|
|
||||||
fCheckpointsEnabled = true;
|
fCheckpointsEnabled = true;
|
||||||
|
fCoinbaseEnforcedProtectionEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
|
@ -341,7 +341,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
|
||||||
waitingOnDependants.push_back(&it->second);
|
waitingOnDependants.push_back(&it->second);
|
||||||
else {
|
else {
|
||||||
CValidationState state;
|
CValidationState state;
|
||||||
assert(CheckInputs(tx, state, mempoolDuplicate, false, 0, false, NULL));
|
assert(ContextualCheckInputs(tx, state, mempoolDuplicate, false, 0, false, Params().GetConsensus(), NULL));
|
||||||
UpdateCoins(tx, state, mempoolDuplicate, 1000000);
|
UpdateCoins(tx, state, mempoolDuplicate, 1000000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -355,7 +355,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
|
||||||
stepsSinceLastRemove++;
|
stepsSinceLastRemove++;
|
||||||
assert(stepsSinceLastRemove < waitingOnDependants.size());
|
assert(stepsSinceLastRemove < waitingOnDependants.size());
|
||||||
} else {
|
} else {
|
||||||
assert(CheckInputs(entry->GetTx(), state, mempoolDuplicate, false, 0, false, NULL));
|
assert(ContextualCheckInputs(entry->GetTx(), state, mempoolDuplicate, false, 0, false, Params().GetConsensus(), NULL));
|
||||||
UpdateCoins(entry->GetTx(), state, mempoolDuplicate, 1000000);
|
UpdateCoins(entry->GetTx(), state, mempoolDuplicate, 1000000);
|
||||||
stepsSinceLastRemove = 0;
|
stepsSinceLastRemove = 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue