PPCoin: Preliminary check on coinstake duplicate
[novacoin.git] / src / main.cpp
index 2769450..e229063 100644 (file)
@@ -731,13 +731,27 @@ unsigned int ComputeMinWork(unsigned int nBase, int64 nTime)
     return bnResult.GetCompact();
 }
 
-unsigned int static GetNextWorkRequired(const CBlockIndex* pindexLast)
+// ppcoin: find last block index up to pindex
+const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake)
+{
+    while (pindex && (pindex->IsProofOfStake() != fProofOfStake))
+        pindex = pindex->pprev;
+    return pindex;
+}
+
+unsigned int static GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake)
 {
     // Genesis block and first block
     if (pindexLast == NULL || pindexLast->pprev == NULL)
         return bnProofOfWorkLimit.GetCompact();
 
-    int64 nActualSpacing = pindexLast->GetBlockTime() - pindexLast->pprev->GetBlockTime();
+    const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake);
+    if (pindexPrev == NULL) 
+        return bnProofOfWorkLimit.GetCompact();
+    const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake);
+    if (pindexPrevPrev == NULL)
+        return bnProofOfWorkLimit.GetCompact();
+    int64 nActualSpacing = pindexPrev->GetBlockTime() - pindexPrevPrev->GetBlockTime();
 
     // ppcoin: target change every block
     // ppcoin: retarget with exponential moving toward target spacing
@@ -1061,8 +1075,12 @@ 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) - 1 + GetSizeOfCompactSize(vtx.size());
+    unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size());
 
     map<uint256, CTxIndex> mapQueuedChanges;
     int64 nFees = 0;
@@ -1264,6 +1282,57 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
 }
 
 
+// ppcoin: coinstake must meet hash target according to the protocol:
+// at least one input must meet the formula
+//     hash(nBits + txPrev.block.nTime + txPrev.nTime + nTime) < bnTarget * nCoinDay
+// 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, prevents computing hash in advance
+//   txPrev.block.nTime: prevent nodes from guessing a good timestamp to generate
+//       transaction for future advantage
+//   txPrev.nTime: prevent nodes from meeting target simultaneously
+//   block/tx hash should not be used here as they can be generated in vast quatities
+//       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
+{
+    CBigNum bnTargetPerCoinDay;
+    bnTargetPerCoinDay.SetCompact(nBits);
+    if (!IsCoinStake())
+        return true;
+
+    BOOST_FOREACH(const CTxIn& txin, vin)
+    {
+        // First try finding the previous transaction in database
+        CTransaction txPrev;
+        CTxIndex txindex;
+        if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
+            continue;  // previous transaction not in main chain
+        if (nTime < txPrev.nTime)
+            return false;  // Transaction timestamp violation
+
+        // Read block header
+        CBlock block;
+        if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
+            return false; // unable to read block of previous transaction
+        if (block.GetBlockTime() + AUTO_CHECKPOINT_TRUST_SPAN > nTime)
+            continue; // only count coins from at least one week ago
+
+        int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
+        CBigNum bnCoinDay = CBigNum(nValueIn) * (nTime-txPrev.nTime) / COIN / (24 * 60 * 60);
+        // Calculate hash
+        CDataStream ss(SER_GETHASH, VERSION);
+        ss << nBits << block.nTime << txPrev.nTime << nTime;
+        if (CBigNum(Hash(ss.begin(), ss.end())) <= bnCoinDay * bnTargetPerCoinDay)
+            return true;
+    }
+
+    return false;
+}
+
 // ppcoin: total coin age spent in transaction, 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
@@ -1402,7 +1471,7 @@ bool CBlock::CheckBlock() const
         return DoS(100, error("CheckBlock() : size limits failed"));
 
     // Check proof of work matches claimed amount
-    if (!CheckProofOfWork(GetHash(), nBits))
+    if (IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits))
         return DoS(50, error("CheckBlock() : proof of work failed"));
 
     // Check timestamp
@@ -1443,6 +1512,13 @@ 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
     if (!CheckBlockSignature())
         return DoS(100, error("CheckBlock() : bad block signature"));
@@ -1464,9 +1540,18 @@ bool CBlock::AcceptBlock()
     CBlockIndex* pindexPrev = (*mi).second;
     int nHeight = pindexPrev->nHeight+1;
 
-    // Check proof of work
-    if (nBits != GetNextWorkRequired(pindexPrev))
-        return DoS(100, error("AcceptBlock() : incorrect proof of work"));
+    // 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"));
 
     // Check timestamp against prev
     if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || GetBlockTime() + nMaxClockDrift < pindexPrev->GetBlockTime())
@@ -1532,7 +1617,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
         bnNewBlock.SetCompact(pblock->nBits);
         CBigNum bnRequired;
         bnRequired.SetCompact(ComputeMinWork(pcheckpoint->nBits, deltaTime));
-        if (bnNewBlock > bnRequired)
+        if (pblock->IsProofOfWork() && bnNewBlock > bnRequired)
         {
             pfrom->Misbehaving(100);
             return error("ProcessBlock() : block with too little proof-of-work");
@@ -1714,6 +1799,7 @@ bool LoadBlockIndex(bool fAllowNew)
         assert(block.hashMerkleRoot == uint256("0xc7311b56de266580cca65be108ae53d7100b5c3b17da8b1106044103abd7a521"));
         block.print();
         assert(block.GetHash() == hashGenesisBlock);
+        assert(block.CheckBlock());
 
         // Start new block file
         unsigned int nFile;
@@ -2906,9 +2992,25 @@ CBlock* CreateNewBlock(CWallet* pwallet)
     pblock->vtx.push_back(txNew);
 
     // ppcoin: if coinstake available add coinstake tx
-    CTransaction txCoinStake;
-    if (pwallet->CreateCoinStake(txNew.vout[0].scriptPubKey, txCoinStake))
-        pblock->vtx.push_back(txCoinStake);
+    static unsigned int nLastCoinStakeCheckTime = GetAdjustedTime() - nMaxClockDrift;  // only initialized at startup
+    pblock->nBits = GetNextTargetRequired(pindexPrev, true);
+    while (nLastCoinStakeCheckTime < GetAdjustedTime())
+    {
+        static CCriticalSection cs;
+        CTransaction txCoinStake;
+        CRITICAL_BLOCK(cs)
+        {
+            nLastCoinStakeCheckTime++;
+            txCoinStake.nTime = nLastCoinStakeCheckTime;
+        }
+        if (pwallet->CreateCoinStake(txNew.vout[0].scriptPubKey, pblock->nBits, txCoinStake))
+        {
+            pblock->vtx.push_back(txCoinStake);
+            break;
+        }
+    }
+    if (pblock->IsProofOfWork())
+        pblock->nBits = GetNextTargetRequired(pindexPrev, false);
 
     // Collect memory pool transactions into the block
     int64 nFees = 0;
@@ -3029,7 +3131,6 @@ CBlock* CreateNewBlock(CWallet* pwallet)
             }
         }
     }
-    pblock->nBits          = GetNextWorkRequired(pindexPrev);
     pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits);
 
     // Fill in header
@@ -3110,12 +3211,12 @@ bool CheckWork(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey)
     uint256 hash = pblock->GetHash();
     uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
 
-    if (hash > hashTarget)
-        return false;
+    if (hash > hashTarget && pblock->IsProofOfWork())
+        return error("BitcoinMiner : proof-of-work not meeting target");
 
     //// debug print
     printf("BitcoinMiner:\n");
-    printf("proof-of-work found  \n  hash: %s  \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str());
+    printf("new block found  \n  hash: %s  \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str());
     pblock->print();
     printf("%s ", DateTimeStrFormat("%x %H:%M", GetTime()).c_str());
     printf("generated %s\n", FormatMoney(pblock->vtx[0].vout[0].nValue).c_str());
@@ -3177,6 +3278,19 @@ void static BitcoinMiner(CWallet *pwallet)
         auto_ptr<CBlock> pblock(CreateNewBlock(pwallet));
         if (!pblock.get())
             return;
+
+        // ppcoin: if proof-of-stake block found then process block
+        if (pblock->IsProofOfStake())
+        {
+            // should be able to sign block - assert here for now
+            assert(pblock->SignBlock(*pwalletMain));
+            printf("BitcoinMiner : proof-of-stake block found %s\n", pblock->GetHash().ToString().c_str()); 
+            SetThreadPriority(THREAD_PRIORITY_NORMAL);
+            CheckWork(pblock.get(), *pwalletMain, reservekey);
+            SetThreadPriority(THREAD_PRIORITY_LOWEST);
+            continue;
+        }
+
         IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce);
 
         printf("Running BitcoinMiner with %d transactions in block\n", pblock->vtx.size());