If I understand this correctly, there was additiotal search space caused by insufficient timestamp check and difficulty retarget every block. Both have nothing to do with PoW.
bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64& nStakeModifier, bool& fGeneratedStakeModifier)
{
nStakeModifier = 0;
fGeneratedStakeModifier = false;
if (!pindexPrev)
{
fGeneratedStakeModifier = true;
return true; // genesis block's modifier is 0
}
// First find current stake modifier and its generation block time
// if it's not old enough, return the same stake modifier
int64 nModifierTime = 0;
if (!GetLastStakeModifier(pindexPrev, nStakeModifier, nModifierTime))
return error("ComputeNextStakeModifier: unable to get last modifier");
if (fDebug)
{
printf("ComputeNextStakeModifier: prev modifier=0x%016"PRI64x" time=%s\n", nStakeModifier, DateTimeStrFormat(nModifierTime).c_str());
}
if (nModifierTime / nModifierInterval >= pindexPrev->GetBlockTime() / nModifierInterval)
return true;
// Sort candidate blocks by timestamp
vector > vSortedByTimestamp;
vSortedByTimestamp.reserve(64 * nModifierInterval / nTargetSpacing);
int64 nSelectionInterval = GetStakeModifierSelectionInterval();
int64 nSelectionIntervalStart = (pindexPrev->GetBlockTime() / nModifierInterval) * nModifierInterval - nSelectionInterval;
const CBlockIndex* pindex = pindexPrev;
while (pindex && pindex->GetBlockTime() >= nSelectionIntervalStart)
{
vSortedByTimestamp.push_back(make_pair(pindex->GetBlockTime(), pindex->GetBlockHash()));
pindex = pindex->pprev;
}
int nHeightFirstCandidate = pindex ? (pindex->nHeight + 1) : 0;
reverse(vSortedByTimestamp.begin(), vSortedByTimestamp.end());
sort(vSortedByTimestamp.begin(), vSortedByTimestamp.end());
// Select 64 blocks from candidate blocks to generate stake modifier
uint64 nStakeModifierNew = 0;
int64 nSelectionIntervalStop = nSelectionIntervalStart;
map mapSelectedBlocks;
for (int nRound=0; nRound {
// add an interval section to the current selection round
nSelectionIntervalStop += GetStakeModifierSelectionIntervalSection(nRound);
// select a block from the candidates of current round
if (!SelectBlockFromCandidates(vSortedByTimestamp, mapSelectedBlocks, nSelectionIntervalStop, nStakeModifier, &pindex))
return error("ComputeNextStakeModifier: unable to select block at round %d", nRound);
// write the entropy bit of the selected block
nStakeModifierNew |= (((uint64)pindex->GetStakeEntropyBit()) << nRound);
// add the selected block from candidates to selected list
mapSelectedBlocks.insert(make_pair(pindex->GetBlockHash(), pindex));
if (fDebug && GetBoolArg("-printstakemodifier"))
printf("ComputeNextStakeModifier: selected round %d stop=%s height=%d bit=%d\n", nRound, DateTimeStrFormat(nSelectionIntervalStop).c_str(), pindex->nHeight, pindex->GetStakeEntropyBit());
}
// Print selection map for visualization of the selected blocks
if (fDebug && GetBoolArg("-printstakemodifier"))
{
string strSelectionMap = "";
// '-' indicates proof-of-work blocks not selected
strSelectionMap.insert(0, pindexPrev->nHeight - nHeightFirstCandidate + 1, '-');
pindex = pindexPrev;
while (pindex && pindex->nHeight >= nHeightFirstCandidate)
{
// '=' indicates proof-of-stake blocks not selected
if (pindex->IsProofOfStake())
strSelectionMap.replace(pindex->nHeight - nHeightFirstCandidate, 1, "=");
pindex = pindex->pprev;
}
BOOST_FOREACH(const PAIRTYPE(uint256, const CBlockIndex*)& item, mapSelectedBlocks)
{
// 'S' indicates selected proof-of-stake blocks
// 'W' indicates selected proof-of-work blocks
strSelectionMap.replace(item.second->nHeight - nHeightFirstCandidate, 1, item.second->IsProofOfStake()? "S" : "W");
}
printf("ComputeNextStakeModifier: selection height [%d, %d] map %s\n", nHeightFirstCandidate, pindexPrev->nHeight, strSelectionMap.c_str());
}
if (fDebug)
{
printf("ComputeNextStakeModifier: new modifier=0x%016"PRI64x" time=%s\n", nStakeModifierNew, DateTimeStrFormat(pindexPrev->GetBlockTime()).c_str());
}
nStakeModifier = nStakeModifierNew;
fGeneratedStakeModifier = true;
return true;
}
Notice how ComputeNextStakeModifier typically includes PoW blocks; you can visualise this if you turn on the debug flags.
What could happen when you don't include PoW blocks and you're only using entropy bits from PoS blocks (GetStakeEntropyBit() from main.cpp)? Especially if you're producing most of the previous PoS blocks? Of course, we can be generally assured that entropy bits from PoW miners are completely random because for them not to be would involve them throwing away a lot of work...
I don't think there's any easy fix not including PoW, and if you make the chain completely PoS you run into the forking issue (that miners can mine both forks of a chain easily and without consequence, so when forks are started they continue to propagate forever).
Nodes do not propagate double stakes.
Okay; how do you select which chain is the correct one?