X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=Novacoin%2FCBlock.cs;h=e8f220799ab111a28ca5a76f5137046dc0cb9a92;hb=624ac1021490395614a0cbee619c79860c22061a;hp=6ff2da3ffdf4c95c45ab3f2e960dac1218607d7f;hpb=f1d2e217ae6e24a84edfa98ca3471a8a836ecaa7;p=NovacoinLibrary.git diff --git a/Novacoin/CBlock.cs b/Novacoin/CBlock.cs index 6ff2da3..e8f2207 100644 --- a/Novacoin/CBlock.cs +++ b/Novacoin/CBlock.cs @@ -20,22 +20,24 @@ using System; using System.Text; using System.Collections.Generic; using System.Diagnostics.Contracts; +using System.IO; +using System.Numerics; namespace Novacoin { [Serializable] - public class BlockConstructorException : Exception + public class BlockException : Exception { - public BlockConstructorException() + public BlockException() { } - public BlockConstructorException(string message) + public BlockException(string message) : base(message) { } - public BlockConstructorException(string message, Exception inner) + public BlockException(string message, Exception inner) : base(message, inner) { } @@ -46,6 +48,16 @@ namespace Novacoin /// 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. /// @@ -75,6 +87,7 @@ namespace Novacoin vtx[i] = new CTransaction(b.vtx[i]); } + signature = new byte[b.signature.Length]; b.signature.CopyTo(signature, 0); } @@ -86,20 +99,23 @@ namespace Novacoin { try { - ByteQueue wBytes = new ByteQueue(blockBytes); + var stream = new MemoryStream(blockBytes); + var reader = new BinaryReader(stream); // Fill the block header fields - header = new CBlockHeader(wBytes.Get(80)); + header = new CBlockHeader(ref reader); // Parse transactions list - vtx = CTransaction.ReadTransactionsList(ref wBytes); + vtx = CTransaction.ReadTransactionsList(ref reader); // Read block signature - signature = wBytes.Get((int)wBytes.GetVarInt()); + signature = reader.ReadBytes((int)VarInt.ReadVarInt(ref reader)); + + reader.Close(); } catch (Exception e) { - throw new BlockConstructorException("Deserialization failed", e); + throw new BlockException("Deserialization failed", e); } } @@ -111,6 +127,171 @@ namespace Novacoin 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 should 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; + } + + 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 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? /// @@ -177,20 +358,25 @@ namespace Novacoin /// Byte sequence public static implicit operator byte[] (CBlock b) { - var r = new List(); + var stream = new MemoryStream(); + var writer = new BinaryWriter(stream); - r.AddRange((byte[])b.header); - r.AddRange(VarInt.EncodeVarInt(b.vtx.LongLength)); // transactions count + writer.Write(b.header); + writer.Write(VarInt.EncodeVarInt(b.vtx.LongLength)); foreach (var tx in b.vtx) { - r.AddRange((byte[])tx); + writer.Write(tx); } - r.AddRange(VarInt.EncodeVarInt(b.signature.LongLength)); - r.AddRange(b.signature); + writer.Write(VarInt.EncodeVarInt(b.signature.LongLength)); + writer.Write(b.signature); + + var resultBytes = stream.ToArray(); + + writer.Close(); - return r.ToArray(); + return resultBytes; } /// @@ -235,7 +421,7 @@ namespace Novacoin /// /// Merkle root /// - public Hash256 hashMerkleRoot + public uint256 hashMerkleRoot { get { @@ -243,7 +429,7 @@ namespace Novacoin foreach (var tx in vtx) { - merkleTree.AddRange(Hash256.ComputeRaw256(tx)); + merkleTree.AddRange(CryptoUtils.ComputeHash256(tx)); } int levelOffset = 0; @@ -256,12 +442,12 @@ namespace Novacoin var left = merkleTree.GetRange((levelOffset + nLeft) * 32, 32).ToArray(); var right = merkleTree.GetRange((levelOffset + nRight) * 32, 32).ToArray(); - merkleTree.AddRange(Hash256.ComputeRaw256(ref left, ref right)); + merkleTree.AddRange(CryptoUtils.ComputeHash256(ref left, ref right)); } levelOffset += nLevelSize; } - return (merkleTree.Count == 0) ? new Hash256() : new Hash256(merkleTree.GetRange(merkleTree.Count-32, 32).ToArray()); + return (merkleTree.Count == 0) ? 0 : (uint256)merkleTree.GetRange(merkleTree.Count-32, 32).ToArray(); } } @@ -285,6 +471,51 @@ namespace Novacoin return sb.ToString(); } - } + + /// + /// Calculate proof-of-work reward. + /// + /// Packed difficulty representation. + /// Amount of fees. + /// Reward value. + public static ulong GetProofOfWorkReward(uint nBits, ulong 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 + + BigInteger bnSubsidyLimit = NetInfo.nMaxMintProofOfWork; + + uint256 nTarget = 0; + nTarget.Compact = nBits; + + BigInteger bnTarget = new BigInteger(nTarget); + BigInteger bnTargetLimit = new BigInteger(NetInfo.nProofOfWorkLimit); + + BigInteger bnLowerBound = CTransaction.nCent; + BigInteger bnUpperBound = bnSubsidyLimit; + + while (bnLowerBound + CTransaction.nCent <= bnUpperBound) + { + BigInteger bnMidValue = (bnLowerBound + bnUpperBound) / 2; + if (bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnTargetLimit > bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnTarget) + bnUpperBound = bnMidValue; + else + bnLowerBound = bnMidValue; + } + + ulong nSubsidy = (ulong)bnUpperBound; + nSubsidy = (nSubsidy / CTransaction.nCent) * CTransaction.nCent; + + + return Math.Min(nSubsidy, NetInfo.nMaxMintProofOfWork) + nFees; + } + } }