/** * 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("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 /// [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); } } /// /// 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.GetMapCursor(prevHash); } } /// /// Next block cursor /// [Ignore] public CBlockStoreItem next { get { if (nextHash == null) { return null; } return CBlockStore.Instance.GetMapCursor(nextHash); } set { nextHash = value.Hash; CBlockStore.Instance.UpdateMapCursor(this); } } [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 < NetInfo.nChainChecksSwitchTime) { return IsProofOfStake ? (new uint256(1) << 256) / (nTarget + 1) : 1; } /* New protocol */ // Calculate work amount for block var nPoWTrust = NetInfo.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 /// [Ignore] public uint256 nChainTrust { get { return Interop.AppendWithZeros(ChainTrust); } set { ChainTrust = Interop.TrimArray(value); } } public long nMint { get; internal set; } public long nMoneySupply { get; internal set; } } /// /// 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); } private set { TxOffset = VarInt.EncodeVarInt(value); } } /// /// Transaction size accessor /// [Ignore] public int nTxSize { get { return (int)VarInt.DecodeVarInt(TxSize); } private set { TxSize = VarInt.EncodeVarInt(value); } } public CMerkleNode(CTransaction tx) { nTxOffset = -1; nParentBlockID = -1; nTxSize = tx.Size; TransactionHash = tx.Hash; if (tx.IsCoinBase) { TransactionFlags |= TxFlags.TX_COINBASE; } else if (tx.IsCoinStake) { TransactionFlags |= TxFlags.TX_COINSTAKE; } else { TransactionFlags |= TxFlags.TX_USER; } } public CMerkleNode(long nBlockId, long nOffset, CTransaction tx) { nParentBlockID = nBlockId; nTxOffset = nOffset; nTxSize = tx.Size; TransactionHash = tx.Hash; if (tx.IsCoinBase) { TransactionFlags |= TxFlags.TX_COINBASE; } else if (tx.IsCoinStake) { TransactionFlags |= TxFlags.TX_COINSTAKE; } else { TransactionFlags |= TxFlags.TX_USER; } } } [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. /// [Ignore] public uint nOut { get { return (uint)VarInt.DecodeVarInt(OutputNumber); } private set { OutputNumber = VarInt.EncodeVarInt(value); } } /// /// Getter for output value. /// [Ignore] public ulong nValue { get { return VarInt.DecodeVarInt(OutputValue); } private set { OutputValue = VarInt.EncodeVarInt(value); } } /// /// Getter ans setter for IsSpent flag. /// [Ignore] public bool IsSpent { get { return (outputFlags & OutputFlags.SPENT) != 0; } set { outputFlags |= value ? OutputFlags.SPENT : OutputFlags.AVAILABLE; } } public TxOutItem(CTxOut o, uint nOut) { nValue = o.nValue; scriptPubKey = o.scriptPubKey; this.nOut = nOut; } } public class CBlockStore : IDisposable { public const uint nMagicNumber = 0xe5e9e8e4; private bool disposed = false; private object LockObj = new object(); /// /// SQLite connection object. /// private SQLiteConnection dbConn; /// /// Current SQLite platform /// private ISQLitePlatform dbPlatform; /// /// 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; private uint nBestHeight; private uint nTimeBestReceived; private int nTransactionsUpdated; /// /// 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); dbPlatform = new SQLitePlatformGeneric(); dbConn = new SQLiteConnection(dbPlatform, 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; } interface InputsJoin : ITxOutItem { byte[] TransactionHash { get; set; } } public bool FetchInputs(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.*, 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), Interop.ToHex(VarInt.EncodeVarInt(tx.vin[i].prevout.n) )); } var queryResults = dbConn.Query(queryBuilder.ToString()); foreach (var item in queryResults) { if (item.IsSpent) { return false; // Already spent } var inputsKey = new COutPoint(item.TransactionHash, item.nOut); item.IsSpent = true; // Add output data to dictionary inputs.Add(inputsKey, (TxOutItem) item); } 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.Add(outPoint, queued[outPoint]); // Mark output as spent queued[outPoint].IsSpent = true; } } else { // 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 } // TODO: return inputs from map throw new NotImplementedException(); } return 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) { 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 if (!SetBestChain(ref itemTemplate)) { return false; // SetBestChain failed. } } return true; } private bool SetBestChain(ref CBlockStoreItem cursor) { uint256 hashBlock = cursor.Hash; if (genesisBlockCursor == null && hashBlock == NetInfo.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 var cursorIntermediate = cursor; // list of blocks that need to be connected afterwards 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 while (cursorIntermediate.prev != null && cursorIntermediate.prev.nChainTrust > bestBlockCursor.nChainTrust) { secondary.Add(cursorIntermediate); cursorIntermediate = cursorIntermediate.prev; } // Switch to new best branch if (!Reorganize(cursorIntermediate)) { 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++; return true; } private void InvalidChainFound(CBlockStoreItem cursor) { throw new NotImplementedException(); } 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(); } private bool SetBestChainInner(CBlockStoreItem cursor) { 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, ref block) || !WriteHashBestChain(hash)) { 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, 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)) { return false; // Invalid block found. } bool fScriptChecks = cursor.nHeight >= Checkpoints.TotalBlocksEstimate; var scriptFlags = scriptflag.SCRIPT_VERIFY_NOCACHE | scriptflag.SCRIPT_VERIFY_P2SH; ulong nFees = 0; ulong nValueIn = 0; ulong nValueOut = 0; uint nSigOps = 0; var queuedMerkleNodes = new Dictionary(); var queued = 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); Dictionary txouts; if (GetOutputs(hashTx, out txouts)) { // Do not allow blocks that contain transactions which 'overwrite' older transactions, // unless those are already completely spent. return false; } nSigOps += tx.LegacySigOpCount; if (nSigOps > CBlock.nMaxSigOps) { return false; // too many sigops } var inputs = new Dictionary(); if (tx.IsCoinBase) { nValueOut += tx.nValueOut; } else { bool Invalid; if (!FetchInputs(tx, ref queued, ref inputs, true, out Invalid)) { return false; // Unable to fetch some inputs. } // 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); if (nSigOps > CBlock.nMaxSigOps) { return false; // too many sigops } ulong nTxValueIn = tx.GetValueIn(inputs); ulong nTxValueOut = tx.nValueOut; nValueIn += nTxValueIn; nValueOut += nTxValueOut; if (!tx.IsCoinStake) { nFees += nTxValueIn - nTxValueOut; } if (!ConnectInputs(tx, inputs, queued, cursor, fScriptChecks, scriptFlags)) { return false; } } 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(tx.vout[i], i); outData.IsSpent = false; queued.Add(outKey, outData); } } if (!block.IsProofOfStake) { ulong nBlockReward = CBlock.GetProofOfWorkReward(cursor.nBits, nFees); // Check coinbase reward if (block.vtx[0].nValueOut > nBlockReward) { return false; // coinbase reward exceeded } } cursor.nMint = (long) (nValueOut - nValueIn + nFees); cursor.nMoneySupply = (cursor.prev != null ? cursor.prev.nMoneySupply : 0) + (long)nValueOut - (long)nValueIn; if (!UpdateDBCursor(ref cursor)) { return false; // Unable to commit changes } if (fJustCheck) { return true; } // Write queued transaction changes var actualMerkleNodes = new Dictionary(); var queuedOutpointItems = new List(); foreach(KeyValuePair outPair in queued) { uint256 txID = outPair.Key.hash; CMerkleNode merkleNode; if (actualMerkleNodes.ContainsKey(txID)) { merkleNode = actualMerkleNodes[txID]; } else { merkleNode = queuedMerkleNodes[txID]; if (!SaveMerkleNode(ref merkleNode)) { // Unable to save merkle tree cursor. return false; } actualMerkleNodes.Add(txID, merkleNode); } var outItem = outPair.Value; outItem.nMerkleNodeID = merkleNode.nMerkleNodeID; queuedOutpointItems.Add(outItem); } if (!SaveOutpoints(ref queuedOutpointItems)) { return false; // Unable to save outpoints } // TODO: the remaining stuff lol :D throw new NotImplementedException(); } /// /// Insert set of outpoints /// /// List of TxOutItem objects. /// Result private bool SaveOutpoints(ref List queuedOutpointItems) { return dbConn.InsertAll(queuedOutpointItems, false) != 0; } /// /// Insert merkle node into db and set actual record id value. /// /// Merkle node object reference. /// Result private bool SaveMerkleNode(ref CMerkleNode merkleNode) { if (dbConn.Insert(merkleNode) == 0) { return false; } merkleNode.nMerkleNodeID = dbPlatform.SQLiteApi.LastInsertRowid(dbConn.Handle); return true; } private bool ConnectInputs(CTransaction tx, Dictionary inputs, Dictionary queued, CBlockStoreItem cursor, bool fScriptChecks, scriptflag scriptFlags) { 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 (NetInfo.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; } /// /// GEt block by hash. /// /// Block hash /// Block object reference /// Block position reference /// Result public bool GetBlock(uint256 blockHash, ref CBlock block, ref long nBlockPos) { CBlockStoreItem cursor; if (!blockMap.TryGetValue(blockHash, out cursor)) { return false; // Unable to fetch block cursor } nBlockPos = cursor.nBlockPos; return cursor.ReadFromFile(ref fStreamReadWrite, out block); } /// /// 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; } public bool GetOutputs(uint256 transactionHash, out Dictionary txouts, bool fUnspentOnly=true) { txouts = null; var queryParams = new object[] { (byte[])transactionHash, fUnspentOnly ? OutputFlags.AVAILABLE : (OutputFlags.AVAILABLE | OutputFlags.SPENT) }; var queryResult = dbConn.Query("select o.* from [Outputs] o left join [MerkleNodes] m on m.[nMerkleNodeID] = o.[nMerkleNodeID] where m.[TransactionHash] = ? and outputFlags = ?", queryParams); if (queryResult.Count != 0) { txouts = new Dictionary(); foreach (var o in queryResult) { var outpointKey = new COutPoint(transactionHash, o.nOut); var outpointData = o; txouts.Add(outpointKey, outpointData); } // There are some unspent inputs. return true; } // This transaction has been spent completely. return false; } public bool WriteNodes(ref CMerkleNode[] merkleNodes) { return true; } /// /// Get block cursor from map. /// /// block hash /// Cursor or null public CBlockStoreItem GetMapCursor(uint256 blockHash) { if (blockHash == 0) { // Genesis block has zero prevHash and no parent. return null; } CBlockStoreItem cursor = null; blockMap.TryGetValue(blockHash, out cursor); return cursor; } /// /// Load cursor from database. /// /// Block hash /// Block cursor object public CBlockStoreItem GetDBCursor(uint256 blockHash) { // Trying to get cursor from the database. var QueryBlockCursor = dbConn.Query("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash); if (QueryBlockCursor.Count == 1) { return QueryBlockCursor[0]; } // Nothing found. return null; } /// /// Update cursor in memory and on disk. /// /// Block cursor /// Result public bool UpdateMapCursor(CBlockStoreItem cursor) { var original = blockMap[cursor.Hash]; return blockMap.TryUpdate(cursor.Hash, cursor, original); } /// /// Update cursor record in database. /// /// Block cursor object /// Result public bool UpdateDBCursor(ref CBlockStoreItem cursor) { return dbConn.Update(cursor) != 0; } 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 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; } } } }