// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2011 The Bitcoin developers
+// Copyright (c) 2011 The PPCoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file license.txt or http://www.opensource.org/licenses/mit-license.php.
#include "headers.h"
map<COutPoint, CInPoint> mapNextTx;
map<uint256, CBlockIndex*> mapBlockIndex;
-uint256 hashGenesisBlock("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
+uint256 hashGenesisBlock("0x00000000b00cf820bc2b23ec0aad05da6c58af7fd71c34a6f21a747fd0f5620b");
static CBigNum bnProofOfWorkLimit(~uint256(0) >> 32);
const int nInitialBlockThreshold = 120; // Regard blocks up until N-threshold as "initial download"
CBlockIndex* pindexGenesisBlock = NULL;
int nBestHeight = -1;
-CBigNum bnBestChainWork = 0;
-CBigNum bnBestInvalidWork = 0;
+uint64 nBestChainTrust = 0;
+uint64 nBestInvalidTrust = 0;
uint256 hashBestChain = 0;
CBlockIndex* pindexBest = NULL;
int64 nTimeBestReceived = 0;
// Settings
int fGenerateBitcoins = false;
-int64 nTransactionFee = 0;
+int64 nTransactionFee = MIN_TX_FEE;
int fLimitProcessors = false;
int nLimitProcessors = 1;
int fMinimizeToTray = true;
}
// Don't accept it if it can't get into a block
- if (nFees < GetMinFee(1000, true, true))
+ if (nFees < GetMinFee(1000, false, true))
return error("AcceptToMemoryPool() : not enough fees");
// Continuously rate-limit free transactions
return nSubsidy + nFees;
}
-unsigned int static GetNextWorkRequired(const CBlockIndex* pindexLast)
+static const int64 nTargetTimespan = 7 * 24 * 60 * 60; // one week
+static const int64 nTargetSpacing = 10 * 60;
+static const int64 nInterval = nTargetTimespan / nTargetSpacing;
+
+//
+// minimum amount of work that could possibly be required nTime after
+// minimum work required was nBase
+//
+unsigned int ComputeMinWork(unsigned int nBase, int64 nTime)
{
- const int64 nTargetTimespan = 14 * 24 * 60 * 60; // two weeks
- const int64 nTargetSpacing = 10 * 60;
- const int64 nInterval = nTargetTimespan / nTargetSpacing;
+ CBigNum bnResult;
+ bnResult.SetCompact(nBase);
+ while (nTime > 0 && bnResult < bnProofOfWorkLimit)
+ {
+ // Maximum 400% adjustment...
+ bnResult *= 4;
+ // ... in best-case exactly 4-times-normal target time
+ nTime -= nTargetTimespan*4;
+ }
+ if (bnResult > bnProofOfWorkLimit)
+ bnResult = bnProofOfWorkLimit;
+ return bnResult.GetCompact();
+}
- // Genesis block
- if (pindexLast == NULL)
+unsigned int static GetNextWorkRequired(const CBlockIndex* pindexLast)
+{
+ // Genesis block and first block
+ if (pindexLast == NULL || pindexLast->pprev == NULL)
return bnProofOfWorkLimit.GetCompact();
- // Only change once per interval
- if ((pindexLast->nHeight+1) % nInterval != 0)
- return pindexLast->nBits;
-
- // Go back by what we want to be 14 days worth of blocks
- const CBlockIndex* pindexFirst = pindexLast;
- for (int i = 0; pindexFirst && i < nInterval-1; i++)
- pindexFirst = pindexFirst->pprev;
- assert(pindexFirst);
-
- // Limit adjustment step
- int64 nActualTimespan = pindexLast->GetBlockTime() - pindexFirst->GetBlockTime();
- printf(" nActualTimespan = %"PRI64d" before bounds\n", nActualTimespan);
- if (nActualTimespan < nTargetTimespan/4)
- nActualTimespan = nTargetTimespan/4;
- if (nActualTimespan > nTargetTimespan*4)
- nActualTimespan = nTargetTimespan*4;
-
- // Retarget
+ int64 nActualSpacing = pindexLast->GetBlockTime() - pindexLast->pprev->GetBlockTime();
+
+ // ppcoin: target change every block
+ // ppcoin: retarget with exponential moving toward target spacing
CBigNum bnNew;
bnNew.SetCompact(pindexLast->nBits);
- bnNew *= nActualTimespan;
- bnNew /= nTargetTimespan;
+ bnNew *= ((nInterval - 1) * nTargetSpacing + nActualSpacing + nActualSpacing);
+ bnNew /= ((nInterval + 1) * nTargetSpacing);
if (bnNew > bnProofOfWorkLimit)
bnNew = bnProofOfWorkLimit;
- /// debug print
- printf("GetNextWorkRequired RETARGET\n");
- printf("nTargetTimespan = %"PRI64d" nActualTimespan = %"PRI64d"\n", nTargetTimespan, nActualTimespan);
- printf("Before: %08x %s\n", pindexLast->nBits, CBigNum().SetCompact(pindexLast->nBits).getuint256().ToString().c_str());
- printf("After: %08x %s\n", bnNew.GetCompact(), bnNew.getuint256().ToString().c_str());
-
return bnNew.GetCompact();
}
void static InvalidChainFound(CBlockIndex* pindexNew)
{
- if (pindexNew->bnChainWork > bnBestInvalidWork)
+ if (pindexNew->nChainTrust > nBestInvalidTrust)
{
- bnBestInvalidWork = pindexNew->bnChainWork;
- CTxDB().WriteBestInvalidWork(bnBestInvalidWork);
+ nBestInvalidTrust = pindexNew->nChainTrust;
+ CTxDB().WriteBestInvalidTrust(nBestInvalidTrust);
MainFrameRepaint();
}
- printf("InvalidChainFound: invalid block=%s height=%d work=%s\n", pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight, pindexNew->bnChainWork.ToString().c_str());
- printf("InvalidChainFound: current best=%s height=%d work=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainWork.ToString().c_str());
- if (pindexBest && bnBestInvalidWork > bnBestChainWork + pindexBest->GetBlockWork() * 6)
+ printf("InvalidChainFound: invalid block=%s height=%d trust=%s\n", pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight, CBigNum(pindexNew->nChainTrust).ToString().c_str());
+ printf("InvalidChainFound: current best=%s height=%d trust=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str());
+ if (pindexBest && nBestInvalidTrust > nBestChainTrust + pindexBest->GetBlockTrust() * 6)
printf("InvalidChainFound: WARNING: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade.\n");
}
if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile)
return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight);
+ // ppcoin: check transaction timestamp
+ if (txPrev.nTime > nTime)
+ return DoS(100, error("ConnectInputs() : transaction timestamp earlier than input transaction"));
+
// Skip ECDSA signature verification when connecting blocks (fBlock=true) during initial download
// (before the last blockchain checkpoint). This is safe because block merkle hashes are
// still computed and checked, and any change will be caught at the next checkpoint.
}
if (!txdb.WriteHashBestChain(pindexNew->GetBlockHash()))
return error("Reorganize() : WriteHashBestChain failed");
+ if (!txdb.WriteAutoCheckpoint(pindexNew->nCheckpoint))
+ return error("Reorganize() : WriteAutoCheckpoint failed");
// Make sure it's successfully written to disk before changing memory structure
if (!txdb.TxnCommit())
if (pindexGenesisBlock == NULL && hash == hashGenesisBlock)
{
txdb.WriteHashBestChain(hash);
+ txdb.WriteAutoCheckpoint(pindexNew->nCheckpoint);
if (!txdb.TxnCommit())
return error("SetBestChain() : TxnCommit failed");
pindexGenesisBlock = pindexNew;
else if (hashPrevBlock == hashBestChain)
{
// Adding to current best branch
- if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash))
+ if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash) || !txdb.WriteAutoCheckpoint(pindexNew->nCheckpoint))
{
txdb.TxnAbort();
InvalidChainFound(pindexNew);
hashBestChain = hash;
pindexBest = pindexNew;
nBestHeight = pindexBest->nHeight;
- bnBestChainWork = pindexNew->bnChainWork;
+ nBestChainTrust = pindexNew->nChainTrust;
nTimeBestReceived = GetTime();
nTransactionsUpdated++;
- printf("SetBestChain: new best=%s height=%d work=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainWork.ToString().c_str());
+ Checkpoints::AdvanceAutoCheckpoint(pindexBest->nCheckpoint);
+ printf("SetBestChain: new best=%s height=%d trust=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str());
return true;
}
+// ppcoin: total coin age spent in block, in the unit of coin-days.
+// Only those coins last spent at least a week ago count. As those
+// transactions not in main chain are not currently indexed so we
+// might not find out about their coin age. Older transactions are
+// guaranteed to be in main chain by auto checkpoint. This rule is
+// introduced to help nodes establish a consistent view of the coin
+// age (trust score) of competing branches.
+uint64 CBlock::GetBlockCoinAge()
+{
+ CBigNum bnCentSecond = 0;
+
+ BOOST_FOREACH(const CTransaction& tx, vtx)
+ {
+ if (tx.IsCoinBase())
+ continue;
+
+ BOOST_FOREACH(const CTxIn& txin, tx.vin)
+ {
+ // First try finding the previous transaction in database
+ CTxDB txdb("r");
+ CTransaction txPrev;
+ CTxIndex txindex;
+ if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
+ continue; // previous transaction not in main chain
+ if (tx.nTime < txPrev.nTime)
+ return 0; // Transaction timestamp violation
+
+ // Read block header
+ CBlock block;
+ if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
+ return 0; // unable to read block of previous transaction
+ if (block.GetBlockTime() + AUTO_CHECKPOINT_TRUST_SPAN > tx.nTime)
+ continue; // only count coins from at least one week ago
+
+ int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
+ bnCentSecond += CBigNum(nValueIn) * (tx.nTime-txPrev.nTime) / CENT;
+
+ if (fDebug && GetBoolArg("-printcoinage"))
+ printf("coin age nValueIn=%-12I64d nTimeDiff=%d bnCentSecond=%s\n", nValueIn, tx.nTime - txPrev.nTime, bnCentSecond.ToString().c_str());
+ }
+ }
+
+ CBigNum bnCoinAge = bnCentSecond * CENT / COIN / (24 * 60 * 60);
+ if (bnCoinAge == 0)
+ bnCoinAge = 1;
+
+ return bnCoinAge.getuint64();
+}
+
+
bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos)
{
// Check for duplicate
if (!pindexNew)
return error("AddToBlockIndex() : new CBlockIndex failed");
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
+
pindexNew->phashBlock = &((*mi).first);
map<uint256, CBlockIndex*>::iterator miPrev = mapBlockIndex.find(hashPrevBlock);
if (miPrev != mapBlockIndex.end())
{
pindexNew->pprev = (*miPrev).second;
pindexNew->nHeight = pindexNew->pprev->nHeight + 1;
+
+ // ppcoin: compute chain checkpoint
+ pindexNew->nCheckpoint = Checkpoints::GetNextChainCheckpoint(pindexNew->pprev);
+ assert (pindexNew->nCheckpoint >= pindexNew->pprev->nCheckpoint);
}
- pindexNew->bnChainWork = (pindexNew->pprev ? pindexNew->pprev->bnChainWork : 0) + pindexNew->GetBlockWork();
+
+ // ppcoin: compute chain trust score
+ uint64 nCoinAge = GetBlockCoinAge();
+ if (!nCoinAge)
+ return error("AddToBlockIndex() : invalid transaction in block");
+ pindexNew->nChainTrust = (pindexNew->pprev ? pindexNew->pprev->nChainTrust : 0) + nCoinAge;
CTxDB txdb;
txdb.TxnBegin();
return false;
// New best
- if (pindexNew->bnChainWork > bnBestChainWork)
+ if (pindexNew->nChainTrust > nBestChainTrust)
if (!SetBestChain(txdb, pindexNew))
return false;
// Check transactions
BOOST_FOREACH(const CTransaction& tx, vtx)
+ {
if (!tx.CheckTransaction())
return DoS(tx.nDoS, error("CheckBlock() : CheckTransaction failed"));
+ // ppcoin: check transaction timestamp
+ if (GetBlockTime() < (int64)tx.nTime)
+ return DoS(50, error("CheckBlock() : block timestamp earlier than transaction timestamp"));
+ }
// Check that it's not full of nonstandard transactions
if (GetSigOpCount() > MAX_BLOCK_SIGOPS)
if (!tx.IsFinal(nHeight, GetBlockTime()))
return DoS(10, error("AcceptBlock() : contains a non-final transaction"));
- // Check that the block chain matches the known block chain up to a checkpoint
- if (!Checkpoints::CheckBlock(nHeight, hash))
- return DoS(100, error("AcceptBlock() : rejected by checkpoint lockin at %d", nHeight));
+ // Check that the block chain matches the known block chain up to a hardened checkpoint
+ if (!Checkpoints::CheckHardened(nHeight, hash))
+ return DoS(100, error("AcceptBlock() : rejected by hardened checkpoint lockin at %d", nHeight));
+
+ // ppcoin: check that the block satisfies automatic checkpoint
+ if (!Checkpoints::CheckAuto(pindexPrev))
+ return DoS(100, error("AcceptBlock() : rejected by automatic checkpoint at %d", Checkpoints::nAutoCheckpoint));
// Write block to history file
if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK)))
if (!pblock->CheckBlock())
return error("ProcessBlock() : CheckBlock FAILED");
+ CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex);
+ if (pcheckpoint && pblock->hashPrevBlock != hashBestChain)
+ {
+ // Extra checks to prevent "fill up memory by spamming with bogus blocks"
+ int64 deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime;
+ if (deltaTime < 0)
+ {
+ pfrom->Misbehaving(100);
+ return error("ProcessBlock() : block with timestamp before last checkpoint");
+ }
+ CBigNum bnNewBlock;
+ bnNewBlock.SetCompact(pblock->nBits);
+ CBigNum bnRequired;
+ bnRequired.SetCompact(ComputeMinWork(pcheckpoint->nBits, deltaTime));
+ if (bnNewBlock > bnRequired)
+ {
+ pfrom->Misbehaving(100);
+ return error("ProcessBlock() : block with too little proof-of-work");
+ }
+ }
+
+
// If don't already have its previous block, shunt it off to holding area until we get it
if (!mapBlockIndex.count(pblock->hashPrevBlock))
{
// vMerkleTree: 4a5e1e
// Genesis block
- const char* pszTimestamp = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks";
+ const char* pszTimestamp = "MarketWatch 07/Nov/2011 Gold tops $1,790 to end at over six-week high";
CTransaction txNew;
+ txNew.nTime = 1324698231;
txNew.vin.resize(1);
txNew.vout.resize(1);
txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector<unsigned char>((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp));
block.hashPrevBlock = 0;
block.hashMerkleRoot = block.BuildMerkleTree();
block.nVersion = 1;
- block.nTime = 1231006505;
+ block.nTime = 1324707839;
block.nBits = 0x1d00ffff;
- block.nNonce = 2083236893;
+ block.nNonce = 486102291;
if (fTestNet)
{
printf("%s\n", block.GetHash().ToString().c_str());
printf("%s\n", hashGenesisBlock.ToString().c_str());
printf("%s\n", block.hashMerkleRoot.ToString().c_str());
- assert(block.hashMerkleRoot == uint256("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"));
+ assert(block.hashMerkleRoot == uint256("0x487e83bd2b5a5196a2a6b87998d779a162101cb02cc64bf9ac33289dd0c22352"));
block.print();
assert(block.GetHash() == hashGenesisBlock);
}
// Longer invalid proof-of-work chain
- if (pindexBest && bnBestInvalidWork > bnBestChainWork + pindexBest->GetBlockWork() * 6)
+ if (pindexBest && nBestInvalidTrust > nBestChainTrust + pindexBest->GetBlockTrust() * 6)
{
nPriority = 2000;
strStatusBar = strRPC = "WARNING: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade.";
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
continue;
- // Transaction fee required depends on block size
- bool fAllowFree = (nBlockSize + nTxSize < 4000 || CTransaction::AllowFree(dPriority));
- int64 nMinFee = tx.GetMinFee(nBlockSize, fAllowFree, true);
+ // Timestamp limit
+ if (tx.nTime > GetAdjustedTime())
+ continue;
+
+ // ppcoin: simplify transaction fee - allow free = false
+ int64 nMinFee = tx.GetMinFee(nBlockSize, false, true);
// Connecting shouldn't fail due to dependency on other memory pool transactions
// because we're already processing them in order of dependency
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());
+ pblock->nTime = max(pblock->GetBlockTime(), pblock->GetMaxTransactionTime());
pblock->nBits = GetNextWorkRequired(pindexPrev);
pblock->nNonce = 0;
// Update nTime every few seconds
pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());
+ pblock->nTime = max(pblock->GetBlockTime(), pblock->GetMaxTransactionTime());
nBlockTime = ByteReverse(pblock->nTime);
}
}