X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=Novacoin%2FCBlockStore.cs;h=c6361218e6bb0ff4b2809d92c5ecd7aef440d685;hb=514b68fc3103fca11d6d06ddb65ed8d2a14507e1;hp=7f1f9aece500b94c130fa7248fd2eeded415b7a7;hpb=5ca5da3cb81d1d5b7be655cf0d139f9cab7211c4;p=NovacoinLibrary.git diff --git a/Novacoin/CBlockStore.cs b/Novacoin/CBlockStore.cs index 7f1f9ae..c636121 100644 --- a/Novacoin/CBlockStore.cs +++ b/Novacoin/CBlockStore.cs @@ -1,51 +1,780 @@ -using System; +/** +* Novacoin classes library +* Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com) + +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. + +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +*/ + + +using System; using System.IO; using System.Linq; +using System.Collections.Concurrent; using SQLite.Net; using SQLite.Net.Attributes; using SQLite.Net.Interop; using SQLite.Net.Platform.Generic; +using SQLiteNetExtensions.Attributes; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Text; namespace Novacoin { [Table("BlockStorage")] - class CBlockStoreItem + public class CBlockStoreItem : IBlockStorageItem { + #region IBlockStorageItem /// /// Item ID in the database /// [PrimaryKey, AutoIncrement] - public int ItemID { get; set; } + public long ItemID { get; set; } /// /// PBKDF2+Salsa20 of block hash /// [Unique] - public byte[] ScryptHash { get; set; } + public byte[] Hash { get; set; } + + /// + /// Version of block schema + /// + [Column("nVersion")] + public uint nVersion { get; set; } + + /// + /// Previous block hash. + /// + [Column("prevHash")] + public byte[] prevHash { get; set; } + + /// + /// Merkle root hash. + /// + [Column("merkleRoot")] + public byte[] merkleRoot { get; set; } + + /// + /// Block timestamp. + /// + [Column("nTime")] + public uint nTime { get; set; } + + /// + /// Compressed difficulty representation. + /// + [Column("nBits")] + public uint nBits { get; set; } + + /// + /// Nonce counter. + /// + [Column("nNonce")] + public uint nNonce { get; set; } + + /// + /// Next block hash. + /// + [Column("nextHash")] + public byte[] nextHash { get; set; } + + /// + /// Block type flags + /// + [Column("BlockTypeFlag")] + public BlockType BlockTypeFlag { get; set; } + + /// + /// Stake modifier + /// + [Column("nStakeModifier")] + public long nStakeModifier { get; set; } + + /// + /// Proof-of-Stake hash + /// + [Column("hashProofOfStake")] + public byte[] hashProofOfStake { get; set; } + + /// + /// Stake generation outpoint. + /// + [Column("prevoutStake")] + public byte[] prevoutStake { get; set; } + + /// + /// Stake generation time. + /// + [Column("nStakeTime")] + public uint nStakeTime { get; set; } + + /// + /// Block height, encoded in VarInt format + /// + [Column("Height")] + public byte[] Height { get; set; } + + /// + /// Block position in file, encoded in VarInt format + /// + [Column("BlockPos")] + public byte[] BlockPos { get; set; } + + /// + /// Block size in bytes, encoded in VarInt format + /// + [Column("BlockSize")] + public byte[] BlockSize { get; set; } + #endregion + + /// + /// Accessor and mutator for BlockPos value. + /// + [Ignore] + public long nBlockPos + { + get { return (long)VarInt.DecodeVarInt(BlockPos); } + set { BlockPos = VarInt.EncodeVarInt(value); } + } + + /// + /// Accessor and mutator for BlockSize value. + /// + [Ignore] + public int nBlockSize + { + get { return (int)VarInt.DecodeVarInt(BlockSize); } + set { BlockSize = VarInt.EncodeVarInt(value); } + } + + /// + /// 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 + /// Header hash + public uint256 FillHeader(CBlockHeader header) + { + uint256 _hash = header.Hash; + + Hash = _hash; + + nVersion = header.nVersion; + prevHash = header.prevHash; + merkleRoot = header.merkleRoot; + nTime = header.nTime; + nBits = header.nBits; + nNonce = header.nNonce; + + return _hash; + } + + /// + /// Reconstruct block header from item data. + /// + public CBlockHeader BlockHeader + { + get + { + CBlockHeader header = new CBlockHeader(); + + header.nVersion = nVersion; + header.prevHash = prevHash; + header.merkleRoot = merkleRoot; + header.nTime = nTime; + header.nBits = nBits; + header.nNonce = nNonce; + + return header; + } + } + + /// + /// Read block from file. + /// + /// Stream with read access. + /// CBlock reference. + /// Result + public bool ReadFromFile(ref Stream reader, out CBlock block) + { + var buffer = new byte[nBlockSize]; + block = null; + + try + { + reader.Seek(nBlockPos, SeekOrigin.Begin); + + if (nBlockSize != reader.Read(buffer, 0, nBlockSize)) + { + return false; + } + + block = new CBlock(buffer); + + return true; + } + catch (IOException) + { + // I/O error + return false; + } + catch (BlockException) + { + // Constructor exception + return false; + } + } + + /// + /// Writes given block to file and prepares cursor object for insertion into the database. + /// + /// Stream with write access. + /// CBlock reference. + /// Result + public bool WriteToFile(ref Stream writer, ref CBlock block) + { + try + { + byte[] blockBytes = block; + + var magicBytes = BitConverter.GetBytes(CBlockStore.nMagicNumber); + var blkLenBytes = BitConverter.GetBytes(blockBytes.Length); + + // Seek to the end and then append magic bytes there. + writer.Seek(0, SeekOrigin.End); + writer.Write(magicBytes, 0, magicBytes.Length); + writer.Write(blkLenBytes, 0, blkLenBytes.Length); + + // Save block size and current position in the block cursor fields. + nBlockPos = writer.Position; + nBlockSize = blockBytes.Length; + + // Write block and flush the stream. + writer.Write(blockBytes, 0, blockBytes.Length); + writer.Flush(); + + return true; + } + catch (IOException) + { + // I/O error + return false; + } + catch (Exception) + { + // Some serialization error + return false; + } + } + + /// + /// Previous block cursor + /// + [Ignore] + public CBlockStoreItem prev { + get { return CBlockStore.Instance.GetCursor(prevHash); } + } + + /// + /// Next block cursor + /// + [Ignore] + public CBlockStoreItem next + { + get + { + if (nextHash == null) + { + return null; + } + + return CBlockStore.Instance.GetCursor(nextHash); + } + set + { + CBlockStoreItem newCursor = this; + newCursor.nextHash = value.Hash; + + CBlockStore.Instance.UpdateCursor(this, ref newCursor); + } + } + + [Ignore] + bool IsInMainChain + { + get { return (next != null); } + } + + /// + /// STake modifier generation flag + /// + [Ignore] + public bool GeneratedStakeModifier + { + get { return (BlockTypeFlag & BlockType.BLOCK_STAKE_MODIFIER) != 0; } + } + + /// + /// Stake entropy bit + /// + [Ignore] + public uint StakeEntropyBit + { + get { return ((uint)(BlockTypeFlag & BlockType.BLOCK_STAKE_ENTROPY) >> 1); } + } + + /// + /// Sets stake modifier and flag. + /// + /// New stake modifier. + /// Set generation flag? + public void SetStakeModifier(long nModifier, bool fGeneratedStakeModifier) + { + nStakeModifier = nModifier; + if (fGeneratedStakeModifier) + BlockTypeFlag |= BlockType.BLOCK_STAKE_MODIFIER; + } + + /// + /// Set entropy bit. + /// + /// Entropy bit value (0 or 1). + /// False if value is our of range. + public bool SetStakeEntropyBit(byte nEntropyBit) + { + if (nEntropyBit > 1) + return false; + BlockTypeFlag |= (nEntropyBit != 0 ? BlockType.BLOCK_STAKE_ENTROPY : 0); + return true; + } + + /// + /// Set proof-of-stake flag. + /// + public void SetProofOfStake() + { + BlockTypeFlag |= BlockType.BLOCK_PROOF_OF_STAKE; + } + + /// + /// Block has no proof-of-stake flag. + /// + [Ignore] + public bool IsProofOfWork + { + get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) == 0; } + } + + /// + /// Block has proof-of-stake flag set. + /// + [Ignore] + public bool IsProofOfStake + { + get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) != 0; } + } + + /// + /// Block trust score. + /// + [Ignore] + public uint256 nBlockTrust + { + get + { + uint256 nTarget = 0; + nTarget.Compact = nBits; + + /* Old protocol */ + if (nTime < NetUtils.nChainChecksSwitchTime) + { + return IsProofOfStake ? (new uint256(1) << 256) / (nTarget + 1) : 1; + } + + /* New protocol */ + + // Calculate work amount for block + var nPoWTrust = NetUtils.nPoWBase / (nTarget + 1); + + // Set nPowTrust to 1 if we are checking PoS block or PoW difficulty is too low + nPoWTrust = (IsProofOfStake || !nPoWTrust) ? 1 : nPoWTrust; + + // Return nPoWTrust for the first 12 blocks + if (prev == null || prev.nHeight < 12) + return nPoWTrust; + + CBlockStoreItem currentIndex = prev; + + if (IsProofOfStake) + { + var nNewTrust = (new uint256(1) << 256) / (nTarget + 1); + + // Return 1/3 of score if parent block is not the PoW block + if (!prev.IsProofOfWork) + { + return nNewTrust / 3; + } + + int nPoWCount = 0; + + // Check last 12 blocks type + while (prev.nHeight - currentIndex.nHeight < 12) + { + if (currentIndex.IsProofOfWork) + { + nPoWCount++; + } + currentIndex = currentIndex.prev; + } + + // Return 1/3 of score if less than 3 PoW blocks found + if (nPoWCount < 3) + { + return nNewTrust / 3; + } + + return nNewTrust; + } + else + { + var nLastBlockTrust = prev.nChainTrust - prev.prev.nChainTrust; + + // Return nPoWTrust + 2/3 of previous block score if two parent blocks are not PoS blocks + if (!prev.IsProofOfStake || !prev.prev.IsProofOfStake) + { + return nPoWTrust + (2 * nLastBlockTrust / 3); + } + + int nPoSCount = 0; + + // Check last 12 blocks type + while (prev.nHeight - currentIndex.nHeight < 12) + { + if (currentIndex.IsProofOfStake) + { + nPoSCount++; + } + currentIndex = currentIndex.prev; + } + + // Return nPoWTrust + 2/3 of previous block score if less than 7 PoS blocks found + if (nPoSCount < 7) + { + return nPoWTrust + (2 * nLastBlockTrust / 3); + } + + nTarget.Compact = prev.nBits; + + if (!nTarget) + { + return 0; + } + + var nNewTrust = (new uint256(1) << 256) / (nTarget + 1); + + // Return nPoWTrust + full trust score for previous block nBits + return nPoWTrust + nNewTrust; + } + } + } + + /// + /// Stake modifier checksum. + /// + public uint nStakeModifierChecksum; + + /// + /// Chain trust score + /// + public uint256 nChainTrust; + } + + /// + /// Block type. + /// + public enum BlockType + { + BLOCK_PROOF_OF_STAKE = (1 << 0), // is proof-of-stake block + BLOCK_STAKE_ENTROPY = (1 << 1), // entropy bit for stake modifier + BLOCK_STAKE_MODIFIER = (1 << 2), // regenerated stake modifier + }; + + /// + /// Transaction type. + /// + public enum TxFlags : byte + { + TX_COINBASE, + TX_COINSTAKE, + TX_USER + } + + /// + /// Output flags. + /// + public enum OutputFlags : byte + { + AVAILABLE, // Unspent output + SPENT // Spent output + } + + [Table("MerkleNodes")] + public class CMerkleNode : IMerkleNode + { + #region IMerkleNode + /// + /// Node identifier + /// + [PrimaryKey, AutoIncrement] + public long nMerkleNodeID { get; set; } + + /// + /// Reference to parent block database item. + /// + [ForeignKey(typeof(CBlockStoreItem), Name = "ItemId")] + public long nParentBlockID { get; set; } + + /// + /// Transaction type flag + /// + [Column("TransactionFlags")] + public TxFlags TransactionFlags { get; set; } + + /// + /// Transaction hash + /// + [Column("TransactionHash")] + public byte[] TransactionHash { get; set; } /// - /// Serialized representation of block header + /// Transaction offset from the beginning of block header, encoded in VarInt format. /// - public byte[] BlockHeader { get; set; } + [Column("TxOffset")] + public byte[] TxOffset { get; set; } /// - /// Block position in file + /// Transaction size, encoded in VarInt format. /// - public long nBlockPos { get; set; } + [Column("TxSize")] + public byte[] TxSize { get; set; } + #endregion /// - /// Block size in bytes + /// Read transaction from file. /// - public int nBlockSize { get; set; } + /// Stream with read access. + /// CTransaction reference. + /// Result + public bool ReadFromFile(ref Stream reader, long nBlockPos, out CTransaction tx) + { + var buffer = new byte[CTransaction.nMaxTxSize]; + + tx = null; + + try + { + reader.Seek(nBlockPos + nTxOffset, SeekOrigin.Begin); // Seek to transaction offset + + if (nTxSize != reader.Read(buffer, 0, nTxSize)) + { + return false; + } + + tx = new CTransaction(buffer); + + return true; + } + catch (IOException) + { + // I/O error + return false; + } + catch (TransactionConstructorException) + { + // Constructor error + return false; + } + } + + /// + /// Transaction offset accessor + /// + [Ignore] + public long nTxOffset + { + get { return (long) VarInt.DecodeVarInt(TxOffset); } + } + + /// + /// Transaction size accessor + /// + [Ignore] + public int nTxSize + { + get { return (int)VarInt.DecodeVarInt(TxSize); } + } + + } + + [Table("Outputs")] + public class TxOutItem : ITxOutItem + { + /// + /// Reference to transaction item. + /// + [ForeignKey(typeof(CMerkleNode), Name = "nMerkleNodeID")] + public long nMerkleNodeID { get; set; } + + /// + /// Output flags + /// + public OutputFlags outputFlags { get; set; } + + /// + /// Output number in VarInt format. + /// + public byte[] OutputNumber { get; set; } + + /// + /// Output value in VarInt format. + /// + public byte[] OutputValue { get; set; } + + /// + /// Second half of script which contains spending instructions. + /// + public byte[] scriptPubKey { get; set; } + + /// + /// Getter for output number. + /// + public uint nOut + { + get { return (uint)VarInt.DecodeVarInt(OutputNumber); } + } + + /// + /// Getter for output value. + /// + public ulong nValue + { + get { return VarInt.DecodeVarInt(OutputValue); } + } + + /// + /// Getter ans setter for IsSpent flag. + /// + public bool IsSpent + { + get { return (outputFlags & OutputFlags.SPENT) != 0; } + set { outputFlags |= value ? OutputFlags.SPENT : OutputFlags.AVAILABLE; } + } } public class CBlockStore : IDisposable { + public const uint nMagicNumber = 0xe5e9e8e4; + private bool disposed = false; private object LockObj = new object(); - private SQLiteConnection dbConn = null; - + + /// + /// SQLite connection object. + /// + private SQLiteConnection dbConn; + + /// + /// Block file. + /// + private string strBlockFile; + + /// + /// Index database file. + /// + private string strDbFile; + + /// + /// Map of block tree nodes. + /// + /// blockHash => CBlockStoreItem + /// + private ConcurrentDictionary blockMap = new ConcurrentDictionary(); + + /// + /// Orphaned blocks map. + /// + private ConcurrentDictionary orphanMap = new ConcurrentDictionary(); + private ConcurrentDictionary orphanMapByPrev = new ConcurrentDictionary(); + + /// + /// Unconfirmed transactions. + /// + /// TxID => Transaction + /// + private ConcurrentDictionary mapUnconfirmedTx = new ConcurrentDictionary(); + + /// + /// Map of the proof-of-stake hashes. This is necessary for stake duplication checks. + /// + private ConcurrentDictionary mapProofOfStake = new ConcurrentDictionary(); + + + private ConcurrentDictionary mapStakeSeen = new ConcurrentDictionary(); + private ConcurrentDictionary mapStakeSeenOrphan = new ConcurrentDictionary(); + + /// + /// Trust score for the longest chain. + /// + private uint256 nBestChainTrust = 0; + + /// + /// Top block of the best chain. + /// + private uint256 nHashBestChain = 0; + + /// + /// Cursor which is pointing us to the end of best chain. + /// + private CBlockStoreItem bestBlockCursor = null; + + /// + /// Cursor which is always pointing us to genesis block. + /// + private CBlockStoreItem genesisBlockCursor = null; + + /// + /// Current and the only instance of block storage manager. Should be a property with private setter though it's enough for the beginning. + /// + public static CBlockStore Instance = null; + + /// + /// Block file stream with read/write access + /// + private Stream fStreamReadWrite; + /// /// Init the block storage manager. /// @@ -53,91 +782,670 @@ namespace Novacoin /// Path to block file public CBlockStore(string IndexDB = "blockstore.dat", string BlockFile = "blk0001.dat") { - bool firstInit = !File.Exists(IndexDB); - dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), IndexDB); + strDbFile = IndexDB; + strBlockFile = BlockFile; + + bool firstInit = !File.Exists(strDbFile); + dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), strDbFile); + + fStreamReadWrite = File.Open(strBlockFile, FileMode.OpenOrCreate, FileAccess.ReadWrite); + + Instance = this; if (firstInit) { lock (LockObj) { + // Create tables dbConn.CreateTable(CreateFlags.AutoIncPK); + dbConn.CreateTable(CreateFlags.AutoIncPK); + dbConn.CreateTable(CreateFlags.ImplicitPK); + + var genesisBlock = new CBlock( + Interop.HexToArray( + "01000000" + // nVersion=1 + "0000000000000000000000000000000000000000000000000000000000000000" + // prevhash is zero + "7b0502ad2f9f675528183f83d6385794fbcaa914e6d385c6cb1d866a3b3bb34c" + // merkle root + "398e1151" + // nTime=1360105017 + "ffff0f1e" + // nBits=0x1e0fffff + "d3091800" + // nNonce=1575379 + "01" + // nTxCount=1 + "01000000" + // nVersion=1 + "398e1151" + // nTime=1360105017 + "01" + // nInputs=1 + "0000000000000000000000000000000000000000000000000000000000000000" + // input txid is zero + "ffffffff" + // n=uint.maxValue + "4d" + // scriptSigLen=77 + "04ffff001d020f274468747470733a2f2f626974636f696e74616c6b2e6f72672f696e6465782e7068703f746f7069633d3133343137392e6d736731353032313936236d736731353032313936" + // scriptSig + "ffffffff" + // nSequence=uint.maxValue + "01" + // nOutputs=1 + "0000000000000000" + // nValue=0 + "00" + // scriptPubkeyLen=0 + "00000000" + // nLockTime=0 + "00" // sigLen=0 + )); + + // Write block to file. + var itemTemplate = new CBlockStoreItem() + { + nHeight = 0 + }; + + itemTemplate.FillHeader(genesisBlock.header); + + if (!AddItemToIndex(ref itemTemplate, ref genesisBlock)) + { + throw new Exception("Unable to write genesis block"); + } + } + } + else + { + var blockTreeItems = dbConn.Query("select * from [BlockStorage] order by [ItemId] asc"); + + // Init list of block items + foreach (var item in blockTreeItems) + { + blockMap.TryAdd(item.Hash, item); + + if (item.IsProofOfStake) + { + // build mapStakeSeen + mapStakeSeen.TryAdd(item.prevoutStake, item.nStakeTime); + } } } } - public bool ParseBlockFile(string BlockFile = "bootstrap.dat") + public bool GetTxOutCursor(COutPoint outpoint, ref TxOutItem txOutCursor) { - // TODO: Rewrite completely. + var queryResults = dbConn.Query("select o.* from [Outputs] o left join [MerkleNodes] m on (m.nMerkleNodeID = o.nMerkleNodeID) where m.[TransactionHash] = ?", (byte[])outpoint.hash); + + if (queryResults.Count == 1) + { + txOutCursor = queryResults[0]; - var QueryGet = dbConn.Query("select * from [BlockStorage] order by [ItemId] desc limit 1"); + return true; + } - var nOffset = 0L; + // Tx not found + + return false; + } + + public bool FetchInputs(ref CTransaction tx, ref Dictionary queued, out TxOutItem[] inputs, bool IsBlock, out bool Invalid) + { + Invalid = true; + inputs = null; + + StringBuilder queryBuilder = new StringBuilder(); + + queryBuilder.Append("select o.* 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), + Interop.ToHex(VarInt.EncodeVarInt(tx.vin[i].prevout.n) + )); + } + + var queryResults = dbConn.Query(queryBuilder.ToString()); + + if (queryResults.Count < tx.vin.Length) + { + // It seems than some transactions are being spent in the same block. + + if (IsBlock) + { + + } + else + { + // TODO: use mapUnconfirmed + + return false; + } + } + + inputs = queryResults.ToArray(); + Invalid = false; + + return true; + } + + private bool AddItemToIndex(ref CBlockStoreItem itemTemplate, ref CBlock block) + { + var writer = new BinaryWriter(fStreamReadWrite).BaseStream; + uint256 blockHash = itemTemplate.Hash; + + if (blockMap.ContainsKey(blockHash)) + { + // Already have this block. + return false; + } + + // Compute chain trust score + itemTemplate.nChainTrust = (itemTemplate.prev != null ? itemTemplate.prev.nChainTrust : 0) + itemTemplate.nBlockTrust; + + if (!itemTemplate.SetStakeEntropyBit(Entropy.GetStakeEntropyBit(itemTemplate.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)) + { + return false; // ComputeNextStakeModifier() failed + } + + itemTemplate.SetStakeModifier(nStakeModifier, fGeneratedStakeModifier); + itemTemplate.nStakeModifierChecksum = StakeModifier.GetStakeModifierChecksum(itemTemplate); + + // TODO: verify stake modifier checkpoints + + // Add to index + if (block.IsProofOfStake) + { + itemTemplate.SetProofOfStake(); + + itemTemplate.prevoutStake = block.vtx[1].vin[0].prevout; + itemTemplate.nStakeTime = block.vtx[1].nTime; + } + + if (!itemTemplate.WriteToFile(ref writer, ref block)) + { + return false; + } + + if (dbConn.Insert(itemTemplate) == 0 || !blockMap.TryAdd(blockHash, itemTemplate)) + { + return false; + } + + if (itemTemplate.nChainTrust > nBestChainTrust) + { + // New best chain + + // TODO: SetBestChain implementation + + /* + if (!SetBestChain(ref itemTemplate)) + { + return false; // SetBestChain failed. + } + */ + } + + return true; + } + + private bool SetBestChain(ref CBlockStoreItem cursor) + { + dbConn.BeginTransaction(); + + uint256 hashBlock = cursor.Hash; + + if (genesisBlockCursor == null && hashBlock == NetUtils.nHashGenesisBlock) + { + genesisBlockCursor = cursor; + } + else if (nHashBestChain == (uint256)cursor.prevHash) + { + if (!SetBestChainInner(cursor)) + { + return false; + } + } + else + { + // the first block in the new chain that will cause it to become the new best chain + CBlockStoreItem cursorIntermediate = cursor; + + // list of blocks that need to be connected afterwards + List secondary = new List(); - if (QueryGet.Count() == 1) + // 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 + while (cursorIntermediate.prev != null && cursorIntermediate.prev.nChainTrust > bestBlockCursor.nChainTrust) + { + secondary.Add(cursorIntermediate); + cursorIntermediate = cursorIntermediate.prev; + } + + // Switch to new best branch + if (!Reorganize(cursorIntermediate)) + { + dbConn.Rollback(); + InvalidChainFound(cursor); + return false; // reorganize failed + } + + + } + + + throw new NotImplementedException(); + } + + private void InvalidChainFound(CBlockStoreItem cursor) + { + throw new NotImplementedException(); + } + + private bool Reorganize(CBlockStoreItem cursorIntermediate) + { + throw new NotImplementedException(); + } + + private bool SetBestChainInner(CBlockStoreItem cursor) + { + uint256 hash = cursor.Hash; + CBlock block; + + // Adding to current best branch + if (!ConnectBlock(cursor, false, out block) || !WriteHashBestChain(hash)) { - var res = QueryGet.First(); - nOffset = res.nBlockPos + res.nBlockSize; + dbConn.Rollback(); + InvalidChainFound(cursor); + return false; } - var fileReader = new BinaryReader(File.OpenRead(BlockFile)); - var fileStream = fileReader.BaseStream; + // Add to current best branch + cursor.prev.next = cursor; - var buffer = new byte[1000000]; // Max block size is 1Mb + dbConn.Commit(); + + // Delete redundant memory transactions + foreach (var tx in block.vtx) + { + CTransaction dummy; + mapUnconfirmedTx.TryRemove(tx.Hash, out dummy); + } + + return true; + } + + private bool ConnectBlock(CBlockStoreItem cursor, bool fJustCheck, out CBlock block) + { + 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)) + { + return false; // Invalid block found. + } + + // TODO: the remaining stuff lol :D + + throw new NotImplementedException(); + } + + private bool WriteHashBestChain(uint256 hash) + { + throw new NotImplementedException(); + } + + /// + /// Try to find proof-of-stake hash in the map. + /// + /// Block hash + /// Proof-of-stake hash + /// Proof-of-Stake hash value + private bool GetProofOfStakeHash(uint256 blockHash, out uint256 hashProofOfStake) + { + return mapProofOfStake.TryGetValue(blockHash, out hashProofOfStake); + } + + public bool AcceptBlock(ref CBlock block) + { + uint256 nHash = block.header.Hash; + + if (blockMap.ContainsKey(nHash)) + { + // Already have this block. + return false; + } + + CBlockStoreItem prevBlockCursor = null; + if (!blockMap.TryGetValue(block.header.prevHash, out prevBlockCursor)) + { + // Unable to get the cursor. + return false; + } + + var prevBlockHeader = prevBlockCursor.BlockHeader; + + // TODO: proof-of-work/proof-of-stake verification + uint nHeight = prevBlockCursor.nHeight + 1; + + // Check timestamp against prev + if (NetUtils.FutureDrift(block.header.nTime) < prevBlockHeader.nTime) + { + // block's timestamp is too early + return false; + } + + // Check that all transactions are finalized + foreach (var tx in block.vtx) + { + if (!tx.IsFinal(nHeight, block.header.nTime)) + { + return false; + } + } + + // TODO: Enforce rule that the coinbase starts with serialized block height + + // Write block to file. + var itemTemplate = new CBlockStoreItem() + { + nHeight = nHeight, + }; + + itemTemplate.FillHeader(block.header); + + if (!AddItemToIndex(ref itemTemplate, ref block)) + { + return false; + } + + return true; + } + + public bool GetBlock(uint256 blockHash, ref CBlock block, ref long nBlockPos) + { + var reader = new BinaryReader(fStreamReadWrite).BaseStream; + + var QueryBlock = dbConn.Query("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash); + + if (QueryBlock.Count == 1) + { + nBlockPos = QueryBlock[0].nBlockPos; + return QueryBlock[0].ReadFromFile(ref reader, out block); + } + + // Block not found + + return false; + } + + + /// + /// Interface for join + /// + interface IBlockJoinMerkle : IBlockStorageItem, IMerkleNode + { + } + + /// + /// Get block and transaction by transaction hash. + /// + /// Transaction hash + /// Block reference + /// Transaction reference + /// Block position reference + /// Transaction position reference + /// Result of operation + public bool GetBlockByTransactionID(uint256 TxID, ref CBlock block, ref CTransaction tx, ref long nBlockPos, ref long nTxPos) + { + var queryResult = dbConn.Query("select *, from [BlockStorage] b left join [MerkleNodes] m on (b.[ItemID] = m.[nParentBlockID]) where m.[TransactionHash] = ?", (byte[])TxID); + + if (queryResult.Count == 1) + { + CBlockStoreItem blockCursor = (CBlockStoreItem) queryResult[0]; + CMerkleNode txCursor = (CMerkleNode)queryResult[0]; + + var reader = new BinaryReader(fStreamReadWrite).BaseStream; + + if (!txCursor.ReadFromFile(ref reader, blockCursor.nBlockPos, out tx)) + { + return false; // Unable to read transaction + } + + return blockCursor.ReadFromFile(ref reader, out block); + } + + // Tx not found + + return false; + } + + /// + /// Get block cursor from map. + /// + /// block hash + /// Cursor or null + public CBlockStoreItem GetCursor(uint256 blockHash) + { + if (blockHash == 0) + { + // Genesis block has zero prevHash and no parent. + return null; + } + + // First, check our block map. + CBlockStoreItem item = null; + if (blockMap.TryGetValue(blockHash, out item)) + { + return item; + } + + // Trying to get cursor from the database. + var QueryBlockCursor = dbConn.Query("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash); + + if (QueryBlockCursor.Count == 1) + { + blockMap.TryAdd(blockHash, QueryBlockCursor[0]); + + return QueryBlockCursor[0]; + } + + // Nothing found. + return null; + } + + /// + /// Update cursor in memory and on disk. + /// + /// Original cursor + /// New cursor + /// + public bool UpdateCursor(CBlockStoreItem originalItem, ref CBlockStoreItem newItem) + { + if (blockMap.TryUpdate(originalItem.Hash, newItem, originalItem)) + { + return dbConn.Update(newItem) != 0; + } + + return false; + } + + public bool ProcessBlock(ref CBlock block) + { + var blockHash = block.header.Hash; + + if (blockMap.ContainsKey(blockHash)) + { + // We already have this block. + return false; + } + + if (orphanMap.ContainsKey(blockHash)) + { + // We already have block in the list of orphans. + return false; + } + + // TODO: Limited duplicity on stake and reserialization of block signature + + if (!block.CheckBlock(true, true, true)) + { + // Preliminary checks failure. + return false; + } + + 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)) + { + return false; // do not error here as we expect this during initial block download + } + if (!mapProofOfStake.ContainsKey(blockHash)) + { + // add to mapProofOfStake + mapProofOfStake.TryAdd(blockHash, hashProofOfStake); + } + + } + + // TODO: difficulty verification + + // If don't already have its previous block, shunt it off to holding area until we get it + if (!blockMap.ContainsKey(block.header.prevHash)) + { + if (block.IsProofOfStake) + { + // TODO: limit duplicity on stake + } + + var block2 = new CBlock(block); + orphanMap.TryAdd(blockHash, block2); + orphanMapByPrev.TryAdd(blockHash, block2); + + return true; + } + + // Store block to disk + if (!AcceptBlock(ref block)) + { + // Accept failed + 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++) + { + var hashPrev = orphansQueue[i]; + + foreach (var pair in orphanMap) + { + var orphanBlock = pair.Value; + + if (orphanBlock.header.prevHash == blockHash) + { + if (AcceptBlock(ref orphanBlock)) + { + orphansQueue.Add(pair.Key); + } + + CBlock dummy1; + orphanMap.TryRemove(pair.Key, out dummy1); + } + } + + CBlock dummy2; + orphanMap.TryRemove(hashPrev, out dummy2); + } + + return true; + } + + public bool ParseBlockFile(string BlockFile = "bootstrap.dat") + { + // TODO: Rewrite completely. + + var nOffset = 0L; + + var buffer = new byte[CBlock.nMaxBlockSize]; // Max block size is 1Mb var intBuffer = new byte[4]; - fileStream.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length + var fStream2 = File.OpenRead(BlockFile); + var readerForBlocks = new BinaryReader(fStream2).BaseStream; + + readerForBlocks.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length dbConn.BeginTransaction(); - while (fileStream.Read(buffer, 0, 4) == 4) // Read magic number + while (readerForBlocks.Read(buffer, 0, 4) == 4) // Read magic number { var nMagic = BitConverter.ToUInt32(buffer, 0); if (nMagic != 0xe5e9e8e4) { - Console.WriteLine("Incorrect magic number."); - break; + throw new Exception("Incorrect magic number."); } - var nBytesRead = fileStream.Read(buffer, 0, 4); + var nBytesRead = readerForBlocks.Read(buffer, 0, 4); if (nBytesRead != 4) { - Console.WriteLine("BLKSZ EOF"); - break; + throw new Exception("BLKSZ EOF"); } var nBlockSize = BitConverter.ToInt32(buffer, 0); - nOffset = fileStream.Position; + nOffset = readerForBlocks.Position; - nBytesRead = fileStream.Read(buffer, 0, nBlockSize); + nBytesRead = readerForBlocks.Read(buffer, 0, nBlockSize); if (nBytesRead == 0 || nBytesRead != nBlockSize) { - Console.WriteLine("BLK EOF"); - break; + throw new Exception("BLK EOF"); } var block = new CBlock(buffer); + var hash = block.header.Hash; - if (nOffset % 1000 == 0) // Commit on each 1000th block + if (blockMap.ContainsKey(hash)) { - Console.WriteLine("Offset={0}, Hash: {1}", nOffset, block.header.Hash.ToString()); - dbConn.Commit(); - dbConn.BeginTransaction(); + continue; } - var result = dbConn.Insert(new CBlockStoreItem() + if (!ProcessBlock(ref block)) { - ScryptHash = block.header.Hash, - BlockHeader = block.header, - nBlockPos = nOffset, - nBlockSize = nBlockSize - }); + throw new Exception("Invalid block: " + block.header.Hash); + } + + 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(); - - fileReader.Dispose(); return true; } @@ -160,6 +1468,8 @@ namespace Novacoin if (disposing) { // Free other state (managed objects). + + fStreamReadWrite.Dispose(); } if (dbConn != null)