X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=Novacoin%2FCBlockStore.cs;h=a4466abf6dc697e8ae4bad0a0abaac04304af89b;hb=bbc180adad56adde0fd4421c9b8b49bca27397a4;hp=c6361218e6bb0ff4b2809d92c5ecd7aef440d685;hpb=514b68fc3103fca11d6d06ddb65ed8d2a14507e1;p=NovacoinLibrary.git diff --git a/Novacoin/CBlockStore.cs b/Novacoin/CBlockStore.cs index c636121..a4466ab 100644 --- a/Novacoin/CBlockStore.cs +++ b/Novacoin/CBlockStore.cs @@ -124,8 +124,14 @@ namespace Novacoin /// /// Block height, encoded in VarInt format /// - [Column("Height")] - public byte[] Height { get; set; } + [Column("nHeight")] + public uint nHeight { get; set; } + + /// + /// Chain trust score, serialized and trimmed uint256 representation. + /// + [Column("ChainTrust")] + public byte[] ChainTrust { get; set; } /// /// Block position in file, encoded in VarInt format @@ -161,17 +167,6 @@ namespace Novacoin } /// - /// Accessor and mutator for Height value. - /// - [Ignore] - public uint nHeight - { - get { return (uint)VarInt.DecodeVarInt(Height); } - set { Height = VarInt.EncodeVarInt(value); } - } - - - /// /// Fill database item with data from given block header. /// /// Block header @@ -509,7 +504,11 @@ namespace Novacoin /// /// Chain trust score /// - public uint256 nChainTrust; + [Ignore] + public uint256 nChainTrust { + get { return Interop.AppendWithZeros(ChainTrust); } + set { ChainTrust = Interop.TrimArray(value); } + } } /// @@ -707,6 +706,11 @@ namespace Novacoin private SQLiteConnection dbConn; /// + /// Current SQLite platform + /// + private ISQLitePlatform dbPlatform; + + /// /// Block file. /// private string strBlockFile; @@ -774,6 +778,9 @@ namespace Novacoin /// Block file stream with read/write access /// private Stream fStreamReadWrite; + private uint nBestHeight; + private uint nTimeBestReceived; + private int nTransactionsUpdated; /// /// Init the block storage manager. @@ -786,7 +793,8 @@ namespace Novacoin strBlockFile = BlockFile; bool firstInit = !File.Exists(strDbFile); - dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), strDbFile); + dbPlatform = new SQLitePlatformGeneric(); + dbConn = new SQLiteConnection(dbPlatform, strDbFile); fStreamReadWrite = File.Open(strBlockFile, FileMode.OpenOrCreate, FileAccess.ReadWrite); @@ -873,14 +881,24 @@ namespace Novacoin return false; } - public bool FetchInputs(ref CTransaction tx, ref Dictionary queued, out TxOutItem[] inputs, bool IsBlock, out bool Invalid) + interface InputsJoin : ITxOutItem { - Invalid = true; - inputs = null; + byte[] TransactionHash { get; set; } + } + + 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; + } StringBuilder queryBuilder = new StringBuilder(); - queryBuilder.Append("select o.* from [Outputs] o left join [MerkleNodes] m on (m.[nMerkleNodeID] = o.[nMerkleNodeID]) where "); + 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++) { @@ -890,27 +908,71 @@ namespace Novacoin )); } - var queryResults = dbConn.Query(queryBuilder.ToString()); + var queryResults = dbConn.Query(queryBuilder.ToString()); - if (queryResults.Count < tx.vin.Length) + foreach (var item in queryResults) { - // It seems than some transactions are being spent in the same block. + if (item.IsSpent) + { + return false; // Already spent + } + var inputsKey = new COutPoint(item.TransactionHash, item.nOut); + + // Add output data to dictionary + inputs[inputsKey] = new CTxOut(item.nValue, item.scriptPubKey); + } + + if (queryResults.Count < tx.vin.Length) + { if (IsBlock) { - + // It seems that some transactions are being spent in the same block. + + foreach (var txin in tx.vin) + { + var outPoint = txin.prevout; + + if (!queued.ContainsKey(outPoint)) + { + return false; // No such transaction + } + + // Add output data to dictionary + inputs[outPoint] = queued[outPoint]; + + // And remove it from queued data + queued.Remove(outPoint); + } } else { - // TODO: use mapUnconfirmed + // Unconfirmed transaction + + foreach (var txin in tx.vin) + { + var outPoint = txin.prevout; + CTransaction txPrev; + + if (!mapUnconfirmedTx.TryGetValue(outPoint.hash, out txPrev)) + { + return false; // No such transaction + } + + if (outPoint.n > txPrev.vout.Length) + { + Invalid = true; + + return false; // nOut is out of range + } + + inputs[outPoint] = txPrev.vout[outPoint.n]; + } return false; } } - inputs = queryResults.ToArray(); - Invalid = false; - return true; } @@ -971,23 +1033,27 @@ namespace Novacoin return false; } - if (dbConn.Insert(itemTemplate) == 0 || !blockMap.TryAdd(blockHash, itemTemplate)) + if (dbConn.Insert(itemTemplate) == 0) { - return false; + return false; // Insert failed + } + + // Get last RowID. + itemTemplate.ItemID = dbPlatform.SQLiteApi.LastInsertRowid(dbConn.Handle); + + if (!blockMap.TryAdd(blockHash, itemTemplate)) + { + return false; // blockMap add failed } if (itemTemplate.nChainTrust > nBestChainTrust) { // New best chain - // TODO: SetBestChain implementation - - /* if (!SetBestChain(ref itemTemplate)) { return false; // SetBestChain failed. } - */ } return true; @@ -995,8 +1061,6 @@ namespace Novacoin private bool SetBestChain(ref CBlockStoreItem cursor) { - dbConn.BeginTransaction(); - uint256 hashBlock = cursor.Hash; if (genesisBlockCursor == null && hashBlock == NetUtils.nHashGenesisBlock) @@ -1013,10 +1077,10 @@ namespace Novacoin else { // the first block in the new chain that will cause it to become the new best chain - CBlockStoreItem cursorIntermediate = cursor; + var cursorIntermediate = cursor; // list of blocks that need to be connected afterwards - List secondary = new List(); + var secondary = new List(); // Reorganize is costly in terms of db load, as it works in a single db transaction. // Try to limit how much needs to be done inside @@ -1029,16 +1093,36 @@ namespace Novacoin // Switch to new best branch if (!Reorganize(cursorIntermediate)) { - dbConn.Rollback(); InvalidChainFound(cursor); return false; // reorganize failed } + // Connect further blocks + foreach (var currentCursor in secondary) + { + CBlock block; + if (!currentCursor.ReadFromFile(ref fStreamReadWrite, out block)) + { + // ReadFromDisk failed + break; + } + // errors now are not fatal, we still did a reorganisation to a new chain in a valid way + if (!SetBestChainInner(currentCursor)) + { + break; + } + } } + nHashBestChain = cursor.Hash; + bestBlockCursor = cursor; + nBestHeight = cursor.nHeight; + nBestChainTrust = cursor.nChainTrust; + nTimeBestReceived = Interop.GetTime(); + nTransactionsUpdated++; - throw new NotImplementedException(); + return true; } private void InvalidChainFound(CBlockStoreItem cursor) @@ -1048,6 +1132,120 @@ namespace Novacoin private bool Reorganize(CBlockStoreItem cursorIntermediate) { + // Find the fork + var fork = bestBlockCursor; + var longer = cursorIntermediate; + + while (fork.ItemID != longer.ItemID) + { + while (longer.nHeight > fork.nHeight) + { + if ((longer = longer.prev) == null) + { + return false; // longer.prev is null + } + } + + if (fork.ItemID == longer.ItemID) + { + break; + } + + if ((fork = fork.prev) == null) + { + return false; // fork.prev is null + } + } + + // List of what to disconnect + var disconnect = new List(); + for (var cursor = bestBlockCursor; cursor.ItemID != fork.ItemID; cursor = cursor.prev) + { + disconnect.Add(cursor); + } + + // List of what to connect + var connect = new List(); + for (var cursor = cursorIntermediate; cursor.ItemID != fork.ItemID; cursor = cursor.prev) + { + connect.Add(cursor); + } + connect.Reverse(); + + // Disconnect shorter branch + var txResurrect = new List(); + foreach (var blockCursor in disconnect) + { + CBlock block; + if (!blockCursor.ReadFromFile(ref fStreamReadWrite, out block)) + { + return false; // ReadFromFile for disconnect failed. + } + if (!DisconnectBlock(blockCursor, ref block)) + { + return false; // DisconnectBlock failed. + } + + // Queue memory transactions to resurrect + foreach (var tx in block.vtx) + { + if (!tx.IsCoinBase && !tx.IsCoinStake) + { + txResurrect.Add(tx); + } + } + } + + + // Connect longer branch + var txDelete = new List(); + foreach (var cursor in connect) + { + CBlock block; + if (!cursor.ReadFromFile(ref fStreamReadWrite, out block)) + { + return false; // ReadFromDisk for connect failed + } + + if (!ConnectBlock(cursor, ref block)) + { + // Invalid block + return false; // ConnectBlock failed + } + + // Queue memory transactions to delete + foreach (var tx in block.vtx) + { + txDelete.Add(tx); + } + } + + if (!WriteHashBestChain(cursorIntermediate.Hash)) + { + return false; // WriteHashBestChain 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) + { + mapUnconfirmedTx.TryAdd(tx.Hash, tx); + } + + // Delete redundant memory transactions that are in the connected branch + foreach (var tx in txDelete) + { + CTransaction dummy; + mapUnconfirmedTx.TryRemove(tx.Hash, out dummy); + } + + return true; // Done + } + + private bool DisconnectBlock(CBlockStoreItem blockCursor, ref CBlock block) + { throw new NotImplementedException(); } @@ -1055,11 +1253,14 @@ namespace Novacoin { uint256 hash = cursor.Hash; CBlock block; + if (!cursor.ReadFromFile(ref fStreamReadWrite, out block)) + { + return false; // Unable to read block from file. + } // Adding to current best branch - if (!ConnectBlock(cursor, false, out block) || !WriteHashBestChain(hash)) + if (!ConnectBlock(cursor, ref block) || !WriteHashBestChain(hash)) { - dbConn.Rollback(); InvalidChainFound(cursor); return false; } @@ -1079,14 +1280,8 @@ namespace Novacoin return true; } - private bool ConnectBlock(CBlockStoreItem cursor, bool fJustCheck, out CBlock block) + private bool ConnectBlock(CBlockStoreItem cursor, ref CBlock block, bool fJustCheck=false) { - var reader = new BinaryReader(fStreamReadWrite).BaseStream; - if (cursor.ReadFromFile(ref reader, out block)) - { - return false; // Unable to read block from file. - } - // Check it again in case a previous version let a bad block in, but skip BlockSig checking if (!block.CheckBlock(!fJustCheck, !fJustCheck, false)) { @@ -1394,8 +1589,6 @@ namespace Novacoin readerForBlocks.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length - dbConn.BeginTransaction(); - while (readerForBlocks.Read(buffer, 0, 4) == 4) // Read magic number { var nMagic = BitConverter.ToUInt32(buffer, 0); @@ -1437,12 +1630,13 @@ 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(); - } + }*/ } dbConn.Commit();