X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=Novacoin%2FCBlockStore.cs;h=62a021f970fa52b15b2cbef829372028de701e02;hb=6b0501a53995f78928332cfcd7ce1cae4b07486f;hp=1b600b9de4d9d19bd70df2897d84933bddb3b17a;hpb=d171cc68784f580b4dd334944014930f638eab89;p=NovacoinLibrary.git diff --git a/Novacoin/CBlockStore.cs b/Novacoin/CBlockStore.cs index 1b600b9..62a021f 100644 --- a/Novacoin/CBlockStore.cs +++ b/Novacoin/CBlockStore.cs @@ -85,7 +85,7 @@ namespace Novacoin private ConcurrentDictionary mapStakeSeen = new ConcurrentDictionary(); - private ConcurrentDictionary mapStakeSeenOrphan = new ConcurrentDictionary(); + private ConcurrentDictionary, uint256> mapStakeSeenOrphan = new ConcurrentDictionary, uint256>(); /// @@ -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().First(); + + genesisBlockCursor = dbConn.Query("select * from [BlockStorage] where [Hash] = ?", (byte[])NetInfo.nHashGenesisBlock).First(); + bestBlockCursor = dbConn.Query("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("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 queued, ref Dictionary inputs, bool IsBlock, out bool Invalid) + public bool FetchInputs(ref CTransaction tx, ref Dictionary queued, ref Dictionary inputs, bool IsBlock, out bool Invalid) { Invalid = false; @@ -324,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)) { @@ -338,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. } @@ -472,6 +479,11 @@ namespace Novacoin nTimeBestReceived = Interop.GetTime(); nTransactionsUpdated++; + if (!UpdateTopChain(cursor)) + { + return false; // unable to set top chain node. + } + return true; } @@ -546,7 +558,6 @@ namespace Novacoin } } - // Connect longer branch var txDelete = new List(); foreach (var cursor in connect) @@ -613,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) @@ -633,12 +650,12 @@ 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(); @@ -680,7 +697,7 @@ namespace Novacoin else { bool Invalid; - if (!FetchInputs(tx, ref queuedOutputs, ref inputs, true, out Invalid)) + if (!FetchInputs(ref tx, ref queuedOutputs, ref inputs, true, out Invalid)) { return false; // Unable to fetch some inputs. } @@ -694,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; @@ -705,7 +722,7 @@ namespace Novacoin nFees += nTxValueIn - nTxValueOut; } - if (!ConnectInputs(tx, ref inputs, ref queuedOutputs, ref cursor, true, fScriptChecks, scriptFlags)) + if (!ConnectInputs(ref tx, ref inputs, ref queuedOutputs, ref cursor, true, fScriptChecks, scriptFlags)) { return false; } @@ -729,7 +746,7 @@ namespace Novacoin 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) @@ -738,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)) { @@ -839,15 +856,15 @@ namespace Novacoin return true; } - private bool ConnectInputs(CTransaction tx, ref Dictionary inputs, ref Dictionary queued, ref CBlockStoreItem cursorBlock, bool fBlock, bool fScriptChecks, scriptflag scriptFlags) + private bool ConnectInputs(ref CTransaction tx, ref Dictionary inputs, ref Dictionary queued, ref CBlockStoreItem cursorBlock, bool fBlock, bool fScriptChecks, scriptflag scriptFlags) { // 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 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; @@ -953,20 +970,22 @@ namespace Novacoin 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 @@ -977,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 @@ -1017,7 +1036,7 @@ namespace Novacoin /// Block hash /// Proof-of-stake hash /// Proof-of-Stake hash value - private bool GetProofOfStakeHash(uint256 blockHash, out uint256 hashProofOfStake) + private bool GetProofOfStakeHash(ref uint256 blockHash, out uint256 hashProofOfStake) { return mapProofOfStake.TryGetValue(blockHash, out hashProofOfStake); } @@ -1032,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. @@ -1041,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 @@ -1060,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(); @@ -1081,7 +1115,7 @@ namespace Novacoin } /// - /// GEt block by hash. + /// Get block by hash. /// /// Block hash /// Block object reference @@ -1108,14 +1142,19 @@ namespace Novacoin /// Block reference /// Block position reference /// Result of operation - 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("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); } @@ -1264,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 } @@ -1292,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; } @@ -1309,32 +1350,39 @@ namespace Novacoin return false; } - // Recursively process any orphan blocks that depended on this one - var orphansQueue = new List(); - 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(); + 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; @@ -1392,15 +1440,7 @@ namespace Novacoin } int nCount = blockMap.Count; - Console.WriteLine("nCount={0}, Hash={1}, NumTx={2}, Time={3}", nCount, block.header.Hash, block.vtx.Length, 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;