-
Notifications
You must be signed in to change notification settings - Fork 141
/
ActionEvaluation.cs
177 lines (166 loc) · 7.34 KB
/
ActionEvaluation.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Security.Cryptography;
using Libplanet.Blockchain.Policies;
using Libplanet.Blocks;
using Libplanet.Tx;
namespace Libplanet.Action
{
/// <summary>
/// A record type to represent an evaluation plan and result of
/// a single action.
/// </summary>
public class ActionEvaluation
{
/// <summary>
/// Creates an <see cref="ActionEvaluation"/> instance
/// with filling properties.
/// </summary>
/// <param name="action">An action to evaluate.</param>
/// <param name="inputContext">An input <see cref="IActionContext"/> to
/// evaluate <paramref name="action"/>.</param>
/// <param name="outputStates">The result states that
/// <paramref name="action"/> makes.</param>
public ActionEvaluation(
IAction action,
IActionContext inputContext,
IAccountStateDelta outputStates
)
{
Action = action;
InputContext = inputContext;
OutputStates = outputStates;
}
/// <summary>
/// An action to evaluate.
/// </summary>
public IAction Action { get; }
/// <summary>
/// An input <see cref="IActionContext"/> to evaluate
/// <see cref="Action"/>.
/// </summary>
/// <remarks>Its <see cref="IActionContext.Random"/> property
/// is not consumed yet.</remarks>
public IActionContext InputContext { get; }
/// <summary>
/// The result states that <see cref="Action"/> makes.
/// </summary>
public IAccountStateDelta OutputStates { get; }
/// <summary>
/// Executes the <paramref name="actions"/> step by step, and emits
/// <see cref="ActionEvaluation"/> for each step.
/// </summary>
/// <param name="blockHash">The <see cref="Block{T}.Hash"/> of <see cref="Block{T}"/> that
/// <paramref name="actions"/> belongs to.</param>
/// <param name="blockIndex">The <see cref="Block{T}.Index"/> of <see cref="Block{T}"/> that
/// <paramref name="actions"/> belongs to.</param>
/// <param name="txid">The <see cref="Transaction{T}.Id"/> of <see cref="Transaction{T}"/>
/// that <paramref name="actions"/> belongs to. This can be <c>null</c> on rehearsal mode
/// or if an action is a <see cref="IBlockPolicy{T}.BlockAction"/>.</param>
/// <param name="previousStates">The states immediately before <paramref name="actions"/>
/// being executed. Note that its <see cref="IAccountStateDelta.UpdatedAddresses"/> are
/// remained to the returned next states.</param>
/// <param name="minerAddress">An address of block miner.</param>
/// <param name="signer">Signer of the <paramref name="actions"/>.</param>
/// <param name="signature"><see cref="Transaction{T}"/> signature used to generate random
/// seeds.</param>
/// <param name="actions">Actions to evaluate.</param>
/// <param name="rehearsal">Pass <c>true</c> if it is intended
/// to be dry-run (i.e., the returned result will be never used).
/// The default value is <c>false</c>.</param>
/// <returns>Enumerates <see cref="ActionEvaluation"/>s for each one in
/// <paramref name="actions"/>. The order is the same to the <paramref name="actions"/>.
/// Note that each <see cref="IActionContext.Random"/> object
/// has a unconsumed state.
/// </returns>
/// <exception cref="UnexpectedlyTerminatedActionException">
/// Thrown when one of <paramref name="actions"/> throws some exception.
/// The actual exception that an <see cref="IAction"/> threw
/// is stored in its <see cref="Exception.InnerException"/> property.
/// </exception>
internal static IEnumerable<ActionEvaluation> EvaluateActionsGradually(
HashDigest<SHA256> blockHash,
long blockIndex,
TxId? txid,
IAccountStateDelta previousStates,
Address minerAddress,
Address signer,
byte[] signature,
IImmutableList<IAction> actions,
bool rehearsal = false)
{
ActionContext CreateActionContext(
IAccountStateDelta prevStates,
int randomSeed
) =>
new ActionContext(
signer: signer,
miner: minerAddress,
blockIndex: blockIndex,
previousStates: prevStates,
randomSeed: randomSeed,
rehearsal: rehearsal
);
byte[] hashedSignature;
using (var hasher = SHA1.Create())
{
hashedSignature = hasher.ComputeHash(signature);
}
int seed =
BitConverter.ToInt32(blockHash.ToByteArray(), 0) ^
(signature.Any() ? BitConverter.ToInt32(hashedSignature, 0) : 0);
IAccountStateDelta states = previousStates;
foreach (IAction action in actions)
{
ActionContext context =
CreateActionContext(states, seed);
IAccountStateDelta nextStates;
try
{
nextStates = action.Execute(context);
}
catch (Exception e)
{
string msg;
if (!rehearsal)
{
msg = $"The action {action} (block #{blockIndex} {blockHash}, tx {txid}) " +
"threw an exception during execution. See also this exception's " +
"InnerException property.";
throw new UnexpectedlyTerminatedActionException(
blockHash, blockIndex, txid, action, msg, e
);
}
msg =
$"The action {action} threw an exception during its " +
"rehearsal. It is probably because the logic of the " +
$"action {action} is not enough generic so that it " +
"can cover every case including rehearsal mode.\n" +
"The IActionContext.Rehearsal property also might be " +
"useful to make the action can deal with the case of " +
"rehearsal mode.\n" +
"See also this exception's InnerException property.";
throw new UnexpectedlyTerminatedActionException(
null, null, null, action, msg, e
);
}
// As IActionContext.Random is stateful, we cannot reuse
// the context which is once consumed by Execute().
ActionContext equivalentContext =
CreateActionContext(states, seed);
yield return new ActionEvaluation(
action,
equivalentContext,
nextStates
);
states = nextStates;
unchecked
{
seed++;
}
}
}
}
}