1
|
|
using System;
|
2
|
|
using System.Collections.Generic;
|
3
|
|
using System.Collections.Immutable;
|
4
|
|
using System.Diagnostics.Contracts;
|
5
|
|
using System.Globalization;
|
6
|
|
using System.Linq;
|
7
|
|
using System.Numerics;
|
8
|
|
using System.Security.Cryptography;
|
9
|
|
using System.Text;
|
10
|
|
using System.Threading;
|
11
|
|
using Bencodex;
|
12
|
|
using Bencodex.Types;
|
13
|
|
using Libplanet.Action;
|
14
|
|
using Libplanet.Assets;
|
15
|
|
using Libplanet.Store.Trie;
|
16
|
|
using Libplanet.Tx;
|
17
|
|
|
18
|
|
namespace Libplanet.Blocks
|
19
|
|
{
|
20
|
|
[Equals]
|
21
|
|
public class Block<T>
|
22
|
|
where T : IAction, new()
|
23
|
|
{
|
24
|
|
/// <summary>
|
25
|
|
/// Creates a <see cref="Block{T}"/> instance by manually filling all field values.
|
26
|
|
/// For a more automated way, see also <see cref="Mine"/> method.
|
27
|
|
/// </summary>
|
28
|
|
/// <param name="index">The height of the block to create. Goes to the <see cref="Index"/>.
|
29
|
|
/// </param>
|
30
|
|
/// <param name="difficulty">The mining difficulty that <paramref name="nonce"/> has to
|
31
|
|
/// satisfy. Goes to the <see cref="Difficulty"/>.</param>
|
32
|
|
/// <param name="totalDifficulty">The total mining difficulty until this block.
|
33
|
|
/// See also <see cref="Difficulty"/>.</param>
|
34
|
|
/// <param name="nonce">The nonce which satisfy the given <paramref name="difficulty"/> with
|
35
|
|
/// any other field values. Goes to the <see cref="Nonce"/>.</param>
|
36
|
|
/// <param name="miner">An optional address refers to who mines this block.
|
37
|
|
/// Goes to the <see cref="Miner"/>.</param>
|
38
|
|
/// <param name="previousHash">The previous block's <see cref="Hash"/>. If it's a genesis
|
39
|
|
/// block (i.e., <paramref name="index"/> is 0) this should be <c>null</c>.
|
40
|
|
/// Goes to the <see cref="PreviousHash"/>.</param>
|
41
|
|
/// <param name="timestamp">The time this block is created. Goes to
|
42
|
|
/// the <see cref="Timestamp"/>.</param>
|
43
|
|
/// <param name="transactions">The transactions to be mined together with this block.
|
44
|
|
/// Transactions become sorted in an unpredicted-before-mined order and then go to
|
45
|
|
/// the <see cref="Transactions"/> property.
|
46
|
|
/// </param>
|
47
|
|
/// <param name="preEvaluationHash">The hash derived from the block <em>except of</em>
|
48
|
|
/// <paramref name="stateRootHash"/> (i.e., without action evaluation).
|
49
|
|
/// Automatically determined if <c>null</c> is passed (which is default).</param>
|
50
|
|
/// <param name="stateRootHash">The <see cref="ITrie.Hash"/> of the states on the block.
|
51
|
|
/// </param>
|
52
|
|
/// <seealso cref="Mine"/>
|
53
|
2
|
public Block(
|
54
|
2
|
long index,
|
55
|
2
|
long difficulty,
|
56
|
2
|
BigInteger totalDifficulty,
|
57
|
2
|
Nonce nonce,
|
58
|
2
|
Address? miner,
|
59
|
2
|
HashDigest<SHA256>? previousHash,
|
60
|
2
|
DateTimeOffset timestamp,
|
61
|
2
|
IEnumerable<Transaction<T>> transactions,
|
62
|
2
|
HashDigest<SHA256>? preEvaluationHash = null,
|
63
|
2
|
HashDigest<SHA256>? stateRootHash = null)
|
64
|
2
|
{
|
65
|
2
|
Index = index;
|
66
|
2
|
Difficulty = difficulty;
|
67
|
2
|
TotalDifficulty = totalDifficulty;
|
68
|
2
|
Nonce = nonce;
|
69
|
2
|
Miner = miner;
|
70
|
2
|
PreviousHash = previousHash;
|
71
|
2
|
Timestamp = timestamp;
|
72
|
2
|
Transactions = transactions.OrderBy(tx => tx.Id).ToArray();
|
73
|
2
|
var codec = new Codec();
|
74
|
2
|
TxHash = Transactions.Any()
|
75
|
2
|
? Hashcash.Hash(
|
76
|
2
|
codec.Encode(
|
77
|
2
|
new Bencodex.Types.List(Transactions.Select(tx =>
|
78
|
2
|
(IValue)tx.ToBencodex(true)))))
|
79
|
2
|
: (HashDigest<SHA256>?)null;
|
80
|
2
|
PreEvaluationHash = preEvaluationHash ?? Hashcash.Hash(SerializeForHash());
|
81
|
2
|
StateRootHash = stateRootHash;
|
82
|
|
|
83
|
|
// FIXME: This does not need to be computed every time?
|
84
|
2
|
Hash = Hashcash.Hash(SerializeForHash(stateRootHash));
|
85
|
|
|
86
|
|
// As the order of transactions should be unpredictable until a block is mined,
|
87
|
|
// the sorter key should be derived from both a block hash and a txid.
|
88
|
2
|
var hashInteger = new BigInteger(PreEvaluationHash.ToByteArray());
|
89
|
|
|
90
|
|
// If there are multiple transactions for the same signer these should be ordered by
|
91
|
|
// their tx nonces. So transactions of the same signer should have the same sort key.
|
92
|
|
// The following logic "flattens" multiple tx ids having the same signer into a single
|
93
|
|
// txid by applying XOR between them.
|
94
|
2
|
IImmutableDictionary<Address, IImmutableSet<Transaction<T>>> signerTxs = Transactions
|
95
|
2
|
.GroupBy(tx => tx.Signer)
|
96
|
2
|
.ToImmutableDictionary(
|
97
|
2
|
g => g.Key,
|
98
|
2
|
g => (IImmutableSet<Transaction<T>>)g.ToImmutableHashSet()
|
99
|
2
|
);
|
100
|
2
|
IImmutableDictionary<Address, BigInteger> signerTxIds = signerTxs
|
101
|
2
|
.ToImmutableDictionary(
|
102
|
2
|
pair => pair.Key,
|
103
|
2
|
pair => pair.Value
|
104
|
2
|
.Select(tx => new BigInteger(tx.Id.ToByteArray()))
|
105
|
2
|
.OrderBy(txid => txid)
|
106
|
1
|
.Aggregate((a, b) => a ^ b)
|
107
|
2
|
);
|
108
|
|
|
109
|
|
// Order signers by values derivied from both block hash and their "flatten" txid:
|
110
|
2
|
IImmutableList<Address> signers = signerTxIds
|
111
|
2
|
.OrderBy(pair => pair.Value ^ hashInteger)
|
112
|
2
|
.Select(pair => pair.Key)
|
113
|
2
|
.ToImmutableArray();
|
114
|
|
|
115
|
|
// Order transactions for each signer by their tx nonces:
|
116
|
2
|
Transactions = signers
|
117
|
2
|
.SelectMany(signer => signerTxs[signer].OrderBy(tx => tx.Nonce))
|
118
|
2
|
.ToImmutableArray();
|
119
|
2
|
}
|
120
|
|
|
121
|
|
/// <summary>
|
122
|
|
/// Creates a <see cref="Block{T}"/> instance from its serialization.
|
123
|
|
/// </summary>
|
124
|
|
/// <param name="dict">The <see cref="Bencodex.Types.Dictionary"/>
|
125
|
|
/// representation of <see cref="Block{T}"/> instance.
|
126
|
|
/// </param>
|
127
|
|
public Block(Bencodex.Types.Dictionary dict)
|
128
|
1
|
: this(new RawBlock(dict))
|
129
|
1
|
{
|
130
|
1
|
}
|
131
|
|
|
132
|
|
public Block(
|
133
|
|
Block<T> block,
|
134
|
|
HashDigest<SHA256>? stateRootHash)
|
135
|
1
|
: this(
|
136
|
1
|
block.Index,
|
137
|
1
|
block.Difficulty,
|
138
|
1
|
block.TotalDifficulty,
|
139
|
1
|
block.Nonce,
|
140
|
1
|
block.Miner,
|
141
|
1
|
block.PreviousHash,
|
142
|
1
|
block.Timestamp,
|
143
|
1
|
block.Transactions,
|
144
|
1
|
block.PreEvaluationHash,
|
145
|
1
|
stateRootHash)
|
146
|
1
|
{
|
147
|
1
|
}
|
148
|
|
|
149
|
|
private Block(RawBlock rb)
|
150
|
1
|
: this(
|
151
|
1
|
rb.Header.Index,
|
152
|
1
|
rb.Header.Difficulty,
|
153
|
1
|
rb.Header.TotalDifficulty,
|
154
|
1
|
new Nonce(rb.Header.Nonce.ToArray()),
|
155
|
1
|
rb.Header.Miner.Any() ? new Address(rb.Header.Miner) : (Address?)null,
|
156
|
1
|
#pragma warning disable MEN002 // Line is too long
|
157
|
1
|
rb.Header.PreviousHash.Any() ? new HashDigest<SHA256>(rb.Header.PreviousHash) : (HashDigest<SHA256>?)null,
|
158
|
1
|
#pragma warning restore MEN002 // Line is too long
|
159
|
1
|
DateTimeOffset.ParseExact(
|
160
|
1
|
rb.Header.Timestamp,
|
161
|
1
|
BlockHeader.TimestampFormat,
|
162
|
1
|
CultureInfo.InvariantCulture).ToUniversalTime(),
|
163
|
1
|
rb.Transactions
|
164
|
1
|
.Select(tx => Transaction<T>.Deserialize(tx.ToArray()))
|
165
|
1
|
.ToList(),
|
166
|
1
|
#pragma warning disable MEN002 // Line is too long
|
167
|
1
|
rb.Header.PreEvaluationHash.Any() ? new HashDigest<SHA256>(rb.Header.PreEvaluationHash) : (HashDigest<SHA256>?)null,
|
168
|
1
|
rb.Header.StateRootHash.Any() ? new HashDigest<SHA256>(rb.Header.StateRootHash) : (HashDigest<SHA256>?)null)
|
169
|
|
#pragma warning restore MEN002 // Line is too long
|
170
|
1
|
{
|
171
|
1
|
}
|
172
|
|
|
173
|
|
/// <summary>
|
174
|
|
/// <see cref="Hash"/> is derived from a serialized <see cref="Block{T}"/>
|
175
|
|
/// after <see cref="Transaction{T}.Actions"/> are evaluated.
|
176
|
|
/// </summary>
|
177
|
|
/// <seealso cref="PreEvaluationHash"/>
|
178
|
|
/// <seealso cref="StateRootHash"/>
|
179
|
2
|
public HashDigest<SHA256> Hash { get; }
|
180
|
|
|
181
|
|
/// <summary>
|
182
|
|
/// The hash derived from the block <em>except of</em>
|
183
|
|
/// <see cref="StateRootHash"/> (i.e., without action evaluation).
|
184
|
|
/// Used for <see cref="BlockHeader.Validate"/> checking <see cref="Nonce"/>.
|
185
|
|
/// </summary>
|
186
|
|
/// <seealso cref="Nonce"/>
|
187
|
|
/// <seealso cref="BlockHeader.Validate"/>
|
188
|
2
|
public HashDigest<SHA256> PreEvaluationHash { get; }
|
189
|
|
|
190
|
|
/// <summary>
|
191
|
|
/// The <see cref="ITrie.Hash"/> of the states on the block.
|
192
|
|
/// </summary>
|
193
|
|
/// <seealso cref="ITrie.Hash"/>
|
194
|
2
|
public HashDigest<SHA256>? StateRootHash { get; }
|
195
|
|
|
196
|
|
[IgnoreDuringEquals]
|
197
|
2
|
public long Index { get; }
|
198
|
|
|
199
|
|
[IgnoreDuringEquals]
|
200
|
2
|
public long Difficulty { get; }
|
201
|
|
|
202
|
|
[IgnoreDuringEquals]
|
203
|
2
|
public BigInteger TotalDifficulty { get; }
|
204
|
|
|
205
|
|
[IgnoreDuringEquals]
|
206
|
2
|
public Nonce Nonce { get; }
|
207
|
|
|
208
|
|
[IgnoreDuringEquals]
|
209
|
2
|
public Address? Miner { get; }
|
210
|
|
|
211
|
|
[IgnoreDuringEquals]
|
212
|
2
|
public HashDigest<SHA256>? PreviousHash { get; }
|
213
|
|
|
214
|
|
[IgnoreDuringEquals]
|
215
|
2
|
public DateTimeOffset Timestamp { get; }
|
216
|
|
|
217
|
|
[IgnoreDuringEquals]
|
218
|
2
|
public HashDigest<SHA256>? TxHash { get; }
|
219
|
|
|
220
|
|
[IgnoreDuringEquals]
|
221
|
2
|
public IEnumerable<Transaction<T>> Transactions { get; }
|
222
|
|
|
223
|
|
public static bool operator ==(Block<T> left, Block<T> right) =>
|
224
|
|
Operator.Weave(left, right);
|
225
|
|
|
226
|
|
public static bool operator !=(Block<T> left, Block<T> right) =>
|
227
|
|
Operator.Weave(left, right);
|
228
|
|
|
229
|
|
/// <summary>
|
230
|
|
/// Generate a block with given <paramref name="transactions"/>.
|
231
|
|
/// </summary>
|
232
|
|
/// <param name="index">Index of the block.</param>
|
233
|
|
/// <param name="difficulty">Difficulty to find the <see cref="Block{T}"/>
|
234
|
|
/// <see cref="Nonce"/>.</param>
|
235
|
|
/// <param name="previousTotalDifficulty">The total difficulty until the previous
|
236
|
|
/// <see cref="Block{T}"/>.</param>
|
237
|
|
/// <param name="miner">The <see cref="Address"/> of miner that mined the block.</param>
|
238
|
|
/// <param name="previousHash">
|
239
|
|
/// The <see cref="HashDigest{SHA256}"/> of previous block.
|
240
|
|
/// </param>
|
241
|
|
/// <param name="timestamp">The <see cref="DateTimeOffset"/> when mining started.</param>
|
242
|
|
/// <param name="transactions"><see cref="Transaction{T}"/>s that are going to be included
|
243
|
|
/// in the block.</param>
|
244
|
|
/// <param name="cancellationToken">
|
245
|
|
/// A cancellation token used to propagate notification that this
|
246
|
|
/// operation should be canceled.</param>
|
247
|
|
/// <returns>A <see cref="Block{T}"/> that mined.</returns>
|
248
|
|
public static Block<T> Mine(
|
249
|
|
long index,
|
250
|
|
long difficulty,
|
251
|
|
BigInteger previousTotalDifficulty,
|
252
|
|
Address miner,
|
253
|
|
HashDigest<SHA256>? previousHash,
|
254
|
|
DateTimeOffset timestamp,
|
255
|
|
IEnumerable<Transaction<T>> transactions,
|
256
|
|
CancellationToken cancellationToken = default(CancellationToken))
|
257
|
2
|
{
|
258
|
2
|
var txs = transactions.OrderBy(tx => tx.Id).ToImmutableArray();
|
259
|
2
|
Block<T> MakeBlock(Nonce n) => new Block<T>(
|
260
|
2
|
index,
|
261
|
2
|
difficulty,
|
262
|
2
|
previousTotalDifficulty + difficulty,
|
263
|
2
|
n,
|
264
|
2
|
miner,
|
265
|
2
|
previousHash,
|
266
|
2
|
timestamp,
|
267
|
2
|
txs);
|
268
|
|
|
269
|
|
// Poor man' way to optimize stamp...
|
270
|
|
// FIXME: We need to rather reorganize the serialization layout.
|
271
|
2
|
byte[] emptyNonce = MakeBlock(new Nonce(new byte[0])).SerializeForHash();
|
272
|
2
|
byte[] oneByteNonce = MakeBlock(new Nonce(new byte[1])).SerializeForHash();
|
273
|
2
|
int offset = 0;
|
274
|
2
|
while (offset < emptyNonce.Length && emptyNonce[offset].Equals(oneByteNonce[offset]))
|
275
|
2
|
{
|
276
|
2
|
offset++;
|
277
|
2
|
}
|
278
|
|
|
279
|
|
const int nonceLength = 2; // In Bencodex, empty bytes are represented as "0:".
|
280
|
2
|
byte[] stampPrefix = new byte[offset];
|
281
|
2
|
Array.Copy(emptyNonce, stampPrefix, stampPrefix.Length);
|
282
|
2
|
byte[] stampSuffix = new byte[emptyNonce.Length - offset - nonceLength];
|
283
|
2
|
Array.Copy(emptyNonce, offset + nonceLength, stampSuffix, 0, stampSuffix.Length);
|
284
|
|
|
285
|
2
|
Nonce nonce = Hashcash.Answer(
|
286
|
2
|
n =>
|
287
|
2
|
{
|
288
|
2
|
int nLen = n.ByteArray.Length;
|
289
|
2
|
byte[] nLenStr = Encoding.ASCII.GetBytes(
|
290
|
2
|
nLen.ToString(CultureInfo.InvariantCulture));
|
291
|
2
|
int totalLen =
|
292
|
2
|
stampPrefix.Length + nLenStr.Length + 1 + nLen + stampSuffix.Length;
|
293
|
2
|
byte[] stamp = new byte[totalLen];
|
294
|
2
|
Array.Copy(stampPrefix, stamp, stampPrefix.Length);
|
295
|
2
|
int pos = stampPrefix.Length;
|
296
|
2
|
Array.Copy(nLenStr, 0, stamp, pos, nLenStr.Length);
|
297
|
2
|
pos += nLenStr.Length;
|
298
|
2
|
stamp[pos] = 0x3a; // ':'
|
299
|
2
|
pos++;
|
300
|
2
|
n.ByteArray.CopyTo(stamp, pos);
|
301
|
2
|
pos += nLen;
|
302
|
2
|
Array.Copy(stampSuffix, 0, stamp, pos, stampSuffix.Length);
|
303
|
2
|
return stamp;
|
304
|
2
|
},
|
305
|
2
|
difficulty,
|
306
|
2
|
cancellationToken
|
307
|
2
|
);
|
308
|
|
|
309
|
2
|
return MakeBlock(nonce);
|
310
|
2
|
}
|
311
|
|
|
312
|
|
/// <summary>
|
313
|
|
/// Decodes a <see cref="Block{T}"/>'s
|
314
|
|
/// <a href="https://bencodex.org/">Bencodex</a> representation.
|
315
|
|
/// </summary>
|
316
|
|
/// <param name="bytes">A <a href="https://bencodex.org/">Bencodex</a>
|
317
|
|
/// representation of a <see cref="Block{T}"/>.</param>
|
318
|
|
/// <returns>A decoded <see cref="Block{T}"/> object.</returns>
|
319
|
|
/// <seealso cref="Serialize()"/>
|
320
|
|
public static Block<T> Deserialize(byte[] bytes)
|
321
|
1
|
{
|
322
|
1
|
IValue value = new Codec().Decode(bytes);
|
323
|
1
|
if (!(value is Bencodex.Types.Dictionary dict))
|
324
|
0
|
{
|
325
|
0
|
throw new DecodingException(
|
326
|
0
|
$"Expected {typeof(Bencodex.Types.Dictionary)} but " +
|
327
|
0
|
$"{value.GetType()}");
|
328
|
|
}
|
329
|
|
|
330
|
1
|
return new Block<T>(dict);
|
331
|
1
|
}
|
332
|
|
|
333
|
|
public byte[] Serialize()
|
334
|
1
|
{
|
335
|
1
|
var codec = new Codec();
|
336
|
1
|
return codec.Encode(ToBencodex());
|
337
|
1
|
}
|
338
|
|
|
339
|
1
|
public Bencodex.Types.Dictionary ToBencodex() => ToRawBlock().ToBencodex();
|
340
|
|
|
341
|
|
/// <summary>
|
342
|
|
/// Executes every <see cref="IAction"/> in the
|
343
|
|
/// <see cref="Transactions"/> step by step, and emits a pair of
|
344
|
|
/// a transaction, and an <see cref="ActionEvaluation"/>
|
345
|
|
/// for each step.
|
346
|
|
/// </summary>
|
347
|
|
/// <param name="accountStateGetter">An <see cref="AccountStateGetter"/>
|
348
|
|
/// delegate to get a previous state.
|
349
|
|
/// A <c>null</c> value, which is default, means a constant function
|
350
|
|
/// that returns <c>null</c>.</param>
|
351
|
|
/// <param name="accountBalanceGetter">An <see cref="AccountBalanceGetter"/> delegate to
|
352
|
|
/// get previous account balance.
|
353
|
|
/// A <c>null</c> value, which is default, means a constant function that returns zero.
|
354
|
|
/// </param>
|
355
|
|
/// <returns>Enumerates pair of a transaction, and
|
356
|
|
/// <see cref="ActionEvaluation"/> for each action.
|
357
|
|
/// The order of pairs are the same to
|
358
|
|
/// the <see cref="Transactions"/> and their
|
359
|
|
/// <see cref="Transaction{T}.Actions"/> (e.g., tx¹-act¹,
|
360
|
|
/// tx¹-act², tx²-act¹, tx²-act²,
|
361
|
|
/// …).
|
362
|
|
/// Note that each <see cref="IActionContext.Random"/> object has
|
363
|
|
/// a unconsumed state.
|
364
|
|
/// </returns>
|
365
|
|
[Pure]
|
366
|
|
public
|
367
|
|
IEnumerable<Tuple<Transaction<T>, ActionEvaluation>> EvaluateActionsPerTx(
|
368
|
|
AccountStateGetter accountStateGetter = null,
|
369
|
|
AccountBalanceGetter accountBalanceGetter = null
|
370
|
|
)
|
371
|
2
|
{
|
372
|
2
|
accountStateGetter ??= a => null;
|
373
|
2
|
accountBalanceGetter ??= (a, c) => new FungibleAssetValue(c);
|
374
|
|
|
375
|
|
IAccountStateDelta delta;
|
376
|
2
|
foreach (Transaction<T> tx in Transactions)
|
377
|
2
|
{
|
378
|
2
|
delta = new AccountStateDeltaImpl(
|
379
|
2
|
accountStateGetter,
|
380
|
2
|
accountBalanceGetter,
|
381
|
2
|
tx.Signer
|
382
|
2
|
);
|
383
|
2
|
IEnumerable<ActionEvaluation> evaluations =
|
384
|
2
|
tx.EvaluateActionsGradually(
|
385
|
2
|
PreEvaluationHash,
|
386
|
2
|
Index,
|
387
|
2
|
delta,
|
388
|
2
|
Miner.Value);
|
389
|
2
|
foreach (var evaluation in evaluations)
|
390
|
2
|
{
|
391
|
2
|
yield return Tuple.Create(tx, evaluation);
|
392
|
2
|
delta = evaluation.OutputStates;
|
393
|
2
|
}
|
394
|
|
|
395
|
2
|
accountStateGetter = delta.GetState;
|
396
|
2
|
accountBalanceGetter = delta.GetBalance;
|
397
|
2
|
}
|
398
|
2
|
}
|
399
|
|
|
400
|
|
/// <summary>
|
401
|
|
/// Executes every <see cref="IAction"/> in the
|
402
|
|
/// <see cref="Transactions"/> and gets result states of each step of
|
403
|
|
/// every <see cref="Transaction{T}"/>.
|
404
|
|
/// <para>It throws an <see cref="InvalidBlockException"/> or
|
405
|
|
/// an <see cref="InvalidTxException"/> if there is any
|
406
|
|
/// integrity error.</para>
|
407
|
|
/// <para>Otherwise it enumerates an <see cref="ActionEvaluation"/>
|
408
|
|
/// for each <see cref="IAction"/>.</para>
|
409
|
|
/// </summary>
|
410
|
|
/// <param name="currentTime">The current time to validate
|
411
|
|
/// time-wise conditions.</param>
|
412
|
|
/// <param name="accountStateGetter">An <see cref="AccountStateGetter"/> delegate to get
|
413
|
|
/// a previous state. A <c>null</c> value, which is default, means a constant function
|
414
|
|
/// that returns <c>null</c>.
|
415
|
|
/// This affects the execution of <see cref="Transaction{T}.Actions"/>.
|
416
|
|
/// </param>
|
417
|
|
/// <param name="accountBalanceGetter">An <see cref="AccountBalanceGetter"/> delegate to
|
418
|
|
/// get previous account balance.
|
419
|
|
/// A <c>null</c> value, which is default, means a constant function that returns zero.
|
420
|
|
/// This affects the execution of <see cref="Transaction{T}.Actions"/>.
|
421
|
|
/// </param>
|
422
|
|
/// <returns>An <see cref="ActionEvaluation"/> for each
|
423
|
|
/// <see cref="IAction"/>.</returns>
|
424
|
|
/// <exception cref="InvalidBlockTimestampException">Thrown when
|
425
|
|
/// the <see cref="Timestamp"/> is invalid, for example, it is the far
|
426
|
|
/// future than the given <paramref name="currentTime"/>.</exception>
|
427
|
|
/// <exception cref="InvalidBlockIndexException">Thrown when
|
428
|
|
/// the <see cref="Index"/>is invalid, for example, it is a negative
|
429
|
|
/// integer.</exception>
|
430
|
|
/// <exception cref="InvalidBlockDifficultyException">Thrown when
|
431
|
|
/// the <see cref="Difficulty"/> is not properly configured,
|
432
|
|
/// for example, it is too easy.</exception>
|
433
|
|
/// <exception cref="InvalidBlockPreviousHashException">Thrown when
|
434
|
|
/// <see cref="PreviousHash"/> is invalid so that
|
435
|
|
/// the <see cref="Block{T}"/>s are not continuous.</exception>
|
436
|
|
/// <exception cref="InvalidBlockNonceException">Thrown when
|
437
|
|
/// the <see cref="Nonce"/> does not satisfy its
|
438
|
|
/// <see cref="Difficulty"/> level.</exception>
|
439
|
|
/// <exception cref="InvalidTxSignatureException">Thrown when its
|
440
|
|
/// <see cref="Transaction{T}.Signature"/> is invalid or not signed by
|
441
|
|
/// the account who corresponds to its
|
442
|
|
/// <see cref="Transaction{T}.PublicKey"/>.</exception>
|
443
|
|
/// <exception cref="InvalidTxPublicKeyException">Thrown when its
|
444
|
|
/// <see cref="Transaction{T}.Signer"/> is not derived from its
|
445
|
|
/// <see cref="Transaction{T}.PublicKey"/>.</exception>
|
446
|
|
/// <exception cref="InvalidTxUpdatedAddressesException">Thrown when
|
447
|
|
/// any <see cref="IAction"/> of <see cref="Transactions"/> tries
|
448
|
|
/// to update the states of <see cref="Address"/>es not included
|
449
|
|
/// in <see cref="Transaction{T}.UpdatedAddresses"/>.</exception>
|
450
|
|
public IEnumerable<ActionEvaluation> Evaluate(
|
451
|
|
DateTimeOffset currentTime,
|
452
|
|
AccountStateGetter accountStateGetter = null,
|
453
|
|
AccountBalanceGetter accountBalanceGetter = null
|
454
|
|
)
|
455
|
2
|
{
|
456
|
2
|
accountStateGetter ??= a => null;
|
457
|
2
|
accountBalanceGetter ??= (a, c) => new FungibleAssetValue(c);
|
458
|
|
|
459
|
2
|
Validate(currentTime);
|
460
|
2
|
Tuple<Transaction<T>, ActionEvaluation>[] txEvaluations =
|
461
|
2
|
EvaluateActionsPerTx(accountStateGetter, accountBalanceGetter).ToArray();
|
462
|
|
|
463
|
2
|
var txUpdatedAddressesPairs = txEvaluations
|
464
|
2
|
.GroupBy(tuple => tuple.Item1)
|
465
|
2
|
.Select(
|
466
|
2
|
grp => (
|
467
|
2
|
grp.Key,
|
468
|
2
|
grp.Last().Item2.OutputStates.UpdatedAddresses
|
469
|
2
|
)
|
470
|
2
|
);
|
471
|
2
|
foreach (
|
472
|
2
|
(Transaction<T> tx, IImmutableSet<Address> updatedAddresses)
|
473
|
2
|
in txUpdatedAddressesPairs)
|
474
|
2
|
{
|
475
|
2
|
if (!tx.UpdatedAddresses.IsSupersetOf(updatedAddresses))
|
476
|
1
|
{
|
477
|
|
const string msg =
|
478
|
|
"Actions in the transaction try to update " +
|
479
|
|
"the addresses not granted.";
|
480
|
1
|
throw new InvalidTxUpdatedAddressesException(
|
481
|
1
|
tx.Id,
|
482
|
1
|
tx.UpdatedAddresses,
|
483
|
1
|
updatedAddresses,
|
484
|
1
|
msg
|
485
|
1
|
);
|
486
|
|
}
|
487
|
2
|
}
|
488
|
|
|
489
|
2
|
return txEvaluations.Select(te => te.Item2);
|
490
|
2
|
}
|
491
|
|
|
492
|
|
/// <summary>
|
493
|
|
/// Gets <see cref="BlockDigest"/> representation of the <see cref="Block{T}"/>.
|
494
|
|
/// </summary>
|
495
|
|
/// <returns><see cref="BlockDigest"/> representation of the <see cref="Block{T}"/>.
|
496
|
|
/// </returns>
|
497
|
|
public BlockDigest ToBlockDigest()
|
498
|
2
|
{
|
499
|
2
|
return new BlockDigest(
|
500
|
2
|
header: GetBlockHeader(),
|
501
|
2
|
txIds: Transactions
|
502
|
2
|
.Select(tx => tx.Id.ToByteArray().ToImmutableArray())
|
503
|
2
|
.ToImmutableArray());
|
504
|
2
|
}
|
505
|
|
|
506
|
|
public override string ToString()
|
507
|
1
|
{
|
508
|
1
|
return Hash.ToString();
|
509
|
1
|
}
|
510
|
|
|
511
|
|
internal BlockHeader GetBlockHeader()
|
512
|
2
|
{
|
513
|
2
|
string timestampAsString = Timestamp.ToString(
|
514
|
2
|
BlockHeader.TimestampFormat,
|
515
|
2
|
CultureInfo.InvariantCulture
|
516
|
2
|
);
|
517
|
2
|
ImmutableArray<byte> previousHashAsArray =
|
518
|
2
|
PreviousHash?.ToByteArray().ToImmutableArray() ?? ImmutableArray<byte>.Empty;
|
519
|
2
|
ImmutableArray<byte> stateRootHashAsArray =
|
520
|
2
|
StateRootHash?.ToByteArray().ToImmutableArray() ?? ImmutableArray<byte>.Empty;
|
521
|
|
|
522
|
|
// FIXME: When hash is not assigned, should throw an exception.
|
523
|
2
|
return new BlockHeader(
|
524
|
2
|
index: Index,
|
525
|
2
|
timestamp: timestampAsString,
|
526
|
2
|
nonce: Nonce.ToByteArray().ToImmutableArray(),
|
527
|
2
|
miner: Miner?.ToByteArray().ToImmutableArray() ?? ImmutableArray<byte>.Empty,
|
528
|
2
|
difficulty: Difficulty,
|
529
|
2
|
totalDifficulty: TotalDifficulty,
|
530
|
2
|
previousHash: previousHashAsArray,
|
531
|
2
|
txHash: TxHash?.ToByteArray().ToImmutableArray() ?? ImmutableArray<byte>.Empty,
|
532
|
2
|
hash: Hash.ToByteArray().ToImmutableArray(),
|
533
|
2
|
preEvaluationHash: PreEvaluationHash.ToByteArray().ToImmutableArray(),
|
534
|
2
|
stateRootHash: stateRootHashAsArray
|
535
|
2
|
);
|
536
|
2
|
}
|
537
|
|
|
538
|
|
internal void Validate(DateTimeOffset currentTime)
|
539
|
2
|
{
|
540
|
2
|
GetBlockHeader().Validate(currentTime);
|
541
|
|
|
542
|
2
|
foreach (Transaction<T> tx in Transactions)
|
543
|
2
|
{
|
544
|
2
|
tx.Validate();
|
545
|
2
|
}
|
546
|
2
|
}
|
547
|
|
|
548
|
|
internal RawBlock ToRawBlock()
|
549
|
1
|
{
|
550
|
|
// For consistency, order transactions by its id.
|
551
|
1
|
return new RawBlock(
|
552
|
1
|
header: GetBlockHeader(),
|
553
|
1
|
transactions: Transactions.OrderBy(tx => tx.Id)
|
554
|
1
|
.Select(tx => tx.Serialize(true).ToImmutableArray()).ToImmutableArray());
|
555
|
1
|
}
|
556
|
|
|
557
|
|
private byte[] SerializeForHash(HashDigest<SHA256>? stateRootHash = null)
|
558
|
2
|
{
|
559
|
2
|
var dict = Bencodex.Types.Dictionary.Empty
|
560
|
2
|
.Add("index", Index)
|
561
|
2
|
.Add(
|
562
|
2
|
"timestamp",
|
563
|
2
|
Timestamp.ToString(BlockHeader.TimestampFormat, CultureInfo.InvariantCulture))
|
564
|
2
|
.Add("difficulty", Difficulty)
|
565
|
2
|
.Add("nonce", Nonce.ToByteArray());
|
566
|
|
|
567
|
2
|
if (!(Miner is null))
|
568
|
2
|
{
|
569
|
2
|
dict = dict.Add("reward_beneficiary", Miner.Value.ToByteArray());
|
570
|
2
|
}
|
571
|
|
|
572
|
2
|
if (!(PreviousHash is null))
|
573
|
2
|
{
|
574
|
2
|
dict = dict.Add("previous_hash", PreviousHash.Value.ToByteArray());
|
575
|
2
|
}
|
576
|
|
|
577
|
2
|
if (!(TxHash is null))
|
578
|
2
|
{
|
579
|
2
|
dict = dict.Add("transaction_fingerprint", TxHash.Value.ToByteArray());
|
580
|
2
|
}
|
581
|
|
|
582
|
2
|
if (!(stateRootHash is null))
|
583
|
1
|
{
|
584
|
1
|
dict = dict.Add("state_root_hash", stateRootHash.Value.ToByteArray());
|
585
|
1
|
}
|
586
|
|
|
587
|
2
|
return new Codec().Encode(dict);
|
588
|
2
|
}
|
589
|
|
|
590
|
|
private readonly struct BlockSerializationContext
|
591
|
|
{
|
592
|
|
public BlockSerializationContext(bool hash, bool transactionData)
|
593
|
0
|
{
|
594
|
0
|
IncludeHash = hash;
|
595
|
0
|
IncludeTransactionData = transactionData;
|
596
|
0
|
}
|
597
|
|
|
598
|
0
|
internal bool IncludeHash { get; }
|
599
|
|
|
600
|
0
|
internal bool IncludeTransactionData { get; }
|
601
|
|
}
|
602
|
|
}
|
603
|
|
}
|