map<COutPoint, CInPoint> mapNextTx;
map<uint256, CBlockIndex*> mapBlockIndex;
-uint256 hashGenesisBlock("0x00000000b00cf820bc2b23ec0aad05da6c58af7fd71c34a6f21a747fd0f5620b");
+uint256 hashGenesisBlock("0x000000006d52486334316794cc38ffeb7ebf35a7ebd661fd39f5f46b0d001575");
static CBigNum bnProofOfWorkLimit(~uint256(0) >> 32);
const int nInitialBlockThreshold = 120; // Regard blocks up until N-threshold as "initial download"
CBlockIndex* pindexGenesisBlock = NULL;
return pblock->GetHash();
}
-int64 static GetBlockValue(int nHeight, int64 nFees)
+int64 static GetBlockValue(unsigned int nBits, int64 nFees)
{
- int64 nSubsidy = 50 * COIN;
+ CBigNum bnSubsidyLimit = 9999 * COIN; // subsidy amount for difficulty 1
+ CBigNum bnTarget;
+ bnTarget.SetCompact(nBits);
+ CBigNum bnTargetLimit = bnProofOfWorkLimit;
+ bnTargetLimit.SetCompact(bnTargetLimit.GetCompact());
+
+ // ppcoin: subsidy is cut in half every 16x multiply of difficulty
+ // A reasonably continuous curve is used to avoid shock to market
+ // (nSubsidyLimit / nSubsidy) ** 4 == bnProofOfWorkLimit / bnTarget
+ CBigNum bnLowerBound = CENT;
+ CBigNum bnUpperBound = bnSubsidyLimit;
+ while (bnLowerBound + CENT <= bnUpperBound)
+ {
+ CBigNum bnMidValue = (bnLowerBound + bnUpperBound) / 2;
+ if (fDebug && GetBoolArg("-printcreation"))
+ printf("GetBlockValue() : lower=%"PRI64d" upper=%"PRI64d" mid=%"PRI64d"\n", bnLowerBound.getuint64(), bnUpperBound.getuint64(), bnMidValue.getuint64());
+ if (bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnTargetLimit > bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnTarget)
+ bnUpperBound = bnMidValue;
+ else
+ bnLowerBound = bnMidValue;
+ }
- // Subsidy is cut in half every 4 years
- nSubsidy >>= (nHeight / 210000);
+ int64 nSubsidy = bnUpperBound.getuint64();
+ nSubsidy = (nSubsidy / CENT) * CENT;
+ if (fDebug && GetBoolArg("-printcreation"))
+ printf("GetBlockValue() : nBits=0x%08x nSubsidy=%"PRI64d"\n", nBits, nSubsidy);
return nSubsidy + nFees;
}
static const int64 nTargetTimespan = 7 * 24 * 60 * 60; // one week
static const int64 nTargetSpacing = 10 * 60;
static const int64 nInterval = nTargetTimespan / nTargetSpacing;
+static const int64 nMaxClockDrift = 2 * 60 * 60; // 2 hours
//
// minimum amount of work that could possibly be required nTime after
int64 nTxFee = nValueIn - GetValueOut();
if (nTxFee < 0)
return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str()));
- if (nTxFee < nMinFee)
- return false;
+ if (nTxFee < nMinFee) //ppcoin: enforce transaction fees for every block
+ return fBlock? DoS(100, error("ConnectInputs() : %s not paying required fee=%s, paid=%s", GetHash().ToString().substr(0,10).c_str(), FormatMoney(nMinFee).c_str(), FormatMoney(nTxFee).c_str())) : false;
nFees += nTxFee;
if (!MoneyRange(nFees))
return DoS(100, error("ConnectInputs() : nFees out of range"));
CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos);
nTxPos += ::GetSerializeSize(tx, SER_DISK);
- if (!tx.ConnectInputs(txdb, mapQueuedChanges, posThisTx, pindex, nFees, true, false))
+ if (!tx.ConnectInputs(txdb, mapQueuedChanges, posThisTx, pindex, nFees, true, false, tx.GetMinFee()))
return false;
}
// Write queued txindex changes
return error("ConnectBlock() : UpdateTxIndex failed");
}
- if (vtx[0].GetValueOut() > GetBlockValue(pindex->nHeight, nFees))
+ if (vtx[0].GetValueOut() > GetBlockValue(nBits, nFees))
return false;
// Update block index on disk without changing it in memory.
}
if (!txdb.WriteHashBestChain(pindexNew->GetBlockHash()))
return error("Reorganize() : WriteHashBestChain failed");
- if (!txdb.WriteAutoCheckpoint(pindexNew->nCheckpoint))
+ if (!txdb.WriteAutoCheckpoint(Checkpoints::GetNextAutoCheckpoint(pindexNew->nCheckpoint)))
return error("Reorganize() : WriteAutoCheckpoint failed");
// Make sure it's successfully written to disk before changing memory structure
if (pindexGenesisBlock == NULL && hash == hashGenesisBlock)
{
txdb.WriteHashBestChain(hash);
- txdb.WriteAutoCheckpoint(pindexNew->nCheckpoint);
+ txdb.WriteAutoCheckpoint(Checkpoints::GetNextAutoCheckpoint(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) || !txdb.WriteAutoCheckpoint(pindexNew->nCheckpoint))
+ if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash) || !txdb.WriteAutoCheckpoint(Checkpoints::GetNextAutoCheckpoint(pindexNew->nCheckpoint)))
{
txdb.TxnAbort();
InvalidChainFound(pindexNew);
// 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()
{
- uint64 nCoinAge = 0;
+ CBigNum bnCentSecond = 0;
BOOST_FOREACH(const CTransaction& tx, vtx)
{
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
// First try finding the previous transaction in database
+ CTxDB txdb("r");
CTransaction txPrev;
- if (!txPrev.ReadFromDisk(txin.prevout))
- {
- // If database lookup fails try memory pool
- CRITICAL_BLOCK(cs_mapTransactions)
- {
- if (!mapTransactions.count(txin.prevout.hash))
- return 0; // Neither found in database nor memory pool
- txPrev = mapTransactions[txin.prevout.hash];
- }
- }
-
+ 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;
- CBigNum bnTxInCoinAge = CBigNum(nValueIn) * (tx.nTime - txPrev.nTime) / COIN / (24 * 60 * 60);
- nCoinAge += bnTxInCoinAge.getuint64();
+ bnCentSecond += CBigNum(nValueIn) * (tx.nTime-txPrev.nTime) / CENT;
if (fDebug && GetBoolArg("-printcoinage"))
- printf("coin age nValueIn=%-12I64d nTimeDiff=%d nCoinAge=%"PRI64d"\n", nValueIn, tx.nTime - txPrev.nTime, nCoinAge);
+ printf("coin age nValueIn=%-12I64d nTimeDiff=%d bnCentSecond=%s\n", nValueIn, tx.nTime - txPrev.nTime, bnCentSecond.ToString().c_str());
}
}
- if (!nCoinAge)
- nCoinAge = 1;
+ CBigNum bnCoinAge = bnCentSecond * CENT / COIN / (24 * 60 * 60);
+ if (bnCoinAge == 0)
+ bnCoinAge = 1;
- return nCoinAge;
+ return bnCoinAge.getuint64();
}
// ppcoin: compute chain trust score
uint64 nCoinAge = GetBlockCoinAge();
if (!nCoinAge)
- return error("AddToBlockIndex() : invalid or orphaned transaction in block");
+ return error("AddToBlockIndex() : invalid transaction in block");
pindexNew->nChainTrust = (pindexNew->pprev ? pindexNew->pprev->nChainTrust : 0) + nCoinAge;
CTxDB txdb;
return DoS(50, error("CheckBlock() : proof of work failed"));
// Check timestamp
- if (GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60)
+ if (GetBlockTime() > GetAdjustedTime() + nMaxClockDrift)
return error("CheckBlock() : block timestamp too far in the future");
// First transaction must be coinbase, the rest must not be
if (vtx[i].IsCoinBase())
return DoS(100, error("CheckBlock() : more than one coinbase"));
+ // Check coinbase timestamp
+ if (GetBlockTime() > (int64)vtx[0].nTime + nMaxClockDrift)
+ return DoS(50, error("CheckBlock() : coinbase timestamp is too early"));
+
// Check transactions
BOOST_FOREACH(const CTransaction& tx, vtx)
{
return DoS(100, error("AcceptBlock() : incorrect proof of work"));
// Check timestamp against prev
- if (GetBlockTime() <= pindexPrev->GetMedianTimePast())
+ if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || GetBlockTime() + nMaxClockDrift < pindexPrev->GetBlockTime())
return error("AcceptBlock() : block's timestamp is too early");
// Check that all transactions are finalized
// Genesis block
const char* pszTimestamp = "MarketWatch 07/Nov/2011 Gold tops $1,790 to end at over six-week high";
CTransaction txNew;
- txNew.nTime = 1324698231;
+ txNew.nTime = 1325878371;
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));
- txNew.vout[0].nValue = 50 * COIN;
+ txNew.vout[0].nValue = GetBlockValue(bnProofOfWorkLimit.GetCompact(), 0);
txNew.vout[0].scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG;
CBlock block;
block.vtx.push_back(txNew);
block.hashPrevBlock = 0;
block.hashMerkleRoot = block.BuildMerkleTree();
block.nVersion = 1;
- block.nTime = 1324707839;
- block.nBits = 0x1d00ffff;
- block.nNonce = 486102291;
+ block.nTime = 1325882536;
+ block.nBits = bnProofOfWorkLimit.GetCompact();
+ block.nNonce = 2081920190;
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("0x487e83bd2b5a5196a2a6b87998d779a162101cb02cc64bf9ac33289dd0c22352"));
+ assert(block.hashMerkleRoot == uint256("0xc7311b56de266580cca65be108ae53d7100b5c3b17da8b1106044103abd7a521"));
block.print();
assert(block.GetHash() == hashGenesisBlock);
}
}
}
- pblock->vtx[0].vout[0].nValue = GetBlockValue(pindexPrev->nHeight+1, nFees);
+ pblock->nBits = GetNextWorkRequired(pindexPrev);
+ pblock->vtx[0].vout[0].nValue = GetBlockValue(pblock->nBits, nFees);
// Fill in header
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->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift);
pblock->nNonce = 0;
return pblock.release();
// Update nTime every few seconds
pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());
pblock->nTime = max(pblock->GetBlockTime(), pblock->GetMaxTransactionTime());
+ pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift);
nBlockTime = ByteReverse(pblock->nTime);
+ if (pblock->GetBlockTime() >= (int64)pblock->vtx[0].nTime + nMaxClockDrift)
+ break; // need to update coinbase timestamp
}
}
}