Remove legacy stake reward calculation code.
[NovacoinLibrary.git] / Novacoin / CBlockStore.cs
index 604df45..62a021f 100644 (file)
@@ -85,7 +85,7 @@ namespace Novacoin
 
 
         private ConcurrentDictionary<COutPoint, uint> mapStakeSeen = new ConcurrentDictionary<COutPoint, uint>();
-        private ConcurrentDictionary<COutPoint, uint> mapStakeSeenOrphan = new ConcurrentDictionary<COutPoint, uint>();
+        private ConcurrentDictionary<Tuple<COutPoint, uint>, uint256> mapStakeSeenOrphan = new ConcurrentDictionary<Tuple<COutPoint, uint>, uint256>();
 
 
         /// <summary>
@@ -177,14 +177,14 @@ namespace Novacoin
                     ));
 
                     // Write block to file.
-                    var itemTemplate = new CBlockStoreItem()
+                    var rootCursor = new CBlockStoreItem()
                     {
                         nHeight = 0
                     };
 
-                    itemTemplate.FillHeader(genesisBlock.header);
+                    rootCursor.FillHeader(genesisBlock.header);
 
-                    if (!AddItemToIndex(ref itemTemplate, ref genesisBlock))
+                    if (!AddItemToIndex(ref rootCursor, ref genesisBlock))
                     {
                         throw new Exception("Unable to write genesis block");
                     }
@@ -197,6 +197,8 @@ namespace Novacoin
                 // Init list of block items
                 foreach (var item in blockTreeItems)
                 {
+                    item.nStakeModifierChecksum = StakeModifier.GetModifierChecksum(item);
+
                     blockMap.TryAdd(item.Hash, item);
 
                     if (item.IsProofOfStake)
@@ -208,10 +210,13 @@ namespace Novacoin
 
                 // Load data about the top node.
                 ChainParams = dbConn.Table<ChainState>().First();
+
+                genesisBlockCursor = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])NetInfo.nHashGenesisBlock).First();
+                bestBlockCursor = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", ChainParams.HashBestChain).First();
             }
         }
 
-        public bool GetTxOutCursor(COutPoint outpoint, ref TxOutItem txOutCursor)
+        public bool GetTxOutCursor(COutPoint outpoint, out TxOutItem txOutCursor)
         {
             var queryResults = dbConn.Query<TxOutItem>("select o.* from [Outputs] o left join [MerkleNodes] m on (m.nMerkleNodeID = o.nMerkleNodeID) where m.[TransactionHash] = ?", (byte[])outpoint.hash);
 
@@ -224,10 +229,12 @@ namespace Novacoin
 
             // Tx not found
 
+            txOutCursor = null;
+
             return false;
         }
         
-        public bool FetchInputs(CTransaction tx, ref Dictionary<COutPoint, TxOutItem> queued, ref Dictionary<COutPoint, TxOutItem> inputs, bool IsBlock, out bool Invalid)
+        public bool FetchInputs(ref CTransaction tx, ref Dictionary<COutPoint, TxOutItem> queued, ref Dictionary<COutPoint, TxOutItem> inputs, bool IsBlock, out bool Invalid)
         {
             Invalid = false;
 
@@ -260,10 +267,8 @@ namespace Novacoin
 
                 var inputsKey = new COutPoint(item.TransactionHash, item.nOut);
 
-                item.IsSpent = true;
-
                 // Add output data to dictionary
-                inputs.Add(inputsKey, (TxOutItem)item);
+                inputs.Add(inputsKey, item.getTxOutItem());
             }
 
             if (queryResults.Count < tx.vin.Length)
@@ -276,6 +281,11 @@ namespace Novacoin
                     {
                         var outPoint = txin.prevout;
 
+                        if (inputs.ContainsKey(outPoint))
+                        {
+                            continue; // We have already seen this input.
+                        }
+
                         if (!queued.ContainsKey(outPoint))
                         {
                             return false; // No such transaction
@@ -285,7 +295,7 @@ namespace Novacoin
                         inputs.Add(outPoint, queued[outPoint]);
 
                         // Mark output as spent
-                        queued[outPoint].IsSpent = true;
+                        // queued[outPoint].IsSpent = true;
                     }
                 }
                 else
@@ -321,9 +331,9 @@ namespace Novacoin
             return true;
         }
 
-        private bool AddItemToIndex(ref CBlockStoreItem itemTemplate, ref CBlock block)
+        private bool AddItemToIndex(ref CBlockStoreItem newCursor, ref CBlock block)
         {
-            uint256 blockHash = itemTemplate.Hash;
+            uint256 blockHash = newCursor.Hash;
 
             if (blockMap.ContainsKey(blockHash))
             {
@@ -335,69 +345,69 @@ namespace Novacoin
             dbConn.BeginTransaction();
 
             // Compute chain trust score
-            itemTemplate.nChainTrust = (itemTemplate.prev != null ? itemTemplate.prev.nChainTrust : 0) + itemTemplate.nBlockTrust;
+            newCursor.nChainTrust = (newCursor.prev != null ? newCursor.prev.nChainTrust : 0) + newCursor.nBlockTrust;
 
-            if (!itemTemplate.SetStakeEntropyBit(Entropy.GetStakeEntropyBit(itemTemplate.nHeight, blockHash)))
+            if (!newCursor.SetStakeEntropyBit(Entropy.GetStakeEntropyBit(newCursor.nHeight, blockHash)))
             {
                 return false; // SetStakeEntropyBit() failed
             }
 
-            // Save proof-of-stake hash value
-            if (itemTemplate.IsProofOfStake)
-            {
-                uint256 hashProofOfStake;
-                if (!GetProofOfStakeHash(blockHash, out hashProofOfStake))
-                {
-                    return false;  // hashProofOfStake not found 
-                }
-                itemTemplate.hashProofOfStake = hashProofOfStake;
-            }
-
             // compute stake modifier
             long nStakeModifier = 0;
             bool fGeneratedStakeModifier = false;
-            if (!StakeModifier.ComputeNextStakeModifier(itemTemplate, ref nStakeModifier, ref fGeneratedStakeModifier))
+            if (!StakeModifier.ComputeNextStakeModifier(ref newCursor, ref nStakeModifier, ref fGeneratedStakeModifier))
             {
                 return false;  // ComputeNextStakeModifier() failed
             }
 
-            itemTemplate.SetStakeModifier(nStakeModifier, fGeneratedStakeModifier);
-            itemTemplate.nStakeModifierChecksum = StakeModifier.GetStakeModifierChecksum(itemTemplate);
+            newCursor.SetStakeModifier(nStakeModifier, fGeneratedStakeModifier);
+            newCursor.nStakeModifierChecksum = StakeModifier.GetModifierChecksum(newCursor);
 
-            // TODO: verify stake modifier checkpoints
+            if (!ModifierCheckpoints.Verify(newCursor.nHeight, newCursor.nStakeModifierChecksum))
+            {
+                return false; // Stake modifier checkpoints mismatch
+            }
 
             // Add to index
             if (block.IsProofOfStake)
             {
-                itemTemplate.SetProofOfStake();
+                newCursor.SetProofOfStake();
 
-                itemTemplate.prevoutStake = block.vtx[1].vin[0].prevout;
-                itemTemplate.nStakeTime = block.vtx[1].nTime;
+                newCursor.prevoutStake = block.vtx[1].vin[0].prevout;
+                newCursor.nStakeTime = block.vtx[1].nTime;
+
+                // Save proof-of-stake hash value
+                uint256 hashProofOfStake;
+                if (!GetProofOfStakeHash(ref blockHash, out hashProofOfStake))
+                {
+                    return false;  // hashProofOfStake not found 
+                }
+                newCursor.hashProofOfStake = hashProofOfStake;
             }
 
-            if (!itemTemplate.WriteToFile(ref fStreamReadWrite, ref block))
+            if (!newCursor.WriteToFile(ref fStreamReadWrite, ref block))
             {
                 return false;
             }
 
-            if (dbConn.Insert(itemTemplate) == 0)
+            if (dbConn.Insert(newCursor) == 0)
             {
                 return false; // Insert failed
             }
 
             // Get last RowID.
-            itemTemplate.ItemID = dbPlatform.SQLiteApi.LastInsertRowid(dbConn.Handle);
+            newCursor.ItemID = dbPlatform.SQLiteApi.LastInsertRowid(dbConn.Handle);
 
-            if (!blockMap.TryAdd(blockHash, itemTemplate))
+            if (!blockMap.TryAdd(blockHash, newCursor))
             {
                 return false; // blockMap add failed
             }
 
-            if (itemTemplate.nChainTrust > ChainParams.nBestChainTrust)
+            if (newCursor.nChainTrust > ChainParams.nBestChainTrust)
             {
                 // New best chain
 
-                if (!SetBestChain(ref itemTemplate))
+                if (!SetBestChain(ref newCursor))
                 {
                     return false; // SetBestChain failed.
                 }
@@ -469,6 +479,11 @@ namespace Novacoin
             nTimeBestReceived = Interop.GetTime();
             nTransactionsUpdated++;
 
+            if (!UpdateTopChain(cursor))
+            {
+                return false; // unable to set top chain node.
+            }
+
             return true;
         }
 
@@ -543,7 +558,6 @@ namespace Novacoin
                 }
             }
 
-
             // Connect longer branch
             var txDelete = new List<CTransaction>();
             foreach (var cursor in connect)
@@ -610,7 +624,13 @@ namespace Novacoin
             }
 
             // Add to current best branch
-            cursor.prev.next = cursor;
+            var prevCursor = cursor.prev;
+            prevCursor.next = cursor;
+
+            if (!UpdateDBCursor(ref prevCursor))
+            {
+                return false; // unable to update
+            }
 
             // Delete redundant memory transactions
             foreach (var tx in block.vtx)
@@ -630,22 +650,29 @@ namespace Novacoin
                 return false; // Invalid block found.
             }
 
-            bool fScriptChecks = cursor.nHeight >= Checkpoints.TotalBlocksEstimate;
+            bool fScriptChecks = cursor.nHeight >= HashCheckpoints.TotalBlocksEstimate;
             var scriptFlags = scriptflag.SCRIPT_VERIFY_NOCACHE | scriptflag.SCRIPT_VERIFY_P2SH;
 
-            ulong nFees = 0;
-            ulong nValueIn = 0;
-            ulong nValueOut = 0;
+            long nFees = 0;
+            long nValueIn = 0;
+            long nValueOut = 0;
             uint nSigOps = 0;
 
             var queuedMerkleNodes = new Dictionary<uint256, CMerkleNode>();
-            var queued = new Dictionary<COutPoint, TxOutItem>();
+            var queuedOutputs = new Dictionary<COutPoint, TxOutItem>();
 
             for (var nTx = 0; nTx < block.vtx.Length; nTx++)
             {
                 var tx = block.vtx[nTx];
                 var hashTx = tx.Hash;
-                var nTxPos = cursor.nBlockPos + block.GetTxOffset(nTx);
+
+                if (!queuedMerkleNodes.ContainsKey(hashTx))
+                {
+                    var nTxPos = cursor.nBlockPos + block.GetTxOffset(nTx);
+                    var mNode = new CMerkleNode(cursor.ItemID, nTxPos, tx);
+
+                    queuedMerkleNodes.Add(hashTx, mNode);
+                }
 
                 Dictionary<COutPoint, TxOutItem> txouts;
                 if (GetOutputs(hashTx, out txouts))
@@ -670,7 +697,7 @@ namespace Novacoin
                 else
                 {
                     bool Invalid;
-                    if (!FetchInputs(tx, ref queued, ref inputs, true, out Invalid))
+                    if (!FetchInputs(ref tx, ref queuedOutputs, ref inputs, true, out Invalid))
                     {
                         return false; // Unable to fetch some inputs.
                     }
@@ -684,8 +711,8 @@ namespace Novacoin
                         return false; // too many sigops
                     }
 
-                    ulong nTxValueIn = tx.GetValueIn(ref inputs);
-                    ulong nTxValueOut = tx.nValueOut;
+                    long nTxValueIn = tx.GetValueIn(ref inputs);
+                    long nTxValueOut = tx.nValueOut;
 
                     nValueIn += nTxValueIn;
                     nValueOut += nTxValueOut;
@@ -695,7 +722,7 @@ namespace Novacoin
                         nFees += nTxValueIn - nTxValueOut;
                     }
 
-                    if (!ConnectInputs(tx, ref inputs, ref queued, ref cursor, true, fScriptChecks, scriptFlags))
+                    if (!ConnectInputs(ref tx, ref inputs, ref queuedOutputs, ref cursor, true, fScriptChecks, scriptFlags))
                     {
                         return false;
                     }
@@ -703,26 +730,23 @@ namespace Novacoin
 
                 for (var i = 0u; i < tx.vout.Length; i++)
                 {
-                    var mNode = new CMerkleNode(cursor.ItemID, nTxPos, tx);
-                    queuedMerkleNodes.Add(hashTx, mNode);
-
                     var outKey = new COutPoint(hashTx, i);
-                    var outData = new TxOutItem();
-
-                    outData.nValue = tx.vout[i].nValue;
-                    outData.scriptPubKey = tx.vout[i].scriptPubKey;
-                    outData.nOut = i;
-
-
-                    outData.IsSpent = false;
+                    var outData = new TxOutItem()
+                    {
+                        nMerkleNodeID = -1,
+                        nValue = tx.vout[i].nValue,
+                        scriptPubKey = tx.vout[i].scriptPubKey,
+                        IsSpent = false,
+                        nOut = i
+                    };
 
-                    queued.Add(outKey, outData);
+                    queuedOutputs.Add(outKey, outData);
                 }
             }
 
             if (!block.IsProofOfStake)
             {
-                ulong nBlockReward = CBlock.GetProofOfWorkReward(cursor.nBits, nFees);
+                long nBlockReward = CBlock.GetProofOfWorkReward(cursor.nBits, nFees);
 
                 // Check coinbase reward
                 if (block.vtx[0].nValueOut > nBlockReward)
@@ -731,8 +755,8 @@ namespace Novacoin
                 }
             }
 
-            cursor.nMint = (long)(nValueOut - nValueIn + nFees);
-            cursor.nMoneySupply = (cursor.prev != null ? cursor.prev.nMoneySupply : 0) + (long)nValueOut - (long)nValueIn;
+            cursor.nMint = nValueOut - nValueIn + nFees;
+            cursor.nMoneySupply = (cursor.prev != null ? cursor.prev.nMoneySupply : 0) + nValueOut - nValueIn;
 
             if (!UpdateDBCursor(ref cursor))
             {
@@ -744,51 +768,75 @@ namespace Novacoin
                 return true;
             }
 
+            // Flush merkle nodes.
+            var savedMerkleNodes = new Dictionary<uint256, CMerkleNode>();
+            foreach (var merklePair in queuedMerkleNodes)
+            {
+                var merkleNode = merklePair.Value;
+
+                if (!SaveMerkleNode(ref merkleNode))
+                {
+                    // Unable to save merkle tree cursor.
+                    return false;
+                }
+
+                savedMerkleNodes.Add(merklePair.Key, merkleNode);
+            }
+
             // Write queued transaction changes
-            var actualMerkleNodes = new Dictionary<uint256, CMerkleNode>();
-            var queuedOutpointItems = new List<TxOutItem>();
-            foreach (KeyValuePair<COutPoint, TxOutItem> outPair in queued)
+            var newOutpointItems = new List<TxOutItem>();
+            var updatedOutpointItems = new List<TxOutItem>();
+            foreach (var outPair in queuedOutputs)
             {
-                uint256 txID = outPair.Key.hash;
-                CMerkleNode merkleNode;
+                var outItem = outPair.Value;
 
-                if (actualMerkleNodes.ContainsKey(txID))
+                if (outItem.nMerkleNodeID == -1)
                 {
-                    merkleNode = actualMerkleNodes[txID];
+                    // This outpoint doesn't exist yet, adding to insert list.
+
+                    outItem.nMerkleNodeID = savedMerkleNodes[outPair.Key.hash].nMerkleNodeID;
+                    newOutpointItems.Add(outItem);
                 }
                 else
                 {
-                    merkleNode = queuedMerkleNodes[txID];
-                    if (!SaveMerkleNode(ref merkleNode))
-                    {
-                        // Unable to save merkle tree cursor.
-                        return false;
-                    }
-                    actualMerkleNodes.Add(txID, merkleNode);
-                }
+                    // This outpount already exists, adding to update list.
 
-                var outItem = outPair.Value;
-                outItem.nMerkleNodeID = merkleNode.nMerkleNodeID;
+                    updatedOutpointItems.Add(outItem);
+                }
+            }
 
-                queuedOutpointItems.Add(outItem);
+            if (updatedOutpointItems.Count != 0 && !UpdateOutpoints(ref updatedOutpointItems))
+            {
+                return false; // Unable to update outpoints
             }
 
-            if (!SaveOutpoints(ref queuedOutpointItems))
+            if (newOutpointItems.Count != 0 && !InsertOutpoints(ref newOutpointItems))
             {
-                return false; // Unable to save outpoints
+                return false; // Unable to insert outpoints
             }
 
             return true;
         }
 
         /// <summary>
-        /// Insert set of outpoints
+        /// Insert set of new outpoints
+        /// </summary>
+        /// <param name="newOutpointItems">List of TxOutItem objects.</param>
+        /// <returns>Result</returns>
+        private bool InsertOutpoints(ref List<TxOutItem> newOutpointItems)
+        {
+            return (dbConn.InsertAll(newOutpointItems, false) != 0);
+        }
+
+
+        /// <summary>
+        /// Update set of outpoints
         /// </summary>
         /// <param name="queuedOutpointItems">List of TxOutItem objects.</param>
         /// <returns>Result</returns>
-        private bool SaveOutpoints(ref List<TxOutItem> queuedOutpointItems)
+        private bool UpdateOutpoints(ref List<TxOutItem> updatedOutpointItems)
         {
-            return dbConn.InsertAll(queuedOutpointItems, false) != 0;
+            return (dbConn.UpdateAll(updatedOutpointItems, false) != 0);
         }
 
         /// <summary>
@@ -808,17 +856,15 @@ namespace Novacoin
             return true;
         }
 
-        private bool ConnectInputs(CTransaction tx, ref Dictionary<COutPoint, TxOutItem> inputs, ref Dictionary<COutPoint, TxOutItem> queued, ref CBlockStoreItem cursorBlock, bool fBlock, bool fScriptChecks, scriptflag scriptFlags)
+        private bool ConnectInputs(ref CTransaction tx, ref Dictionary<COutPoint, TxOutItem> inputs, ref Dictionary<COutPoint, TxOutItem> queued, ref CBlockStoreItem cursorBlock, bool fBlock, bool fScriptChecks, scriptflag scriptFlags)
         {
-            // Take over previous transactions' spent pointers
+            // Take over previous transactions' spent items
             // fBlock is true when this is called from AcceptBlock when a new best-block is added to the blockchain
-            // fMiner is true when called from the internal bitcoin miner
-            // ... both are false when called from CTransaction::AcceptToMemoryPool
 
             if (!tx.IsCoinBase)
             {
-                ulong nValueIn = 0;
-                ulong nFees = 0;
+                long nValueIn = 0;
+                long nFees = 0;
                 for (uint i = 0; i < tx.vin.Length; i++)
                 {
                     var prevout = tx.vin[i].prevout;
@@ -826,26 +872,44 @@ namespace Novacoin
                     var input = inputs[prevout];
 
                     CBlockStoreItem parentBlockCursor;
-                    var merkleItem = GetMerkleCursor(input, out parentBlockCursor);
 
-                    if (merkleItem == null)
+                    if (input.nMerkleNodeID == -1)
                     {
-                        return false; // Unable to find merkle node
-                    }
+                        // This input seems as is confirmed by the same block.
 
-                    // If prev is coinbase or coinstake, check that it's matured
-                    if (merkleItem.IsCoinBase || merkleItem.IsCoinStake)
-                    {
-                        if (cursorBlock.nHeight - parentBlockCursor.nHeight < NetInfo.nGeneratedMaturity)
+                        if (!queued.ContainsKey(prevout))
                         {
-                            return false; // tried to spend non-matured generation input.
+                            return false; // No such output has been queued by this block.
                         }
-                    }
 
-                    // check transaction timestamp
-                    if (merkleItem.nTime > tx.nTime)
+                        // TODO: Ensure that neither coinbase nor coinstake outputs are 
+                        //    available for spending in the generation block.
+                    }
+                    else
                     {
-                        return false; // transaction timestamp earlier than input transaction
+                        // This input has been confirmed by one of the earlier accepted blocks.
+
+                        var merkleItem = GetMerkleCursor(input, out parentBlockCursor);
+
+                        if (merkleItem == null)
+                        {
+                            return false; // Unable to find merkle node
+                        }
+
+                        // If prev is coinbase or coinstake, check that it's matured
+                        if (merkleItem.IsCoinBase || merkleItem.IsCoinStake)
+                        {
+                            if (cursorBlock.nHeight - parentBlockCursor.nHeight < NetInfo.nGeneratedMaturity)
+                            {
+                                return false; // tried to spend non-matured generation input.
+                            }
+                        }
+
+                        // check transaction timestamp
+                        if (merkleItem.nTime > tx.nTime)
+                        {
+                            return false; // transaction timestamp earlier than input transaction
+                        }
                     }
 
                     // Check for negative or overflow input values
@@ -891,26 +955,37 @@ namespace Novacoin
                     // Write back
                     if (fBlock)
                     {
-                        queued.Add(prevout, input);
+                        if (input.nMerkleNodeID != -1)
+                        {
+                            // Input has been confirmed earlier.
+                            queued.Add(prevout, input);
+                        }
+                        else
+                        {
+                            // Input has been confirmed by current block.
+                            queued[prevout] = input;
+                        }
                     }
                 }
 
                 if (tx.IsCoinStake)
                 {
-                    // ppcoin: coin stake tx earns reward instead of paying fee
-                    ulong nCoinAge;
-                    if (!tx.GetCoinAge(ref inputs, out nCoinAge))
+                    if (HashCheckpoints.LastCheckpointTime < tx.nTime)
                     {
-                        return false; // unable to get coin age for coinstake
-                    }
-
-                    ulong nReward = tx.nValueOut - nValueIn;
+                        // Coin stake tx earns reward instead of paying fee
+                        long nCoinAge;
+                        if (!tx.GetCoinAge(ref inputs, out nCoinAge))
+                        {
+                            return false; // unable to get coin age for coinstake
+                        }
 
-                    ulong nCalculatedReward = CBlock.GetProofOfStakeReward(nCoinAge, cursorBlock.nBits, tx.nTime) - tx.GetMinFee(1, false, CTransaction.MinFeeMode.GMF_BLOCK) + CTransaction.nCent;
+                        long nReward = tx.nValueOut - nValueIn;
+                        long nCalculatedReward = CBlock.GetProofOfStakeReward(nCoinAge, cursorBlock.nBits, tx.nTime) - tx.GetMinFee(1, false, CTransaction.MinFeeMode.GMF_BLOCK) + CTransaction.nCent;
 
-                    if (nReward > nCalculatedReward)
-                    {
-                        return false; // coinstake pays too much
+                        if (nReward > nCalculatedReward)
+                        {
+                            return false; // coinstake pays too much
+                        }
                     }
                 }
                 else
@@ -921,7 +996,7 @@ namespace Novacoin
                     }
 
                     // Tally transaction fees
-                    ulong nTxFee = nValueIn - tx.nValueOut;
+                    long nTxFee = nValueIn - tx.nValueOut;
                     if (nTxFee < 0)
                     {
                         return false; // nTxFee < 0
@@ -961,7 +1036,7 @@ namespace Novacoin
         /// <param name="blockHash">Block hash</param>
         /// <param name="hashProofOfStake">Proof-of-stake hash</param>
         /// <returns>Proof-of-Stake hash value</returns>
-        private bool GetProofOfStakeHash(uint256 blockHash, out uint256 hashProofOfStake)
+        private bool GetProofOfStakeHash(ref uint256 blockHash, out uint256 hashProofOfStake)
         {
             return mapProofOfStake.TryGetValue(blockHash, out hashProofOfStake);
         }
@@ -976,7 +1051,7 @@ namespace Novacoin
                 return false;
             }
 
-            CBlockStoreItem prevBlockCursor = null;
+            CBlockStoreItem prevBlockCursor;
             if (!blockMap.TryGetValue(block.header.prevHash, out prevBlockCursor))
             {
                 // Unable to get the cursor.
@@ -985,7 +1060,6 @@ namespace Novacoin
 
             var prevBlockHeader = prevBlockCursor.BlockHeader;
 
-            // TODO: proof-of-work/proof-of-stake verification
             uint nHeight = prevBlockCursor.nHeight + 1;
 
             // Check timestamp against prev
@@ -1004,17 +1078,33 @@ namespace Novacoin
                 }
             }
 
-            // TODO: Enforce rule that the coinbase starts with serialized block height
+            // Check that the block chain matches the known block chain up to a checkpoint
+            if (!HashCheckpoints.Verify(nHeight, nHash))
+            {
+                return false;  // rejected by checkpoint lock-in
+            }
+
+            // Enforce rule that the coinbase starts with serialized block height
+            var expect = new CScript();
+            expect.AddNumber((int)nHeight);
+
+            byte[] expectBytes = expect;
+            byte[] scriptSig = block.vtx[0].vin[0].scriptSig;
+
+            if (!expectBytes.SequenceEqual(scriptSig.Take(expectBytes.Length)))
+            {
+                return false; // coinbase doesn't start with serialized height.
+            }
 
             // Write block to file.
-            var itemTemplate = new CBlockStoreItem()
+            var newCursor = new CBlockStoreItem()
             {
                 nHeight = nHeight,
             };
 
-            itemTemplate.FillHeader(block.header);
+            newCursor.FillHeader(block.header);
 
-            if (!AddItemToIndex(ref itemTemplate, ref block))
+            if (!AddItemToIndex(ref newCursor, ref block))
             {
                 dbConn.Rollback();
 
@@ -1025,7 +1115,7 @@ namespace Novacoin
         }
 
         /// <summary>
-        /// GEt block by hash.
+        /// Get block by hash.
         /// </summary>
         /// <param name="blockHash">Block hash</param>
         /// <param name="block">Block object reference</param>
@@ -1052,14 +1142,19 @@ namespace Novacoin
         /// <param name="block">Block reference</param>
         /// <param name="nBlockPos">Block position reference</param>
         /// <returns>Result of operation</returns>
-        public bool GetBlockByTransactionID(uint256 TxID, ref CBlock block, ref long nBlockPos)
+        public bool GetBlockByTransactionID(uint256 TxID, out CBlock block, out long nBlockPos)
         {
+            block = null;
+            nBlockPos = -1;
+
             var queryResult = dbConn.Query<CBlockStoreItem>("select b.* from [BlockStorage] b left join [MerkleNodes] m on (b.[ItemID] = m.[nParentBlockID]) where m.[TransactionHash] = ?", (byte[])TxID);
 
             if (queryResult.Count == 1)
             {
                 CBlockStoreItem blockCursor = queryResult[0];
 
+                nBlockPos = blockCursor.nBlockPos;
+
                 return blockCursor.ReadFromFile(ref fStreamReadWrite, out block);
             }
 
@@ -1208,16 +1303,8 @@ namespace Novacoin
 
             if (block.IsProofOfStake)
             {
-                if (!block.SignatureOK)
-                {
-                    // Proof-of-Stake signature validation failure.
-                    return false;
-                }
-
-                // TODO: proof-of-stake validation
-
                 uint256 hashProofOfStake = 0, targetProofOfStake = 0;
-                if (!StakeModifier.CheckProofOfStake(block.vtx[1], block.header.nBits, ref hashProofOfStake, ref targetProofOfStake))
+                if (!StakeModifier.CheckProofOfStake(block.vtx[1], block.header.nBits, out hashProofOfStake, out targetProofOfStake))
                 {
                     return false; // do not error here as we expect this during initial block download
                 }
@@ -1236,12 +1323,22 @@ namespace Novacoin
             {
                 if (block.IsProofOfStake)
                 {
-                    // TODO: limit duplicity on stake
+                    var proof = block.ProofOfStake;
+
+                    // Limited duplicity on stake: prevents block flood attack
+                    // Duplicate stake allowed only when there is orphan child block
+                    if (mapStakeSeenOrphan.ContainsKey(proof) && !orphanMapByPrev.ContainsKey(blockHash))
+                    {
+                        return false; // duplicate proof-of-stake
+                    }
+                    else
+                    {
+                        mapStakeSeenOrphan.TryAdd(proof, blockHash);
+                    }
                 }
 
-                var block2 = new CBlock(block);
-                orphanMap.TryAdd(blockHash, block2);
-                orphanMapByPrev.TryAdd(blockHash, block2);
+                orphanMap.TryAdd(blockHash, block);
+                orphanMapByPrev.TryAdd(blockHash, block);
 
                 return true;
             }
@@ -1253,32 +1350,39 @@ namespace Novacoin
                 return false;
             }
 
-            // Recursively process any orphan blocks that depended on this one
-            var orphansQueue = new List<uint256>();
-            orphansQueue.Add(blockHash);
-
-            for (int i = 0; i < orphansQueue.Count; i++)
+            if (orphanMapByPrev.Count > 0)
             {
-                var hashPrev = orphansQueue[i];
+                // Recursively process any orphan blocks that depended on this one
 
-                foreach (var pair in orphanMap)
+                var orphansQueue = new List<uint256>();
+                orphansQueue.Add(blockHash);
+
+                for (int i = 0; i < orphansQueue.Count; i++)
                 {
-                    var orphanBlock = pair.Value;
+                    var hashPrev = orphansQueue[i];
 
-                    if (orphanBlock.header.prevHash == blockHash)
+                    foreach (var pair in orphanMapByPrev)
                     {
-                        if (AcceptBlock(ref orphanBlock))
+                        var orphanBlock = pair.Value;
+
+                        if (orphanBlock.header.prevHash == blockHash)
                         {
-                            orphansQueue.Add(pair.Key);
-                        }
+                            if (AcceptBlock(ref orphanBlock))
+                            {
+                                orphansQueue.Add(pair.Key);
+                            }
+
+                            CBlock dummy1;
+                            orphanMap.TryRemove(pair.Key, out dummy1);
 
-                        CBlock dummy1;
-                        orphanMap.TryRemove(pair.Key, out dummy1);
+                            uint256 dummyHash;
+                            mapStakeSeenOrphan.TryRemove(orphanBlock.ProofOfStake, out dummyHash);
+                        }
                     }
-                }
 
-                CBlock dummy2;
-                orphanMap.TryRemove(hashPrev, out dummy2);
+                    CBlock dummy2;
+                    orphanMapByPrev.TryRemove(hashPrev, out dummy2);
+                }
             }
 
             return true;
@@ -1336,15 +1440,7 @@ namespace Novacoin
                 }
 
                 int nCount = blockMap.Count;
-                Console.WriteLine("nCount={0}, Hash={1}, Time={2}", nCount, block.header.Hash, DateTime.Now); // Commit on each 100th block
-
-                /*
-                if (nCount % 100 == 0 && nCount != 0)
-                {
-                    Console.WriteLine("Commit...");
-                    dbConn.Commit();
-                    dbConn.BeginTransaction();
-                }*/
+                Console.WriteLine("nCount={0}, Hash={1}, NumTx={2}, Time={3}", nCount, block.header.Hash, block.vtx.Length, DateTime.Now);
             }
 
             return true;