Bitcoin, Particl, and unit-e feature different checks in validation. Some of them consensus critical, some of them to detect DoS attacks early, etc.
static bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true)
{
// Check proof of work matches claimed amount
if (fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits, consensusParams))
return state.DoS(50, false, REJECT_INVALID, "high-hash", false, "proof of work failed");
return true;
}
- bitcoin 0.16: validation.cpp:3007
- particl 0.16: validation.cpp:4035
- unit-e: block_validator.cpp:202
bool mutated;
uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated);
if (block.hashMerkleRoot != hashMerkleRoot2)
return state.DoS(100, false, REJECT_INVALID, "bad-txnmrklroot", true, "hashMerkleRoot mismatch");
- bitcoin 0.16: validation.cpp:3013
- particl 0.16: validation.cpp:4041
- unit-e: block_validator.cpp:194
This check checks that there are no duplicate transactions, which is a way of corrupting a block without validation noticing. This was actually the reason for CVE-2012-2459.
There is a way to not have this happen in the first place, by changing the structure of the merkle tree: BIP 98 describes how.
// Check for merkle tree malleability (CVE-2012-2459): repeating sequences
// of transactions in a block without affecting the merkle root of a block,
// while still invalidating it.
if (mutated)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-duplicate", true, "duplicate transaction");
- bitcoin 0.16: validation.cpp:3024
- particl 0.16: validation.cpp:4025
if (block.vtx.empty() || block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT || ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT)
return state.DoS(100, false, REJECT_INVALID, "bad-blk-length", false, "size limits failed");
if (block.vtx.empty() || !block.vtx[0]->IsCoinBase())
return state.DoS(100, false, REJECT_INVALID, "bad-cb-missing", false, "first tx is not coinbase");
for (unsigned int i = 1; i < block.vtx.size(); i++)
if (block.vtx[i]->IsCoinBase())
return state.DoS(100, false, REJECT_INVALID, "bad-cb-multiple", false, "more than one coinbase");
for (const auto& tx : block.vtx)
if (!CheckTransaction(*tx, state, true))
return state.Invalid(false, state.GetRejectCode(), state.GetRejectReason(),
strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), state.GetDebugMessage()));
if (nSigOps * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST)
return state.DoS(100, false, REJECT_INVALID, "bad-blk-sigops", false, "out-of-bounds SigOpCount");
This checks the Proof-of-Work requirement in the context of the current and the previous block, and whether the difficulty was correctly accounted for. While unit-e has a similar difficulty adjustment, it does not have Proof-of-Work and so this particular check was removed from the codebase.
The pull request which removed it is #612, the commit on master is 1aecc28b.
const Consensus::Params& consensusParams = params.GetConsensus();
if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams))
return state.DoS(100, false, REJECT_INVALID, "bad-diffbits", false, "incorrect proof of work");
For shortcutting verification of whe whole blockchain and introducing a kind of finalization, bitcoin uses checkpoints (which can be disabled at user's will). Since unit-e has finalization, there are no hard-coded checkpoints in unit-e and this check is consequently no longer present.
- bitcoin 0.16 validation.cpp:3141
if (fCheckpointsEnabled) {
// Don't accept any forks from the main chain prior to last checkpoint.
// GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our
// MapBlockIndex.
CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(params.Checkpoints());
if (pcheckpoint && nHeight < pcheckpoint->nHeight)
return state.DoS(100, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight), REJECT_CHECKPOINT, "bad-fork-prior-to-checkpoint");
}
- bitcoin 0.16 validation.cpp:3146
- particl 0.16 validation.cpp:4288
if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast())
return state.Invalid(false, REJECT_INVALID, "time-too-old", "block's timestamp is too early");
- bitcoin 0.16 validation.cpp:3150
- particl 0.16 validation.cpp:4293
if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME)
return state.Invalid(false, REJECT_INVALID, "time-too-new", "block timestamp too far in the future");
These checks are not relevant for unit-e
as we removed the softforks and
respective bip versions for these pre-bip9 deployed softforks. They are
enabled by default and enforced for all blocks since (including) the genesis
block.
if((block.nVersion < 2 && nHeight >= consensusParams.BIP34Height) ||
(block.nVersion < 3 && nHeight >= consensusParams.BIP66Height) ||
(block.nVersion < 4 && nHeight >= consensusParams.BIP65Height))
return state.Invalid(false, REJECT_OBSOLETE, strprintf("bad-version(0x%08x)", block.nVersion),
strprintf("rejected nVersion=0x%08x block", block.nVersion));
for (const auto& tx : block.vtx) {
if (!IsFinalTx(*tx, nHeight, nLockTimeCutoff)) {
return state.DoS(10, false, REJECT_INVALID, "bad-txns-nonfinal", false, "non-final transaction");
}
}
if (nHeight >= consensusParams.BIP34Height)
{
CScript expect = CScript() << nHeight;
if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() ||
!std::equal(expect.begin(), expect.end(), block.vtx[0]->vin[0].scriptSig.begin())) {
return state.DoS(100, false, REJECT_INVALID, "bad-cb-height", false, "block height mismatch in coinbase");
}
}
bool malleated = false;
uint256 hashWitness = BlockWitnessMerkleRoot(block, &malleated);
// The malleation check is ignored; as the transaction tree itself
// already does not permit it, it is impossible to trigger in the
// witness tree.
if (block.vtx[0]->vin[0].scriptWitness.stack.size() != 1 || block.vtx[0]->vin[0].scriptWitness.stack[0].size() != 32) {
return state.DoS(100, false, REJECT_INVALID, "bad-witness-nonce-size", true, strprintf("%s : invalid witness nonce size", __func__));
}
CHash256().Write(hashWitness.begin(), 32).Write(&block.vtx[0]->vin[0].scriptWitness.stack[0][0], 32).Finalize(hashWitness.begin());
if (memcmp(hashWitness.begin(), &block.vtx[0]->vout[commitpos].scriptPubKey[6], 32)) {
return state.DoS(100, false, REJECT_INVALID, "bad-witness-merkle-match", true, strprintf("%s : witness merkle commitment mismatch", __func__));
}
if (!fHaveWitness) {
for (const auto& tx : block.vtx) {
if (tx->HasWitness()) {
return state.DoS(100, false, REJECT_INVALID, "unexpected-witness", true, strprintf("%s : unexpected witness data found", __func__));
}
}
}
if (GetBlockWeight(block) > MAX_BLOCK_WEIGHT) {
return state.DoS(100, false, REJECT_INVALID, "bad-blk-weight", false, strprintf("%s : weight limit failed", __func__));
}
if (pindex->nStatus & BLOCK_FAILED_MASK)
return state.Invalid(error("%s: block %s is marked invalid", __func__, hash.ToString()), 0, "duplicate");
BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock);
if (mi == mapBlockIndex.end())
return state.DoS(10, error("%s: prev block not found", __func__), 0, "prev-blk-not-found");
if (pindexPrev->nStatus & BLOCK_FAILED_MASK)
return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");
if (!pindexPrev->IsValid(BLOCK_VALID_SCRIPTS)) {
for (const CBlockIndex* failedit : g_failed_blocks) {
if (pindexPrev->GetAncestor(failedit->nHeight) == failedit) {
assert(failedit->nStatus & BLOCK_FAILED_VALID);
CBlockIndex* invalid_walk = pindexPrev;
while (invalid_walk != failedit) {
invalid_walk->nStatus |= BLOCK_FAILED_CHILD;
setDirtyBlockIndex.insert(invalid_walk);
invalid_walk = invalid_walk->pprev;
}
return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");
}
}
}
We try to touch ConnectBlock
as little as possible and it's not going to be substituted
in the foreseeable future.
This has been removed
from unit-e
as it was superfluos with BIP-34
enabled by default.
if (fEnforceBIP30) {
for (const auto& tx : block.vtx) {
for (size_t o = 0; o < tx->vout.size(); o++) {
if (view.HaveCoin(COutPoint(tx->GetHash(), o))) {
return state.DoS(100, error("ConnectBlock(): tried to overwrite transaction"),
REJECT_INVALID, "bad-txns-BIP30");
}
}
}
}
if (!MoneyRange(nFees)) {
return state.DoS(100, error("%s: accumulated fee in the block out of range.", __func__),
REJECT_INVALID, "bad-txns-accumulated-fee-outofrange");
}
if (!SequenceLocks(tx, nLockTimeFlags, &prevheights, *pindex)) {
return state.DoS(100, error("%s: contains a non-BIP68-final transaction", __func__),
REJECT_INVALID, "bad-txns-nonfinal");
}
// GetTransactionSigOpCost counts 3 types of sigops:
// * legacy (always)
// * p2sh (when P2SH enabled in flags and excludes coinbase)
// * witness (when witness enabled in flags and excludes coinbase)
nSigOpsCost += GetTransactionSigOpCost(tx, view, flags);
if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST)
return state.DoS(100, error("ConnectBlock(): too many sigops"),
REJECT_INVALID, "bad-blk-sigops");
CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus());
if (block.vtx[0]->GetValueOut() > blockReward)
return state.DoS(100,
error("ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)",
block.vtx[0]->GetValueOut(), blockReward),
REJECT_INVALID, "bad-cb-amount");
if (!control.Wait())
return state.DoS(100, error("%s: CheckQueue failed", __func__), REJECT_INVALID, "block-validation-failed");
See script_error.cpp for a list of possible script errors.
The only conceptual change which will be done with respect to CheckTransaction
is that the coinbase
transaction is checked as part of BlockValidator::CheckBlock
, separately from other transactions.
This function will be invoked only for transactions of TxType::STANDARD
, not any other (also not
TxType::COINBASE
therefore). It is thus not necessary to change anything in here (though we could
get rid of the if (tx.IsCoinBase())
branch).
bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fCheckDuplicateInputs)
{
// Basic checks that don't depend on any context
if (tx.vin.empty())
return state.DoS(10, false, REJECT_INVALID, "bad-txns-vin-empty");
if (tx.vout.empty())
return state.DoS(10, false, REJECT_INVALID, "bad-txns-vout-empty");
// Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability)
if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-oversize");
// Check for negative or overflow output values
CAmount nValueOut = 0;
for (const auto& txout : tx.vout)
{
if (txout.nValue < 0)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative");
if (txout.nValue > MAX_MONEY)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge");
nValueOut += txout.nValue;
if (!MoneyRange(nValueOut))
return state.DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge");
}
// Check for duplicate inputs - note that this check is slow so we skip it in CheckBlock
if (fCheckDuplicateInputs) {
std::set<COutPoint> vInOutPoints;
for (const auto& txin : tx.vin)
{
if (!vInOutPoints.insert(txin.prevout).second)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-duplicate");
}
}
if (tx.IsCoinBase())
{
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
return state.DoS(100, false, REJECT_INVALID, "bad-cb-length");
}
else
{
for (const auto& txin : tx.vin)
if (txin.prevout.IsNull())
return state.DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null");
}
return true;
}
This function is not to be touched in unit-e
at all.
bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee)
{
// are the actual inputs available?
if (!inputs.HaveInputs(tx)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-missingorspent", false,
strprintf("%s: inputs missing/spent", __func__));
}
CAmount nValueIn = 0;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
const COutPoint &prevout = tx.vin[i].prevout;
const Coin& coin = inputs.AccessCoin(prevout);
assert(!coin.IsSpent());
// If prev is coinbase, check that it's matured
if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) {
return state.Invalid(false,
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight));
}
// Check for negative or overflow input values
nValueIn += coin.out.nValue;
if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
}
}
const CAmount value_out = tx.GetValueOut();
if (nValueIn < value_out) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false,
strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out)));
}
// Tally transaction fees
const CAmount txfee_aux = nValueIn - value_out;
if (!MoneyRange(txfee_aux)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
}
txfee = txfee_aux;
return true;
}
if (block.IsProofOfStake())
{
pindex->bnStakeModifier = ComputeStakeModifierV2(pindex->pprev, pindex->prevoutStake.hash);
setDirtyBlockIndex.insert(pindex);
uint256 hashProof, targetProofOfStake;
if (!CheckProofOfStake(pindex->pprev, *block.vtx[0], block.nTime, block.nBits, hashProof, targetProofOfStake))
return state.DoS(100, error("%s: Check proof of stake failed.", __func__), REJECT_INVALID, "bad-proof-of-stake");
};
if (nStakeReward < 0 || nStakeReward > nCalculatedStakeReward)
return state.DoS(100, error("ConnectBlock() : coinstake pays too much(actual=%d vs calculated=%d)", nStakeReward, nCalculatedStakeReward), REJECT_INVALID, "bad-cs-amount");
CAmount nMinDevPart = (nCalculatedStakeReward * pDevFundSettings->nMinDevStakePercent) / 100;
CAmount nMaxHolderPart = nCalculatedStakeReward - nMinDevPart;
if (nMinDevPart < 0 || nMaxHolderPart < 0)
return state.DoS(100, error("%s: bad coinstake split amount (foundation=%d vs reward=%d)", __func__, nMinDevPart, nMaxHolderPart), REJECT_INVALID, "bad-cs-amount");
CTransactionRef txPrevCoinstake;
if (!coinStakeCache.GetCoinStake(pindex->pprev->GetBlockHash(), txPrevCoinstake))
return state.DoS(100, error("%s: Failed to get previous coinstake.", __func__), REJECT_INVALID, "bad-cs-amount");
if (pindex->nHeight % pDevFundSettings->nDevOutputPeriod == 0)
{
// Fund output must exist and match cfwd, cfwd data output must be unset
// nStakeReward must == nDevBfwd + nCalculatedStakeReward
if (nStakeReward != nDevBfwd + nCalculatedStakeReward)
return state.DoS(100, error("%s: bad stake-reward (actual=%d vs expected=%d)", __func__, nStakeReward, nDevBfwd + nCalculatedStakeReward), REJECT_INVALID, "bad-cs-amount");
CTxDestination dfDest = CBitcoinAddress(pDevFundSettings->sDevFundAddresses).Get();
if (dfDest.type() == typeid(CNoDestination))
return error("%s: Failed to get foundation fund destination: %s.", __func__, pDevFundSettings->sDevFundAddresses);
CScript devFundScriptPubKey = GetScriptForDestination(dfDest);
// output 1 must be to the dev fund
const CTxOutStandard *outputDF = txCoinstake->vpout[1]->GetStandardOutput();
if (!outputDF)
return state.DoS(100, error("%s: Bad foundation fund output.", __func__), REJECT_INVALID, "bad-cs");
if (outputDF->scriptPubKey != devFundScriptPubKey)
return state.DoS(100, error("%s: Bad foundation fund output script.", __func__), REJECT_INVALID, "bad-cs");
if (outputDF->nValue < nDevBfwd + nMinDevPart) // max value is clamped already
return state.DoS(100, error("%s: Bad foundation-reward (actual=%d vs minfundpart=%d)", __func__, nStakeReward, nDevBfwd + nMinDevPart), REJECT_INVALID, "bad-cs-fund-amount");
if (txCoinstake->GetDevFundCfwd(nDevCfwdCheck))
return state.DoS(100, error("%s: Coinstake foundation cfwd must be unset.", __func__), REJECT_INVALID, "bad-cs-cfwd");
}
if (nStakeReward < 0 || nStakeReward > nMaxHolderPart)
return state.DoS(100, error("%s: Bad stake-reward (actual=%d vs maxholderpart=%d)", __func__, nStakeReward, nMaxHolderPart), REJECT_INVALID, "bad-cs-amount");
if (block.GetHash() != Params().GenesisBlock().GetHash())
return state.DoS(100, error("ConnectBlock() : Found block that isn't coinstake or genesis."), REJECT_INVALID, "bad-cs");
CAmount nDevCfwd = nDevBfwd + nCalculatedStakeReward - nStakeReward;
if (!txCoinstake->GetDevFundCfwd(nDevCfwdCheck)
|| nDevCfwdCheck != nDevCfwd)
return state.DoS(100, error("%s: Coinstake foundation fund carried forward mismatch (actual=%d vs expected=%d)", __func__, nDevCfwdCheck, nDevCfwd), REJECT_INVALID, "bad-cs-cfwd");
if (fParticlMode
&& !block.IsParticlVersion())
return state.DoS(100, false, REJECT_INVALID, "block-version", false, "bad block version");
// Check timestamp
if (fParticlMode
&& !block.hashPrevBlock.IsNull() // allow genesis block to be created in the future
&& block.GetBlockTime() > FutureDrift(GetAdjustedTime()))
return state.DoS(50, false, REJECT_INVALID, "block-timestamp", false, "block timestamp too far in the future");
where FutureDrift
is defined as:
inline int64_t FutureDrift(int64_t nTime) { return nTime + 15; } // FutureDriftV2
This is one of two call sites to FutureDrift
.
// Check proof of work matches claimed amount
if (!fParticlMode
&& fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits, consensusParams))
return state.DoS(50, false, REJECT_INVALID, "high-hash", false, "proof of work failed");
particl performs a check for whether a piece of stake is unique / has been seen to be used before. particl flags such blocks (it used to reject, but that code is commented out). Nodes that send blocks with duplicate stake are penalized with a ban score of 10.
- particl 0.16 validation.cpp:4056
- particl 0.17 validation.cpp:4052
- particl 0.18 validation.cpp:4064
if (!IsInitialBlockDownload()
&& block.vtx[0]->IsCoinStake()
&& !CheckStakeUnique(block))
{
//state.DoS(10, false, REJECT_INVALID, "bad-cs-duplicate", false, "duplicate coinstake");
state.nFlags |= BLOCK_FAILED_DUPLICATE_STAKE;
/*
// TODO: ask peers which stake kernel they have
if (chainActive.Tip()->nHeight < GetNumBlocksOfPeers() - 8) // peers have significantly longer chain, this node must've got the wrong stake 1st
{
LogPrint(BCLog::POS, "%s: Ignoring CheckStakeUnique for block %s, chain height behind peers.\n", __func__, block.GetHash().ToString());
const COutPoint &kernel = block.vtx[0]->vin[0].prevout;
mapStakeSeen[kernel] = block.GetHash();
} else
return state.DoS(20, false, REJECT_INVALID, "bad-cs-duplicate", false, "duplicate coinstake");
*/
};
Same check as in bitcoin, just it is augmented to take into consideration coinstake
transactions as particl makes a distinction of IsCoinBase
and IsCoinStake
. Unit-e
does not distinguish these, there is only coinbase (which is particls coinstake). The
reason particl distinguishes these is that they accept bitcoin PoW blocks (the genesis
block is such a block) and particl PoS blocks (having a coinstake transaction).
- particl 0.16 validation.cpp:4079
// First transaction must be coinbase (genesis only) or coinstake
// 2nd txn may be coinbase in early blocks: check further in ContextualCheckBlock
if (!(block.vtx[0]->IsCoinBase() || block.vtx[0]->IsCoinStake())) // only genesis can be coinbase, check in ContextualCheckBlock
return state.DoS(100, false, REJECT_INVALID, "bad-cb-missing", false, "first tx is not coinbase");
Same check as in bitcoin, just it is augmented to take into consideration coinstake
transactions as particl makes a distinction of IsCoinBase
and IsCoinStake
. Unit-e
does not distinguish these, there is only coinbase (which is particls coinstake). The
reason particl distinguishes these is that they accept bitcoin PoW blocks (the genesis
block is such a block) and particl PoS blocks (having a coinstake transaction).
- particl 0.16 validation.cpp:4084
// 2nd txn may never be coinstake, remaining txns must not be coinbase/stake
for (size_t i = 1; i < block.vtx.size(); i++)
if ((i > 1 && block.vtx[i]->IsCoinBase()) || block.vtx[i]->IsCoinStake())
return state.DoS(100, false, REJECT_INVALID, "bad-cb-multiple", false, "more than one coinbase or coinstake");
- particl 0.16 validation.cpp:4087
- unit-e block_validator.cpp:212
if (!CheckBlockSignature(block))
return state.DoS(100, false, REJECT_INVALID, "bad-block-signature", false, "bad block signature");
This is particl's counterpart to bitcoins bad-diffbits
and has to do with difficulty calculation.
bitcoin uses GetNextWorkRequired
(pow.cpp
) whereas particl uses GetNextTargetRequired
(validation.cpp
)
which is a PoS function.
- particl 0.16 validation.cpp:4268
// Check proof-of-stake
if (block.nBits != GetNextTargetRequired(pindexPrev))
return state.DoS(100, false, REJECT_INVALID, "bad-proof-of-stake", true, strprintf("%s: Bad proof-of-stake target", __func__));
- particl 0.16 validation.cpp:4351
// Limit the number of outputs in a coinstake txn to 6: 1 data + 1 foundation + 4 user
if (nPrevTime >= consensusParams.OpIsCoinstakeTime)
{
if (block.vtx[0]->vpout.size() > 6)
return state.DoS(100, false, REJECT_INVALID, "bad-cs-outputs", false, "Too many outputs in coinstake");
};
This is particl's version of BIP34. They put the "coinbase committment" as it is called in bitcoin in a special output which contains the height of the block. This output is a data output as particl has a system of different kinds of outputs, one of them being an explicit "data" output.
- particl 0.16 validation.cpp:4357
- unit-e block_validator.cpp:83
// coinstake output 0 must be data output of blockheight
int i;
if (!block.vtx[0]->GetCoinStakeHeight(i))
return state.DoS(100, false, REJECT_INVALID, "bad-cs-malformed", false, "coinstake txn is malformed");
- particl 0.16 validation.cpp:4357
if (i != nHeight)
return state.DoS(100, false, REJECT_INVALID, "bad-cs-height", false, "block height mismatch in coinstake");
- particl 0.16 validation.cpp:4377
// check witness merkleroot, TODO: should witnessmerkleroot be hashed?
bool malleated = false;
uint256 hashWitness = BlockWitnessMerkleRoot(block, &malleated);
if (hashWitness != block.hashWitnessMerkleRoot)
return state.DoS(100, false, REJECT_INVALID, "bad-witness-merkle-match", true, strprintf("%s : witness merkle commitment mismatch", __func__));
- particl 0.16 validation.cpp:4380
if (!CheckCoinStakeTimestamp(nHeight, block.GetBlockTime()))
return state.DoS(50, false, REJECT_INVALID, "bad-coinstake-time", true, strprintf("%s: coinstake timestamp violation nTimeBlock=%d", __func__, block.GetBlockTime()));
- particl 0.16 validation.cpp:4384
// Check timestamp against prev
if (block.GetBlockTime() <= pindexPrev->GetPastTimeLimit() || FutureDrift(block.GetBlockTime()) < pindexPrev->GetBlockTime())
return state.DoS(50, false, REJECT_INVALID, "bad-block-time", true, strprintf("%s: block's timestamp is too early", __func__));
where FutureDrift
is defined as:
inline int64_t FutureDrift(int64_t nTime) { return nTime + 15; } // FutureDriftV2
This is one of two call sites to FutureDrift
.
- particl 0.16 validation.cpp:4399
// Blocks are connected at end of import / reindex
// CheckProofOfStake is run again during connectblock
if (!IsInitialBlockDownload() // checks (!fImporting && !fReindex)
&& !CheckProofOfStake(pindexPrev, *block.vtx[0], block.nTime, block.nBits, hashProof, targetProofOfStake))
{
LogPrintf("WARNING: ContextualCheckBlock(): check proof-of-stake failed for block %s\n", block.GetHash().ToString());
//return false; // do not error here as we expect this during initial block download
if (pindexPrev->bnStakeModifier.IsNull())
// Can happen if the block is received out of order - CheckProofOfStake will run again on connectblock.
LogPrint(BCLog::POS, "%s: Accepting failed CheckProofOfStake block, missing stake-modifier.\n", __func__);
else
return state.DoS(50, false, REJECT_INVALID, "bad-proof-of-stake", true, strprintf("%s: CheckProofOfStake failed.", __func__));
};
- particl 0.16 validation.cpp:4419
if (nHeight > 0 && !block.vtx[0]->IsCoinStake()) // only genesis block can start with coinbase
return state.DoS(100, false, REJECT_INVALID, "bad-cs-missing", false, "first tx is not coinstake");
particl does not just have a genesis block, but also an initial import of 70 blocks which dispersed the funds in the beginning. unit-e does not have such a thing.
- particl 0.16 validation.cpp:4421
if (nHeight > 0 // skip genesis
&& Params().GetLastImportHeight() >= (uint32_t)nHeight)
{
// 2nd txn must be coinbase
if (block.vtx.size() < 2 || !block.vtx[1]->IsCoinBase())
return state.DoS(100, false, REJECT_INVALID, "bad-cb", false, "Second txn of import block must be coinbase");
// Check hash of genesis import txn matches expected hash.
uint256 txnHash = block.vtx[1]->GetHash();
if (!Params().CheckImportCoinbase(nHeight, txnHash))
return state.DoS(100, false, REJECT_INVALID, "bad-cb", false, "Incorrect outputs hash.");
} else
{
// 2nd txn can't be coinbase if block height > GetLastImportHeight
if (block.vtx.size() > 1 && block.vtx[1]->IsCoinBase())
return state.DoS(100, false, REJECT_INVALID, "bad-cb-multiple", false, "unexpected coinbase");
};