Fix persistent PoS chain forks with deterministic tiebreaker and tighter timestamps
PoS blocks at the same height have identical difficulty, producing equal chain trust scores. The old "strictly greater" comparison meant first-seen-wins, causing permanent forks when nodes received competing blocks in different order. v5.4 fork (block 2186941) adds: - Deterministic tiebreaker: equal-trust chains resolve to the lower tip hash - Tighter time drift: ±3 min (was ±10 min), reducing the competing block window Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+13
-3
@@ -2568,7 +2568,17 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const u
|
||||
|
||||
// New best — keep the batch open so SetBestChain can add ConnectBlock
|
||||
// writes to the same transaction, cutting the per-block commit count in half.
|
||||
// v5.4: deterministic tiebreaker — when two chains have equal trust,
|
||||
// all nodes agree on the one whose tip has the lower block hash.
|
||||
// This prevents permanent forks from PoS blocks with identical difficulty.
|
||||
bool fNewBest = false;
|
||||
if (pindexNew->nChainTrust > nBestChainTrust)
|
||||
fNewBest = true;
|
||||
else if (pindexNew->nChainTrust == nBestChainTrust && pindexBest &&
|
||||
pindexNew->GetBlockHash() < pindexBest->GetBlockHash())
|
||||
fNewBest = true;
|
||||
|
||||
if (fNewBest)
|
||||
{
|
||||
if (!SetBestChain(txdb, pindexNew))
|
||||
return false;
|
||||
@@ -2719,7 +2729,7 @@ bool CBlock::AcceptBlock()
|
||||
if (nHeight % 10000 == 0 || nHeight > 2186900)
|
||||
printf("ProcessBlock(): Check proof-of-stake/work OK for block %d\n", nHeight);
|
||||
// Check timestamp against prev
|
||||
if (GetBlockTime() <= pindexPrev->GetPastTimeLimit() || FutureDrift(GetBlockTime()) < pindexPrev->GetBlockTime())
|
||||
if (GetBlockTime() <= pindexPrev->GetPastTimeLimit() || FutureDrift(GetBlockTime(), nHeight) < pindexPrev->GetBlockTime())
|
||||
return error("AcceptBlock() : block's timestamp is too early");
|
||||
|
||||
// Check that all transactions are finalized
|
||||
@@ -2999,13 +3009,13 @@ bool CBlock::SignBlock(CWallet& wallet, int64_t nFees)
|
||||
{
|
||||
if (wallet.CreateCoinStake(wallet, nBits, nSearchTime-nLastCoinStakeSearchTime, nFees, txCoinStake, key))
|
||||
{
|
||||
if (txCoinStake.nTime >= max(pindexBest->GetPastTimeLimit()+1, PastDrift(pindexBest->GetBlockTime())))
|
||||
if (txCoinStake.nTime >= max(pindexBest->GetPastTimeLimit()+1, PastDrift(pindexBest->GetBlockTime(), pindexBest->nHeight + 1)))
|
||||
{
|
||||
// make sure coinstake would meet timestamp protocol
|
||||
// as it would be the same as the block timestamp
|
||||
vtx[0].nTime = nTime = txCoinStake.nTime;
|
||||
nTime = max(pindexBest->GetPastTimeLimit()+1, GetMaxTransactionTime());
|
||||
nTime = max(GetBlockTime(), PastDrift(pindexBest->GetBlockTime()));
|
||||
nTime = max(GetBlockTime(), PastDrift(pindexBest->GetBlockTime(), pindexBest->nHeight + 1));
|
||||
|
||||
// we have to make sure that we have no future timestamps in
|
||||
// our transactions set
|
||||
|
||||
+6
-2
@@ -29,6 +29,7 @@ class CNode;
|
||||
static const int CUTOFF_POW_BLOCK = 9000;
|
||||
static const int CRAPCHAIN_CUTOFF_BLOCK = 17691; // pre-Pharao (version 4) blockchain until block 17691
|
||||
static const int FORK_HEIGHT_V5 = 17651; // v5 hard fork: decentralization + Tor v3 (next block after last checkpoint)
|
||||
static const int FORK_HEIGHT_V5_4 = 2186941; // v5.4: tighter timestamps, deterministic fork resolution
|
||||
|
||||
static const unsigned int MAX_BLOCK_SIZE = 1000000;
|
||||
static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2;
|
||||
@@ -56,8 +57,11 @@ static const int fHaveUPnP = false;
|
||||
|
||||
static const uint256 hashGenesisBlockOfficial("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
|
||||
static const uint256 hashGenesisBlockTestNet ("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
|
||||
inline int64_t PastDrift(int64_t nTime) { return nTime - 10 * 60; } // up to 10 minutes from the past
|
||||
inline int64_t FutureDrift(int64_t nTime) { return nTime + 10 * 60; } // up to 10 minutes from the future
|
||||
inline int64_t GetMaxTimeDrift(int nHeight) { return (nHeight >= FORK_HEIGHT_V5_4) ? 3 * 60 : 10 * 60; }
|
||||
inline int64_t PastDrift(int64_t nTime, int nHeight) { return nTime - GetMaxTimeDrift(nHeight); }
|
||||
inline int64_t FutureDrift(int64_t nTime, int nHeight) { return nTime + GetMaxTimeDrift(nHeight); }
|
||||
inline int64_t PastDrift(int64_t nTime) { return PastDrift(nTime, nBestHeight); }
|
||||
inline int64_t FutureDrift(int64_t nTime) { return FutureDrift(nTime, nBestHeight); }
|
||||
|
||||
|
||||
extern CScript COINBASE_FLAGS;
|
||||
|
||||
+1
-1
@@ -365,7 +365,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
// Fill in header
|
||||
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
|
||||
pblock->nTime = max(pindexPrev->GetPastTimeLimit()+1, pblock->GetMaxTransactionTime());
|
||||
pblock->nTime = max(pblock->GetBlockTime(), PastDrift(pindexPrev->GetBlockTime()));
|
||||
pblock->nTime = max(pblock->GetBlockTime(), PastDrift(pindexPrev->GetBlockTime(), pindexPrev->nHeight + 1));
|
||||
if (!fProofOfStake)
|
||||
pblock->UpdateTime(pindexPrev);
|
||||
pblock->nNonce = 0;
|
||||
|
||||
Reference in New Issue
Block a user