X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=Novacoin%2FCBlock.cs;h=69c8a28e1d389e8d0c200edf1ee79d7214b128dd;hb=6b0501a53995f78928332cfcd7ce1cae4b07486f;hp=4eb3a335b88f29b5cbfe099a95b3f8e0a0f70264;hpb=8b06904374c83d9e4216bcd202913c5e5ce10442;p=NovacoinLibrary.git diff --git a/Novacoin/CBlock.cs b/Novacoin/CBlock.cs index 4eb3a33..69c8a28 100644 --- a/Novacoin/CBlock.cs +++ b/Novacoin/CBlock.cs @@ -1,12 +1,62 @@ -using System; +/** + * 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 . + */ + +using System; +using System.Text; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.IO; namespace Novacoin { - /// - /// Represents the block. Block consists of header, transaction array and header signature. - /// - public class CBlock + [Serializable] + public class BlockException : Exception + { + public BlockException() + { + } + + public BlockException(string message) + : base(message) + { + } + + public BlockException(string message, Exception inner) + : base(message, inner) + { + } + } + + /// + /// Represents the block. Block consists of header, transaction array and header signature. + /// + public class CBlock { + /// + /// Maximum block size is 1Mb. + /// + public const uint nMaxBlockSize = 1000000; + + /// + /// Sanity threshold for amount of sigops. + /// + public const uint nMaxSigOps = 20000; + /// /// Block header. /// @@ -15,16 +65,515 @@ namespace Novacoin /// /// Transactions array. /// - public CTransaction[] tx; + public CTransaction[] vtx; - /// - /// Block header signature. - /// - public byte[] signature = {}; + /// + /// Block header signature. + /// + public byte[] signature = new byte[0]; + + /// + /// Copy constructor. + /// + /// CBlock instance. + public CBlock(CBlock b) + { + header = new CBlockHeader(b.header); + vtx = new CTransaction[b.vtx.Length]; + + for (int i = 0; i < b.vtx.Length; i++) + { + vtx[i] = new CTransaction(b.vtx[i]); + } + + signature = new byte[b.signature.Length]; + b.signature.CopyTo(signature, 0); + } - public CBlock () + /// + /// Parse byte sequence and initialize new block instance + /// + /// Bytes sequence. + public CBlock (byte[] blockBytes) { + try + { + var stream = new MemoryStream(blockBytes); + var reader = new BinaryReader(stream); + + // Fill the block header fields + header = new CBlockHeader(ref reader); + + // Parse transactions list + vtx = CTransaction.ReadTransactionsList(ref reader); + + // Read block signature + signature = reader.ReadBytes((int)VarInt.ReadVarInt(ref reader)); + + reader.Close(); + } + catch (Exception e) + { + throw new BlockException("Deserialization failed", e); + } } - } + + public CBlock() + { + // Initialize empty array of transactions. Please note that such + // configuration is not valid real block since it has to provide + // at least one transaction. + vtx = new CTransaction[0]; + } + + public bool CheckBlock(bool fCheckPOW = true, bool fCheckMerkleRoot = true, bool fCheckSig = true) + { + var uniqueTX = new List(); // tx hashes + uint nSigOps = 0; // total sigops + + // Basic sanity checkings + if (vtx.Length == 0 || Size > nMaxBlockSize) + { + return false; + } + + bool fProofOfStake = IsProofOfStake; + + // First transaction must be coinbase, the rest must not be + if (!vtx[0].IsCoinBase) + { + return false; + } + + if (!vtx[0].CheckTransaction()) + { + return false; + } + + uniqueTX.Add(vtx[0].Hash); + nSigOps += vtx[0].LegacySigOpCount; + + if (fProofOfStake) + { + // Proof-of-STake related checkings. Note that we know here that 1st transactions is coinstake. We don't need + // check the type of 1st transaction because it's performed earlier by IsProofOfStake() + + // nNonce must be zero for proof-of-stake blocks + if (header.nNonce != 0) + { + return false; + } + + // Coinbase output must be empty if proof-of-stake block + if (vtx[0].vout.Length != 1 || !vtx[0].vout[0].IsEmpty) + { + return false; + } + + // Check coinstake timestamp + if (header.nTime != vtx[1].nTime) + { + return false; + } + + // Check proof-of-stake block signature + if (fCheckSig && !SignatureOK) + { + return false; // Proof-of-Stake signature checking failure. + } + + if (!vtx[1].CheckTransaction()) + { + return false; + } + + uniqueTX.Add(vtx[1].Hash); + nSigOps += vtx[1].LegacySigOpCount; + } + else + { + // Check proof of work matches claimed amount + if (fCheckPOW && !CheckProofOfWork(header.Hash, header.nBits)) + { + return false; + } + + // Check timestamp + if (header.nTime > NetInfo.FutureDrift(NetInfo.GetAdjustedTime())) + { + return false; + } + + // Check coinbase timestamp + if (header.nTime < NetInfo.PastDrift(vtx[0].nTime)) + { + return false; + } + } + + // Iterate all transactions starting from second for proof-of-stake block + // or first for proof-of-work block + for (int i = fProofOfStake ? 2 : 1; i < vtx.Length; i++) + { + var tx = vtx[i]; + + // Reject coinbase transactions at non-zero index + if (tx.IsCoinBase) + { + return false; + } + + // Reject coinstake transactions at index != 1 + if (tx.IsCoinStake) + { + return false; + } + + // Check transaction timestamp + if (header.nTime < tx.nTime) + { + return false; + } + + // Check transaction consistency + if (!tx.CheckTransaction()) + { + return false; + } + + // Add transaction hash into list of unique transaction IDs + uniqueTX.Add(tx.Hash); + + // Calculate sigops count + nSigOps += tx.LegacySigOpCount; + } + + // Check for duplicate txids. + if (uniqueTX.Count != vtx.Length) + { + return false; + } + + // Reject block if validation would consume too much resources. + if (nSigOps > nMaxSigOps) + { + return false; + } + + // Check merkle root + if (fCheckMerkleRoot && hashMerkleRoot != header.merkleRoot) + { + return false; + } + + return true; + } + + private static bool CheckProofOfWork(uint256 hash, uint nBits) + { + uint256 nTarget = new uint256(); + nTarget.Compact = nBits; + + // Check range + if (nTarget > NetInfo.nProofOfWorkLimit) + { + // nBits below minimum work + return false; + } + + // Check proof of work matches claimed amount + if (hash > nTarget) + { + // hash doesn't match nBits + return false; + } + + return true; + } + + /// + /// Is this a Proof-of-Stake block? + /// + public bool IsProofOfStake + { + get + { + return (vtx.Length > 1 && vtx[1].IsCoinStake); + } + } + + /// + /// Was this signed correctly? + /// + public bool SignatureOK + { + get + { + if (IsProofOfStake) + { + if (signature.Length == 0) + { + return false; // No signature + } + + txnouttype whichType; + IList solutions; + + if (!ScriptCode.Solver(vtx[1].vout[1].scriptPubKey, out whichType, out solutions)) + { + return false; // No solutions found + } + + if (whichType == txnouttype.TX_PUBKEY) + { + CPubKey pubkey; + + try + { + pubkey = new CPubKey(solutions[0]); + } + catch (Exception) + { + return false; // Error while loading public key + } + + return pubkey.VerifySignature(header.Hash, signature); + } + } + else + { + // Proof-of-Work blocks have no signature + + return true; + } + + return false; + } + } + + /// + /// Get instance as sequence of bytes + /// + /// Byte sequence + public static implicit operator byte[] (CBlock b) + { + var stream = new MemoryStream(); + var writer = new BinaryWriter(stream); + + writer.Write(b.header); + writer.Write(VarInt.EncodeVarInt(b.vtx.LongLength)); + + foreach (var tx in b.vtx) + { + writer.Write(tx); + } + + writer.Write(VarInt.EncodeVarInt(b.signature.LongLength)); + writer.Write(b.signature); + + var resultBytes = stream.ToArray(); + + writer.Close(); + + return resultBytes; + } + + /// + /// Serialized size + /// + public uint Size + { + get + { + uint nSize = 80 + VarInt.GetEncodedSize(vtx.Length); // CBlockHeader + NumTx + + foreach (var tx in vtx) + { + nSize += tx.Size; + } + + nSize += VarInt.GetEncodedSize(signature.Length) + (uint)signature.Length; + + return nSize; + } + } + + /// + /// Get transaction offset inside block. + /// + /// Transaction index. + /// Offset in bytes from the beginning of block header. + public uint GetTxOffset(int nTx) + { + Contract.Requires(nTx >= 0 && nTx < vtx.Length, "Transaction index you've specified is incorrect."); + + uint nOffset = 80 + VarInt.GetEncodedSize(vtx.Length); // CBlockHeader + NumTx + + for (int i = 0; i < nTx; i++) + { + nOffset += vtx[i].Size; + } + + return nOffset; + } + + /// + /// Merkle root + /// + public uint256 hashMerkleRoot + { + get { + + var merkleTree = new List(); + + foreach (var tx in vtx) + { + merkleTree.AddRange(CryptoUtils.ComputeHash256(tx)); + } + + int levelOffset = 0; + for (int nLevelSize = vtx.Length; nLevelSize > 1; nLevelSize = (nLevelSize + 1) / 2) + { + for (int nLeft = 0; nLeft < nLevelSize; nLeft += 2) + { + int nRight = Math.Min(nLeft + 1, nLevelSize - 1); + + var left = merkleTree.GetRange((levelOffset + nLeft) * 32, 32).ToArray(); + var right = merkleTree.GetRange((levelOffset + nRight) * 32, 32).ToArray(); + + merkleTree.AddRange(CryptoUtils.ComputeHash256(ref left, ref right)); + } + levelOffset += nLevelSize; + } + + return (merkleTree.Count == 0) ? 0 : (uint256)merkleTree.GetRange(merkleTree.Count-32, 32).ToArray(); + } + } + + public override string ToString() + { + var sb = new StringBuilder(); + + sb.AppendFormat("CBlock(\n header={0},\n", header.ToString()); + + foreach(var tx in vtx) + { + sb.AppendFormat("{0}", tx.ToString()); + } + + if (IsProofOfStake) + { + sb.AppendFormat(", signature={0}, signatureOK={1}\n", Interop.ToHex(signature), SignatureOK); + } + + sb.Append(")"); + + return sb.ToString(); + } + + /// + /// Calculate proof-of-work reward. + /// + /// Packed difficulty representation. + /// Amount of fees. + /// Reward value. + public static long GetProofOfWorkReward(uint nBits, long nFees) + { + // NovaCoin: subsidy is cut in half every 64x multiply of PoW difficulty + // A reasonably continuous curve is used to avoid shock to market + // (nSubsidyLimit / nSubsidy) ** 6 == bnProofOfWorkLimit / bnTarget + // + // Human readable form: + // + // nSubsidy = 100 / (diff ^ 1/6) + // + // Please note that we're using bisection to find an approximate solutuion + + + uint256 nTarget = 0; + nTarget.Compact = nBits; + + BigNum bnTarget = nTarget; + BigNum bnTargetLimit = NetInfo.nProofOfWorkLimit; + + BigNum bnSubsidyLimit = NetInfo.nMaxMintProofOfWork; + BigNum bnLowerBound = CTransaction.nCent; + BigNum bnUpperBound = bnSubsidyLimit; + + while (bnLowerBound + CTransaction.nCent <= bnUpperBound) + { + BigNum bnMidValue = (bnLowerBound + bnUpperBound) / 2; + if (bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnTargetLimit > bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnTarget) + bnUpperBound = bnMidValue; + else + bnLowerBound = bnMidValue; + } + + long nSubsidy = bnUpperBound; + nSubsidy = (nSubsidy / CTransaction.nCent) * CTransaction.nCent; + + return Math.Min(nSubsidy, NetInfo.nMaxMintProofOfWork) + nFees; + } + + public static long GetProofOfStakeReward(long nCoinAge, uint nBits, uint nTime) + { + // Second stage of emission process is mostly PoS-based. + + long nRewardCoinYear, nSubsidy, nSubsidyLimit = 10 * CTransaction.nCoin; + // Base stake mint rate, 100% year interest + BigNum bnRewardCoinYearLimit = NetInfo.nMaxMintProofOfStake; + + uint256 nTarget = 0; + nTarget.Compact = nBits; + + BigNum bnTarget = nTarget; + BigNum bnTargetLimit = NetInfo.GetProofOfStakeLimit(0, nTime); + + // A reasonably continuous curve is used to avoid shock to market + + BigNum bnLowerBound = CTransaction.nCent, // Lower interest bound is 1% per year + bnUpperBound = bnRewardCoinYearLimit, // Upper interest bound is 100% per year + bnMidPart, bnRewardPart; + + while (bnLowerBound + CTransaction.nCent <= bnUpperBound) + { + BigNum bnMidValue = (bnLowerBound + bnUpperBound) / 2; + + // Reward for coin-year is cut in half every 8x multiply of PoS difficulty + // + // (nRewardCoinYearLimit / nRewardCoinYear) ** 3 == bnProofOfStakeLimit / bnTarget + // + // Human readable form: nRewardCoinYear = 1 / (posdiff ^ 1/3) + + bnMidPart = bnMidValue * bnMidValue * bnMidValue; + bnRewardPart = bnRewardCoinYearLimit * bnRewardCoinYearLimit * bnRewardCoinYearLimit; + + if (bnMidPart * bnTargetLimit > bnRewardPart * bnTarget) + { + bnUpperBound = bnMidValue; + } + else + { + bnLowerBound = bnMidValue; + } + } + + nRewardCoinYear = bnUpperBound; + nRewardCoinYear = Math.Min((nRewardCoinYear / CTransaction.nCent) * CTransaction.nCent, NetInfo.nMaxMintProofOfStake); + + nSubsidy = nCoinAge * nRewardCoinYear * 33 / (365 * 33 + 8); + + // Set reasonable reward limit for large inputs + // This will stimulate large holders to use smaller inputs, that's good + // for the network protection + return Math.Min(nSubsidy, nSubsidyLimit); + } + + public Tuple ProofOfStake + { + get { return IsProofOfStake ? new Tuple(vtx[1].vin[0].prevout, vtx[1].nTime) : new Tuple(new COutPoint(), 0); } + } + +} }