4 using System.Collections.Concurrent;
7 using SQLite.Net.Attributes;
8 using SQLite.Net.Interop;
9 using SQLite.Net.Platform.Generic;
10 using SQLiteNetExtensions.Attributes;
11 using System.Collections.Generic;
16 /// Block headers table
18 [Table("BlockStorage")]
19 public class CBlockStoreItem
22 /// Item ID in the database
24 [PrimaryKey, AutoIncrement]
25 public int ItemID { get; set; }
28 /// PBKDF2+Salsa20 of block hash
31 public byte[] Hash { get; set; }
34 /// Version of block schema
36 public uint nVersion { get; set; }
39 /// Previous block hash.
41 public byte[] prevHash { get; set; }
46 public byte[] merkleRoot { get; set; }
51 public uint nTime { get; set; }
54 /// Compressed difficulty representation.
56 public uint nBits { get; set; }
61 public uint nNonce { get; set; }
66 public byte[] nextHash { get; set; }
71 public BlockType BlockTypeFlag { get; set; }
76 public long nStakeModifier { get; set; }
81 public byte nEntropyBit { get; set; }
84 /// Proof-of-Stake hash
86 public byte[] hashProofOfStake { get; set; }
91 public uint nHeight { get; set; }
94 /// Block position in file
96 public long nBlockPos { get; set; }
99 /// Block size in bytes
101 public int nBlockSize { get; set; }
104 /// Fill database item with data from given block header.
106 /// <param name="header">Block header</param>
107 /// <returns>Header hash</returns>
108 public uint256 FillHeader(CBlockHeader header)
111 Hash = _hash = header.Hash;
113 nVersion = header.nVersion;
114 prevHash = header.prevHash;
115 merkleRoot = header.merkleRoot;
116 nTime = header.nTime;
117 nBits = header.nBits;
118 nNonce = header.nNonce;
124 /// Reconstruct block header from item data.
126 public CBlockHeader BlockHeader
130 CBlockHeader header = new CBlockHeader();
132 header.nVersion = nVersion;
133 header.prevHash = prevHash;
134 header.merkleRoot = merkleRoot;
135 header.nTime = nTime;
136 header.nBits = nBits;
137 header.nNonce = nNonce;
144 /// Read block from file.
146 /// <param name="reader">Stream with read access.</param>
147 /// <param name="reader">CBlock reference.</param>
148 /// <returns>Result</returns>
149 public bool ReadFromFile(ref Stream reader, out CBlock block)
151 var buffer = new byte[nBlockSize];
156 reader.Seek(nBlockPos, SeekOrigin.Begin);
158 if (nBlockSize != reader.Read(buffer, 0, nBlockSize))
163 block = new CBlock(buffer);
172 catch (BlockException)
174 // Constructor exception
180 /// Writes given block to file and prepares cursor object for insertion into the database.
182 /// <param name="writer">Stream with write access.</param>
183 /// <param name="block">CBlock reference.</param>
184 /// <returns>Result</returns>
185 public bool WriteToFile(ref Stream writer, ref CBlock block)
189 byte[] blockBytes = block;
191 var magicBytes = BitConverter.GetBytes(CBlockStore.nMagicNumber);
192 var blkLenBytes = BitConverter.GetBytes(blockBytes.Length);
194 // Seek to the end and then append magic bytes there.
195 writer.Seek(0, SeekOrigin.End);
196 writer.Write(magicBytes, 0, magicBytes.Length);
197 writer.Write(blkLenBytes, 0, blkLenBytes.Length);
199 // Save block size and current position in the block cursor fields.
200 nBlockPos = writer.Position;
201 nBlockSize = blockBytes.Length;
203 // Write block and flush the stream.
204 writer.Write(blockBytes, 0, blockBytes.Length);
216 // Some serialization error
222 /// Previous block cursor
224 public CBlockStoreItem prev {
225 get { return CBlockStore.Instance.GetCursor(prevHash); }
229 /// Next block cursor
231 public CBlockStoreItem next
233 get { return CBlockStore.Instance.GetCursor(nextHash); }
237 /// STake modifier generation flag
239 public bool GeneratedStakeModifier
241 get { return (BlockTypeFlag & BlockType.BLOCK_STAKE_MODIFIER) != 0; }
244 public uint StakeEntropyBit
246 get { return ((uint)(BlockTypeFlag & BlockType.BLOCK_STAKE_ENTROPY) >> 1); }
250 /// Sets stake modifier and flag.
252 /// <param name="nModifier">New stake modifier.</param>
253 /// <param name="fGeneratedStakeModifier">Set generation flag?</param>
254 public void SetStakeModifier(long nModifier, bool fGeneratedStakeModifier)
256 nStakeModifier = nModifier;
257 if (fGeneratedStakeModifier)
258 BlockTypeFlag |= BlockType.BLOCK_STAKE_MODIFIER;
264 /// <param name="nEntropyBit">Entropy bit value (0 or 1).</param>
265 /// <returns>False if value is our of range.</returns>
266 public bool SetStakeEntropyBit(byte nEntropyBit)
270 BlockTypeFlag |= (nEntropyBit != 0 ? BlockType.BLOCK_STAKE_ENTROPY : 0);
275 /// Set proof-of-stake flag.
277 public void SetProofOfStake()
279 BlockTypeFlag |= BlockType.BLOCK_PROOF_OF_STAKE;
283 /// Block has no proof-of-stake flag.
285 public bool IsProofOfWork
287 get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) != 0; }
291 /// Block has proof-of-stake flag set.
293 public bool IsProofOfStake
295 get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) == 0; }
304 public enum BlockType
306 BLOCK_PROOF_OF_STAKE = (1 << 0), // is proof-of-stake block
307 BLOCK_STAKE_ENTROPY = (1 << 1), // entropy bit for stake modifier
308 BLOCK_STAKE_MODIFIER = (1 << 2), // regenerated stake modifier
312 /// Transaction type.
321 [Table("TransactionStorage")]
322 public class CTransactionStoreItem
328 public byte[] TransactionHash { get; set; }
333 [ForeignKey(typeof(CBlockStoreItem), Name = "Hash")]
334 public byte[] BlockHash { get; set; }
337 /// Transaction type flag
339 public TxType txType { get; set; }
342 /// Tx position in file
344 public long nTxPos { get; set; }
349 public int nTxSize { get; set; }
352 /// Read transaction from file.
354 /// <param name="reader">Stream with read access.</param>
355 /// <param name="tx">CTransaction reference.</param>
356 /// <returns>Result</returns>
357 public bool ReadFromFile(ref Stream reader, out CTransaction tx)
359 var buffer = new byte[CTransaction.nMaxTxSize];
364 reader.Seek(nTxPos, SeekOrigin.Begin); // Seek to transaction offset
366 if (nTxSize != reader.Read(buffer, 0, nTxSize))
371 tx = new CTransaction(buffer);
380 catch (TransactionConstructorException)
388 public class CBlockStore : IDisposable
390 public const uint nMagicNumber = 0xe5e9e8e4;
392 private bool disposed = false;
393 private object LockObj = new object();
396 /// SQLite connection object.
398 private SQLiteConnection dbConn;
403 private string strBlockFile;
406 /// Index database file.
408 private string strDbFile;
411 /// Map of block tree nodes.
413 private ConcurrentDictionary<uint256, CBlockStoreItem> blockMap = new ConcurrentDictionary<uint256, CBlockStoreItem>();
416 /// Orphaned blocks map.
418 private ConcurrentDictionary<uint256, CBlock> orphanMap = new ConcurrentDictionary<uint256, CBlock>();
419 private ConcurrentDictionary<uint256, CBlock> orphanMapByPrev = new ConcurrentDictionary<uint256, CBlock>();
422 /// Map of unspent items.
424 private ConcurrentDictionary<uint256, CTransactionStoreItem> txMap = new ConcurrentDictionary<uint256, CTransactionStoreItem>();
426 public static CBlockStore Instance;
429 /// Block file stream with read access
431 private Stream fStreamReadWrite;
434 /// Init the block storage manager.
436 /// <param name="IndexDB">Path to index database</param>
437 /// <param name="BlockFile">Path to block file</param>
438 public CBlockStore(string IndexDB = "blockstore.dat", string BlockFile = "blk0001.dat")
441 strBlockFile = BlockFile;
443 bool firstInit = !File.Exists(strDbFile);
444 dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), strDbFile);
446 fStreamReadWrite = File.Open(strBlockFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
453 dbConn.CreateTable<CBlockStoreItem>(CreateFlags.AutoIncPK);
454 dbConn.CreateTable<CTransactionStoreItem>(CreateFlags.ImplicitPK);
456 var genesisBlock = new CBlock(
458 "01000000" + // nVersion=1
459 "0000000000000000000000000000000000000000000000000000000000000000" + // prevhash is zero
460 "7b0502ad2f9f675528183f83d6385794fbcaa914e6d385c6cb1d866a3b3bb34c" + // merkle root
461 "398e1151" + // nTime=1360105017
462 "ffff0f1e" + // nBits=0x1e0fffff
463 "d3091800" + // nNonce=1575379
465 "01000000" + // nVersion=1
466 "398e1151" + // nTime=1360105017
468 "0000000000000000000000000000000000000000000000000000000000000000" + // input txid is zero
469 "ffffffff" + // n=uint.maxValue
470 "4d" + // scriptSigLen=77
471 "04ffff001d020f274468747470733a2f2f626974636f696e74616c6b2e6f72672f696e6465782e7068703f746f7069633d3133343137392e6d736731353032313936236d736731353032313936" + // scriptSig
472 "ffffffff" + // nSequence=uint.maxValue
474 "0000000000000000" + // nValue=0
475 "00" + // scriptPubkeyLen=0
476 "00000000" + // nLockTime=0
480 // Write block to file.
481 var itemTemplate = new CBlockStoreItem()
486 itemTemplate.FillHeader(genesisBlock.header);
488 if (!AddItemToIndex(ref itemTemplate, ref genesisBlock))
490 throw new Exception("Unable to write genesis block");
496 var blockTreeItems = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] order by [ItemId] asc");
498 // Init list of block items
499 foreach (var item in blockTreeItems)
501 blockMap.TryAdd(item.Hash, item);
508 public bool GetTransaction(uint256 TxID, ref CTransaction tx)
510 var reader = new BinaryReader(fStreamReadWrite).BaseStream;
511 var QueryTx = dbConn.Query<CTransactionStoreItem>("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID);
513 if (QueryTx.Count == 1)
515 return QueryTx[0].ReadFromFile(ref reader, out tx);
523 private bool AddItemToIndex(ref CBlockStoreItem itemTemplate, ref CBlock block)
525 var writer = new BinaryWriter(fStreamReadWrite).BaseStream;
526 uint256 blockHash = itemTemplate.Hash;
528 if (blockMap.ContainsKey(blockHash))
530 // Already have this block.
534 // TODO: compute chain trust, set stake entropy bit, record proof-of-stake hash value
536 // TODO: compute stake modifier
539 if (block.IsProofOfStake)
541 itemTemplate.SetProofOfStake();
544 if (!itemTemplate.WriteToFile(ref writer, ref block))
549 dbConn.Insert(itemTemplate);
551 // We have no SetBestChain and ConnectBlock/Disconnect block yet, so adding these transactions manually.
552 for (int i = 0; i < block.vtx.Length; i++)
554 // Handle trasactions
556 if (!block.vtx[i].VerifyScripts())
561 var nTxOffset = itemTemplate.nBlockPos + block.GetTxOffset(i);
562 TxType txnType = TxType.TX_USER;
564 if (block.vtx[i].IsCoinBase)
566 txnType = TxType.TX_COINBASE;
568 else if (block.vtx[i].IsCoinStake)
570 txnType = TxType.TX_COINSTAKE;
573 var NewTxItem = new CTransactionStoreItem()
575 TransactionHash = block.vtx[i].Hash,
576 BlockHash = blockHash,
578 nTxSize = block.vtx[i].Size,
582 dbConn.Insert(NewTxItem);
585 return blockMap.TryAdd(blockHash, itemTemplate);
588 public bool AcceptBlock(ref CBlock block)
590 uint256 nHash = block.header.Hash;
592 if (blockMap.ContainsKey(nHash))
594 // Already have this block.
598 CBlockStoreItem prevBlockCursor = null;
599 if (!blockMap.TryGetValue(block.header.prevHash, out prevBlockCursor))
601 // Unable to get the cursor.
605 var prevBlockHeader = prevBlockCursor.BlockHeader;
607 // TODO: proof-of-work/proof-of-stake verification
608 uint nHeight = prevBlockCursor.nHeight + 1;
610 // Check timestamp against prev
611 if (NetUtils.FutureDrift(block.header.nTime) < prevBlockHeader.nTime)
613 // block's timestamp is too early
617 // Check that all transactions are finalized
618 foreach (var tx in block.vtx)
620 if (!tx.IsFinal(nHeight, block.header.nTime))
626 // TODO: Enforce rule that the coinbase starts with serialized block height
628 // Write block to file.
629 var itemTemplate = new CBlockStoreItem()
632 nEntropyBit = Entropy.GetStakeEntropyBit(nHeight, nHash)
635 itemTemplate.FillHeader(block.header);
637 if (!AddItemToIndex(ref itemTemplate, ref block))
645 public bool GetBlock(uint256 blockHash, ref CBlock block)
647 var reader = new BinaryReader(fStreamReadWrite).BaseStream;
649 var QueryBlock = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
651 if (QueryBlock.Count == 1)
653 return QueryBlock[0].ReadFromFile(ref reader, out block);
662 /// Get block cursor from map.
664 /// <param name="blockHash">block hash</param>
665 /// <returns>Cursor or null</returns>
666 public CBlockStoreItem GetCursor(uint256 blockHash)
670 // Genesis block has zero prevHash and no parent.
674 // First, check our block map.
675 CBlockStoreItem item = null;
676 if (blockMap.TryGetValue(blockHash, out item))
681 // Trying to get cursor from the database.
682 var QueryBlockCursor = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
684 if (QueryBlockCursor.Count == 1)
686 return QueryBlockCursor[0];
693 public bool ProcessBlock(ref CBlock block)
695 var blockHash = block.header.Hash;
697 if (blockMap.ContainsKey(blockHash))
699 // We already have this block.
703 if (orphanMap.ContainsKey(blockHash))
705 // We already have block in the list of orphans.
709 // TODO: Limited duplicity on stake and reserialization of block signature
711 if (!block.CheckBlock(true, true, true))
713 // Preliminary checks failure.
717 if (block.IsProofOfStake)
719 if (!block.SignatureOK || !block.vtx[1].VerifyScripts())
721 // Proof-of-Stake signature validation failure.
725 // TODO: proof-of-stake validation
728 // TODO: difficulty verification
730 // If don't already have its previous block, shunt it off to holding area until we get it
731 if (!blockMap.ContainsKey(block.header.prevHash))
733 if (block.IsProofOfStake)
735 // TODO: limit duplicity on stake
738 var block2 = new CBlock(block);
739 orphanMap.TryAdd(blockHash, block2);
740 orphanMapByPrev.TryAdd(blockHash, block2);
745 // Store block to disk
746 if (!AcceptBlock(ref block))
752 // Recursively process any orphan blocks that depended on this one
753 var orphansQueue = new List<uint256>();
754 orphansQueue.Add(blockHash);
756 for (int i = 0; i < orphansQueue.Count; i++)
758 var hashPrev = orphansQueue[i];
760 foreach (var pair in orphanMap)
762 var orphanBlock = pair.Value;
764 if (orphanBlock.header.prevHash == blockHash)
766 if (AcceptBlock(ref orphanBlock))
768 orphansQueue.Add(pair.Key);
772 orphanMap.TryRemove(pair.Key, out dummy1);
777 orphanMap.TryRemove(hashPrev, out dummy2);
783 public bool ParseBlockFile(string BlockFile = "bootstrap.dat")
785 // TODO: Rewrite completely.
789 var buffer = new byte[CBlock.nMaxBlockSize]; // Max block size is 1Mb
790 var intBuffer = new byte[4];
792 var fStream2 = File.OpenRead(BlockFile);
793 var readerForBlocks = new BinaryReader(fStream2).BaseStream;
795 readerForBlocks.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length
797 dbConn.BeginTransaction();
799 while (readerForBlocks.Read(buffer, 0, 4) == 4) // Read magic number
801 var nMagic = BitConverter.ToUInt32(buffer, 0);
802 if (nMagic != 0xe5e9e8e4)
804 throw new Exception("Incorrect magic number.");
807 var nBytesRead = readerForBlocks.Read(buffer, 0, 4);
810 throw new Exception("BLKSZ EOF");
813 var nBlockSize = BitConverter.ToInt32(buffer, 0);
815 nOffset = readerForBlocks.Position;
817 nBytesRead = readerForBlocks.Read(buffer, 0, nBlockSize);
819 if (nBytesRead == 0 || nBytesRead != nBlockSize)
821 throw new Exception("BLK EOF");
824 var block = new CBlock(buffer);
825 var hash = block.header.Hash;
827 if (blockMap.ContainsKey(hash))
832 if (!ProcessBlock(ref block))
834 throw new Exception("Invalid block: " + block.header.Hash);
837 int nCount = blockMap.Count;
838 Console.WriteLine("nCount={0}, Hash={1}, Time={2}", nCount, block.header.Hash, DateTime.Now); // Commit on each 100th block
840 if (nCount % 100 == 0 && nCount != 0)
842 Console.WriteLine("Commit...");
844 dbConn.BeginTransaction();
858 public void Dispose()
861 GC.SuppressFinalize(this);
864 protected virtual void Dispose(bool disposing)
870 // Free other state (managed objects).
872 fStreamReadWrite.Dispose();