/** * 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")] public class CBlockStoreItem : IBlockStorageItem { #region IBlockStorageItem /// /// Item ID in the database /// [PrimaryKey, AutoIncrement] public long ItemID { get; set; } /// /// PBKDF2+Salsa20 of block hash /// [Unique] 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; } /// /// Transaction offset from the beginning of block header, encoded in VarInt format. /// [Column("TxOffset")] public byte[] TxOffset { get; set; } /// /// Transaction size, encoded in VarInt format. /// [Column("TxSize")] public byte[] TxSize { get; set; } #endregion /// /// Read transaction from file. /// /// 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(); /// /// 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. /// /// Path to index database /// Path to block file public CBlockStore(string IndexDB = "blockstore.dat", string BlockFile = "blk0001.dat") { 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 GetTxOutCursor(COutPoint outpoint, ref 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); if (queryResults.Count == 1) { txOutCursor = queryResults[0]; return true; } // 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(); // 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)) { dbConn.Rollback(); InvalidChainFound(cursor); return false; } // Add to current best branch cursor.prev.next = cursor; 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]; 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 (readerForBlocks.Read(buffer, 0, 4) == 4) // Read magic number { var nMagic = BitConverter.ToUInt32(buffer, 0); if (nMagic != 0xe5e9e8e4) { throw new Exception("Incorrect magic number."); } var nBytesRead = readerForBlocks.Read(buffer, 0, 4); if (nBytesRead != 4) { throw new Exception("BLKSZ EOF"); } var nBlockSize = BitConverter.ToInt32(buffer, 0); nOffset = readerForBlocks.Position; nBytesRead = readerForBlocks.Read(buffer, 0, nBlockSize); if (nBytesRead == 0 || nBytesRead != nBlockSize) { throw new Exception("BLK EOF"); } var block = new CBlock(buffer); var hash = block.header.Hash; if (blockMap.ContainsKey(hash)) { continue; } if (!ProcessBlock(ref block)) { 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(); return true; } ~CBlockStore() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { // Free other state (managed objects). fStreamReadWrite.Dispose(); } if (dbConn != null) { dbConn.Close(); dbConn = null; } disposed = true; } } } }