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 BlockType BlockTypeFlag { get; set; }
71 public long nStakeModifier { get; set; }
76 public byte nEntropyBit { get; set; }
81 public uint nHeight { get; set; }
84 /// Block position in file
86 public long nBlockPos { get; set; }
89 /// Block size in bytes
91 public int nBlockSize { get; set; }
94 /// Fill database item with data from given block header.
96 /// <param name="header">Block header</param>
97 /// <returns>Header hash</returns>
98 public uint256 FillHeader(CBlockHeader header)
101 Hash = _hash = header.Hash;
103 nVersion = header.nVersion;
104 prevHash = header.prevHash;
105 merkleRoot = header.merkleRoot;
106 nTime = header.nTime;
107 nBits = header.nBits;
108 nNonce = header.nNonce;
114 /// Reconstruct block header from item data.
116 public CBlockHeader BlockHeader
120 CBlockHeader header = new CBlockHeader();
122 header.nVersion = nVersion;
123 header.prevHash = prevHash;
124 header.merkleRoot = merkleRoot;
125 header.nTime = nTime;
126 header.nBits = nBits;
127 header.nNonce = nNonce;
134 /// Read block from file.
136 /// <param name="reader">Stream with read access.</param>
137 /// <param name="reader">CBlock reference.</param>
138 /// <returns>Result</returns>
139 public bool ReadFromFile(ref Stream reader, out CBlock block)
141 var buffer = new byte[nBlockSize];
146 reader.Seek(nBlockPos, SeekOrigin.Begin);
148 if (nBlockSize != reader.Read(buffer, 0, nBlockSize))
153 block = new CBlock(buffer);
162 catch (BlockException)
164 // Constructor exception
170 /// Writes given block to file and prepares cursor object for insertion into the database.
172 /// <param name="writer">Stream with write access.</param>
173 /// <param name="block">CBlock reference.</param>
174 /// <returns>Result</returns>
175 public bool WriteToFile(ref Stream writer, ref CBlock block)
179 byte[] blockBytes = block;
181 var magicBytes = BitConverter.GetBytes(CBlockStore.nMagicNumber);
182 var blkLenBytes = BitConverter.GetBytes(blockBytes.Length);
184 // Seek to the end and then append magic bytes there.
185 writer.Seek(0, SeekOrigin.End);
186 writer.Write(magicBytes, 0, magicBytes.Length);
187 writer.Write(blkLenBytes, 0, blkLenBytes.Length);
189 // Save block size and current position in the block cursor fields.
190 nBlockPos = writer.Position;
191 nBlockSize = blockBytes.Length;
193 // Write block and flush the stream.
194 writer.Write(blockBytes, 0, blockBytes.Length);
206 // Some serialization error
212 /// Previous block cursor
214 public public CBlockStoreItem prev {
215 get { return CBlockStore.Instance.GetCursor(prevHash); }
219 /// STake modifier generation flag
221 public bool GeneratedStakeModifier
223 get { return (BlockTypeFlag & BlockType.BLOCK_STAKE_MODIFIER) != 0; }
227 /// Sets stake modifier and flag.
229 /// <param name="nModifier">New stake modifier.</param>
230 /// <param name="fGeneratedStakeModifier">Set generation flag?</param>
231 public void SetStakeModifier(long nModifier, bool fGeneratedStakeModifier)
233 nStakeModifier = nModifier;
234 if (fGeneratedStakeModifier)
235 BlockTypeFlag |= BlockType.BLOCK_STAKE_MODIFIER;
241 /// <param name="nEntropyBit">Entropy bit value (0 or 1).</param>
242 /// <returns>False if value is our of range.</returns>
243 public bool SetStakeEntropyBit(byte nEntropyBit)
247 BlockTypeFlag |= (nEntropyBit != 0 ? BlockType.BLOCK_STAKE_ENTROPY : 0);
252 /// Set proof-of-stake flag.
254 public void SetProofOfStake()
256 BlockTypeFlag |= BlockType.BLOCK_PROOF_OF_STAKE;
260 /// Block has no proof-of-stake flag.
262 public bool IsProofOfWork
264 get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) != 0; }
268 /// Block has proof-of-stake flag set.
270 public bool IsProofOfStake
272 get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) == 0; }
281 public enum BlockType
283 BLOCK_PROOF_OF_STAKE = (1 << 0), // is proof-of-stake block
284 BLOCK_STAKE_ENTROPY = (1 << 1), // entropy bit for stake modifier
285 BLOCK_STAKE_MODIFIER = (1 << 2), // regenerated stake modifier
289 /// Transaction type.
298 [Table("TransactionStorage")]
299 public class CTransactionStoreItem
305 public byte[] TransactionHash { get; set; }
310 [ForeignKey(typeof(CBlockStoreItem), Name = "Hash")]
311 public byte[] BlockHash { get; set; }
314 /// Transaction type flag
316 public TxType txType { get; set; }
319 /// Tx position in file
321 public long nTxPos { get; set; }
326 public int nTxSize { get; set; }
329 /// Read transaction from file.
331 /// <param name="reader">Stream with read access.</param>
332 /// <param name="tx">CTransaction reference.</param>
333 /// <returns>Result</returns>
334 public bool ReadFromFile(ref Stream reader, out CTransaction tx)
336 var buffer = new byte[CTransaction.nMaxTxSize];
341 reader.Seek(nTxPos, SeekOrigin.Begin); // Seek to transaction offset
343 if (nTxSize != reader.Read(buffer, 0, nTxSize))
348 tx = new CTransaction(buffer);
357 catch (TransactionConstructorException)
365 public class CBlockStore : IDisposable
367 public const uint nMagicNumber = 0xe5e9e8e4;
369 private bool disposed = false;
370 private object LockObj = new object();
373 /// SQLite connection object.
375 private SQLiteConnection dbConn;
380 private string strBlockFile;
383 /// Index database file.
385 private string strDbFile;
388 /// Map of block tree nodes.
390 private ConcurrentDictionary<uint256, CBlockStoreItem> blockMap = new ConcurrentDictionary<uint256, CBlockStoreItem>();
393 /// Orphaned blocks map.
395 private ConcurrentDictionary<uint256, CBlock> orphanMap = new ConcurrentDictionary<uint256, CBlock>();
396 private ConcurrentDictionary<uint256, CBlock> orphanMapByPrev = new ConcurrentDictionary<uint256, CBlock>();
399 /// Map of unspent items.
401 private ConcurrentDictionary<uint256, CTransactionStoreItem> txMap = new ConcurrentDictionary<uint256, CTransactionStoreItem>();
403 public static CBlockStore Instance;
406 /// Block file stream with read access
408 private Stream fStreamReadWrite;
411 /// Init the block storage manager.
413 /// <param name="IndexDB">Path to index database</param>
414 /// <param name="BlockFile">Path to block file</param>
415 public CBlockStore(string IndexDB = "blockstore.dat", string BlockFile = "blk0001.dat")
418 strBlockFile = BlockFile;
420 bool firstInit = !File.Exists(strDbFile);
421 dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), strDbFile);
423 fStreamReadWrite = File.Open(strBlockFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
430 dbConn.CreateTable<CBlockStoreItem>(CreateFlags.AutoIncPK);
431 dbConn.CreateTable<CTransactionStoreItem>(CreateFlags.ImplicitPK);
433 var genesisBlock = new CBlock(
435 "01000000" + // nVersion=1
436 "0000000000000000000000000000000000000000000000000000000000000000" + // prevhash is zero
437 "7b0502ad2f9f675528183f83d6385794fbcaa914e6d385c6cb1d866a3b3bb34c" + // merkle root
438 "398e1151" + // nTime=1360105017
439 "ffff0f1e" + // nBits=0x1e0fffff
440 "d3091800" + // nNonce=1575379
442 "01000000" + // nVersion=1
443 "398e1151" + // nTime=1360105017
445 "0000000000000000000000000000000000000000000000000000000000000000" + // input txid is zero
446 "ffffffff" + // n=uint.maxValue
447 "4d" + // scriptSigLen=77
448 "04ffff001d020f274468747470733a2f2f626974636f696e74616c6b2e6f72672f696e6465782e7068703f746f7069633d3133343137392e6d736731353032313936236d736731353032313936" + // scriptSig
449 "ffffffff" + // nSequence=uint.maxValue
451 "0000000000000000" + // nValue=0
452 "00" + // scriptPubkeyLen=0
453 "00000000" + // nLockTime=0
457 // Write block to file.
458 var itemTemplate = new CBlockStoreItem()
463 itemTemplate.FillHeader(genesisBlock.header);
465 if (!AddItemToIndex(ref itemTemplate, ref genesisBlock))
467 throw new Exception("Unable to write genesis block");
473 var blockTreeItems = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] order by [ItemId] asc");
475 // Init list of block items
476 foreach (var item in blockTreeItems)
478 blockMap.TryAdd(item.Hash, item);
485 public bool GetTransaction(uint256 TxID, ref CTransaction tx)
487 var reader = new BinaryReader(fStreamReadWrite).BaseStream;
488 var QueryTx = dbConn.Query<CTransactionStoreItem>("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID);
490 if (QueryTx.Count == 1)
492 return QueryTx[0].ReadFromFile(ref reader, out tx);
500 private bool AddItemToIndex(ref CBlockStoreItem itemTemplate, ref CBlock block)
502 var writer = new BinaryWriter(fStreamReadWrite).BaseStream;
503 uint256 blockHash = itemTemplate.Hash;
505 if (blockMap.ContainsKey(blockHash))
507 // Already have this block.
511 // TODO: compute chain trust, set stake entropy bit, record proof-of-stake hash value
513 // TODO: compute stake modifier
516 itemTemplate.BlockTypeFlag = block.IsProofOfStake ? BlockType.PROOF_OF_STAKE : BlockType.PROOF_OF_WORK;
518 if (!itemTemplate.WriteToFile(ref writer, ref block))
523 dbConn.Insert(itemTemplate);
525 // We have no SetBestChain and ConnectBlock/Disconnect block yet, so adding these transactions manually.
526 for (int i = 0; i < block.vtx.Length; i++)
528 // Handle trasactions
530 if (!block.vtx[i].VerifyScripts())
535 var nTxOffset = itemTemplate.nBlockPos + block.GetTxOffset(i);
536 TxType txnType = TxType.TX_USER;
538 if (block.vtx[i].IsCoinBase)
540 txnType = TxType.TX_COINBASE;
542 else if (block.vtx[i].IsCoinStake)
544 txnType = TxType.TX_COINSTAKE;
547 var NewTxItem = new CTransactionStoreItem()
549 TransactionHash = block.vtx[i].Hash,
550 BlockHash = blockHash,
552 nTxSize = block.vtx[i].Size,
556 dbConn.Insert(NewTxItem);
559 return blockMap.TryAdd(blockHash, itemTemplate);
562 public bool AcceptBlock(ref CBlock block)
564 uint256 nHash = block.header.Hash;
566 if (blockMap.ContainsKey(nHash))
568 // Already have this block.
572 CBlockStoreItem prevBlockCursor = null;
573 if (!blockMap.TryGetValue(block.header.prevHash, out prevBlockCursor))
575 // Unable to get the cursor.
579 var prevBlockHeader = prevBlockCursor.BlockHeader;
581 // TODO: proof-of-work/proof-of-stake verification
582 uint nHeight = prevBlockCursor.nHeight + 1;
584 // Check timestamp against prev
585 if (NetUtils.FutureDrift(block.header.nTime) < prevBlockHeader.nTime)
587 // block's timestamp is too early
591 // Check that all transactions are finalized
592 foreach (var tx in block.vtx)
594 if (!tx.IsFinal(nHeight, block.header.nTime))
600 // TODO: Enforce rule that the coinbase starts with serialized block height
602 // Write block to file.
603 var itemTemplate = new CBlockStoreItem()
606 nEntropyBit = Entropy.GetStakeEntropyBit(nHeight, nHash)
609 itemTemplate.FillHeader(block.header);
611 if (!AddItemToIndex(ref itemTemplate, ref block))
619 public bool GetBlock(uint256 blockHash, ref CBlock block)
621 var reader = new BinaryReader(fStreamReadWrite).BaseStream;
623 var QueryBlock = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
625 if (QueryBlock.Count == 1)
627 return QueryBlock[0].ReadFromFile(ref reader, out block);
636 /// Get block cursor from map.
638 /// <param name="blockHash">block hash</param>
639 /// <returns>Cursor or null</returns>
640 public CBlockStoreItem GetCursor(uint256 blockHash)
644 // Genesis block has zero prevHash and no parent.
648 // First, check our block map.
649 CBlockStoreItem item = null;
650 if (blockMap.TryGetValue(blockHash, out item))
655 // Trying to get cursor from the database.
656 var QueryBlockCursor = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
658 if (QueryBlockCursor.Count == 1)
660 return QueryBlockCursor[0];
667 public bool ProcessBlock(ref CBlock block)
669 var blockHash = block.header.Hash;
671 if (blockMap.ContainsKey(blockHash))
673 // We already have this block.
677 if (orphanMap.ContainsKey(blockHash))
679 // We already have block in the list of orphans.
683 // TODO: Limited duplicity on stake and reserialization of block signature
685 if (!block.CheckBlock(true, true, true))
687 // Preliminary checks failure.
691 if (block.IsProofOfStake)
693 if (!block.SignatureOK || !block.vtx[1].VerifyScripts())
695 // Proof-of-Stake signature validation failure.
699 // TODO: proof-of-stake validation
702 // TODO: difficulty verification
704 // If don't already have its previous block, shunt it off to holding area until we get it
705 if (!blockMap.ContainsKey(block.header.prevHash))
707 if (block.IsProofOfStake)
709 // TODO: limit duplicity on stake
712 var block2 = new CBlock(block);
713 orphanMap.TryAdd(blockHash, block2);
714 orphanMapByPrev.TryAdd(blockHash, block2);
719 // Store block to disk
720 if (!AcceptBlock(ref block))
726 // Recursively process any orphan blocks that depended on this one
727 var orphansQueue = new List<uint256>();
728 orphansQueue.Add(blockHash);
730 for (int i = 0; i < orphansQueue.Count; i++)
732 var hashPrev = orphansQueue[i];
734 foreach (var pair in orphanMap)
736 var orphanBlock = pair.Value;
738 if (orphanBlock.header.prevHash == blockHash)
740 if (AcceptBlock(ref orphanBlock))
742 orphansQueue.Add(pair.Key);
746 orphanMap.TryRemove(pair.Key, out dummy1);
751 orphanMap.TryRemove(hashPrev, out dummy2);
757 public bool ParseBlockFile(string BlockFile = "bootstrap.dat")
759 // TODO: Rewrite completely.
763 var buffer = new byte[CBlock.nMaxBlockSize]; // Max block size is 1Mb
764 var intBuffer = new byte[4];
766 var fStream2 = File.OpenRead(BlockFile);
767 var readerForBlocks = new BinaryReader(fStream2).BaseStream;
769 readerForBlocks.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length
771 dbConn.BeginTransaction();
773 while (readerForBlocks.Read(buffer, 0, 4) == 4) // Read magic number
775 var nMagic = BitConverter.ToUInt32(buffer, 0);
776 if (nMagic != 0xe5e9e8e4)
778 throw new Exception("Incorrect magic number.");
781 var nBytesRead = readerForBlocks.Read(buffer, 0, 4);
784 throw new Exception("BLKSZ EOF");
787 var nBlockSize = BitConverter.ToInt32(buffer, 0);
789 nOffset = readerForBlocks.Position;
791 nBytesRead = readerForBlocks.Read(buffer, 0, nBlockSize);
793 if (nBytesRead == 0 || nBytesRead != nBlockSize)
795 throw new Exception("BLK EOF");
798 var block = new CBlock(buffer);
799 var hash = block.header.Hash;
801 if (blockMap.ContainsKey(hash))
806 if (!ProcessBlock(ref block))
808 throw new Exception("Invalid block: " + block.header.Hash);
811 int nCount = blockMap.Count;
812 Console.WriteLine("nCount={0}, Hash={1}, Time={2}", nCount, block.header.Hash, DateTime.Now); // Commit on each 100th block
814 if (nCount % 100 == 0 && nCount != 0)
816 Console.WriteLine("Commit...");
818 dbConn.BeginTransaction();
832 public void Dispose()
835 GC.SuppressFinalize(this);
838 protected virtual void Dispose(bool disposing)
844 // Free other state (managed objects).
846 fStreamReadWrite.Dispose();