-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SmartContract/Native: implement adjustable opcode pricing #2045
SmartContract/Native: implement adjustable opcode pricing #2045
Conversation
This defines a new Policy contract setting, BaseExecFee which contains a price of basic VM execution unit --- NOP instruction. It then redefines all opcode prices as coefficients applied to that base fee. The following invariants hold for this: * transaction fees are expressed in GAS * BaseExecFee is expressed in GAS * opcode price is a coefficient now * syscall price is expressed in GAS * ApplicationEngine.AddGas() also accepts GAS values, so it's a caller's responsibility to calculate that in whatever fashion he likes. Caveats: * while coefficients are based on table from neo-project#1875 (which is built on instruction execution time relations for two complete VM implementations), some modifications were applied: - it's impossible for SYSCALL to have non-0 cost now (tests just hang) - all slot operations have the same price * block and consensus payloads are adjusted to use BaseExecFee, but probably ECDsaVerifyPrice is still a constant * it's not really tested other than unit tests In general, this allows to define any execution cost in NOPs and then use BaseExecFee to get the price. The same principle could be applied to storage pricing based on FeePerByte (StoragePrice could just be `100 * FeePerByte`), but that's a bit different topic. Closes neo-project#1875.
ECDsaVerifyPrice is still a constant and it dominates here.
Concentrate on adjustability for now.
@@ -62,6 +62,7 @@ public class MemoryPool : IReadOnlyCollection<Transaction> | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not only price of opcode, but also price of interop services should be included.
@@ -256,7 +256,6 @@ public void FeeIsSignatureContractDetailed() | |||
verificationGas += engine.GasConsumed; | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should add UT for cases where fee ratio is changed.
@@ -62,6 +62,7 @@ public class MemoryPool : IReadOnlyCollection<Transaction> | |||
|
|||
private int _maxTxPerBlock; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should taken into account various hard coded verification prices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it is right now, it looks a good and clean PR, @roman-khimov, that is a good feature and will make maintenance simpler as well as increase protection of the network.
Use currentShapshot we already have.
Conflicts |
…rices Resolve opcode price conflict (neo-project#2020).
Fixed. |
Let's move on. |
MemoryPool contents is always valid (verified) against some snapshot. This snapshot is only changed when new block is added. Between blocks we only have one valid chain state that can be read by multiple threads without any issues, thus we can execute concurrently not only state-independent, but also state-dependent parts of transaction verification. To simplify execution flow (minimize single-threaded Blockchain message handling and eliminate duplicate DB accesses for ContainsTransaction) TransactionRouter is an independent entity now, though of course we can also attach the same actor functionality to MemoryPool itself. TransactionVerificationContext balance checking is moved to MemoryPool from Transaction with this, Transaction shouldn't care (it can check overall GAS balance though). This is directly related to neo-project#2045 work (it solves state access problem there) and in some sense is an alternative to neo-project#2054 (makes fee calculation easier, though IsStandardContract() trick to optimize out these contracts during reverification is still relevant and can be added here). At this stage it's just a prototype, some additional optimizations and simplifications are possible of course, but this prototype shows the direction and the main question for now is whether this direction is interesting for us.
[OpCode.PUSHINT8] = 1 << 0, | ||
[OpCode.PUSHINT16] = 1 << 0, | ||
[OpCode.PUSHINT32] = 1 << 0, | ||
[OpCode.PUSHINT64] = 1 << 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These PUSHes (as well as PUSHM1..PUSH16) do cost more than NOP, I'd suggest 1<<2
here based on data we have (and it's very similar for two implementations with real costs around 4-5 NOPs).
[OpCode.JMPIF_L] = 1 << 1, | ||
[OpCode.JMPIFNOT] = 1 << 1, | ||
[OpCode.JMPIFNOT_L] = 1 << 1, | ||
[OpCode.JMPEQ] = 1 << 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JMPs with embedded comparisons also reliably use more execution time than simple JMPs, not a lot, but enough to justify 1<<2
vs 1<<1
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be good for JMPEQ, JMPEQ_L, JMPNE and JMPNE_L
[OpCode.ISNULL] = 60, | ||
[OpCode.ISTYPE] = 60, | ||
[OpCode.CONVERT] = 80000, | ||
[OpCode.DEPTH] = 1 << 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's similar to PUSHINT, so if we're to change them, it needs to be changed also.
[OpCode.DEPTH] = 1 << 1, | ||
[OpCode.DROP] = 1 << 1, | ||
[OpCode.NIP] = 1 << 1, | ||
[OpCode.XDROP] = 1 << 4, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest 1<<6
here, there are edge cases for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think 10 to 20 times is OK
[OpCode.ROLL] = 1 << 4, | ||
[OpCode.REVERSE3] = 1 << 1, | ||
[OpCode.REVERSE4] = 1 << 1, | ||
[OpCode.REVERSEN] = 1 << 4, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggesting at least 1 << 5
here, N can be quite big.
[OpCode.TUCK] = 1 << 1, | ||
[OpCode.SWAP] = 1 << 1, | ||
[OpCode.ROT] = 1 << 1, | ||
[OpCode.ROLL] = 1 << 4, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has the same problem as XDROP.
Just to get things clear, we can argue about specific prices for a long time, but that won't bring us closer to preview4, so I'm OK with almost any values just to be done with this release. Still, we do have a lot of real performance data for these opcodes and we should take that into account, because if we don't do it now, some well-known disproportions will be hardcoded for eternity. The most important of them to me is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can merge it first and adjust the price value in the future if necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have updated some price values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be merged for me
[ContractMethod(0_03000000, CallFlags.WriteStates)] | ||
private bool SetExecFeeFactor(ApplicationEngine engine, uint value) | ||
{ | ||
if (value == 0 || value > MaxExecFeeFactor) throw new ArgumentOutOfRangeException(nameof(value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe if we allow 0 (checking the magic), we can configure a free private network easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is too dangerous. Maybe a minimum value should be added to protocol.json.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This certainly can be done later if needed.
This defines a new Policy contract setting, BaseExecFee which contains the price
of basic VM execution unit --- NOP instruction. It then redefines all opcode
prices as coefficients applied to that base fee.
The following invariants hold for this:
responsibility to calculate that in whatever fashion he likes.
Caveats:
instruction execution time relations for two complete VM implementations),
some modifications were applied:
ECDsaVerifyPrice is still a constant
In general, this allows to define any execution cost in NOPs and then use
BaseExecFee to get the price. The same principle could be applied to storage
pricing based on FeePerByte (StoragePrice could just be
100 * FeePerByte
),but that's a bit different topic.
Closes #1875.