X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=Novacoin%2FCBlockStore.cs;h=6b03f083979e2b6e3fea2f4fa71767967944179f;hb=1e2160be4bf5122f8bbea1c493df9a26be91e4cf;hp=2299e15406f1adbbe78389f677639e0afdbbf073;hpb=f02df773a836f8b3df12bd21fd418f7c17b77821;p=NovacoinLibrary.git diff --git a/Novacoin/CBlockStore.cs b/Novacoin/CBlockStore.cs index 2299e15..6b03f08 100644 --- a/Novacoin/CBlockStore.cs +++ b/Novacoin/CBlockStore.cs @@ -26,6 +26,8 @@ using SQLite.Net.Interop; using SQLite.Net.Platform.Generic; using System.Collections.Generic; using System.Text; +using System.Diagnostics.Contracts; +using System.Linq; namespace Novacoin { @@ -206,10 +208,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); @@ -222,28 +227,29 @@ 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; if (tx.IsCoinBase) { // Coinbase transactions have no inputs to fetch. - return true; + return true; } StringBuilder queryBuilder = new StringBuilder(); - + queryBuilder.Append("select o.*, m.[TransactionHash] from [Outputs] o left join [MerkleNodes] m on (m.[nMerkleNodeID] = o.[nMerkleNodeID]) where "); for (var i = 0; i < tx.vin.Length; i++) { - queryBuilder.AppendFormat(" {0} (m.[TransactionHash] = x'{1}' and o.[OutputNumber] = x'{2}')", - (i > 0 ? "or" : string.Empty), Interop.ToHex(tx.vin[i].prevout.hash), + queryBuilder.AppendFormat(" {0} (m.[TransactionHash] = x'{1}' and o.[OutputNumber] = x'{2}')", + (i > 0 ? "or" : string.Empty), Interop.ToHex(tx.vin[i].prevout.hash), Interop.ToHex(VarInt.EncodeVarInt(tx.vin[i].prevout.n) )); } @@ -257,12 +263,10 @@ namespace Novacoin return false; // Already spent } - var inputsKey = new COutPoint(item.TransactionHash, item.nOut); - - item.IsSpent = true; + var inputsKey = new COutPoint(item.TransactionHash, item.nOut); // Add output data to dictionary - inputs.Add(inputsKey, (TxOutItem) item); + inputs.Add(inputsKey, item.getTxOutItem()); } if (queryResults.Count < tx.vin.Length) @@ -275,6 +279,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 @@ -284,7 +293,7 @@ namespace Novacoin inputs.Add(outPoint, queued[outPoint]); // Mark output as spent - queued[outPoint].IsSpent = true; + // queued[outPoint].IsSpent = true; } } else @@ -307,7 +316,7 @@ namespace Novacoin return false; // nOut is out of range } - + // TODO: return inputs from map throw new NotImplementedException(); @@ -322,7 +331,6 @@ namespace Novacoin private bool AddItemToIndex(ref CBlockStoreItem itemTemplate, ref CBlock block) { - var writer = new BinaryWriter(fStreamReadWrite).BaseStream; uint256 blockHash = itemTemplate.Hash; if (blockMap.ContainsKey(blockHash)) @@ -331,6 +339,9 @@ namespace Novacoin return false; } + // Begin transaction + dbConn.BeginTransaction(); + // Compute chain trust score itemTemplate.nChainTrust = (itemTemplate.prev != null ? itemTemplate.prev.nChainTrust : 0) + itemTemplate.nBlockTrust; @@ -339,17 +350,6 @@ namespace Novacoin 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; @@ -359,7 +359,7 @@ namespace Novacoin } itemTemplate.SetStakeModifier(nStakeModifier, fGeneratedStakeModifier); - itemTemplate.nStakeModifierChecksum = StakeModifier.GetStakeModifierChecksum(itemTemplate); + itemTemplate.nStakeModifierChecksum = StakeModifier.GetStakeModifierChecksum(ref itemTemplate); // TODO: verify stake modifier checkpoints @@ -370,9 +370,17 @@ namespace Novacoin itemTemplate.prevoutStake = block.vtx[1].vin[0].prevout; itemTemplate.nStakeTime = block.vtx[1].nTime; + + // Save proof-of-stake hash value + uint256 hashProofOfStake; + if (!GetProofOfStakeHash(ref blockHash, out hashProofOfStake)) + { + return false; // hashProofOfStake not found + } + itemTemplate.hashProofOfStake = hashProofOfStake; } - if (!itemTemplate.WriteToFile(ref writer, ref block)) + if (!itemTemplate.WriteToFile(ref fStreamReadWrite, ref block)) { return false; } @@ -384,7 +392,7 @@ namespace Novacoin // Get last RowID. itemTemplate.ItemID = dbPlatform.SQLiteApi.LastInsertRowid(dbConn.Handle); - + if (!blockMap.TryAdd(blockHash, itemTemplate)) { return false; // blockMap add failed @@ -400,6 +408,9 @@ namespace Novacoin } } + // Commit transaction + dbConn.Commit(); + return true; } @@ -463,6 +474,11 @@ namespace Novacoin nTimeBestReceived = Interop.GetTime(); nTransactionsUpdated++; + if (!UpdateTopChain(cursor)) + { + return false; // unable to set top chain node. + } + return true; } @@ -537,7 +553,6 @@ namespace Novacoin } } - // Connect longer branch var txDelete = new List(); foreach (var cursor in connect) @@ -566,9 +581,6 @@ namespace Novacoin return false; // UpdateTopChain failed } - // Make sure it's successfully written to disk - dbConn.Commit(); - // Resurrect memory transactions that were in the disconnected branch foreach (var tx in txResurrect) { @@ -607,9 +619,13 @@ namespace Novacoin } // Add to current best branch - cursor.prev.next = cursor; + var prevCursor = cursor.prev; + prevCursor.next = cursor; - dbConn.Commit(); + if (!UpdateDBCursor(ref prevCursor)) + { + return false; // unable to update + } // Delete redundant memory transactions foreach (var tx in block.vtx) @@ -621,7 +637,7 @@ namespace Novacoin return true; } - private bool ConnectBlock(CBlockStoreItem cursor, ref CBlock block, bool fJustCheck=false) + private bool ConnectBlock(CBlockStoreItem cursor, ref CBlock block, bool fJustCheck = false) { // Check it again in case a previous version let a bad block in, but skip BlockSig checking if (!block.CheckBlock(!fJustCheck, !fJustCheck, false)) @@ -638,13 +654,20 @@ namespace Novacoin uint nSigOps = 0; var queuedMerkleNodes = new Dictionary(); - var queued = new Dictionary(); + var queuedOutputs = new Dictionary(); 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 txouts; if (GetOutputs(hashTx, out txouts)) @@ -669,7 +692,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. } @@ -677,13 +700,13 @@ namespace Novacoin // Add in sigops done by pay-to-script-hash inputs; // this is to prevent a "rogue miner" from creating // an incredibly-expensive-to-validate block. - nSigOps += tx.GetP2SHSigOpCount(inputs); + nSigOps += tx.GetP2SHSigOpCount(ref inputs); if (nSigOps > CBlock.nMaxSigOps) { return false; // too many sigops } - ulong nTxValueIn = tx.GetValueIn(inputs); + ulong nTxValueIn = tx.GetValueIn(ref inputs); ulong nTxValueOut = tx.nValueOut; nValueIn += nTxValueIn; @@ -694,7 +717,7 @@ namespace Novacoin nFees += nTxValueIn - nTxValueOut; } - if (!ConnectInputs(tx, inputs, queued, cursor, fScriptChecks, scriptFlags)) + if (!ConnectInputs(ref tx, ref inputs, ref queuedOutputs, ref cursor, true, fScriptChecks, scriptFlags)) { return false; } @@ -702,20 +725,17 @@ 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); } } @@ -730,7 +750,7 @@ namespace Novacoin } } - cursor.nMint = (long) (nValueOut - nValueIn + nFees); + cursor.nMint = (long)(nValueOut - nValueIn + nFees); cursor.nMoneySupply = (cursor.prev != null ? cursor.prev.nMoneySupply : 0) + (long)nValueOut - (long)nValueIn; if (!UpdateDBCursor(ref cursor)) @@ -743,51 +763,75 @@ namespace Novacoin return true; } + // Flush merkle nodes. + var savedMerkleNodes = new Dictionary(); + 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(); - var queuedOutpointItems = new List(); - foreach(KeyValuePair outPair in queued) + var newOutpointItems = new List(); + var updatedOutpointItems = new List(); + 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; } /// - /// Insert set of outpoints + /// Insert set of new outpoints + /// + /// List of TxOutItem objects. + /// Result + private bool InsertOutpoints(ref List newOutpointItems) + { + return (dbConn.InsertAll(newOutpointItems, false) != 0); + } + + + /// + /// Update set of outpoints /// /// List of TxOutItem objects. /// Result - private bool SaveOutpoints(ref List queuedOutpointItems) + private bool UpdateOutpoints(ref List updatedOutpointItems) { - return dbConn.InsertAll(queuedOutpointItems, false) != 0; + return (dbConn.UpdateAll(updatedOutpointItems, false) != 0); } /// @@ -807,11 +851,164 @@ namespace Novacoin return true; } - private bool ConnectInputs(CTransaction tx, Dictionary inputs, Dictionary queued, CBlockStoreItem cursor, 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) { - throw new NotImplementedException(); + // 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; + for (uint i = 0; i < tx.vin.Length; i++) + { + var prevout = tx.vin[i].prevout; + Contract.Assert(inputs.ContainsKey(prevout)); + var input = inputs[prevout]; + + CBlockStoreItem parentBlockCursor; + + if (input.nMerkleNodeID == -1) + { + // This input seems as is confirmed by the same block. + + if (!queued.ContainsKey(prevout)) + { + return false; // No such output has been queued by this block. + } + + // TODO: Ensure that neither coinbase nor coinstake outputs are + // available for spending in the generation block. + } + else + { + // 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 + nValueIn += input.nValue; + if (!CTransaction.MoneyRange(input.nValue) || !CTransaction.MoneyRange(nValueIn)) + { + return false; // txin values out of range + } + + } + + // The first loop above does all the inexpensive checks. + // Only if ALL inputs pass do we perform expensive ECDSA signature checks. + // Helps prevent CPU exhaustion attacks. + for (int i = 0; i < tx.vin.Length; i++) + { + var prevout = tx.vin[i].prevout; + Contract.Assert(inputs.ContainsKey(prevout)); + var input = inputs[prevout]; + + // Check for conflicts (double-spend) + if (input.IsSpent) + { + return false; + } + + // Skip ECDSA signature verification when connecting blocks (fBlock=true) + // before the last blockchain checkpoint. This is safe because block merkle hashes are + // still computed and checked, and any change will be caught at the next checkpoint. + if (fScriptChecks) + { + // Verify signature + if (!ScriptCode.VerifyScript(tx.vin[i].scriptSig, input.scriptPubKey, tx, i, (int)scriptflag.SCRIPT_VERIFY_P2SH, 0)) + { + return false; // VerifyScript failed. + } + } + + // Mark outpoint as spent + input.IsSpent = true; + inputs[prevout] = input; + + // Write back + if (fBlock) + { + 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)) + { + return false; // unable to get coin age for coinstake + } + + ulong nReward = tx.nValueOut - nValueIn; + + ulong 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 + } + } + else + { + if (nValueIn < tx.nValueOut) + { + return false; // value in < value out + } + + // Tally transaction fees + ulong nTxFee = nValueIn - tx.nValueOut; + if (nTxFee < 0) + { + return false; // nTxFee < 0 + } + + nFees += nTxFee; + + if (!CTransaction.MoneyRange(nFees)) + { + return false; // nFees out of range + } + } + + } + + return true; } + /// /// Set new top node or current best chain. /// @@ -832,7 +1029,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); } @@ -847,7 +1044,7 @@ namespace Novacoin return false; } - CBlockStoreItem prevBlockCursor = null; + CBlockStoreItem prevBlockCursor; if (!blockMap.TryGetValue(block.header.prevHash, out prevBlockCursor)) { // Unable to get the cursor. @@ -887,6 +1084,8 @@ namespace Novacoin if (!AddItemToIndex(ref itemTemplate, ref block)) { + dbConn.Rollback(); + return false; } @@ -921,14 +1120,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); } @@ -964,13 +1168,6 @@ namespace Novacoin return false; } - public bool WriteNodes(ref CMerkleNode[] merkleNodes) - { - - - return true; - } - /// /// Get block cursor from map. /// @@ -991,6 +1188,34 @@ namespace Novacoin } /// + /// Get merkle node cursor by output metadata. + /// + /// Output metadata object + /// Merkle node cursor or null + public CMerkleNode GetMerkleCursor(TxOutItem item, out CBlockStoreItem blockCursor) + { + blockCursor = null; + + // Trying to get cursor from the database. + var QueryMerkleCursor = dbConn.Query("select * from [MerkleNodes] where [nMerkleNodeID] = ?", item.nMerkleNodeID); + + if (QueryMerkleCursor.Count == 1) + { + var merkleNode = QueryMerkleCursor[0]; + + // Search for block + var results = blockMap.Where(x => x.Value.ItemID == merkleNode.nParentBlockID).Select(x => x.Value).ToArray(); + + blockCursor = results[0]; + + return merkleNode; + } + + // Nothing found. + return null; + } + + /// /// Load cursor from database. /// /// Block hash @@ -1056,16 +1281,10 @@ 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 } @@ -1142,11 +1361,10 @@ namespace Novacoin var intBuffer = new byte[4]; var fStream2 = File.OpenRead(BlockFile); - var readerForBlocks = new BinaryReader(fStream2).BaseStream; - readerForBlocks.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length + fStream2.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length - while (readerForBlocks.Read(buffer, 0, 4) == 4) // Read magic number + while (fStream2.Read(buffer, 0, 4) == 4) // Read magic number { var nMagic = BitConverter.ToUInt32(buffer, 0); if (nMagic != 0xe5e9e8e4) @@ -1154,7 +1372,7 @@ namespace Novacoin throw new Exception("Incorrect magic number."); } - var nBytesRead = readerForBlocks.Read(buffer, 0, 4); + var nBytesRead = fStream2.Read(buffer, 0, 4); if (nBytesRead != 4) { throw new Exception("BLKSZ EOF"); @@ -1162,9 +1380,9 @@ namespace Novacoin var nBlockSize = BitConverter.ToInt32(buffer, 0); - nOffset = readerForBlocks.Position; + nOffset = fStream2.Position; - nBytesRead = readerForBlocks.Read(buffer, 0, nBlockSize); + nBytesRead = fStream2.Read(buffer, 0, nBlockSize); if (nBytesRead == 0 || nBytesRead != nBlockSize) { @@ -1185,19 +1403,9 @@ 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); } - dbConn.Commit(); - return true; }