X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=Novacoin%2FCBlockStore.cs;h=c6361218e6bb0ff4b2809d92c5ecd7aef440d685;hb=514b68fc3103fca11d6d06ddb65ed8d2a14507e1;hp=935a4abf127881f5fc5b32ad5931b8a4808e0c73;hpb=217d4232e9feeb787b9163b2ca1e701840d5b65b;p=NovacoinLibrary.git diff --git a/Novacoin/CBlockStore.cs b/Novacoin/CBlockStore.cs index 935a4ab..c636121 100644 --- a/Novacoin/CBlockStore.cs +++ b/Novacoin/CBlockStore.cs @@ -28,20 +28,20 @@ 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 { - /// - /// Block headers table - /// [Table("BlockStorage")] - public class CBlockStoreItem + public class CBlockStoreItem : IBlockStorageItem { + #region IBlockStorageItem /// /// Item ID in the database /// [PrimaryKey, AutoIncrement] - public int ItemID { get; set; } + public long ItemID { get; set; } /// /// PBKDF2+Salsa20 of block hash @@ -52,77 +52,124 @@ namespace Novacoin /// /// 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; } /// - /// Stake modifier checksum. + /// Proof-of-Stake hash /// - public uint nStakeModifierChecksum { get; set; } + [Column("hashProofOfStake")] + public byte[] hashProofOfStake { get; set; } /// - /// Chain trust score + /// Stake generation outpoint. /// - public byte[] ChainTrust { get; set; } + [Column("prevoutStake")] + public byte[] prevoutStake { get; set; } /// - /// Proof-of-Stake hash + /// Stake generation time. /// - public byte[] hashProofOfStake { get; set; } + [Column("nStakeTime")] + public uint nStakeTime { get; set; } /// - /// Block height + /// Block height, encoded in VarInt format /// - public uint nHeight { get; set; } + [Column("Height")] + public byte[] Height { get; set; } /// - /// Block position in file + /// Block position in file, encoded in VarInt format /// - public long nBlockPos { get; set; } + [Column("BlockPos")] + public byte[] BlockPos { get; set; } /// - /// Block size in bytes + /// Block size in bytes, encoded in VarInt format /// - public int nBlockSize { get; set; } + [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. @@ -131,8 +178,9 @@ namespace Novacoin /// Header hash public uint256 FillHeader(CBlockHeader header) { - uint256 _hash; - Hash = _hash = header.Hash; + uint256 _hash = header.Hash; + + Hash = _hash; nVersion = header.nVersion; prevHash = header.prevHash; @@ -256,7 +304,22 @@ namespace Novacoin [Ignore] public CBlockStoreItem next { - get { return CBlockStore.Instance.GetCursor(nextHash); } + 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] @@ -335,25 +398,6 @@ namespace Novacoin } /// - /// Chain trust score. - /// - [Ignore] - public uint256 nChainTrust { - get - { - if (ChainTrust.Length != 32) - { - byte[] tmp = ChainTrust; - Array.Resize(ref tmp, 32); - ChainTrust = tmp; - } - - return ChainTrust; - } - set { ChainTrust = Interop.TrimArray(value); } - } - - /// /// Block trust score. /// [Ignore] @@ -457,6 +501,15 @@ namespace Novacoin } } + /// + /// Stake modifier checksum. + /// + public uint nStakeModifierChecksum; + + /// + /// Chain trust score + /// + public uint256 nChainTrust; } /// @@ -472,42 +525,62 @@ namespace Novacoin /// /// Transaction type. /// - public enum TxType + public enum TxFlags : byte { TX_COINBASE, TX_COINSTAKE, TX_USER } - [Table("TransactionStorage")] - public class CTransactionStoreItem + /// + /// Output flags. + /// + public enum OutputFlags : byte { + AVAILABLE, // Unspent output + SPENT // Spent output + } + + [Table("MerkleNodes")] + public class CMerkleNode : IMerkleNode + { + #region IMerkleNode /// - /// Transaction hash + /// Node identifier /// - [PrimaryKey] - public byte[] TransactionHash { get; set; } + [PrimaryKey, AutoIncrement] + public long nMerkleNodeID { get; set; } /// - /// Block hash + /// Reference to parent block database item. /// - [ForeignKey(typeof(CBlockStoreItem), Name = "Hash")] - public byte[] BlockHash { get; set; } + [ForeignKey(typeof(CBlockStoreItem), Name = "ItemId")] + public long nParentBlockID { get; set; } /// /// Transaction type flag /// - public TxType txType { get; set; } + [Column("TransactionFlags")] + public TxFlags TransactionFlags { get; set; } + + /// + /// Transaction hash + /// + [Column("TransactionHash")] + public byte[] TransactionHash { get; set; } /// - /// Tx position in file + /// Transaction offset from the beginning of block header, encoded in VarInt format. /// - public long nTxPos { get; set; } + [Column("TxOffset")] + public byte[] TxOffset { get; set; } /// - /// Transaction size + /// Transaction size, encoded in VarInt format. /// - public int nTxSize { get; set; } + [Column("TxSize")] + public byte[] TxSize { get; set; } + #endregion /// /// Read transaction from file. @@ -515,14 +588,15 @@ namespace Novacoin /// Stream with read access. /// CTransaction reference. /// Result - public bool ReadFromFile(ref Stream reader, out CTransaction tx) + public bool ReadFromFile(ref Stream reader, long nBlockPos, out CTransaction tx) { var buffer = new byte[CTransaction.nMaxTxSize]; + tx = null; try { - reader.Seek(nTxPos, SeekOrigin.Begin); // Seek to transaction offset + reader.Seek(nBlockPos + nTxOffset, SeekOrigin.Begin); // Seek to transaction offset if (nTxSize != reader.Read(buffer, 0, nTxSize)) { @@ -544,6 +618,80 @@ namespace Novacoin 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 @@ -570,6 +718,8 @@ namespace Novacoin /// /// Map of block tree nodes. + /// + /// blockHash => CBlockStoreItem /// private ConcurrentDictionary blockMap = new ConcurrentDictionary(); @@ -580,16 +730,48 @@ namespace Novacoin private ConcurrentDictionary orphanMapByPrev = new ConcurrentDictionary(); /// - /// Map of unspent items. + /// Unconfirmed transactions. + /// + /// TxID => Transaction /// - private ConcurrentDictionary txMap = new ConcurrentDictionary(); + private ConcurrentDictionary mapUnconfirmedTx = new ConcurrentDictionary(); + /// + /// Map of the proof-of-stake hashes. This is necessary for stake duplication checks. + /// private ConcurrentDictionary mapProofOfStake = new ConcurrentDictionary(); - public static CBlockStore Instance; + + 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 access + /// Block file stream with read/write access /// private Stream fStreamReadWrite; @@ -616,7 +798,8 @@ namespace Novacoin { // Create tables dbConn.CreateTable(CreateFlags.AutoIncPK); - dbConn.CreateTable(CreateFlags.ImplicitPK); + dbConn.CreateTable(CreateFlags.AutoIncPK); + dbConn.CreateTable(CreateFlags.ImplicitPK); var genesisBlock = new CBlock( Interop.HexToArray( @@ -664,18 +847,25 @@ namespace Novacoin foreach (var item in blockTreeItems) { blockMap.TryAdd(item.Hash, item); + + if (item.IsProofOfStake) + { + // build mapStakeSeen + mapStakeSeen.TryAdd(item.prevoutStake, item.nStakeTime); + } } } } - public bool GetTransaction(uint256 TxID, ref CTransaction tx) + public bool GetTxOutCursor(COutPoint outpoint, ref TxOutItem txOutCursor) { - var reader = new BinaryReader(fStreamReadWrite).BaseStream; - var QueryTx = dbConn.Query("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID); + 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 (QueryTx.Count == 1) + if (queryResults.Count == 1) { - return QueryTx[0].ReadFromFile(ref reader, out tx); + txOutCursor = queryResults[0]; + + return true; } // Tx not found @@ -683,6 +873,47 @@ namespace Novacoin 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; @@ -706,16 +937,14 @@ namespace Novacoin if (itemTemplate.IsProofOfStake) { uint256 hashProofOfStake; - if (!CBlockStore.Instance.GetProofOfStakeHash(blockHash, out hashProofOfStake)) + if (!GetProofOfStakeHash(blockHash, out hashProofOfStake)) { return false; // hashProofOfStake not found } itemTemplate.hashProofOfStake = hashProofOfStake; } - // TODO: compute stake modifier - - // ppcoin: compute stake modifier + // compute stake modifier long nStakeModifier = 0; bool fGeneratedStakeModifier = false; if (!StakeModifier.ComputeNextStakeModifier(itemTemplate, ref nStakeModifier, ref fGeneratedStakeModifier)) @@ -732,6 +961,9 @@ namespace Novacoin 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)) @@ -739,43 +971,136 @@ namespace Novacoin return false; } - dbConn.Insert(itemTemplate); + if (dbConn.Insert(itemTemplate) == 0 || !blockMap.TryAdd(blockHash, itemTemplate)) + { + return false; + } - // We have no SetBestChain and ConnectBlock/Disconnect block yet, so adding these transactions manually. - for (int i = 0; i < block.vtx.Length; i++) + if (itemTemplate.nChainTrust > nBestChainTrust) { - // Handle trasactions + // New best chain + + // TODO: SetBestChain implementation - if (!block.vtx[i].VerifyScripts()) + /* + if (!SetBestChain(ref itemTemplate)) { - return false; + return false; // SetBestChain failed. } + */ + } - var nTxOffset = itemTemplate.nBlockPos + block.GetTxOffset(i); - TxType txnType = TxType.TX_USER; + return true; + } - if (block.vtx[i].IsCoinBase) + 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)) { - txnType = TxType.TX_COINBASE; + return false; } - else if (block.vtx[i].IsCoinStake) + } + 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) { - txnType = TxType.TX_COINSTAKE; + secondary.Add(cursorIntermediate); + cursorIntermediate = cursorIntermediate.prev; } - var NewTxItem = new CTransactionStoreItem() + // Switch to new best branch + if (!Reorganize(cursorIntermediate)) { - TransactionHash = block.vtx[i].Hash, - BlockHash = blockHash, - nTxPos = nTxOffset, - nTxSize = block.vtx[i].Size, - txType = txnType - }; - - dbConn.Insert(NewTxItem); + dbConn.Rollback(); + InvalidChainFound(cursor); + return false; // reorganize failed + } + + } - return blockMap.TryAdd(blockHash, itemTemplate); + + 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(); } /// @@ -862,14 +1187,40 @@ namespace Novacoin return false; } - public bool GetByTransactionID(uint256 TxID, ref CBlock block, ref CTransaction tx, ref long nBlockPos, ref long nTxPos) + + /// + /// 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 QueryTx = dbConn.Query("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID); + var queryResult = dbConn.Query("select *, from [BlockStorage] b left join [MerkleNodes] m on (b.[ItemID] = m.[nParentBlockID]) where m.[TransactionHash] = ?", (byte[])TxID); - if (QueryTx.Count == 1) + if (queryResult.Count == 1) { - nTxPos = QueryTx[0].nTxPos; - return GetBlock(QueryTx[0].BlockHash, ref block, ref nBlockPos); + 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 @@ -902,6 +1253,8 @@ namespace Novacoin if (QueryBlockCursor.Count == 1) { + blockMap.TryAdd(blockHash, QueryBlockCursor[0]); + return QueryBlockCursor[0]; } @@ -909,6 +1262,22 @@ namespace Novacoin 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; @@ -935,7 +1304,7 @@ namespace Novacoin if (block.IsProofOfStake) { - if (!block.SignatureOK || !block.vtx[1].VerifyScripts()) + if (!block.SignatureOK) { // Proof-of-Stake signature validation failure. return false;