From ced87004f5e3d01ccb655b690289df227f3f87bf Mon Sep 17 00:00:00 2001 From: CryptoManiac Date: Sat, 5 Sep 2015 20:11:43 +0300 Subject: [PATCH] Few new structures to be used in the near future. --- Novacoin/CBlockStore.cs | 432 +++++++++++++++++++++++++++++++++++++++++---- Novacoin/CScript.cs | 9 + Novacoin/CTxOut.cs | 11 ++ Novacoin/StakeModifier.cs | 7 +- 4 files changed, 419 insertions(+), 40 deletions(-) diff --git a/Novacoin/CBlockStore.cs b/Novacoin/CBlockStore.cs index 935a4ab..7d24209 100644 --- a/Novacoin/CBlockStore.cs +++ b/Novacoin/CBlockStore.cs @@ -28,6 +28,7 @@ using SQLite.Net.Interop; using SQLite.Net.Platform.Generic; using SQLiteNetExtensions.Attributes; using System.Collections.Generic; +using System.Diagnostics.Contracts; namespace Novacoin { @@ -41,7 +42,7 @@ namespace Novacoin /// Item ID in the database /// [PrimaryKey, AutoIncrement] - public int ItemID { get; set; } + public long ItemID { get; set; } /// /// PBKDF2+Salsa20 of block hash @@ -95,20 +96,20 @@ namespace Novacoin public long nStakeModifier { get; set; } /// - /// Stake modifier checksum. + /// Proof-of-Stake hash /// - public uint nStakeModifierChecksum { get; set; } + public byte[] hashProofOfStake { get; set; } /// - /// Chain trust score + /// Stake generation outpoint. /// - public byte[] ChainTrust { get; set; } + public byte[] prevoutStake { get; set; } /// - /// Proof-of-Stake hash + /// Stake generation time. /// - public byte[] hashProofOfStake { get; set; } - + public uint nStakeTime { get; set; } + /// /// Block height /// @@ -257,6 +258,13 @@ namespace Novacoin public CBlockStoreItem next { get { return CBlockStore.Instance.GetCursor(nextHash); } + set + { + CBlockStoreItem newCursor = this; + newCursor.nextHash = value.Hash; + + CBlockStore.Instance.UpdateCursor(this, ref newCursor); + } } [Ignore] @@ -335,25 +343,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 +446,15 @@ namespace Novacoin } } + /// + /// Stake modifier checksum. + /// + public uint nStakeModifierChecksum; + + /// + /// Chain trust score + /// + public uint256 nChainTrust; } /// @@ -479,6 +477,161 @@ namespace Novacoin TX_USER } + /// + /// Transaction type. + /// + public enum OutputType + { + TX_USER = (1 << 0), // User output + TX_COINBASE = (1 << 1), // Coinbase output + TX_COINSTAKE = (1 << 2), // Coinstake output + TX_AVAILABLE = (2 << 0), // Unspent output + TX_SPENT = (2 << 1) // Spent output + } + + [Table("MerkleNodes")] + public class MerkleNode + { + [PrimaryKey, AutoIncrement] + public long nMerkleNodeID { get; set; } + + /// + /// Reference to parent block database item. + /// + [ForeignKey(typeof(CBlockStoreItem), Name = "ItemId")] + public long nParentBlockID { get; set; } + + /// + /// Transaction hash + /// + public byte[] TransactionHash { get; set; } + + public static bool QueryParentBlockCursor(uint256 transactionHash, out CBlockStoreItem cursor) + { + throw new NotImplementedException(); + } + } + + [Table("Outputs")] + public class TxOutItem + { + /// + /// Link the transaction hash with database item identifier. + /// + private static ConcurrentDictionary outMap = new ConcurrentDictionary(); + + /// + /// Reference to transaction item. + /// + [ForeignKey(typeof(MerkleNode), Name = "nMerkleNodeID")] + public long nMerkleNodeID { get; set; } + + /// + /// Output flags + /// + public OutputType 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; } + + /// + /// Construct new item from provided transaction data. + /// + /// + public TxOutItem(CTransaction tx, uint nOut) + { + Contract.Requires(nOut < tx.vout.Length); + + long nMerkleId = 0; + if (!outMap.TryGetValue(tx.Hash, out nMerkleId)) + { + // Not in the blockchain + nMerkleNodeID = -1; + } + + OutputNumber = VarInt.EncodeVarInt(nOut); + OutputValue = VarInt.EncodeVarInt(tx.vout[nOut].nValue); + scriptPubKey = tx.vout[nOut].scriptPubKey; + + if (tx.IsCoinBase) + { + outputFlags |= OutputType.TX_COINBASE; + } + else if (tx.IsCoinStake) + { + outputFlags |= OutputType.TX_COINSTAKE; + } + } + + /// + /// Getter for output number. + /// + [Ignore] + public uint nOut + { + get { return (uint)VarInt.DecodeVarInt(OutputNumber); } + } + + /// + /// Getter for output value. + /// + [Ignore] + public ulong nValue + { + get { return VarInt.DecodeVarInt(OutputValue); } + } + + /// + /// Is this a user transaction output? + /// + [Ignore] + public bool IsUser + { + get { return (outputFlags & OutputType.TX_USER) != 0; } + } + + /// + /// Is this a coinbase transaction output? + /// + [Ignore] + public bool IsCoinBase + { + get { return (outputFlags & OutputType.TX_COINBASE) != 0; } + } + + /// + /// Is this a coinstake transaction output? + /// + [Ignore] + public bool IsCoinStake + { + get { return (outputFlags & OutputType.TX_COINSTAKE) != 0; } + } + + /// + /// Getter ans setter for IsSpent flag. + /// + [Ignore] + public bool IsSpent + { + get { return (outputFlags & OutputType.TX_SPENT) != 0; } + set { outputFlags |= value ? OutputType.TX_SPENT : OutputType.TX_AVAILABLE; } + } + } + + [Table("TransactionStorage")] public class CTransactionStoreItem { @@ -510,6 +663,11 @@ namespace Novacoin public int nTxSize { get; set; } /// + /// Serialized output array + /// + public byte[] vOut { get; set; } + + /// /// Read transaction from file. /// /// Stream with read access. @@ -544,6 +702,15 @@ namespace Novacoin return false; } } + + /// + /// Outputs array access + /// + [Ignore] + public CTxOut[] Outputs { + get { return CTxOut.DeserializeOutputsArray(vOut); } + set { vOut = CTxOut.SerializeOutputsArray(value); } + } } public class CBlockStore : IDisposable @@ -583,13 +750,48 @@ namespace Novacoin /// Map of unspent items. /// private ConcurrentDictionary txMap = 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(); + + /// + /// Unconfirmed transactions. + /// + private ConcurrentDictionary mapUnconfirmedTx = 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; @@ -664,6 +866,12 @@ namespace Novacoin foreach (var item in blockTreeItems) { blockMap.TryAdd(item.Hash, item); + + if (item.IsProofOfStake) + { + // build mapStakeSeen + mapStakeSeen.TryAdd(item.prevoutStake, item.nStakeTime); + } } } } @@ -706,16 +914,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 +938,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,12 +948,29 @@ namespace Novacoin return false; } - dbConn.Insert(itemTemplate); + if (dbConn.Insert(itemTemplate) == 0 || !blockMap.TryAdd(blockHash, itemTemplate)) + { + return false; + } + + if (itemTemplate.nChainTrust > nBestChainTrust) + { + // New best chain + + // TODO: SetBestChain implementation + + /* + if (!SetBestChain(ref itemTemplate)) + { + return false; // SetBestChain failed. + } + */ + } // We have no SetBestChain and ConnectBlock/Disconnect block yet, so adding these transactions manually. for (int i = 0; i < block.vtx.Length; i++) { - // Handle trasactions + // Handle trasactions using our temporary stub algo if (!block.vtx[i].VerifyScripts()) { @@ -775,7 +1001,117 @@ namespace Novacoin dbConn.Insert(NewTxItem); } - return blockMap.TryAdd(blockHash, itemTemplate); + return true; + } + + 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)) + { + return false; + } + } + 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) + { + secondary.Add(cursorIntermediate); + cursorIntermediate = cursorIntermediate.prev; + } + + // Switch to new best branch + if (!Reorganize(cursorIntermediate)) + { + dbConn.Rollback(); + InvalidChainFound(cursor); + return false; // reorganize failed + } + + + } + + + 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,7 +1198,7 @@ namespace Novacoin return false; } - public bool GetByTransactionID(uint256 TxID, ref CBlock block, ref CTransaction tx, ref long nBlockPos, ref long nTxPos) + 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); @@ -902,6 +1238,8 @@ namespace Novacoin if (QueryBlockCursor.Count == 1) { + blockMap.TryAdd(blockHash, QueryBlockCursor[0]); + return QueryBlockCursor[0]; } @@ -909,6 +1247,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 +1289,7 @@ namespace Novacoin if (block.IsProofOfStake) { - if (!block.SignatureOK || !block.vtx[1].VerifyScripts()) + if (!block.SignatureOK) { // Proof-of-Stake signature validation failure. return false; diff --git a/Novacoin/CScript.cs b/Novacoin/CScript.cs index 817d2fa..d27b337 100644 --- a/Novacoin/CScript.cs +++ b/Novacoin/CScript.cs @@ -501,6 +501,15 @@ namespace Novacoin } /// + /// Implicit cast of byte array to CScript. + /// + /// + public static implicit operator CScript(byte[] scriptBytes) + { + return new CScript(scriptBytes); + } + + /// /// Script size /// public int Size diff --git a/Novacoin/CTxOut.cs b/Novacoin/CTxOut.cs index 4712f42..3017fea 100644 --- a/Novacoin/CTxOut.cs +++ b/Novacoin/CTxOut.cs @@ -39,6 +39,17 @@ namespace Novacoin public CScript scriptPubKey; /// + /// Initialize new outpoint using provided value and script. + /// + /// Input value + /// Spending instructions. + public CTxOut(ulong nValue, CScript scriptPubKey) + { + this.nValue = nValue; + this.scriptPubKey = scriptPubKey; + } + + /// /// Initialize new CTxOut instance as a copy of another instance. /// /// CTxOut instance. diff --git a/Novacoin/StakeModifier.cs b/Novacoin/StakeModifier.cs index e6b6f50..67ed568 100644 --- a/Novacoin/StakeModifier.cs +++ b/Novacoin/StakeModifier.cs @@ -448,11 +448,16 @@ namespace Novacoin CTransaction txPrev = null; long nBlockPos = 0, nTxPos = 0; - if (!CBlockStore.Instance.GetByTransactionID(txin.prevout.hash, ref block, ref txPrev, ref nBlockPos, ref nTxPos)) + if (!CBlockStore.Instance.GetBlockByTransactionID(txin.prevout.hash, ref block, ref txPrev, ref nBlockPos, ref nTxPos)) { return false; // unable to read block of previous transaction } + if (!ScriptCode.VerifyScript(txin.scriptSig, txPrev.vout[txin.prevout.n].scriptPubKey, tx, 0, (int)scriptflag.SCRIPT_VERIFY_P2SH, 0)) + { + return false; // vin[0] signature check failed + } + if (!CheckStakeKernelHash(nBits, block.header.Hash, block.header.nTime, (uint)(nTxPos - nBlockPos), txPrev, txin.prevout, tx.nTime, ref hashProofOfStake, ref targetProofOfStake)) { return false; // check kernel failed on coinstake -- 1.7.1