Skip to content
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

Merged
merged 44 commits into from
Dec 10, 2020

Conversation

roman-khimov
Copy link
Contributor

@roman-khimov roman-khimov commented Nov 3, 2020

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:

  • 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 Redefine opcode prices and make them adjustable #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
    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 #1875.

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.
Concentrate on adjustability for now.
@@ -62,6 +62,7 @@ public class MemoryPool : IReadOnlyCollection<Transaction>

Copy link
Contributor

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;
}
Copy link
Contributor

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;
Copy link
Contributor

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.

Copy link
Member

@vncoelho vncoelho left a 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.
@shargon shargon mentioned this pull request Nov 11, 2020
@Tommo-L
Copy link
Contributor

Tommo-L commented Nov 12, 2020

Conflicts

@roman-khimov
Copy link
Contributor Author

Fixed.

@superboyiii superboyiii mentioned this pull request Nov 19, 2020
44 tasks
@superboyiii
Copy link
Member

Let's move on.

@roman-khimov
Copy link
Contributor Author

I guess it depends on #2054 now which depends on #2052.

roman-khimov added a commit to roman-khimov/neo that referenced this pull request Nov 26, 2020
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.
@erikzhang
Copy link
Member

@Qiao-Jin

[OpCode.PUSHINT8] = 1 << 0,
[OpCode.PUSHINT16] = 1 << 0,
[OpCode.PUSHINT32] = 1 << 0,
[OpCode.PUSHINT64] = 1 << 0,
Copy link
Contributor Author

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,
Copy link
Contributor Author

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.

Copy link
Contributor

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,
Copy link
Contributor Author

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,
Copy link
Contributor Author

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.

Copy link
Contributor

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,
Copy link
Contributor Author

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,
Copy link
Contributor Author

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.

@roman-khimov
Copy link
Contributor Author

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 PICKITEM which is a frequently used opcode that is actually way cheaper in execution than it's priced now (HASKEY and REMOVE are similar in their performance to PICKITEM, so all three should be priced the same).

erikzhang
erikzhang previously approved these changes Dec 10, 2020
Copy link
Member

@erikzhang erikzhang left a 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.

Copy link
Member

@erikzhang erikzhang left a 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.

Copy link
Member

@shargon shargon left a 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));
Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Redefine opcode prices and make them adjustable
8 participants