-\feffusing System;
+\feff/**
+* Novacoin classes library
+* Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com)
+
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+using System;
using System.IO;
using System.Linq;
using System.Collections.Concurrent;
using SQLite.Net.Platform.Generic;
using SQLiteNetExtensions.Attributes;
using System.Collections.Generic;
+using System.Diagnostics.Contracts;
namespace Novacoin
{
/// Item ID in the database
/// </summary>
[PrimaryKey, AutoIncrement]
- public int ItemID { get; set; }
+ public long ItemID { get; set; }
/// <summary>
/// PBKDF2+Salsa20 of block hash
public uint nNonce { get; set; }
/// <summary>
+ /// Next block hash.
+ /// </summary>
+ public byte[] nextHash { get; set; }
+
+ /// <summary>
/// Block type flags
/// </summary>
public BlockType BlockTypeFlag { get; set; }
public long nStakeModifier { get; set; }
/// <summary>
- /// Stake entropy bit
+ /// Proof-of-Stake hash
/// </summary>
- public byte nEntropyBit { get; set; }
+ public byte[] hashProofOfStake { get; set; }
/// <summary>
+ /// Stake generation outpoint.
+ /// </summary>
+ public byte[] prevoutStake { get; set; }
+
+ /// <summary>
+ /// Stake generation time.
+ /// </summary>
+ public uint nStakeTime { get; set; }
+
+ /// <summary>
/// Block height
/// </summary>
public uint nHeight { get; set; }
/// <summary>
/// Previous block cursor
/// </summary>
+ [Ignore]
public CBlockStoreItem prev {
get { return CBlockStore.Instance.GetCursor(prevHash); }
}
/// <summary>
+ /// Next block cursor
+ /// </summary>
+ [Ignore]
+ public CBlockStoreItem next
+ {
+ get { return CBlockStore.Instance.GetCursor(nextHash); }
+ set
+ {
+ CBlockStoreItem newCursor = this;
+ newCursor.nextHash = value.Hash;
+
+ CBlockStore.Instance.UpdateCursor(this, ref newCursor);
+ }
+ }
+
+ [Ignore]
+ bool IsInMainChain
+ {
+ get { return (next != null); }
+ }
+
+ /// <summary>
/// STake modifier generation flag
/// </summary>
+ [Ignore]
public bool GeneratedStakeModifier
{
get { return (BlockTypeFlag & BlockType.BLOCK_STAKE_MODIFIER) != 0; }
}
/// <summary>
+ /// Stake entropy bit
+ /// </summary>
+ [Ignore]
+ public uint StakeEntropyBit
+ {
+ get { return ((uint)(BlockTypeFlag & BlockType.BLOCK_STAKE_ENTROPY) >> 1); }
+ }
+
+ /// <summary>
/// Sets stake modifier and flag.
/// </summary>
/// <param name="nModifier">New stake modifier.</param>
/// <summary>
/// Block has no proof-of-stake flag.
/// </summary>
+ [Ignore]
public bool IsProofOfWork
{
- get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) != 0; }
+ get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) == 0; }
}
/// <summary>
/// Block has proof-of-stake flag set.
/// </summary>
+ [Ignore]
public bool IsProofOfStake
{
- get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) == 0; }
+ get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) != 0; }
+ }
+
+ /// <summary>
+ /// Block trust score.
+ /// </summary>
+ [Ignore]
+ public uint256 nBlockTrust
+ {
+ get
+ {
+ uint256 nTarget = 0;
+ nTarget.Compact = nBits;
+
+ /* Old protocol */
+ if (nTime < NetUtils.nChainChecksSwitchTime)
+ {
+ return IsProofOfStake ? (new uint256(1) << 256) / (nTarget + 1) : 1;
+ }
+
+ /* New protocol */
+
+ // Calculate work amount for block
+ var nPoWTrust = NetUtils.nPoWBase / (nTarget + 1);
+
+ // Set nPowTrust to 1 if we are checking PoS block or PoW difficulty is too low
+ nPoWTrust = (IsProofOfStake || !nPoWTrust) ? 1 : nPoWTrust;
+
+ // Return nPoWTrust for the first 12 blocks
+ if (prev == null || prev.nHeight < 12)
+ return nPoWTrust;
+
+ CBlockStoreItem currentIndex = prev;
+
+ if (IsProofOfStake)
+ {
+ var nNewTrust = (new uint256(1) << 256) / (nTarget + 1);
+
+ // Return 1/3 of score if parent block is not the PoW block
+ if (!prev.IsProofOfWork)
+ {
+ return nNewTrust / 3;
+ }
+
+ int nPoWCount = 0;
+
+ // Check last 12 blocks type
+ while (prev.nHeight - currentIndex.nHeight < 12)
+ {
+ if (currentIndex.IsProofOfWork)
+ {
+ nPoWCount++;
+ }
+ currentIndex = currentIndex.prev;
+ }
+
+ // Return 1/3 of score if less than 3 PoW blocks found
+ if (nPoWCount < 3)
+ {
+ return nNewTrust / 3;
+ }
+
+ return nNewTrust;
+ }
+ else
+ {
+ var nLastBlockTrust = prev.nChainTrust - prev.prev.nChainTrust;
+
+ // Return nPoWTrust + 2/3 of previous block score if two parent blocks are not PoS blocks
+ if (!prev.IsProofOfStake || !prev.prev.IsProofOfStake)
+ {
+ return nPoWTrust + (2 * nLastBlockTrust / 3);
+ }
+
+ int nPoSCount = 0;
+
+ // Check last 12 blocks type
+ while (prev.nHeight - currentIndex.nHeight < 12)
+ {
+ if (currentIndex.IsProofOfStake)
+ {
+ nPoSCount++;
+ }
+ currentIndex = currentIndex.prev;
+ }
+
+ // Return nPoWTrust + 2/3 of previous block score if less than 7 PoS blocks found
+ if (nPoSCount < 7)
+ {
+ return nPoWTrust + (2 * nLastBlockTrust / 3);
+ }
+
+ nTarget.Compact = prev.nBits;
+
+ if (!nTarget)
+ {
+ return 0;
+ }
+
+ var nNewTrust = (new uint256(1) << 256) / (nTarget + 1);
+
+ // Return nPoWTrust + full trust score for previous block nBits
+ return nPoWTrust + nNewTrust;
+ }
+ }
}
+ /// <summary>
+ /// Stake modifier checksum.
+ /// </summary>
+ public uint nStakeModifierChecksum;
+ /// <summary>
+ /// Chain trust score
+ /// </summary>
+ public uint256 nChainTrust;
}
/// <summary>
TX_USER
}
+ /// <summary>
+ /// Transaction type.
+ /// </summary>
+ 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; }
+
+ /// <summary>
+ /// Reference to parent block database item.
+ /// </summary>
+ [ForeignKey(typeof(CBlockStoreItem), Name = "ItemId")]
+ public long nParentBlockID { get; set; }
+
+ /// <summary>
+ /// Transaction hash
+ /// </summary>
+ public byte[] TransactionHash { get; set; }
+
+ public static bool QueryParentBlockCursor(uint256 transactionHash, out CBlockStoreItem cursor)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [Table("Outputs")]
+ public class TxOutItem
+ {
+ /// <summary>
+ /// Link the transaction hash with database item identifier.
+ /// </summary>
+ private static ConcurrentDictionary<uint256, long> outMap = new ConcurrentDictionary<uint256, long>();
+
+ /// <summary>
+ /// Reference to transaction item.
+ /// </summary>
+ [ForeignKey(typeof(MerkleNode), Name = "nMerkleNodeID")]
+ public long nMerkleNodeID { get; set; }
+
+ /// <summary>
+ /// Output flags
+ /// </summary>
+ public OutputType outputFlags { get; set; }
+
+ /// <summary>
+ /// Output number in VarInt format.
+ /// </summary>
+ public byte[] OutputNumber { get; set; }
+
+ /// <summary>
+ /// Output value in VarInt format.
+ /// </summary>
+ public byte[] OutputValue { get; set; }
+
+ /// <summary>
+ /// Second half of script which contains spending instructions.
+ /// </summary>
+ public byte[] scriptPubKey { get; set; }
+
+ /// <summary>
+ /// Construct new item from provided transaction data.
+ /// </summary>
+ /// <param name="o"></param>
+ public TxOutItem(CTransaction tx, uint nOut)
+ {
+ Contract.Requires<ArgumentException>(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;
+ }
+ }
+
+ /// <summary>
+ /// Getter for output number.
+ /// </summary>
+ [Ignore]
+ public uint nOut
+ {
+ get { return (uint)VarInt.DecodeVarInt(OutputNumber); }
+ }
+
+ /// <summary>
+ /// Getter for output value.
+ /// </summary>
+ [Ignore]
+ public ulong nValue
+ {
+ get { return VarInt.DecodeVarInt(OutputValue); }
+ }
+
+ /// <summary>
+ /// Is this a user transaction output?
+ /// </summary>
+ [Ignore]
+ public bool IsUser
+ {
+ get { return (outputFlags & OutputType.TX_USER) != 0; }
+ }
+
+ /// <summary>
+ /// Is this a coinbase transaction output?
+ /// </summary>
+ [Ignore]
+ public bool IsCoinBase
+ {
+ get { return (outputFlags & OutputType.TX_COINBASE) != 0; }
+ }
+
+ /// <summary>
+ /// Is this a coinstake transaction output?
+ /// </summary>
+ [Ignore]
+ public bool IsCoinStake
+ {
+ get { return (outputFlags & OutputType.TX_COINSTAKE) != 0; }
+ }
+
+ /// <summary>
+ /// Getter ans setter for IsSpent flag.
+ /// </summary>
+ [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
{
public int nTxSize { get; set; }
/// <summary>
+ /// Serialized output array
+ /// </summary>
+ public byte[] vOut { get; set; }
+
+ /// <summary>
/// Read transaction from file.
/// </summary>
/// <param name="reader">Stream with read access.</param>
return false;
}
}
+
+ /// <summary>
+ /// Outputs array access
+ /// </summary>
+ [Ignore]
+ public CTxOut[] Outputs {
+ get { return CTxOut.DeserializeOutputsArray(vOut); }
+ set { vOut = CTxOut.SerializeOutputsArray(value); }
+ }
}
public class CBlockStore : IDisposable
/// Map of unspent items.
/// </summary>
private ConcurrentDictionary<uint256, CTransactionStoreItem> txMap = new ConcurrentDictionary<uint256, CTransactionStoreItem>();
+
+ /// <summary>
+ /// Map of the proof-of-stake hashes. This is necessary for stake duplication checks.
+ /// </summary>
+ private ConcurrentDictionary<uint256, uint256> mapProofOfStake = new ConcurrentDictionary<uint256, uint256>();
+
+
+ private ConcurrentDictionary<COutPoint, uint> mapStakeSeen = new ConcurrentDictionary<COutPoint, uint>();
+ private ConcurrentDictionary<COutPoint, uint> mapStakeSeenOrphan = new ConcurrentDictionary<COutPoint, uint>();
+
+ /// <summary>
+ /// Unconfirmed transactions.
+ /// </summary>
+ private ConcurrentDictionary<uint256, CTransaction> mapUnconfirmedTx = new ConcurrentDictionary<uint256, CTransaction>();
- public static CBlockStore Instance;
+ /// <summary>
+ /// Trust score for the longest chain.
+ /// </summary>
+ private uint256 nBestChainTrust = 0;
/// <summary>
- /// Block file stream with read access
+ /// Top block of the best chain.
+ /// </summary>
+ private uint256 nHashBestChain = 0;
+
+ /// <summary>
+ /// Cursor which is pointing us to the end of best chain.
+ /// </summary>
+ private CBlockStoreItem bestBlockCursor = null;
+
+ /// <summary>
+ /// Cursor which is always pointing us to genesis block.
+ /// </summary>
+ private CBlockStoreItem genesisBlockCursor = null;
+
+ /// <summary>
+ /// Current and the only instance of block storage manager. Should be a property with private setter though it's enough for the beginning.
+ /// </summary>
+ public static CBlockStore Instance = null;
+
+ /// <summary>
+ /// Block file stream with read/write access
/// </summary>
private Stream fStreamReadWrite;
fStreamReadWrite = File.Open(strBlockFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
+ Instance = this;
+
if (firstInit)
{
lock (LockObj)
foreach (var item in blockTreeItems)
{
blockMap.TryAdd(item.Hash, item);
+
+ if (item.IsProofOfStake)
+ {
+ // build mapStakeSeen
+ mapStakeSeen.TryAdd(item.prevoutStake, item.nStakeTime);
+ }
}
}
-
- Instance = this;
}
public bool GetTransaction(uint256 TxID, ref CTransaction tx)
return false;
}
- // TODO: compute chain trust, set stake entropy bit, record proof-of-stake hash value
+ // Compute chain trust score
+ itemTemplate.nChainTrust = (itemTemplate.prev != null ? itemTemplate.prev.nChainTrust : 0) + itemTemplate.nBlockTrust;
- // TODO: compute stake modifier
+ if (!itemTemplate.SetStakeEntropyBit(Entropy.GetStakeEntropyBit(itemTemplate.nHeight, blockHash)))
+ {
+ return false; // SetStakeEntropyBit() failed
+ }
+
+ // Save proof-of-stake hash value
+ if (itemTemplate.IsProofOfStake)
+ {
+ uint256 hashProofOfStake;
+ if (!GetProofOfStakeHash(blockHash, out hashProofOfStake))
+ {
+ return false; // hashProofOfStake not found
+ }
+ itemTemplate.hashProofOfStake = hashProofOfStake;
+ }
+
+ // compute stake modifier
+ long nStakeModifier = 0;
+ bool fGeneratedStakeModifier = false;
+ if (!StakeModifier.ComputeNextStakeModifier(itemTemplate, ref nStakeModifier, ref fGeneratedStakeModifier))
+ {
+ return false; // ComputeNextStakeModifier() failed
+ }
+
+ itemTemplate.SetStakeModifier(nStakeModifier, fGeneratedStakeModifier);
+ itemTemplate.nStakeModifierChecksum = StakeModifier.GetStakeModifierChecksum(itemTemplate);
+
+ // TODO: verify stake modifier checkpoints
// Add to index
- itemTemplate.BlockTypeFlag = block.IsProofOfStake ? BlockType.PROOF_OF_STAKE : BlockType.PROOF_OF_WORK;
+ 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))
{
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())
{
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<CBlockStoreItem> secondary = new List<CBlockStoreItem>();
+
+ // 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();
+ }
+
+ /// <summary>
+ /// Try to find proof-of-stake hash in the map.
+ /// </summary>
+ /// <param name="blockHash">Block hash</param>
+ /// <param name="hashProofOfStake">Proof-of-stake hash</param>
+ /// <returns>Proof-of-Stake hash value</returns>
+ private bool GetProofOfStakeHash(uint256 blockHash, out uint256 hashProofOfStake)
+ {
+ return mapProofOfStake.TryGetValue(blockHash, out hashProofOfStake);
}
public bool AcceptBlock(ref CBlock block)
var itemTemplate = new CBlockStoreItem()
{
nHeight = nHeight,
- nEntropyBit = Entropy.GetStakeEntropyBit(nHeight, nHash)
};
itemTemplate.FillHeader(block.header);
return true;
}
- public bool GetBlock(uint256 blockHash, ref CBlock block)
+ public bool GetBlock(uint256 blockHash, ref CBlock block, ref long nBlockPos)
{
var reader = new BinaryReader(fStreamReadWrite).BaseStream;
if (QueryBlock.Count == 1)
{
+ nBlockPos = QueryBlock[0].nBlockPos;
return QueryBlock[0].ReadFromFile(ref reader, out block);
}
return false;
}
+ public bool GetBlockByTransactionID(uint256 TxID, ref CBlock block, ref CTransaction tx, ref long nBlockPos, ref long nTxPos)
+ {
+ var QueryTx = dbConn.Query<CTransactionStoreItem>("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID);
+
+ if (QueryTx.Count == 1)
+ {
+ nTxPos = QueryTx[0].nTxPos;
+ return GetBlock(QueryTx[0].BlockHash, ref block, ref nBlockPos);
+ }
+
+ // Tx not found
+
+ return false;
+ }
+
/// <summary>
/// Get block cursor from map.
/// </summary>
if (QueryBlockCursor.Count == 1)
{
+ blockMap.TryAdd(blockHash, QueryBlockCursor[0]);
+
return QueryBlockCursor[0];
}
return null;
}
+ /// <summary>
+ /// Update cursor in memory and on disk.
+ /// </summary>
+ /// <param name="originalItem">Original cursor</param>
+ /// <param name="newItem">New cursor</param>
+ /// <returns></returns>
+ 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;
if (block.IsProofOfStake)
{
- if (!block.SignatureOK || !block.vtx[1].VerifyScripts())
+ if (!block.SignatureOK)
{
// Proof-of-Stake signature validation failure.
return false;
}
// TODO: proof-of-stake validation
+
+ uint256 hashProofOfStake = 0, targetProofOfStake = 0;
+ if (!StakeModifier.CheckProofOfStake(block.vtx[1], block.header.nBits, ref hashProofOfStake, ref targetProofOfStake))
+ {
+ return false; // do not error here as we expect this during initial block download
+ }
+ if (!mapProofOfStake.ContainsKey(blockHash))
+ {
+ // add to mapProofOfStake
+ mapProofOfStake.TryAdd(blockHash, hashProofOfStake);
+ }
+
}
// TODO: difficulty verification