PPCoin: Coinstake timestamp rule
[novacoin.git] / src / main.cpp
index a857edc..3552d4e 100644 (file)
@@ -29,7 +29,7 @@ unsigned int nTransactionsUpdated = 0;
 map<COutPoint, CInPoint> mapNextTx;
 
 map<uint256, CBlockIndex*> mapBlockIndex;
-set<COutPoint> setStakeSeen;
+set<pair<COutPoint, unsigned int> > setStakeSeen;
 uint256 hashGenesisBlock("0x000000006d52486334316794cc38ffeb7ebf35a7ebd661fd39f5f46b0d001575");
 static CBigNum bnProofOfWorkLimit(~uint256(0) >> 32);
 const int nInitialBlockThreshold = 120; // Regard blocks up until N-threshold as "initial download"
@@ -45,7 +45,7 @@ CMedianFilter<int> cPeerBlockCounts(5, 0); // Amount of blocks that other nodes
 
 map<uint256, CBlock*> mapOrphanBlocks;
 multimap<uint256, CBlock*> mapOrphanBlocksByPrev;
-set<COutPoint> setStakeSeenOrphan;
+set<pair<COutPoint, unsigned int> > setStakeSeenOrphan;
 
 map<uint256, CDataStream*> mapOrphanTransactions;
 multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev;
@@ -710,10 +710,10 @@ int64 GetProofOfStakeReward(int64 nCoinAge)
     return nSubsidy;
 }
 
-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
+static const int64 nTargetTimespan = 7 * 24 * 60 * 60;  // one week
+static const int64 nTargetSpacingStake = 10 * 60;       // ten minutes
+static const int64 nTargetSpacingWorkMax = 2 * 60 * 60; // two hours
+static const int64 nMaxClockDrift = 2 * 60 * 60;        // two hours
 
 //
 // minimum amount of work that could possibly be required nTime after
@@ -761,6 +761,8 @@ unsigned int static GetNextTargetRequired(const CBlockIndex* pindexLast, bool fP
     // ppcoin: retarget with exponential moving toward target spacing
     CBigNum bnNew;
     bnNew.SetCompact(pindexPrev->nBits);
+    int64 nTargetSpacing = fProofOfStake? nTargetSpacingStake : min(nTargetSpacingWorkMax, nTargetSpacingStake * (1 + pindexLast->nHeight - pindexPrev->nHeight));
+    int64 nInterval = nTargetTimespan / nTargetSpacing;
     bnNew *= ((nInterval - 1) * nTargetSpacing + nActualSpacing + nActualSpacing);
     bnNew /= ((nInterval + 1) * nTargetSpacing);
 
@@ -1079,10 +1081,6 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex)
     if (!CheckBlock())
         return false;
 
-    // ppcoin: coin stake tx must meet target protocol
-    if (IsProofOfStake() && !vtx[1].CheckProofOfStake(txdb, nBits))
-        return error("ConnectBlock() : Block %s unable to meet hash target for coinstake", GetHash().ToString().c_str());
-
     //// issue here: it doesn't know the version
     unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size());
 
@@ -1306,7 +1304,7 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
 //   quantities so as to generate blocks faster, degrading the system back into
 //   a proof-of-work situation.
 //
-bool CTransaction::CheckProofOfStake(CTxDB& txdb, unsigned int nBits) const
+bool CTransaction::CheckProofOfStake(unsigned int nBits) const
 {
     CBigNum bnTargetPerCoinDay;
     bnTargetPerCoinDay.SetCompact(nBits);
@@ -1318,13 +1316,19 @@ bool CTransaction::CheckProofOfStake(CTxDB& txdb, unsigned int nBits) const
     const CTxIn& txin = vin[0];
 
     // First try finding the previous transaction in database
+    CTxDB txdb("r");
     CTransaction txPrev;
     CTxIndex txindex;
     if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
         return false;  // previous transaction not in main chain
+    txdb.Close();
     if (nTime < txPrev.nTime)
         return false;  // Transaction timestamp violation
 
+    // Verify signature
+    if (!VerifySignature(txPrev, *this, 0))
+        return DoS(100, error("CheckProofOfStake() : VerifySignature failed on coinstake %s", GetHash().ToString().c_str()));
+
     // Read block header
     CBlock block;
     if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
@@ -1340,7 +1344,7 @@ bool CTransaction::CheckProofOfStake(CTxDB& txdb, unsigned int nBits) const
     if (CBigNum(Hash(ss.begin(), ss.end())) <= bnCoinDay * bnTargetPerCoinDay)
         return true;
     else
-        return false;
+        return DoS(100, error("CheckProofOfStake() : check target failed on coinstake %s", GetHash().ToString().c_str()));
 }
 
 // ppcoin: total coin age spent in transaction, in the unit of coin-days.
@@ -1425,7 +1429,7 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos)
         return error("AddToBlockIndex() : new CBlockIndex failed");
     map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
     if (pindexNew->fProofOfStake) 
-        setStakeSeen.insert(pindexNew->prevoutStake);
+        setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime));
 
     pindexNew->phashBlock = &((*mi).first);
     map<uint256, CBlockIndex*>::iterator miPrev = mapBlockIndex.find(hashPrevBlock);
@@ -1510,6 +1514,10 @@ bool CBlock::CheckBlock() const
     if (GetBlockTime() > (int64)vtx[0].nTime + nMaxClockDrift)
         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"));
+
     // Check transactions
     BOOST_FOREACH(const CTransaction& tx, vtx)
     {
@@ -1528,14 +1536,7 @@ bool CBlock::CheckBlock() const
     if (hashMerkleRoot != BuildMerkleTree())
         return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch"));
 
-    // Coin base vout[0] scriptPubKey must be the same as coin stake vout[1]
-    // scriptPubKey
-    if (vtx.size() > 1 && vtx[1].IsCoinStake() &&
-        vtx[0].vout[0].scriptPubKey != vtx[1].vout[1].scriptPubKey)
-        return DoS(100, error("CheckBlock() : block key mismatch"));
-        
-
-    // Check block signature
+    // ppcoin: check block signature
     if (!CheckBlockSignature())
         return DoS(100, error("CheckBlock() : bad block signature"));
 
@@ -1556,19 +1557,14 @@ bool CBlock::AcceptBlock()
     CBlockIndex* pindexPrev = (*mi).second;
     int nHeight = pindexPrev->nHeight+1;
 
-    // ppcoin: check for coinstake duplicate
-    if (IsProofOfStake())
-    {   // check if coinstake is already connected; that would imply the owner
-        // of the coinstake sent multiple blocks with the same coinstake
-        CTxIndex txindex;
-        if (CTxDB("r").ReadTxIndex(vtx[1].GetHash(), txindex))
-            return error("AcceptBlock() : block %s has duplicate coinstake %s", hash.ToString().c_str(), vtx[1].GetHash().ToString().c_str());
-    }
-
     // Check proof-of-work or proof-of-stake
     if (nBits != GetNextTargetRequired(pindexPrev, IsProofOfStake()))
         return DoS(100, error("AcceptBlock() : incorrect proof-of-work/proof-of-stake"));
 
+    // ppcoin: coinstake tx must meet target protocol
+    if (IsProofOfStake() && !vtx[1].CheckProofOfStake(nBits))
+        return error("AcceptBlock() : Block %s unable to meet hash target for coinstake", GetHash().ToString().c_str());
+
     // Check timestamp against prev
     if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || GetBlockTime() + nMaxClockDrift < pindexPrev->GetBlockTime())
         return error("AcceptBlock() : block's timestamp is too early");
@@ -1603,6 +1599,9 @@ bool CBlock::AcceptBlock()
                 if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : 140700))
                     pnode->PushInventory(CInv(MSG_BLOCK, hash));
 
+    // ppcoin: check pending sync-checkpoint
+    Checkpoints::AcceptPendingSyncCheckpoint(hash);
+
     return true;
 }
 
@@ -1616,8 +1615,10 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
         return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,20).c_str());
 
     // ppcoin: check proof-of-stake
-    if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()))
-        return error("ProcessBlock() : duplicate proof-of-stake (%s) for block %s", pblock->GetProofOfStake().ToString().c_str(), hash.ToString().c_str());
+    // Limited duplicity on stake: prevents block flood attack
+    // Duplicate stake allowed only when there is orphan child block
+    if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash))
+        return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str());
 
     // Preliminary checks
     if (!pblock->CheckBlock())
@@ -1653,8 +1654,10 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
         // ppcoin: check proof-of-stake
         if (pblock2->IsProofOfStake())
         {
-            if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()))
-                return error("ProcessBlock() : duplicate proof-of-stake (%s) for orphan block %s", pblock2->GetProofOfStake().ToString().c_str(), hash.ToString().c_str());
+            // Limited duplicity on stake: prevents block flood attack
+            // Duplicate stake allowed only when there is orphan child block
+            if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash))
+                return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for orphan block %s", pblock2->GetProofOfStake().first.ToString().c_str(), pblock2->GetProofOfStake().second, hash.ToString().c_str());
             else
                 setStakeSeenOrphan.insert(pblock2->GetProofOfStake());
         }
@@ -1837,6 +1840,13 @@ bool LoadBlockIndex(bool fAllowNew)
             return error("LoadBlockIndex() : writing genesis block to disk failed");
         if (!block.AddToBlockIndex(nFile, nBlockPos))
             return error("LoadBlockIndex() : genesis block not accepted");
+
+        // ppcoin: initialize synchronized checkpoint
+        CTxDB txdbc;
+        if (!txdbc.WriteSyncCheckpoint(hashGenesisBlock))
+            return error("LoadBlockIndex() : failed to init sync checkpoint");
+        txdbc.Close();
+        Checkpoints::hashSyncCheckpoint = hashGenesisBlock;
     }
 
     return true;
@@ -2162,6 +2172,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
             BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts)
                 item.second.RelayTo(pfrom);
 
+        // ppcoin: relay sync-checkpoint
+        CRITICAL_BLOCK(Checkpoints::cs_hashSyncCheckpoint)
+            if (!Checkpoints::checkpointMessage.IsNull())
+                Checkpoints::checkpointMessage.RelayTo(pfrom);
+
         pfrom->fSuccessfullyConnected = true;
 
         printf("version message: version %d, blocks=%d\n", pfrom->nVersion, pfrom->nStartingHeight);
@@ -2575,6 +2590,20 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
         }
     }
 
+    else if (strCommand == "checkpoint")
+    {
+        CSyncCheckpoint checkpoint;
+        vRecv >> checkpoint;
+
+        if (checkpoint.ProcessSyncCheckpoint(pfrom))
+        {
+            // Relay
+            pfrom->hashCheckpointKnown = checkpoint.hashCheckpoint;
+            CRITICAL_BLOCK(cs_vNodes)
+                BOOST_FOREACH(CNode* pnode, vNodes)
+                    checkpoint.RelayTo(pnode);
+        }
+    }
 
     else
     {
@@ -3021,7 +3050,7 @@ CBlock* CreateNewBlock(CWallet* pwallet)
     pblock->vtx.push_back(txNew);
 
     // ppcoin: if coinstake available add coinstake tx
-    static unsigned int nLastCoinStakeCheckTime = GetAdjustedTime() - nMaxClockDrift;  // only initialized at startup
+    static unsigned int nLastCoinStakeCheckTime = GetAdjustedTime() - nMaxClockDrift + 60;  // only initialized at startup
     CBlockIndex* pindexPrev = pindexBest;
     while (nLastCoinStakeCheckTime < GetAdjustedTime())
     {
@@ -3031,7 +3060,9 @@ CBlock* CreateNewBlock(CWallet* pwallet)
         CTransaction txCoinStake;
         CRITICAL_BLOCK(cs)
         {
-            nLastCoinStakeCheckTime++;
+            // mining may have been suspended for a while so 
+            // need to take max to satisfy the timestamp protocol
+            nLastCoinStakeCheckTime = max(++nLastCoinStakeCheckTime, (unsigned int) (GetAdjustedTime() - nMaxClockDrift + 60));
             txCoinStake.nTime = nLastCoinStakeCheckTime;
         }
         if (pwallet->CreateCoinStake(pblock->nBits, txCoinStake))
@@ -3163,7 +3194,8 @@ CBlock* CreateNewBlock(CWallet* pwallet)
             }
         }
     }
-    pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits);
+    if (pblock->IsProofOfWork())
+        pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits);
 
     // Fill in header
     pblock->hashPrevBlock  = pindexPrev->GetBlockHash();