Skip to content

Commit c1a1c5e

Browse files
authored
Merge pull request #3974 from planetarium/refactor/gas-tracer
Refactor GasTracer
2 parents 625e617 + f2f8525 commit c1a1c5e

File tree

5 files changed

+284
-21
lines changed

5 files changed

+284
-21
lines changed

CHANGES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ To be released.
2323
- (Libplanet.Action) Added `GasTracer` static class. [[#3912]]
2424
- (Libplanet.Action) Added `LastCommit` property to `IActionContext`
2525
interface and its implementations. [[#3912]]
26-
26+
- (Libplanet.Action) Added `CancelTrace` method to `GasTracer`. [[#3974]]
2727

2828
### Backward-incompatible network protocol changes
2929

@@ -40,6 +40,7 @@ To be released.
4040
### CLI tools
4141

4242
[#3912]: https://github.com/planetarium/libplanet/pull/3912
43+
[#3974]: https://github.com/planetarium/libplanet/pull/3974
4344

4445

4546
Version 5.3.1

src/Libplanet.Action/ActionEvaluator.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -471,13 +471,14 @@ internal IEnumerable<ActionEvaluation> EvaluateTx(
471471
IWorld previousState)
472472
{
473473
GasTracer.Initialize(tx.GasLimit ?? long.MaxValue);
474-
GasTracer.StartTrace();
475474
var evaluations = ImmutableList<ActionEvaluation>.Empty;
476475
if (_policyActionsRegistry.BeginTxActions.Length > 0)
477476
{
477+
GasTracer.IsTxAction = true;
478478
evaluations = evaluations.AddRange(
479479
EvaluatePolicyBeginTxActions(block, tx, previousState));
480480
previousState = evaluations.Last().OutputState;
481+
GasTracer.IsTxAction = false;
481482
}
482483

483484
ImmutableList<IAction> actions =
@@ -500,7 +501,7 @@ internal IEnumerable<ActionEvaluation> EvaluateTx(
500501
EvaluatePolicyEndTxActions(block, tx, previousState));
501502
}
502503

503-
GasTracer.EndTrace();
504+
GasTracer.Release();
504505

505506
return evaluations;
506507
}

src/Libplanet.Action/GasMeter.cs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ namespace Libplanet.Action
22
{
33
internal class GasMeter : IGasMeter
44
{
5-
public GasMeter(long gasLimit, long gasUsed = 0)
5+
public GasMeter(long gasLimit)
66
{
7-
SetGasLimit(gasLimit);
8-
GasUsed = gasUsed;
7+
if (gasLimit < 0)
8+
{
9+
throw new GasLimitNegativeException();
10+
}
11+
12+
GasLimit = gasLimit;
913
}
1014

1115
public long GasAvailable => GasLimit - GasUsed;
@@ -39,15 +43,5 @@ public void UseGas(long gas)
3943

4044
GasUsed = newGasUsed;
4145
}
42-
43-
private void SetGasLimit(long gasLimit)
44-
{
45-
if (gasLimit < 0)
46-
{
47-
throw new GasLimitNegativeException();
48-
}
49-
50-
GasLimit = gasLimit;
51-
}
5246
}
5347
}

src/Libplanet.Action/GasTracer.cs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public static class GasTracer
1616

1717
private static readonly AsyncLocal<bool> IsTrace = new AsyncLocal<bool>();
1818

19+
private static readonly AsyncLocal<bool> IsTraceCancelled = new AsyncLocal<bool>();
20+
1921
/// <summary>
2022
/// The amount of gas used so far.
2123
/// </summary>
@@ -26,6 +28,8 @@ public static class GasTracer
2628
/// </summary>
2729
public static long GasAvailable => GasMeterValue.GasAvailable;
2830

31+
internal static bool IsTxAction { get; set; }
32+
2933
private static GasMeter GasMeterValue
3034
=> GasMeter.Value ?? throw new InvalidOperationException(
3135
"GasTracer is not initialized.");
@@ -41,23 +45,39 @@ public static void UseGas(long gas)
4145
if (IsTrace.Value)
4246
{
4347
GasMeterValue.UseGas(gas);
48+
if (IsTraceCancelled.Value)
49+
{
50+
throw new InvalidOperationException("GasTracing was canceled.");
51+
}
4452
}
4553
}
4654

47-
internal static void Initialize(long gasLimit)
55+
public static void CancelTrace()
4856
{
49-
GasMeter.Value = new GasMeter(gasLimit);
50-
IsTrace.Value = false;
57+
if (!IsTxAction)
58+
{
59+
throw new InvalidOperationException("CancelTrace can only be called in TxAction.");
60+
}
61+
62+
if (IsTraceCancelled.Value)
63+
{
64+
throw new InvalidOperationException("GasTracing is already canceled.");
65+
}
66+
67+
IsTraceCancelled.Value = true;
5168
}
5269

53-
internal static void StartTrace()
70+
internal static void Initialize(long gasLimit)
5471
{
72+
GasMeter.Value = new GasMeter(gasLimit);
5573
IsTrace.Value = true;
74+
IsTraceCancelled.Value = false;
5675
}
5776

58-
internal static void EndTrace()
77+
internal static void Release()
5978
{
6079
IsTrace.Value = false;
80+
IsTraceCancelled.Value = false;
6181
}
6282
}
6383
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using Bencodex.Types;
5+
using Libplanet.Action;
6+
using Libplanet.Action.Loader;
7+
using Libplanet.Action.State;
8+
using Libplanet.Blockchain;
9+
using Libplanet.Blockchain.Policies;
10+
using Libplanet.Crypto;
11+
using Libplanet.Store;
12+
using Libplanet.Store.Trie;
13+
using Libplanet.Types.Assets;
14+
using Libplanet.Types.Blocks;
15+
using Libplanet.Types.Tx;
16+
using Xunit;
17+
using static Libplanet.Action.State.KeyConverters;
18+
using static Libplanet.Tests.TestUtils;
19+
20+
namespace Libplanet.Tests.Action
21+
{
22+
public partial class ActionEvaluatorTest
23+
{
24+
[Theory]
25+
[InlineData(false, 1, 1)]
26+
[InlineData(true, 1, 0)]
27+
public void Evaluate_WithGasTracer(
28+
bool cancelTrace, long goldAmount, long expectedGoldAmount)
29+
{
30+
var gold = Currency.Uncapped("FOO", 18, null);
31+
var gas = Currency.Uncapped("GAS", 18, null);
32+
var privateKey = new PrivateKey();
33+
var policy = new BlockPolicy(
34+
new PolicyActionsRegistry(
35+
beginBlockActions: ImmutableArray<IAction>.Empty,
36+
endBlockActions: ImmutableArray<IAction>.Empty,
37+
beginTxActions: ImmutableArray.Create<IAction>(
38+
new GasTraceAction() { CancelTrace = cancelTrace }),
39+
endTxActions: ImmutableArray<IAction>.Empty),
40+
getMaxTransactionsBytes: _ => 50 * 1024);
41+
42+
var store = new MemoryStore();
43+
var stateStore = new TrieStateStore(new MemoryKeyValueStore());
44+
var chain = TestUtils.MakeBlockChain(
45+
policy: policy,
46+
store: store,
47+
stateStore: stateStore,
48+
actionLoader: new SingleActionLoader(typeof(UseGasAction)));
49+
var action = new UseGasAction
50+
{
51+
GasUsage = 10,
52+
MintValue = gold * goldAmount,
53+
Receiver = privateKey.Address,
54+
Memo = string.Empty,
55+
};
56+
57+
var tx = Transaction.Create(
58+
nonce: 0,
59+
privateKey: privateKey,
60+
genesisHash: chain.Genesis.Hash,
61+
actions: new[] { action }.ToPlainValues(),
62+
maxGasPrice: gas * 10,
63+
gasLimit: 10);
64+
var expectedGold = gold * expectedGoldAmount;
65+
66+
chain.StageTransaction(tx);
67+
var miner = new PrivateKey();
68+
Block block = chain.ProposeBlock(miner);
69+
chain.Append(block, CreateBlockCommit(block));
70+
var evaluations = chain.ActionEvaluator.Evaluate(
71+
block, chain.GetNextStateRootHash((BlockHash)block.PreviousHash));
72+
73+
var actualGold = chain.GetNextWorldState().GetBalance(privateKey.Address, gold);
74+
75+
Assert.Equal(expectedGold, actualGold);
76+
}
77+
78+
[Fact]
79+
public void Evaluate_CancelTrace_BeginBlockAction_Throw()
80+
{
81+
var policy = new BlockPolicy(
82+
new PolicyActionsRegistry(
83+
beginBlockActions: ImmutableArray.Create<IAction>(
84+
new GasTraceAction() { CancelTrace = true }),
85+
endBlockActions: ImmutableArray<IAction>.Empty,
86+
beginTxActions: ImmutableArray<IAction>.Empty,
87+
endTxActions: ImmutableArray<IAction>.Empty),
88+
getMaxTransactionsBytes: _ => 50 * 1024);
89+
var evaluations = Evaluate_CancelTrace(policy);
90+
var exception = (UnexpectedlyTerminatedActionException)evaluations[0].Exception;
91+
92+
Assert.IsType<GasTraceAction>(exception.Action);
93+
Assert.IsType<InvalidOperationException>(exception.InnerException);
94+
Assert.Equal(
95+
"CancelTrace can only be called in TxAction.", exception.InnerException.Message);
96+
}
97+
98+
[Fact]
99+
public void Evaluate_CancelTrace_EndBlockAction_Throw()
100+
{
101+
var policy = new BlockPolicy(
102+
new PolicyActionsRegistry(
103+
beginBlockActions: ImmutableArray<IAction>.Empty,
104+
endBlockActions: ImmutableArray.Create<IAction>(
105+
new GasTraceAction() { CancelTrace = true }),
106+
beginTxActions: ImmutableArray<IAction>.Empty,
107+
endTxActions: ImmutableArray<IAction>.Empty),
108+
getMaxTransactionsBytes: _ => 50 * 1024);
109+
var evaluations = Evaluate_CancelTrace(policy);
110+
var exception = (UnexpectedlyTerminatedActionException)evaluations[1].Exception;
111+
112+
Assert.IsType<GasTraceAction>(exception.Action);
113+
Assert.IsType<InvalidOperationException>(exception.InnerException);
114+
Assert.Equal(
115+
"CancelTrace can only be called in TxAction.", exception.InnerException.Message);
116+
}
117+
118+
[Fact]
119+
public void Evaluate_CancelTrace_EndTxAction_Throw()
120+
{
121+
var policy = new BlockPolicy(
122+
new PolicyActionsRegistry(
123+
beginBlockActions: ImmutableArray<IAction>.Empty,
124+
endBlockActions: ImmutableArray<IAction>.Empty,
125+
beginTxActions: ImmutableArray<IAction>.Empty,
126+
endTxActions: ImmutableArray.Create<IAction>(
127+
new GasTraceAction() { CancelTrace = true })),
128+
getMaxTransactionsBytes: _ => 50 * 1024);
129+
var evaluations = Evaluate_CancelTrace(policy);
130+
var exception = (UnexpectedlyTerminatedActionException)evaluations[1].Exception;
131+
132+
Assert.IsType<GasTraceAction>(exception.Action);
133+
Assert.IsType<InvalidOperationException>(exception.InnerException);
134+
Assert.Equal(
135+
"CancelTrace can only be called in TxAction.", exception.InnerException.Message);
136+
}
137+
138+
[Fact]
139+
public void Evaluate_CancelTrace_Action_Throw()
140+
{
141+
var policy = new BlockPolicy(
142+
new PolicyActionsRegistry(
143+
beginBlockActions: ImmutableArray<IAction>.Empty,
144+
endBlockActions: ImmutableArray<IAction>.Empty,
145+
beginTxActions: ImmutableArray<IAction>.Empty,
146+
endTxActions: ImmutableArray<IAction>.Empty),
147+
getMaxTransactionsBytes: _ => 50 * 1024);
148+
var gold = Currency.Uncapped("FOO", 18, null);
149+
var gas = Currency.Uncapped("GAS", 18, null);
150+
var privateKey = new PrivateKey();
151+
152+
var store = new MemoryStore();
153+
var stateStore = new TrieStateStore(new MemoryKeyValueStore());
154+
var chain = TestUtils.MakeBlockChain(
155+
policy: policy,
156+
store: store,
157+
stateStore: stateStore,
158+
actionLoader: new SingleActionLoader(typeof(GasTraceAction)));
159+
var action = new GasTraceAction
160+
{
161+
CancelTrace = true,
162+
};
163+
164+
var tx = Transaction.Create(
165+
nonce: 0,
166+
privateKey: privateKey,
167+
genesisHash: chain.Genesis.Hash,
168+
actions: new[] { action }.ToPlainValues(),
169+
maxGasPrice: gas * 10,
170+
gasLimit: 10);
171+
172+
chain.StageTransaction(tx);
173+
var miner = new PrivateKey();
174+
Block block = chain.ProposeBlock(miner);
175+
chain.Append(block, CreateBlockCommit(block));
176+
var evaluations = chain.ActionEvaluator.Evaluate(
177+
block, chain.GetNextStateRootHash((BlockHash)block.PreviousHash));
178+
var exception = (UnexpectedlyTerminatedActionException)evaluations[0].Exception;
179+
180+
Assert.IsType<GasTraceAction>(exception.Action);
181+
Assert.IsType<InvalidOperationException>(exception.InnerException);
182+
Assert.Equal(
183+
"CancelTrace can only be called in TxAction.", exception.InnerException.Message);
184+
}
185+
186+
private IReadOnlyList<ICommittedActionEvaluation> Evaluate_CancelTrace(BlockPolicy policy)
187+
{
188+
var gold = Currency.Uncapped("FOO", 18, null);
189+
var gas = Currency.Uncapped("GAS", 18, null);
190+
var privateKey = new PrivateKey();
191+
192+
var store = new MemoryStore();
193+
var stateStore = new TrieStateStore(new MemoryKeyValueStore());
194+
var chain = TestUtils.MakeBlockChain(
195+
policy: policy,
196+
store: store,
197+
stateStore: stateStore,
198+
actionLoader: new SingleActionLoader(typeof(UseGasAction)));
199+
var action = new UseGasAction
200+
{
201+
GasUsage = 10,
202+
MintValue = gold * 10,
203+
Receiver = privateKey.Address,
204+
Memo = string.Empty,
205+
};
206+
207+
var tx = Transaction.Create(
208+
nonce: 0,
209+
privateKey: privateKey,
210+
genesisHash: chain.Genesis.Hash,
211+
actions: new[] { action }.ToPlainValues(),
212+
maxGasPrice: gas * 10,
213+
gasLimit: 10);
214+
215+
chain.StageTransaction(tx);
216+
var miner = new PrivateKey();
217+
Block block = chain.ProposeBlock(miner);
218+
chain.Append(block, CreateBlockCommit(block));
219+
return chain.ActionEvaluator.Evaluate(
220+
block, chain.GetNextStateRootHash((BlockHash)block.PreviousHash));
221+
}
222+
223+
private sealed class GasTraceAction : IAction
224+
{
225+
public bool CancelTrace { get; set; }
226+
227+
public IValue PlainValue => new List(
228+
(Bencodex.Types.Boolean)CancelTrace);
229+
230+
public void LoadPlainValue(IValue plainValue)
231+
{
232+
var list = (List)plainValue;
233+
CancelTrace = (Bencodex.Types.Boolean)list[0];
234+
}
235+
236+
public IWorld Execute(IActionContext context)
237+
{
238+
if (CancelTrace)
239+
{
240+
GasTracer.CancelTrace();
241+
}
242+
243+
return context.PreviousState;
244+
}
245+
}
246+
}
247+
}

0 commit comments

Comments
 (0)