ExecutionEngine: collect parameters affecting opcode price - #571
Conversation
Signed-off-by: Tural Devrishev <tural@nspcc.ru>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #571 +/- ##
==========================================
- Coverage 85.55% 85.49% -0.06%
==========================================
Files 41 42 +1
Lines 2720 2896 +176
Branches 345 349 +4
==========================================
+ Hits 2327 2476 +149
- Misses 303 330 +27
Partials 90 90 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
AnnaShaleva
left a comment
There was a problem hiding this comment.
Will submit detailed handlers review later.
Signed-off-by: Tural Devrishev <tural@nspcc.ru>
Signed-off-by: Tural Devrishev <tural@nspcc.ru>
Signed-off-by: Tural Devrishev <tural@nspcc.ru>
AnnaShaleva
left a comment
There was a problem hiding this comment.
Will check it one more time together with neo-project/neo#4536.
Signed-off-by: Tural Devrishev <tural@nspcc.ru>
Signed-off-by: Tural Devrishev <tural@nspcc.ru>
Signed-off-by: Tural Devrishev <tural@nspcc.ru>
AnnaShaleva
left a comment
There was a problem hiding this comment.
We still need to update parameters construction for Map-related opcodes (ref. nspcc-dev/neo-go#4222); other than that the PR looks legit, let's wait for the update.
Signed-off-by: Tural Devrishev <tural@nspcc.ru>
Signed-off-by: Tural Devrishev <tural@nspcc.ru>
Signed-off-by: Tural Devrishev <tural@nspcc.ru>
| /// Typ specifies type of <see cref="StackItemType"/> which in most of the cases serves | ||
| /// as an operand of the given opcode. | ||
| /// </summary> | ||
| public StackItemType Type { get; internal set; } |
There was a problem hiding this comment.
Why this is not initialized every time? Is this required?
There was a problem hiding this comment.
It's required for some of opcodes. For other opcodes price doesn't depend on the operand type.
There was a problem hiding this comment.
Could you give me an example please?
There was a problem hiding this comment.
Sure. CONVERT requires Type since it works differently for Array/Struct and ByteStrings:
https://github.com/Turalchik/neo/blob/f0699631792accf4cdd717073091199f0bd650a3/src/Neo/SmartContract/Fee.cs#L357
PICKITEM requires Type argument since its price depends on whether the operand is a compound type or not:
https://github.com/Turalchik/neo/blob/f0699631792accf4cdd717073091199f0bd650a3/src/Neo/SmartContract/Fee.cs#L368
And APPEND doesn't actually depend on the operand type. RefsDelta and NClonedItems arguments cover possible price fluctuations for different operand types:
https://github.com/Turalchik/neo/blob/f0699631792accf4cdd717073091199f0bd650a3/src/Neo/SmartContract/Fee.cs#L353
There was a problem hiding this comment.
In general, different combinations of Type, RefsDelta, Length and NClonedItems are used for every opcode. The set of required parameters is defined separately for every opcode via opcode handler investigation and via benchmarks of edge cases in nspcc-dev/neo-go#4218.
Signed-off-by: Tural Devrishev <tural@nspcc.ru>
| { | ||
| engine.ReferenceCounter.RemoveStackReference(value); | ||
| throw new CatchableException($"The index of {nameof(VMArray)} is out of range, {index}/[0, {array.Count})."); | ||
| var r4 = engine.ReferenceCounter.Count; |
There was a problem hiding this comment.
Should be good if we add some uts for this catcheable exceptions (json)
This can be done in a different PR
| var r2 = engine.ReferenceCounter.Count; | ||
| engine.Push(x.ConvertTo(toType)); | ||
| var (type, length) = (StackItemType.Any, 0); | ||
| if (fromType == StackItemType.Array && toType == StackItemType.Struct || fromType == StackItemType.Struct && toType == StackItemType.Array) |
There was a problem hiding this comment.
From array to array or from struct to struct?
There was a problem hiding this comment.
When converting Array to Struct
neo-vm/src/Neo.VM/Types/Array.cs
Lines 81 to 86 in 99095c2
Struct to Arrayneo-vm/src/Neo.VM/Types/Struct.cs
Lines 69 to 74 in 99095c2
Array to Array (or Struct to Struct) is a no-op.
There was a problem hiding this comment.
but type will be Any in RunStats?
There was a problem hiding this comment.
No, Type will be equal to StackItemType.Array
There was a problem hiding this comment.
Yes, Type is equal to StackItemType.Any in cases where the length of the array, structure, byte array, or other parameters do not affect the opcode operation time. When an Array is converted to an Array (or something like that), the memory is not copied, respectively, the instruction executes in constant time relative to the size of the array. The price of CONVERT depends on the size of the data structure only when the Type is equal to StackItemType.Array or StackItemType.ByteString https://github.com/neo-project/neo/blob/7c1e64512984ebdd39c0d26df25abb5670af7760/src/Neo/SmartContract/ApplicationEngine.OpCodePricesV1.cs#L151-L157
There was a problem hiding this comment.
I think they should pay as if was converted, these optimizations should be done in compiler side. We can be faster, but they call convert
There was a problem hiding this comment.
Price should reflect real resources spent and any imbalance is a problem. We're not spending any real resources on same-type conversions, these operations are fast, price should just reflect that instead of trying to punish anyone. Why anyone is doing that is a separate question, it's the same as NOP price --- we don't care why people use NOP (theoretically they shouldn't), we just charge some tiny amount of GAS for it and move the instruction pointer to the next (more interesting or not) instruction.
Review summaryThis PR is a solid foundation for dynamic opcode pricing: it introduces I think this is mergeable after a few verification items, not a redesign. Must verify before merge
Suggestions (non-blocking if correct)
Nits
Existing discussion
|
shargon
left a comment
There was a problem hiding this comment.
As talked with @roman-khimov , my ut must be updated, but the code looks good to me
vncoelho
left a comment
There was a problem hiding this comment.
lgtm
There is no much test for now
| RefsDelta = refsDelta; | ||
| } | ||
|
|
||
| /// <summary> |
There was a problem hiding this comment.
Maybe some virtual method here for additional info to this stats, that we can in the future trigger for specific case and serve as a flag for price calculation.
Not sure yet.
| /// <summary> | ||
| /// Contains opcode-specific parameters used to calculate dynamic price. | ||
| /// </summary> | ||
| public struct RunStats |
There was a problem hiding this comment.
I suggest another name such as OpCodeRunStats, OpcodeStackStats, OpcodePriceStats
| ExecutionContext context = CurrentContext!; | ||
| Instruction? currentInstruction = context.CurrentInstruction; | ||
| Instruction instruction = currentInstruction ?? Instruction.RET; | ||
| RunStats runStats = default; |
There was a problem hiding this comment.
RunStats can be a field of ExecutionEngine?
|
UT fails |
AnnaShaleva
left a comment
There was a problem hiding this comment.
LGTM, will approve after unit-test fix.
Signed-off-by: Tural Devrishev <tural@nspcc.ru>
This PR adds collection of parameters in the
OpcodePriceArgsstruct (length, reference count changes, etc.) that affect opcode costs for dynamic pricing. The pricing is calculated in the related PR neo-project/neo#4536.A partial port of nspcc-dev/neo-go#4087. Ref. nspcc-dev/neo-go#4043.