2 * Novacoin classes library
3 * Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com)
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 using System.Collections.Concurrent;
26 using SQLite.Net.Attributes;
27 using SQLite.Net.Interop;
28 using SQLite.Net.Platform.Generic;
29 using SQLiteNetExtensions.Attributes;
30 using System.Collections.Generic;
35 /// Block headers table
37 [Table("BlockStorage")]
38 public class CBlockStoreItem
41 /// Item ID in the database
43 [PrimaryKey, AutoIncrement]
44 public int ItemID { get; set; }
47 /// PBKDF2+Salsa20 of block hash
50 public byte[] Hash { get; set; }
53 /// Version of block schema
55 public uint nVersion { get; set; }
58 /// Previous block hash.
60 public byte[] prevHash { get; set; }
65 public byte[] merkleRoot { get; set; }
70 public uint nTime { get; set; }
73 /// Compressed difficulty representation.
75 public uint nBits { get; set; }
80 public uint nNonce { get; set; }
85 public byte[] nextHash { get; set; }
90 public BlockType BlockTypeFlag { get; set; }
95 public long nStakeModifier { get; set; }
98 /// Stake modifier checksum.
100 public uint nStakeModifierChecksum { get; set; }
103 /// Chain trust score
105 public byte[] ChainTrust { get; set; }
108 /// Proof-of-Stake hash
110 public byte[] hashProofOfStake { get; set; }
115 public uint nHeight { get; set; }
118 /// Block position in file
120 public long nBlockPos { get; set; }
123 /// Block size in bytes
125 public int nBlockSize { get; set; }
128 /// Fill database item with data from given block header.
130 /// <param name="header">Block header</param>
131 /// <returns>Header hash</returns>
132 public uint256 FillHeader(CBlockHeader header)
135 Hash = _hash = header.Hash;
137 nVersion = header.nVersion;
138 prevHash = header.prevHash;
139 merkleRoot = header.merkleRoot;
140 nTime = header.nTime;
141 nBits = header.nBits;
142 nNonce = header.nNonce;
148 /// Reconstruct block header from item data.
150 public CBlockHeader BlockHeader
154 CBlockHeader header = new CBlockHeader();
156 header.nVersion = nVersion;
157 header.prevHash = prevHash;
158 header.merkleRoot = merkleRoot;
159 header.nTime = nTime;
160 header.nBits = nBits;
161 header.nNonce = nNonce;
168 /// Read block from file.
170 /// <param name="reader">Stream with read access.</param>
171 /// <param name="reader">CBlock reference.</param>
172 /// <returns>Result</returns>
173 public bool ReadFromFile(ref Stream reader, out CBlock block)
175 var buffer = new byte[nBlockSize];
180 reader.Seek(nBlockPos, SeekOrigin.Begin);
182 if (nBlockSize != reader.Read(buffer, 0, nBlockSize))
187 block = new CBlock(buffer);
196 catch (BlockException)
198 // Constructor exception
204 /// Writes given block to file and prepares cursor object for insertion into the database.
206 /// <param name="writer">Stream with write access.</param>
207 /// <param name="block">CBlock reference.</param>
208 /// <returns>Result</returns>
209 public bool WriteToFile(ref Stream writer, ref CBlock block)
213 byte[] blockBytes = block;
215 var magicBytes = BitConverter.GetBytes(CBlockStore.nMagicNumber);
216 var blkLenBytes = BitConverter.GetBytes(blockBytes.Length);
218 // Seek to the end and then append magic bytes there.
219 writer.Seek(0, SeekOrigin.End);
220 writer.Write(magicBytes, 0, magicBytes.Length);
221 writer.Write(blkLenBytes, 0, blkLenBytes.Length);
223 // Save block size and current position in the block cursor fields.
224 nBlockPos = writer.Position;
225 nBlockSize = blockBytes.Length;
227 // Write block and flush the stream.
228 writer.Write(blockBytes, 0, blockBytes.Length);
240 // Some serialization error
246 /// Previous block cursor
249 public CBlockStoreItem prev {
250 get { return CBlockStore.Instance.GetCursor(prevHash); }
254 /// Next block cursor
257 public CBlockStoreItem next
259 get { return CBlockStore.Instance.GetCursor(nextHash); }
265 get { return (next != null); }
269 /// STake modifier generation flag
272 public bool GeneratedStakeModifier
274 get { return (BlockTypeFlag & BlockType.BLOCK_STAKE_MODIFIER) != 0; }
278 /// Stake entropy bit
281 public uint StakeEntropyBit
283 get { return ((uint)(BlockTypeFlag & BlockType.BLOCK_STAKE_ENTROPY) >> 1); }
287 /// Sets stake modifier and flag.
289 /// <param name="nModifier">New stake modifier.</param>
290 /// <param name="fGeneratedStakeModifier">Set generation flag?</param>
291 public void SetStakeModifier(long nModifier, bool fGeneratedStakeModifier)
293 nStakeModifier = nModifier;
294 if (fGeneratedStakeModifier)
295 BlockTypeFlag |= BlockType.BLOCK_STAKE_MODIFIER;
301 /// <param name="nEntropyBit">Entropy bit value (0 or 1).</param>
302 /// <returns>False if value is our of range.</returns>
303 public bool SetStakeEntropyBit(byte nEntropyBit)
307 BlockTypeFlag |= (nEntropyBit != 0 ? BlockType.BLOCK_STAKE_ENTROPY : 0);
312 /// Set proof-of-stake flag.
314 public void SetProofOfStake()
316 BlockTypeFlag |= BlockType.BLOCK_PROOF_OF_STAKE;
320 /// Block has no proof-of-stake flag.
323 public bool IsProofOfWork
325 get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) == 0; }
329 /// Block has proof-of-stake flag set.
332 public bool IsProofOfStake
334 get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) != 0; }
338 /// Chain trust score.
341 public uint256 nChainTrust {
344 if (ChainTrust.Length != 32)
346 byte[] tmp = ChainTrust;
347 Array.Resize(ref tmp, 32);
353 set { ChainTrust = Interop.TrimArray(value); }
357 /// Block trust score.
360 public uint256 nBlockTrust
365 nTarget.Compact = nBits;
368 if (nTime < NetUtils.nChainChecksSwitchTime)
370 return IsProofOfStake ? (new uint256(1) << 256) / (nTarget + 1) : 1;
375 // Calculate work amount for block
376 var nPoWTrust = NetUtils.nPoWBase / (nTarget + 1);
378 // Set nPowTrust to 1 if we are checking PoS block or PoW difficulty is too low
379 nPoWTrust = (IsProofOfStake || !nPoWTrust) ? 1 : nPoWTrust;
381 // Return nPoWTrust for the first 12 blocks
382 if (prev == null || prev.nHeight < 12)
385 CBlockStoreItem currentIndex = prev;
389 var nNewTrust = (new uint256(1) << 256) / (nTarget + 1);
391 // Return 1/3 of score if parent block is not the PoW block
392 if (!prev.IsProofOfWork)
394 return nNewTrust / 3;
399 // Check last 12 blocks type
400 while (prev.nHeight - currentIndex.nHeight < 12)
402 if (currentIndex.IsProofOfWork)
406 currentIndex = currentIndex.prev;
409 // Return 1/3 of score if less than 3 PoW blocks found
412 return nNewTrust / 3;
419 var nLastBlockTrust = prev.nChainTrust - prev.prev.nChainTrust;
421 // Return nPoWTrust + 2/3 of previous block score if two parent blocks are not PoS blocks
422 if (!prev.IsProofOfStake || !prev.prev.IsProofOfStake)
424 return nPoWTrust + (2 * nLastBlockTrust / 3);
429 // Check last 12 blocks type
430 while (prev.nHeight - currentIndex.nHeight < 12)
432 if (currentIndex.IsProofOfStake)
436 currentIndex = currentIndex.prev;
439 // Return nPoWTrust + 2/3 of previous block score if less than 7 PoS blocks found
442 return nPoWTrust + (2 * nLastBlockTrust / 3);
445 nTarget.Compact = prev.nBits;
452 var nNewTrust = (new uint256(1) << 256) / (nTarget + 1);
454 // Return nPoWTrust + full trust score for previous block nBits
455 return nPoWTrust + nNewTrust;
465 public enum BlockType
467 BLOCK_PROOF_OF_STAKE = (1 << 0), // is proof-of-stake block
468 BLOCK_STAKE_ENTROPY = (1 << 1), // entropy bit for stake modifier
469 BLOCK_STAKE_MODIFIER = (1 << 2), // regenerated stake modifier
473 /// Transaction type.
482 [Table("TransactionStorage")]
483 public class CTransactionStoreItem
489 public byte[] TransactionHash { get; set; }
494 [ForeignKey(typeof(CBlockStoreItem), Name = "Hash")]
495 public byte[] BlockHash { get; set; }
498 /// Transaction type flag
500 public TxType txType { get; set; }
503 /// Tx position in file
505 public long nTxPos { get; set; }
510 public int nTxSize { get; set; }
513 /// Read transaction from file.
515 /// <param name="reader">Stream with read access.</param>
516 /// <param name="tx">CTransaction reference.</param>
517 /// <returns>Result</returns>
518 public bool ReadFromFile(ref Stream reader, out CTransaction tx)
520 var buffer = new byte[CTransaction.nMaxTxSize];
525 reader.Seek(nTxPos, SeekOrigin.Begin); // Seek to transaction offset
527 if (nTxSize != reader.Read(buffer, 0, nTxSize))
532 tx = new CTransaction(buffer);
541 catch (TransactionConstructorException)
549 public class CBlockStore : IDisposable
551 public const uint nMagicNumber = 0xe5e9e8e4;
553 private bool disposed = false;
554 private object LockObj = new object();
557 /// SQLite connection object.
559 private SQLiteConnection dbConn;
564 private string strBlockFile;
567 /// Index database file.
569 private string strDbFile;
572 /// Map of block tree nodes.
574 private ConcurrentDictionary<uint256, CBlockStoreItem> blockMap = new ConcurrentDictionary<uint256, CBlockStoreItem>();
577 /// Orphaned blocks map.
579 private ConcurrentDictionary<uint256, CBlock> orphanMap = new ConcurrentDictionary<uint256, CBlock>();
580 private ConcurrentDictionary<uint256, CBlock> orphanMapByPrev = new ConcurrentDictionary<uint256, CBlock>();
583 /// Map of unspent items.
585 private ConcurrentDictionary<uint256, CTransactionStoreItem> txMap = new ConcurrentDictionary<uint256, CTransactionStoreItem>();
587 private ConcurrentDictionary<uint256, uint256> mapProofOfStake = new ConcurrentDictionary<uint256, uint256>();
589 public static CBlockStore Instance;
592 /// Block file stream with read access
594 private Stream fStreamReadWrite;
597 /// Init the block storage manager.
599 /// <param name="IndexDB">Path to index database</param>
600 /// <param name="BlockFile">Path to block file</param>
601 public CBlockStore(string IndexDB = "blockstore.dat", string BlockFile = "blk0001.dat")
604 strBlockFile = BlockFile;
606 bool firstInit = !File.Exists(strDbFile);
607 dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), strDbFile);
609 fStreamReadWrite = File.Open(strBlockFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
618 dbConn.CreateTable<CBlockStoreItem>(CreateFlags.AutoIncPK);
619 dbConn.CreateTable<CTransactionStoreItem>(CreateFlags.ImplicitPK);
621 var genesisBlock = new CBlock(
623 "01000000" + // nVersion=1
624 "0000000000000000000000000000000000000000000000000000000000000000" + // prevhash is zero
625 "7b0502ad2f9f675528183f83d6385794fbcaa914e6d385c6cb1d866a3b3bb34c" + // merkle root
626 "398e1151" + // nTime=1360105017
627 "ffff0f1e" + // nBits=0x1e0fffff
628 "d3091800" + // nNonce=1575379
630 "01000000" + // nVersion=1
631 "398e1151" + // nTime=1360105017
633 "0000000000000000000000000000000000000000000000000000000000000000" + // input txid is zero
634 "ffffffff" + // n=uint.maxValue
635 "4d" + // scriptSigLen=77
636 "04ffff001d020f274468747470733a2f2f626974636f696e74616c6b2e6f72672f696e6465782e7068703f746f7069633d3133343137392e6d736731353032313936236d736731353032313936" + // scriptSig
637 "ffffffff" + // nSequence=uint.maxValue
639 "0000000000000000" + // nValue=0
640 "00" + // scriptPubkeyLen=0
641 "00000000" + // nLockTime=0
645 // Write block to file.
646 var itemTemplate = new CBlockStoreItem()
651 itemTemplate.FillHeader(genesisBlock.header);
653 if (!AddItemToIndex(ref itemTemplate, ref genesisBlock))
655 throw new Exception("Unable to write genesis block");
661 var blockTreeItems = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] order by [ItemId] asc");
663 // Init list of block items
664 foreach (var item in blockTreeItems)
666 blockMap.TryAdd(item.Hash, item);
671 public bool GetTransaction(uint256 TxID, ref CTransaction tx)
673 var reader = new BinaryReader(fStreamReadWrite).BaseStream;
674 var QueryTx = dbConn.Query<CTransactionStoreItem>("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID);
676 if (QueryTx.Count == 1)
678 return QueryTx[0].ReadFromFile(ref reader, out tx);
686 private bool AddItemToIndex(ref CBlockStoreItem itemTemplate, ref CBlock block)
688 var writer = new BinaryWriter(fStreamReadWrite).BaseStream;
689 uint256 blockHash = itemTemplate.Hash;
691 if (blockMap.ContainsKey(blockHash))
693 // Already have this block.
697 // Compute chain trust score
698 itemTemplate.nChainTrust = (itemTemplate.prev != null ? itemTemplate.prev.nChainTrust : 0) + itemTemplate.nBlockTrust;
700 if (!itemTemplate.SetStakeEntropyBit(Entropy.GetStakeEntropyBit(itemTemplate.nHeight, blockHash)))
702 return false; // SetStakeEntropyBit() failed
705 // Save proof-of-stake hash value
706 if (itemTemplate.IsProofOfStake)
708 uint256 hashProofOfStake;
709 if (!CBlockStore.Instance.GetProofOfStakeHash(blockHash, out hashProofOfStake))
711 return false; // hashProofOfStake not found
713 itemTemplate.hashProofOfStake = hashProofOfStake;
716 // TODO: compute stake modifier
718 // ppcoin: compute stake modifier
719 long nStakeModifier = 0;
720 bool fGeneratedStakeModifier = false;
721 if (!StakeModifier.ComputeNextStakeModifier(itemTemplate, ref nStakeModifier, ref fGeneratedStakeModifier))
723 return false; // ComputeNextStakeModifier() failed
726 itemTemplate.SetStakeModifier(nStakeModifier, fGeneratedStakeModifier);
727 itemTemplate.nStakeModifierChecksum = StakeModifier.GetStakeModifierChecksum(itemTemplate);
729 // TODO: verify stake modifier checkpoints
732 if (block.IsProofOfStake)
734 itemTemplate.SetProofOfStake();
737 if (!itemTemplate.WriteToFile(ref writer, ref block))
742 dbConn.Insert(itemTemplate);
744 // We have no SetBestChain and ConnectBlock/Disconnect block yet, so adding these transactions manually.
745 for (int i = 0; i < block.vtx.Length; i++)
747 // Handle trasactions
749 if (!block.vtx[i].VerifyScripts())
754 var nTxOffset = itemTemplate.nBlockPos + block.GetTxOffset(i);
755 TxType txnType = TxType.TX_USER;
757 if (block.vtx[i].IsCoinBase)
759 txnType = TxType.TX_COINBASE;
761 else if (block.vtx[i].IsCoinStake)
763 txnType = TxType.TX_COINSTAKE;
766 var NewTxItem = new CTransactionStoreItem()
768 TransactionHash = block.vtx[i].Hash,
769 BlockHash = blockHash,
771 nTxSize = block.vtx[i].Size,
775 dbConn.Insert(NewTxItem);
778 return blockMap.TryAdd(blockHash, itemTemplate);
782 /// Try to find proof-of-stake hash in the map.
784 /// <param name="blockHash">Block hash</param>
785 /// <param name="hashProofOfStake">Proof-of-stake hash</param>
786 /// <returns>Proof-of-Stake hash value</returns>
787 private bool GetProofOfStakeHash(uint256 blockHash, out uint256 hashProofOfStake)
789 return mapProofOfStake.TryGetValue(blockHash, out hashProofOfStake);
792 public bool AcceptBlock(ref CBlock block)
794 uint256 nHash = block.header.Hash;
796 if (blockMap.ContainsKey(nHash))
798 // Already have this block.
802 CBlockStoreItem prevBlockCursor = null;
803 if (!blockMap.TryGetValue(block.header.prevHash, out prevBlockCursor))
805 // Unable to get the cursor.
809 var prevBlockHeader = prevBlockCursor.BlockHeader;
811 // TODO: proof-of-work/proof-of-stake verification
812 uint nHeight = prevBlockCursor.nHeight + 1;
814 // Check timestamp against prev
815 if (NetUtils.FutureDrift(block.header.nTime) < prevBlockHeader.nTime)
817 // block's timestamp is too early
821 // Check that all transactions are finalized
822 foreach (var tx in block.vtx)
824 if (!tx.IsFinal(nHeight, block.header.nTime))
830 // TODO: Enforce rule that the coinbase starts with serialized block height
832 // Write block to file.
833 var itemTemplate = new CBlockStoreItem()
838 itemTemplate.FillHeader(block.header);
840 if (!AddItemToIndex(ref itemTemplate, ref block))
848 public bool GetBlock(uint256 blockHash, ref CBlock block, ref long nBlockPos)
850 var reader = new BinaryReader(fStreamReadWrite).BaseStream;
852 var QueryBlock = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
854 if (QueryBlock.Count == 1)
856 nBlockPos = QueryBlock[0].nBlockPos;
857 return QueryBlock[0].ReadFromFile(ref reader, out block);
865 public bool GetByTransactionID(uint256 TxID, ref CBlock block, ref CTransaction tx, ref long nBlockPos, ref long nTxPos)
867 var QueryTx = dbConn.Query<CTransactionStoreItem>("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID);
869 if (QueryTx.Count == 1)
871 nTxPos = QueryTx[0].nTxPos;
872 return GetBlock(QueryTx[0].BlockHash, ref block, ref nBlockPos);
881 /// Get block cursor from map.
883 /// <param name="blockHash">block hash</param>
884 /// <returns>Cursor or null</returns>
885 public CBlockStoreItem GetCursor(uint256 blockHash)
889 // Genesis block has zero prevHash and no parent.
893 // First, check our block map.
894 CBlockStoreItem item = null;
895 if (blockMap.TryGetValue(blockHash, out item))
900 // Trying to get cursor from the database.
901 var QueryBlockCursor = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
903 if (QueryBlockCursor.Count == 1)
905 return QueryBlockCursor[0];
912 public bool ProcessBlock(ref CBlock block)
914 var blockHash = block.header.Hash;
916 if (blockMap.ContainsKey(blockHash))
918 // We already have this block.
922 if (orphanMap.ContainsKey(blockHash))
924 // We already have block in the list of orphans.
928 // TODO: Limited duplicity on stake and reserialization of block signature
930 if (!block.CheckBlock(true, true, true))
932 // Preliminary checks failure.
936 if (block.IsProofOfStake)
938 if (!block.SignatureOK || !block.vtx[1].VerifyScripts())
940 // Proof-of-Stake signature validation failure.
944 // TODO: proof-of-stake validation
946 uint256 hashProofOfStake = 0, targetProofOfStake = 0;
947 if (!StakeModifier.CheckProofOfStake(block.vtx[1], block.header.nBits, ref hashProofOfStake, ref targetProofOfStake))
949 return false; // do not error here as we expect this during initial block download
951 if (!mapProofOfStake.ContainsKey(blockHash))
953 // add to mapProofOfStake
954 mapProofOfStake.TryAdd(blockHash, hashProofOfStake);
959 // TODO: difficulty verification
961 // If don't already have its previous block, shunt it off to holding area until we get it
962 if (!blockMap.ContainsKey(block.header.prevHash))
964 if (block.IsProofOfStake)
966 // TODO: limit duplicity on stake
969 var block2 = new CBlock(block);
970 orphanMap.TryAdd(blockHash, block2);
971 orphanMapByPrev.TryAdd(blockHash, block2);
976 // Store block to disk
977 if (!AcceptBlock(ref block))
983 // Recursively process any orphan blocks that depended on this one
984 var orphansQueue = new List<uint256>();
985 orphansQueue.Add(blockHash);
987 for (int i = 0; i < orphansQueue.Count; i++)
989 var hashPrev = orphansQueue[i];
991 foreach (var pair in orphanMap)
993 var orphanBlock = pair.Value;
995 if (orphanBlock.header.prevHash == blockHash)
997 if (AcceptBlock(ref orphanBlock))
999 orphansQueue.Add(pair.Key);
1003 orphanMap.TryRemove(pair.Key, out dummy1);
1008 orphanMap.TryRemove(hashPrev, out dummy2);
1014 public bool ParseBlockFile(string BlockFile = "bootstrap.dat")
1016 // TODO: Rewrite completely.
1020 var buffer = new byte[CBlock.nMaxBlockSize]; // Max block size is 1Mb
1021 var intBuffer = new byte[4];
1023 var fStream2 = File.OpenRead(BlockFile);
1024 var readerForBlocks = new BinaryReader(fStream2).BaseStream;
1026 readerForBlocks.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length
1028 dbConn.BeginTransaction();
1030 while (readerForBlocks.Read(buffer, 0, 4) == 4) // Read magic number
1032 var nMagic = BitConverter.ToUInt32(buffer, 0);
1033 if (nMagic != 0xe5e9e8e4)
1035 throw new Exception("Incorrect magic number.");
1038 var nBytesRead = readerForBlocks.Read(buffer, 0, 4);
1039 if (nBytesRead != 4)
1041 throw new Exception("BLKSZ EOF");
1044 var nBlockSize = BitConverter.ToInt32(buffer, 0);
1046 nOffset = readerForBlocks.Position;
1048 nBytesRead = readerForBlocks.Read(buffer, 0, nBlockSize);
1050 if (nBytesRead == 0 || nBytesRead != nBlockSize)
1052 throw new Exception("BLK EOF");
1055 var block = new CBlock(buffer);
1056 var hash = block.header.Hash;
1058 if (blockMap.ContainsKey(hash))
1063 if (!ProcessBlock(ref block))
1065 throw new Exception("Invalid block: " + block.header.Hash);
1068 int nCount = blockMap.Count;
1069 Console.WriteLine("nCount={0}, Hash={1}, Time={2}", nCount, block.header.Hash, DateTime.Now); // Commit on each 100th block
1071 if (nCount % 100 == 0 && nCount != 0)
1073 Console.WriteLine("Commit...");
1075 dbConn.BeginTransaction();
1089 public void Dispose()
1092 GC.SuppressFinalize(this);
1095 protected virtual void Dispose(bool disposing)
1101 // Free other state (managed objects).
1103 fStreamReadWrite.Dispose();