diff --git a/src/avalanche/avalanche.h b/src/avalanche/avalanche.h --- a/src/avalanche/avalanche.h +++ b/src/avalanche/avalanche.h @@ -59,6 +59,12 @@ */ static constexpr double AVALANCHE_DEFAULT_MIN_AVAPROOFS_NODE_COUNT = 8; +/** + * Is post-consensus policy for early block penalty enabled by default? + */ +static constexpr bool AVALANCHE_POLICY_EARLY_BLOCK_PENALTY_DEFAULT_ENABLED = + false; + /** * Global avalanche instance. */ diff --git a/src/chainparams.cpp b/src/chainparams.cpp --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -100,6 +100,10 @@ // two days consensus.nDAAHalfLife = 2 * 24 * 60 * 60; + // PoW penalty for early blocks + consensus.earlyBlockPowTargetPenaltyFactor = 10; + consensus.earlyBlockPowTargetPenaltyWindow = 300; + // nPowTargetTimespan / nPowTargetSpacing consensus.nMinerConfirmationWindow = 2016; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY] = { @@ -264,6 +268,10 @@ // two days consensus.nDAAHalfLife = 2 * 24 * 60 * 60; + // PoW penalty for early blocks + consensus.earlyBlockPowTargetPenaltyFactor = 10; + consensus.earlyBlockPowTargetPenaltyWindow = 300; + // nPowTargetTimespan / nPowTargetSpacing consensus.nMinerConfirmationWindow = 2016; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY] = { @@ -411,6 +419,10 @@ // two days consensus.nDAAHalfLife = 2 * 24 * 60 * 60; + // No penalty on regtest + consensus.earlyBlockPowTargetPenaltyFactor = 1; + consensus.earlyBlockPowTargetPenaltyWindow = 1; + // Faster than normal for regtest (144 instead of 2016) consensus.nMinerConfirmationWindow = 144; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY] = { diff --git a/src/consensus/params.h b/src/consensus/params.h --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -130,6 +130,10 @@ uint256 nMinimumChainWork; BlockHash defaultAssumeValid; + /** Avalanche post-consensus parameters */ + int64_t earlyBlockPowTargetPenaltyFactor; + int64_t earlyBlockPowTargetPenaltyWindow; + int DeploymentHeight(BuriedDeployment dep) const { switch (dep) { case DEPLOYMENT_P2SH: diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -1424,6 +1424,21 @@ "%u).", DEFAULT_MAX_AVALANCHE_OUTBOUND_CONNECTIONS), ArgsManager::ALLOW_INT, OptionsCategory::AVALANCHE); + argsman.AddArg( + "-avapolicyearlyblockpenalty", + strprintf( + "Enable the avalanche post-consensus policy to apply a " + "difficulty penalty to early blocks that arrive within a penalty " + "window measured in " + "seconds from the previous block (mainnet: %u, testnet: %u, " + "regtest: %u). Early blocks will be voted " + "against but will still finalize if the rest of the network " + "accepts the block. (default: %u)", + defaultChainParams->GetConsensus().earlyBlockPowTargetPenaltyWindow, + testnetChainParams->GetConsensus().earlyBlockPowTargetPenaltyWindow, + regtestChainParams->GetConsensus().earlyBlockPowTargetPenaltyWindow, + AVALANCHE_POLICY_EARLY_BLOCK_PENALTY_DEFAULT_ENABLED), + ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); // Add the hidden options argsman.AddHiddenArgs(hidden_args); diff --git a/src/validation.h b/src/validation.h --- a/src/validation.h +++ b/src/validation.h @@ -1001,7 +1001,8 @@ bool AcceptBlock(const Config &config, const std::shared_ptr &pblock, BlockValidationState &state, bool fRequested, - const FlatFilePos *dbp, bool *fNewBlock) + const FlatFilePos *dbp, bool *fNewBlock, + bool fPostConsensusActive = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Block (dis)connection on a given view: diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4098,18 +4098,24 @@ /** * Store a block on disk. * - * @param[in] config The global config. - * @param[in,out] pblock The block we want to accept. - * @param[in] fRequested A boolean to indicate if this block was requested - * from our peers. - * @param[in] dbp If non-null, the disk position of the block. - * @param[in,out] fNewBlock True if block was first received via this call. + * @param[in] config The global config. + * @param[in,out] pblock The block we want to accept. + * @param[in] fRequested A boolean to indicate if this block was + * requested from our peers. + * @param[in] dbp If non-null, the disk position of the + * block. + * @param[in,out] fNewBlock True if block was first received via + * this call. + * @param[in] fPostConsensusActive A boolean to indicate if Avalanche + * post-consensus voting is currently + * active. * @return True if the block is accepted as a valid block and written to disk. */ bool CChainState::AcceptBlock(const Config &config, const std::shared_ptr &pblock, BlockValidationState &state, bool fRequested, - const FlatFilePos *dbp, bool *fNewBlock) { + const FlatFilePos *dbp, bool *fNewBlock, + bool fPostConsensusActive) { AssertLockHeld(cs_main); const CBlock &block = *pblock; @@ -4231,6 +4237,60 @@ } } + // Apply Avalanche post-consensus policies if voting is active + if (fPostConsensusActive && + gArgs.GetBoolArg( + "-avapolicyearlyblockpenalty", + AVALANCHE_POLICY_EARLY_BLOCK_PENALTY_DEFAULT_ENABLED) && + pindex->pprev && !IsBlockAvalancheFinalized(pindex)) { + const CBlockIndex *parent = pindex->pprev; + const int64_t timeDiff = + pindex->nTimeReceived >= parent->nTimeReceived + ? pindex->nTimeReceived - parent->nTimeReceived + : 0; + + if (timeDiff < consensusParams.earlyBlockPowTargetPenaltyWindow) { + const CBlockHeader header = pindex->GetBlockHeader(); + LogPrintf("Early block hash: %s, parentTime: %d, receivedTime: %d, " + "timeDiff: %d\n", + header.GetHash().ToString(), parent->nTimeReceived, + pindex->nTimeReceived, timeDiff); + arith_uint256 target; + arith_uint256 oldTarget; + bool negative; + bool overflow; + uint32_t powRequired = + GetNextWorkRequired(parent, &header, m_params); + target.SetCompact(powRequired, &negative, &overflow); + oldTarget = target; + target *= consensusParams.earlyBlockPowTargetPenaltyWindow; + target /= (1 - consensusParams.earlyBlockPowTargetPenaltyFactor) * + timeDiff + + consensusParams.earlyBlockPowTargetPenaltyFactor * + consensusParams.earlyBlockPowTargetPenaltyWindow; + const uint32_t newPowLimit = target.GetCompact(negative); + + LogPrintf("Early block:\n" + "oldTarget: %s, oldPowLimit: 0x%.8x\n" + "newTarget: %s, newPowLimit: 0x%.8x\n", + oldTarget.ToString(), powRequired, target.ToString(), + newPowLimit); + + if (!CheckProofOfWork(header.GetHash(), newPowLimit, + consensusParams)) { + // Mark this block as parked + pindex->nStatus = pindex->nStatus.withParked(); + setDirtyBlockIndex.insert(pindex); + LogPrintf("Early block rejected due to PoW " + "penalty: %s\n", + header.GetHash().ToString()); + } else { + LogPrintf("Early block accepted: %s\n", + header.GetHash().ToString()); + } + } + } + // Header is valid/has work and the merkle tree is good. // Relay now, but if it does not build on our best tip, let the // SendMessages loop relay it. @@ -4268,6 +4328,9 @@ bool force_processing, bool *new_block) { AssertLockNotHeld(cs_main); + bool postConsensusActive = + g_avalanche && g_avalanche->isQuorumEstablished(); + { if (new_block) { *new_block = false; @@ -4289,7 +4352,8 @@ if (ret) { // Store to disk ret = ActiveChainstate().AcceptBlock( - config, block, state, force_processing, nullptr, new_block); + config, block, state, force_processing, nullptr, new_block, + postConsensusActive); } if (!ret) {