Skip to content

Commit

Permalink
PPCoin: Upgrade Proof-of-Stake Protocol
Browse files Browse the repository at this point in the history
        - Use new stake modifier to generate stake kernel
        - Stake kernel hash weight starts at 0 after nStakeMinAge passed
        - Coinstake timestamp must equal to block timestamp
        - Upgrade protocol to 60003 (v0.3)
        - Testnet switched to new protocol
  • Loading branch information
sunnyking committed Feb 3, 2013
1 parent 4579bbc commit b0b7eb2
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 36 deletions.
139 changes: 120 additions & 19 deletions src/kernel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@

using namespace std;

// Protocol switch time of v0.3 kernel protocol
unsigned int nProtocolV03SwitchTime = 1363800000;
unsigned int nProtocolV03TestSwitchTime = 1359781000;

// Modifier interval: time to elapse before new modifier is computed
// Set to 6-hour for production network and 20-minute for test network
unsigned int nModifierInterval = MODIFIER_INTERVAL;

// Whether the given coinstake is subject to new v0.3 protocol
bool IsProtocolV03(unsigned int nTimeCoinStake)
{
return (nTimeCoinStake >= (fTestNet? nProtocolV03TestSwitchTime : nProtocolV03SwitchTime));
}

// Get the last stake modifier and its generation time from a given block
static bool GetLastStakeModifier(const CBlockIndex* pindex, uint64& nStakeModifier, int64& nModifierTime)
{
Expand All @@ -25,7 +39,7 @@ static bool GetLastStakeModifier(const CBlockIndex* pindex, uint64& nStakeModifi
static int64 GetStakeModifierSelectionIntervalSection(int nSection)
{
assert (nSection >= 0 && nSection < 64);
return (MODIFIER_INTERVAL * 63 / (63 + ((63 - nSection) * (MODIFIER_INTERVAL_RATIO - 1))));
return (nModifierInterval * 63 / (63 + ((63 - nSection) * (MODIFIER_INTERVAL_RATIO - 1))));
}

// Get stake modifier selection interval (in seconds)
Expand Down Expand Up @@ -117,14 +131,14 @@ bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64& nStakeModif
{
printf("ComputeNextStakeModifier: prev modifier=0x%016"PRI64x" time=%s\n", nStakeModifier, DateTimeStrFormat(nModifierTime).c_str());
}
if (nModifierTime / MODIFIER_INTERVAL >= pindexPrev->GetBlockTime() / MODIFIER_INTERVAL)
if (nModifierTime / nModifierInterval >= pindexPrev->GetBlockTime() / nModifierInterval)
return true;

// Sort candidate blocks by timestamp
vector<pair<int64, uint256> > vSortedByTimestamp;
vSortedByTimestamp.reserve(64 * MODIFIER_INTERVAL / STAKE_TARGET_SPACING);
vSortedByTimestamp.reserve(64 * nModifierInterval / STAKE_TARGET_SPACING);
int64 nSelectionInterval = GetStakeModifierSelectionInterval();
int64 nSelectionIntervalStart = (pindexPrev->GetBlockTime() / MODIFIER_INTERVAL) * MODIFIER_INTERVAL - nSelectionInterval;
int64 nSelectionIntervalStart = (pindexPrev->GetBlockTime() / nModifierInterval) * nModifierInterval - nSelectionInterval;
const CBlockIndex* pindex = pindexPrev;
while (pindex && pindex->GetBlockTime() >= nSelectionIntervalStart)
{
Expand Down Expand Up @@ -187,15 +201,45 @@ bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64& nStakeModif
return true;
}

// v0.2 kernel protocol
// The stake modifier used to hash for a stake kernel is chosen as the stake
// modifier about a selection interval later than the coin generating the kernel
static bool GetKernelStakeModifier(uint256 hashBlockFrom, uint64& nStakeModifier, int& nStakeModifierHeight, int64& nStakeModifierTime)
{
nStakeModifier = 0;
if (!mapBlockIndex.count(hashBlockFrom))
return error("GetKernelStakeModifier() : block not indexed");
const CBlockIndex* pindexFrom = mapBlockIndex[hashBlockFrom];
nStakeModifierHeight = pindexFrom->nHeight;
nStakeModifierTime = pindexFrom->GetBlockTime();
int64 nStakeModifierSelectionInterval = GetStakeModifierSelectionInterval();
const CBlockIndex* pindex = pindexFrom;
// loop to find the stake modifier later by a selection interval
while (nStakeModifierTime < pindexFrom->GetBlockTime() + nStakeModifierSelectionInterval)
{
if (!pindex->pnext)
return error("GetKernelStakeModifier() : reached best block %s at height %d", pindex->GetBlockHash().ToString().c_str(), pindex->nHeight);
pindex = pindex->pnext;
if (pindex->GeneratedStakeModifier())
{
nStakeModifierHeight = pindex->nHeight;
nStakeModifierTime = pindex->GetBlockTime();
}
}
nStakeModifier = pindex->nStakeModifier;
return true;
}

// ppcoin kernel protocol
// coinstake must meet hash target according to the protocol:
// kernel (input 0) must meet the formula
// hash(nBits + txPrev.block.nTime + txPrev.offset + txPrev.nTime + txPrev.vout.n + nTime) < bnTarget * nCoinDay
// hash(nStakeModifier + txPrev.block.nTime + txPrev.offset + txPrev.nTime + txPrev.vout.n + nTime) < bnTarget * nCoinDayWeight
// this ensures that the chance of getting a coinstake is proportional to the
// amount of coin age one owns.
// The reason this hash is chosen is the following:
// nBits: encodes all past block timestamps, making computing hash in advance
// more difficult
// nStakeModifier:
// (v0.3) scrambles computation to make it very difficult to precompute
// future proof-of-stake at the time of the coin's confirmation
// (v0.2) nBits (deprecated): encodes all past block timestamps
// txPrev.block.nTime: prevent nodes from guessing a good timestamp to
// generate transaction for future advantage
// txPrev.offset: offset of txPrev inside block, to reduce the chance of
Expand All @@ -208,28 +252,77 @@ bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64& nStakeModif
// quantities so as to generate blocks faster, degrading the system back into
// a proof-of-work situation.
//
bool CheckStakeKernelHash(unsigned int nBits, unsigned int nTimeBlockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake)
bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake, bool fPrintProofOfStake)
{
if (nTimeTx < txPrev.nTime) // Transaction timestamp violation
return error("CheckStakeKernelHash() : nTime violation");

if (nTimeBlockFrom + nStakeMinAge > nTimeTx) // Meeting min age requirement
unsigned int nTimeBlockFrom = blockFrom.GetBlockTime();
if (nTimeBlockFrom + nStakeMinAge > nTimeTx) // Min age requirement
return error("CheckStakeKernelHash() : min age violation");

CBigNum bnTargetPerCoinDay;
bnTargetPerCoinDay.SetCompact(nBits);
int64 nValueIn = txPrev.vout[prevout.n].nValue;
CBigNum bnCoinDay = CBigNum(nValueIn) * min(nTimeTx-txPrev.nTime, (unsigned int)STAKE_MAX_AGE) / COIN / (24 * 60 * 60);
// v0.3 protocol kernel hash weight starts from 0 at the 30-day min age
// this change increases active coins participating the hash and helps
// to secure the network when proof-of-stake difficulty is low
int64 nTimeWeight = min((int64)nTimeTx - txPrev.nTime, (int64)STAKE_MAX_AGE) - (IsProtocolV03(nTimeTx)? nStakeMinAge : 0);
CBigNum bnCoinDayWeight = CBigNum(nValueIn) * nTimeWeight / COIN / (24 * 60 * 60);
// Calculate hash
CDataStream ss(SER_GETHASH, 0);
ss << nBits << nTimeBlockFrom << nTxPrevOffset << txPrev.nTime << prevout.n << nTimeTx;
uint64 nStakeModifier = 0;
int nStakeModifierHeight = 0;
int64 nStakeModifierTime = 0;
if (IsProtocolV03(nTimeTx)) // v0.3 protocol
{
if (!GetKernelStakeModifier(blockFrom.GetHash(), nStakeModifier, nStakeModifierHeight, nStakeModifierTime))
return error("CheckStakeKernelHash() : failed to get kernel modifier for block %s", blockFrom.GetHash().ToString().c_str());
ss << nStakeModifier;
}
else // v0.2 protocol
{
ss << nBits;
}

ss << nTimeBlockFrom << nTxPrevOffset << txPrev.nTime << prevout.n << nTimeTx;
hashProofOfStake = Hash(ss.begin(), ss.end());
if (CBigNum(hashProofOfStake) <= bnCoinDay * bnTargetPerCoinDay)
return true;
else
if (fPrintProofOfStake)
{
if (IsProtocolV03(nTimeTx))
printf("CheckStakeKernelHash() : using modifier 0x%016"PRI64x" at height=%d timestamp=%s for block from height=%d timestamp=%s\n",
nStakeModifier, nStakeModifierHeight,
DateTimeStrFormat(nStakeModifierTime).c_str(),
mapBlockIndex[blockFrom.GetHash()]->nHeight,
DateTimeStrFormat(blockFrom.GetBlockTime()).c_str());
printf("CheckStakeKernelHash() : check protocol=%s modifier=0x%016"PRI64x" nTimeBlockFrom=%u nTxPrevOffset=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n",
IsProtocolV03(nTimeTx)? "0.3" : "0.2",
IsProtocolV03(nTimeTx)? nStakeModifier : (uint64) nBits,
nTimeBlockFrom, nTxPrevOffset, txPrev.nTime, prevout.n, nTimeTx,
hashProofOfStake.ToString().c_str());
}

// Now check if proof-of-stake hash meets target protocol
if (CBigNum(hashProofOfStake) > bnCoinDayWeight * bnTargetPerCoinDay)
return false;
if (fDebug && !fPrintProofOfStake)
{
if (IsProtocolV03(nTimeTx))
printf("CheckStakeKernelHash() : using modifier 0x%016"PRI64x" at height=%d timestamp=%s for block from height=%d timestamp=%s\n",
nStakeModifier, nStakeModifierHeight,
DateTimeStrFormat(nStakeModifierTime).c_str(),
mapBlockIndex[blockFrom.GetHash()]->nHeight,
DateTimeStrFormat(blockFrom.GetBlockTime()).c_str());
printf("CheckStakeKernelHash() : pass protocol=%s modifier=0x%016"PRI64x" nTimeBlockFrom=%u nTxPrevOffset=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n",
IsProtocolV03(nTimeTx)? "0.3" : "0.2",
IsProtocolV03(nTimeTx)? nStakeModifier : (uint64) nBits,
nTimeBlockFrom, nTxPrevOffset, txPrev.nTime, prevout.n, nTimeTx,
hashProofOfStake.ToString().c_str());
}
return true;
}

// Check kernel hash target and coinstake signature
bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hashProofOfStake)
{
if (!tx.IsCoinStake())
Expand All @@ -255,9 +348,17 @@ bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hash
if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
return fDebug? error("CheckProofOfStake() : read block failed") : false; // unable to read block of previous transaction

if (CheckStakeKernelHash(nBits, block.nTime, txindex.pos.nTxPos - txindex.pos.nBlockPos, txPrev, txin.prevout, tx.nTime, hashProofOfStake))
return true;
else
return tx.DoS(100, error("CheckProofOfStake() : check kernel failed on coinstake %s", tx.GetHash().ToString().c_str()));
if (!CheckStakeKernelHash(nBits, block, txindex.pos.nTxPos - txindex.pos.nBlockPos, txPrev, txin.prevout, tx.nTime, hashProofOfStake, fDebug))
return tx.DoS(100, error("CheckProofOfStake() : check kernel failed on coinstake %s, hashProof=%s", tx.GetHash().ToString().c_str(), hashProofOfStake.ToString().c_str()));

return true;
}

// Check whether the coinstake timestamp meets protocol
bool CheckCoinStakeTimestamp(int64 nTimeBlock, int64 nTimeTx)
{
if (IsProtocolV03(nTimeTx)) // v0.3 protocol
return (nTimeBlock == nTimeTx);
else // v0.2 protocol
return ((nTimeTx <= nTimeBlock) && (nTimeBlock <= nTimeTx + nMaxClockDrift));
}
15 changes: 13 additions & 2 deletions src/kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,32 @@
#include "main.h"

// MODIFIER_INTERVAL: time to elapse before new modifier is computed
static const int MODIFIER_INTERVAL = 6 * 60 * 60;
static const unsigned int MODIFIER_INTERVAL = 6 * 60 * 60;
extern unsigned int nModifierInterval;

// MODIFIER_INTERVAL_RATIO:
// ratio of group interval length between the last group and the first group
static const int MODIFIER_INTERVAL_RATIO = 3;

// Protocol switch time of v0.3 kernel protocol
extern unsigned int nProtocolV03SwitchTime;
extern unsigned int nProtocolV03TestSwitchTime;

// Whether a given coinstake is subject to new v0.3 protocol
bool IsProtocolV03(unsigned int nTimeCoinStake);

// Compute the hash modifier for proof-of-stake
bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64& nStakeModifier, bool& fGeneratedStakeModifier);

// Check whether stake kernel meets hash target
// Sets hashProofOfStake on success return
bool CheckStakeKernelHash(unsigned int nBits, unsigned int nTimeBlockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake);
bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake, bool fPrintProofOfStake=false);

// Check kernel hash target and coinstake signature
// Sets hashProofOfStake on success return
bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hashProofOfStake);

// Check whether the coinstake timestamp meets protocol
bool CheckCoinStakeTimestamp(int64 nTimeBlock, int64 nTimeTx);

#endif // PPCOIN_KERNEL_H
23 changes: 16 additions & 7 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1857,8 +1857,8 @@ bool CBlock::CheckBlock() const
return DoS(50, error("CheckBlock() : coinbase timestamp is too early"));

// Check coinstake timestamp
if (IsProofOfStake() && GetBlockTime() > (int64)vtx[1].nTime + nMaxClockDrift)
return DoS(50, error("CheckBlock() : coinstake timestamp is too early"));
if (IsProofOfStake() && !CheckCoinStakeTimestamp(GetBlockTime(), (int64)vtx[1].nTime))
return DoS(50, error("CheckBlock() : coinstake timestamp violation nTimeBlock=%u nTimeTx=%u", GetBlockTime(), vtx[1].nTime));

// Check coinbase reward
if (vtx[0].GetValueOut() > (IsProofOfWork()? (GetProofOfWorkReward(nBits) - vtx[0].GetMinFee() + MIN_TX_FEE) : 0))
Expand Down Expand Up @@ -2204,6 +2204,7 @@ bool LoadBlockIndex(bool fAllowNew)
nStakeMinAge = 60 * 60 * 24; // test net min age is 1 day
nCoinbaseMaturity = 60;
bnInitialHashTarget = CBigNum(~uint256(0) >> 29);
nModifierInterval = 60 * 20; // test net modifier interval is 20 minutes
}

printf("%s Network: genesis=0x%s nBitsLimit=0x%08x nBitsInitial=0x%08x nStakeMinAge=%d nCoinbaseMaturity=%d\n",
Expand Down Expand Up @@ -3586,13 +3587,18 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake)
{
pblock->nBits = GetNextTargetRequired(pindexPrev, true);
CTransaction txCoinStake;
int64 nSearchTime = GetAdjustedTime();
int64 nSearchTime = txCoinStake.nTime; // search to current time
if (nSearchTime > nLastCoinStakeSearchTime)
{
if (pwallet->CreateCoinStake(*pwallet, pblock->nBits, nSearchTime-nLastCoinStakeSearchTime, txCoinStake))
{
pblock->vtx.push_back(txCoinStake);
pblock->vtx[0].vout[0].SetEmpty();
if (txCoinStake.nTime >= max(pindexPrev->GetMedianTimePast()+1, pindexPrev->GetBlockTime() - nMaxClockDrift))
{ // make sure coinstake would meet timestamp protocol
// as it would be the same as the block timestamp
pblock->vtx[0].vout[0].SetEmpty();
pblock->vtx[0].nTime = txCoinStake.nTime;
pblock->vtx.push_back(txCoinStake);
}
}
nLastCoinStakeSearchInterval = nSearchTime - nLastCoinStakeSearchTime;
nLastCoinStakeSearchTime = nSearchTime;
Expand Down Expand Up @@ -3687,7 +3693,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake)
continue;

// Timestamp limit
if (tx.nTime > GetAdjustedTime())
if (tx.nTime > GetAdjustedTime() || (pblock->IsProofOfStake() && tx.nTime > pblock->vtx[1].nTime))
continue;

// ppcoin: simplify transaction fee - allow free = false
Expand Down Expand Up @@ -3749,9 +3755,12 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake)
// Fill in header
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
if (pblock->IsProofOfStake())
pblock->nTime = pblock->vtx[1].nTime; //same as coinstake timestamp
pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, pblock->GetMaxTransactionTime());
pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift);
pblock->UpdateTime(pindexPrev);
if (pblock->IsProofOfWork())
pblock->UpdateTime(pindexPrev);
pblock->nNonce = 0;

return pblock.release();
Expand Down
2 changes: 1 addition & 1 deletion src/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ extern const std::string CLIENT_DATE;
// network protocol versioning
//

static const int PROTOCOL_VERSION = 60002;
static const int PROTOCOL_VERSION = 60003;

// earlier versions not supported as of Feb 2012, and are disconnected
// NOTE: as of bitcoin v0.6 message serialization (vSend, vRecv) still
Expand Down
16 changes: 9 additions & 7 deletions src/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1247,18 +1247,18 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int
CBlock block;
if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
continue;
if (block.GetBlockTime() + nStakeMinAge > txNew.nTime - nMaxClockDrift)
static int nMaxStakeSearchInterval = 60;
if (block.GetBlockTime() + nStakeMinAge > txNew.nTime - nMaxStakeSearchInterval)
continue; // only count coins meeting min age requirement

bool fKernelFound = false;
for (int n=0; n<min(nSearchInterval,(int64)5) && !fKernelFound && !fShutdown; n++)
for (unsigned int n=0; n<min(nSearchInterval,(int64)nMaxStakeSearchInterval) && !fKernelFound && !fShutdown; n++)
{
// Randomly pick a timestamp from protocol allowed range
txNew.nTime = GetAdjustedTime() - GetRandInt(nMaxClockDrift - 60);
// Calculate hash
// Search backward in time from the given txNew timestamp
// Search nSearchInterval seconds back up to nMaxStakeSearchInterval
uint256 hashProofOfStake = 0;
COutPoint prevoutStake = COutPoint(pcoin.first->GetHash(), pcoin.second);
if (CheckStakeKernelHash(nBits, block.nTime, txindex.pos.nTxPos - txindex.pos.nBlockPos, *pcoin.first, prevoutStake, txNew.nTime, hashProofOfStake))
if (CheckStakeKernelHash(nBits, block, txindex.pos.nTxPos - txindex.pos.nBlockPos, *pcoin.first, prevoutStake, txNew.nTime - n, hashProofOfStake))
{
// Found a kernel
if (fDebug && GetBoolArg("-printcoinstake"))
Expand Down Expand Up @@ -1295,7 +1295,8 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int
}
else
scriptPubKeyOut = scriptPubKeyKernel;


txNew.nTime -= n;
txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second));
nCredit += pcoin.first->vout[pcoin.second].nValue;
vwtxPrev.push_back(pcoin.first);
Expand All @@ -1305,6 +1306,7 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int
if (fDebug && GetBoolArg("-printcoinstake"))
printf("CreateCoinStake : added kernel type=%d\n", whichType);
fKernelFound = true;
break;
}
}
if (fKernelFound || fShutdown)
Expand Down

0 comments on commit b0b7eb2

Please sign in to comment.