/**
* 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
{
[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.
///
public CBlockHeader header;
///
/// Transactions array.
///
public CTransaction[] vtx;
///
/// 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);
}
///
/// 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 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 > NetUtils.FutureDrift(NetUtils.GetAdjustedTime()))
{
return false;
}
// Check coinbase timestamp
if (header.nTime < NetUtils.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 > NetUtils.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 int Size
{
get
{
int nSize = 80 + VarInt.GetEncodedSize(vtx.Length); // CBlockHeader + NumTx
foreach (var tx in vtx)
{
nSize += tx.Size;
}
nSize += VarInt.GetEncodedSize(signature.Length) + signature.Length;
return nSize;
}
}
///
/// Get transaction offset inside block.
///
/// Transaction index.
/// Offset in bytes from the beginning of block header.
public int GetTxOffset(int nTx)
{
Contract.Requires(nTx >= 0 && nTx < vtx.Length, "Transaction index you've specified is incorrect.");
int 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();
}
}
}