Turn ByteQueue into MemoryStream wrapper, use MemoryStream for serialization of COutP...
[NovacoinLibrary.git] / Novacoin / CBlock.cs
1 \feff/**
2  *  Novacoin classes library
3  *  Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com)
4
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Affero General Public License as
7  *  published by the Free Software Foundation, either version 3 of the
8  *  License, or (at your option) any later version.
9
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Affero General Public License for more details.
14
15  *  You should have received a copy of the GNU Affero General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 using System;
20 using System.Text;
21 using System.Collections.Generic;
22 using System.Diagnostics.Contracts;
23
24 namespace Novacoin
25 {
26     [Serializable]
27     public class BlockException : Exception
28     {
29         public BlockException()
30         {
31         }
32
33         public BlockException(string message)
34                 : base(message)
35         {
36         }
37
38         public BlockException(string message, Exception inner)
39                 : base(message, inner)
40         {
41         }
42     }
43
44     /// <summary>
45     /// Represents the block. Block consists of header, transaction array and header signature.
46     /// </summary>
47     public class CBlock
48         {
49                 /// <summary>
50                 /// Block header.
51                 /// </summary>
52                 public CBlockHeader header;
53
54                 /// <summary>
55                 /// Transactions array.
56                 /// </summary>
57                 public CTransaction[] vtx;
58
59         /// <summary>
60         /// Block header signature.
61         /// </summary>
62         public byte[] signature = new byte[0];
63
64         /// <summary>
65         /// Copy constructor.
66         /// </summary>
67         /// <param name="b">CBlock instance.</param>
68         public CBlock(CBlock b)
69         {
70             header = new CBlockHeader(b.header);
71             vtx = new CTransaction[b.vtx.Length];
72
73             for (int i = 0; i < b.vtx.Length; i++)
74             {
75                 vtx[i] = new CTransaction(b.vtx[i]);
76             }
77
78             signature = new byte[b.signature.Length];
79             b.signature.CopyTo(signature, 0);
80         }
81
82         /// <summary>
83         /// Parse byte sequence and initialize new block instance
84         /// </summary>
85         /// <param name="blockBytes">Bytes sequence.</param>
86                 public CBlock (byte[] blockBytes)
87                 {
88             try
89             {
90                 ByteQueue wBytes = new ByteQueue(ref blockBytes);
91
92                 // Fill the block header fields
93                 header = new CBlockHeader(wBytes.Get(80));
94
95                 // Parse transactions list
96                 vtx = CTransaction.ReadTransactionsList(ref wBytes);
97
98                 // Read block signature
99                 signature = wBytes.Get((int)wBytes.GetVarInt());
100             }
101             catch (Exception e)
102             {
103                 throw new BlockException("Deserialization failed", e);
104             }
105                 }
106
107         public CBlock()
108         {
109             // Initialize empty array of transactions. Please note that such 
110             // configuration is not valid real block since it has to provide 
111             // at least one transaction.
112             vtx = new CTransaction[0];
113         }
114
115         public bool CheckBlock(bool fCheckPOW = true, bool fCheckMerkleRoot = true, bool fCheckSig = true)
116         {
117             var uniqueTX = new List<Hash256>(); // tx hashes
118             uint nSigOps = 0; // total sigops
119
120             // Basic sanity checkings
121             if (vtx.Length == 0 || Size > 1000000)
122             {
123                 return false;
124             }
125
126             bool fProofOfStake = IsProofOfStake;
127
128             // First transaction must be coinbase, the rest must not be
129             if (!vtx[0].IsCoinBase)
130             {
131                 return false;
132             }
133
134             if (!vtx[0].CheckTransaction())
135             {
136                 return false;
137             }
138
139             uniqueTX.Add(vtx[0].Hash);
140             nSigOps += vtx[0].LegacySigOpCount;
141
142             if (fProofOfStake)
143             {
144                 // Proof-of-STake related checkings. Note that we know here that 1st transactions is coinstake. We don't need 
145                 //   check the type of 1st transaction because it's performed earlier by IsProofOfStake()
146
147                 // nNonce must be zero for proof-of-stake blocks
148                 if (header.nNonce != 0)
149                 {
150                     return false;
151                 }
152
153                 // Coinbase output should be empty if proof-of-stake block
154                 if (vtx[0].vout.Length != 1 || !vtx[0].vout[0].IsEmpty)
155                 {
156                     return false;
157                 }
158
159                 // Check coinstake timestamp
160                 if (header.nTime != vtx[1].nTime)
161                 {
162                     return false;
163                 }
164
165                 // Check proof-of-stake block signature
166                 if (fCheckSig && !SignatureOK)
167                 {
168                     return false;
169                 }
170
171                 if (!vtx[1].CheckTransaction())
172                 {
173                     return false;
174                 }
175
176                 uniqueTX.Add(vtx[1].Hash);
177                 nSigOps += vtx[1].LegacySigOpCount;
178             }
179             else
180             {
181                 // Check proof of work matches claimed amount
182                 if (fCheckPOW && !CheckProofOfWork(header.Hash, header.nBits))
183                 {
184                     return false;
185                 }
186
187                 // Check timestamp
188                 if (header.nTime > NetUtils.FutureDrift(NetUtils.GetAdjustedTime()))
189                 {
190                     return false;
191                 }
192
193                 // Check coinbase timestamp
194                 if (header.nTime < NetUtils.PastDrift(vtx[0].nTime))
195                 {
196                     return false;
197                 }
198             }
199
200             // Iterate all transactions starting from second for proof-of-stake block 
201             //    or first for proof-of-work block
202             for (int i = fProofOfStake ? 2 : 1; i < vtx.Length; i++)
203             {
204                 var tx = vtx[i];
205
206                 // Reject coinbase transactions at non-zero index
207                 if (tx.IsCoinBase)
208                 {
209                     return false;
210                 }
211
212                 // Reject coinstake transactions at index != 1
213                 if (tx.IsCoinStake)
214                 {
215                     return false;
216                 }
217
218                 // Check transaction timestamp
219                 if (header.nTime < tx.nTime)
220                 {
221                     return false;
222                 }
223
224                 // Check transaction consistency
225                 if (!tx.CheckTransaction())
226                 {
227                     return false;
228                 }
229
230                 // Add transaction hash into list of unique transaction IDs
231                 uniqueTX.Add(tx.Hash);
232
233                 // Calculate sigops count
234                 nSigOps += tx.LegacySigOpCount;
235             }
236
237             // Check for duplicate txids. 
238             if (uniqueTX.Count != vtx.Length)
239             {
240                 return false;
241             }
242
243             // Reject block if validation would consume too much resources.
244             if (nSigOps > 50000)
245             {
246                 return false;
247             }
248
249             // Check merkle root
250             if (fCheckMerkleRoot && hashMerkleRoot != header.merkleRoot)
251             {
252                 return false;
253             }
254
255             return true;
256         }
257
258         private bool CheckProofOfWork(ScryptHash256 hash, uint nBits)
259         {
260             // TODO: stub!
261
262             return true;
263         }
264
265         /// <summary>
266         /// Is this a Proof-of-Stake block?
267         /// </summary>
268         public bool IsProofOfStake
269         {
270             get
271             {
272                 return (vtx.Length > 1 && vtx[1].IsCoinStake);
273             }
274         }
275
276         /// <summary>
277         /// Was this signed correctly?
278         /// </summary>
279         public bool SignatureOK
280         {
281             get
282             {
283                 if (IsProofOfStake)
284                 {
285                     if (signature.Length == 0)
286                     {
287                         return false; // No signature
288                     }
289
290                     txnouttype whichType;
291                     IList<byte[]> solutions;
292
293                     if (!ScriptCode.Solver(vtx[1].vout[1].scriptPubKey, out whichType, out solutions))
294                     {
295                         return false; // No solutions found
296                     }
297
298                     if (whichType == txnouttype.TX_PUBKEY)
299                     {
300                         CPubKey pubkey;
301
302                         try
303                         {
304                             pubkey = new CPubKey(solutions[0]);
305                         }
306                         catch (Exception)
307                         {
308                             return false; // Error while loading public key
309                         }
310
311                         return pubkey.VerifySignature(header.Hash, signature);
312                     }
313                 }
314                 else
315                 {
316                     // Proof-of-Work blocks have no signature
317
318                     return true;
319                 }
320
321                 return false;
322             }
323         }
324
325         /// <summary>
326         /// Get instance as sequence of bytes
327         /// </summary>
328         /// <returns>Byte sequence</returns>
329         public static implicit operator byte[] (CBlock b)
330         {
331             var r = new List<byte>();
332
333             r.AddRange((byte[])b.header);
334             r.AddRange(VarInt.EncodeVarInt(b.vtx.LongLength)); // transactions count
335
336             foreach (var tx in b.vtx)
337             {
338                 r.AddRange((byte[])tx);
339             }
340
341             r.AddRange(VarInt.EncodeVarInt(b.signature.LongLength));
342             r.AddRange(b.signature);
343
344             return r.ToArray();
345         }
346
347         /// <summary>
348         /// Serialized size
349         /// </summary>
350         public int Size
351         {
352             get
353             {
354                 int nSize = 80 + VarInt.GetEncodedSize(vtx.Length); // CBlockHeader + NumTx
355
356                 foreach (var tx in vtx)
357                 {
358                     nSize += tx.Size;
359                 }
360
361                 nSize += VarInt.GetEncodedSize(signature.Length) + signature.Length;
362
363                 return nSize;
364             }
365         }
366
367         /// <summary>
368         /// Get transaction offset inside block.
369         /// </summary>
370         /// <param name="nTx">Transaction index.</param>
371         /// <returns>Offset in bytes from the beginning of block header.</returns>
372         public int GetTxOffset(int nTx)
373         {
374             Contract.Requires<ArgumentException>(nTx >= 0 && nTx < vtx.Length, "Transaction index you've specified is incorrect.");
375
376             int nOffset = 80 + VarInt.GetEncodedSize(vtx.Length); // CBlockHeader + NumTx
377
378             for (int i = 0; i < nTx; i++)
379             {
380                 nOffset += vtx[i].Size;
381             }
382
383             return nOffset;
384         }
385
386         /// <summary>
387         /// Merkle root
388         /// </summary>
389         public Hash256 hashMerkleRoot
390         {
391             get {
392                 
393                 var merkleTree = new List<byte>();
394
395                 foreach (var tx in vtx)
396                 {
397                     merkleTree.AddRange(Hash256.ComputeRaw256(tx));
398                 }
399
400                 int levelOffset = 0;
401                 for (int nLevelSize = vtx.Length; nLevelSize > 1; nLevelSize = (nLevelSize + 1) / 2)
402                 {
403                     for (int nLeft = 0; nLeft < nLevelSize; nLeft += 2)
404                     {
405                         int nRight = Math.Min(nLeft + 1, nLevelSize - 1);
406
407                         var left = merkleTree.GetRange((levelOffset + nLeft) * 32, 32).ToArray();
408                         var right = merkleTree.GetRange((levelOffset + nRight) * 32, 32).ToArray();
409
410                         merkleTree.AddRange(Hash256.ComputeRaw256(ref left, ref right));
411                     }
412                     levelOffset += nLevelSize;
413                 }
414
415                 return (merkleTree.Count == 0) ? new Hash256() : new Hash256(merkleTree.GetRange(merkleTree.Count-32, 32).ToArray());
416             }
417         }
418
419         public override string ToString()
420         {
421             var sb = new StringBuilder();
422
423             sb.AppendFormat("CBlock(\n header={0},\n", header.ToString());
424
425             foreach(var tx in vtx)
426             {
427                 sb.AppendFormat("{0}", tx.ToString());
428             }
429
430             if (IsProofOfStake)
431             {
432                 sb.AppendFormat(", signature={0}, signatureOK={1}\n", Interop.ToHex(signature), SignatureOK);
433             }
434
435             sb.Append(")");
436             
437             return sb.ToString();
438         }
439         }
440 }
441