-
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
Mode to disable backward jumps on Neo AppEngine 3 #837
Mode to disable backward jumps on Neo AppEngine 3 #837
Conversation
Codecov Report
@@ Coverage Diff @@
## master #837 +/- ##
==========================================
+ Coverage 61.3% 61.32% +0.02%
==========================================
Files 196 196
Lines 13430 13440 +10
==========================================
+ Hits 8233 8242 +9
- Misses 5197 5198 +1
Continue to review full report at Codecov.
|
case OpCode.JMP: | ||
case OpCode.JMPIF: | ||
case OpCode.JMPIFNOT: | ||
case OpCode.CALL: |
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.
With syscall, you can jump to your own contract again
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.
From what we've been talking, most syscalls will probably be disabled on verification. In my opinion now, only crypto stuff and block header information should be available... no appcalls, no storage, ... so it will be fast as a bullet and state independent.
Is this realy useful? |
Honestly, i can't see a use of case, because you will pay for verification |
The point is that failed verifications are not paid Shargon, and after you already executed them. Having loops here is bad. If really needed, it can be unrolled. Loops here only allow you to do damaging stuff. A simple and hard to solve example: you attach 100 GAS and a while( true). How many steps it takes to consume 100 gas? After that tx is denied and nothing spent (verification fails). But this isnt the worst case, because it just damages the borders... imagine you.attach while (true) and a time lock (or fast expirarion ). This could allow this tx to pass several nodes until it finally fails on the consensus node mempool (and nothing is paid). Loops are devil creatures. Just to mention that things were different on Neo2... we had global utxo, and much logic for inputs needed loops. We couldnt do auto transfers inside contracts, so we needed to "summon" the full contract here, with all its loops. We had 10 gas limit on verification, that controlled these cases, sinve we couldnt pay for verification anyway. So, I think this is a natural change afterall. |
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.
Mode disable jumps would be an optional param for those that opt for their contract to be like that, right @igormcoelho?
Idea is to disable loops for all verifications... |
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 agree with the "philosophy", the problem with this PR is that I didn't see the real usage of it. Are we using this limited mode somewhere else?
what about a max fee for verification? |
This PR resolves a problem that cannot be solved in other way: someone spending time on verification and not actually paying by it, because tx will fail. If we dont do this, we will eventually do a much wider block, for example only allowing checksig/multisig... this is a reasonable alternative to it. |
I'm now in favor of this, because this way, the transaction length will be almost directly proportional to the fee the user must pay. |
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.
Hi Igor,
Can you use Verification trigger instead?
I believe you won't have to worry about syscalls, it will be limited already if we use the Verification trigger and I don't think we will ever need a limited mode for the Application trigger
I think this may create obstacles to the implementation of the compiler. |
Nothing for the magic couple @lightszero & @igormcoelho....whuahhehau |
@erikzhang hopefully, this annotation here will allow us to handle it transparently (VERIFICATION=true) |
But I agree Erik, this is not perfect yet... disable backward call may disable "valid" calls. methodA methodB call methodA (valid) intention is just disable recursion, we may find a better way for that. But for jumps, solution is correct in my.opinion... only ifs will survive. [EDIT] |
@igormcoelho I agree with you, verification must be lightweight. |
What about a new class VerificationEngine: ApplicationEngine ? |
verificationGas += engine.GasConsumed; | ||
} | ||
// no gas is spent (backwards jump is not allowed) | ||
Assert.AreEqual(verificationGas, 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.
@erikzhang @shargon this basic test is already giving evidence that, for Verification, zero gas is spent (no backward jumps allowed), but on Application, it goes until funds are exhausted (and fails. this wouldn't consume the funds on verification, and this could be repeated again and again).
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.
How can we prevent the issue with CALLS?
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.
On CALLS, we have two options... count StepInto(), or just count it on PreExecute(). Must see what's better.
appGas += engine.GasConsumed; | ||
} | ||
// many gas are spent before fault | ||
Assert.AreEqual(appGas, 100000040); |
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 are consuming more gas than limit... 40 units more here, it shouldn't I guess.
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.
private bool AddGas(long gas)
{
GasConsumed = checked(GasConsumed + gas);
return testMode || GasConsumed <= gas_amount;
}
the method first increase the gas, so is normal to overpass this limit i think
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 is passing 40, its reasonable... the other is passing 1 million.
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 should break before adding to this variable, I guess.
appGas += engine.GasConsumed; | ||
} | ||
// many gas are spent before fault | ||
Assert.AreEqual(appGas, 22528000); |
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.
Now it's passing much overlimit... million gas-satoshi extra, we should fix that.
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.
} | ||
// no gas should be spent (recursion is not allowed) | ||
// TODO: fix recursion | ||
Assert.AreEqual(verificationGas, 22528000); |
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 should be zero (or near zero), as long as we block recursive behavior. Perhaps stack context count is enough... not sure.
@lock9 your change was adopted... you're right, Shargon also agreed that it was better filtering directly by VerificationTrigger, instead of a new variable. |
Co-Authored-By: Shargon <shargon@gmail.com>
…' into feat_3x_neoapp_limited_mode
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 this looks ok, however, I'm insecure about approving it, it seems that this has not been approved by everyone, but I'm in favor of this.
Could any of you share your opinion @neo-project/core ?
My concern is that it can make the compiler hard to work. Because we can't guarantee that the compiler will not generate backward jump code (under various optimization). |
I am in favor of this, but as Erik said, a valid SmartContract could be compiled with a backward jump (on verification) |
If we make the verification trigger a method that we have to override, won't this help? Today I understand that is very hard to know what is being included in the verification trigger logic, but if we isolate it in a method, maybe it will help? |
for JMP I believe it's very safe, it's not supposed to break compilers... for CALL, I agree with you, we cannot do that. That's why for CALL I prefer to have some instruction counting, or very short callstack max size (5?) This is not finished from CALL side yet, because something is bothering my mind... I keep thinking that there may be a way of a double recursion function to keep stack size small under infinite loop......... but is it possible?? Example: FUNC1 and FUNC2 somehow, they keep calling each other... dropping results after it reach max call stack 5, and then allow calling again.... is it possible? I mean, is it possible to do that, when backward JMP are not allowed? |
This is an implementation of neo-project/neo-vm#174 on application layer. It closes neo-project/neo-vm#151.
closes #789
===
First of all, thanks for all the things you guys made me learn in the past months, it has been an amazing opportunity for me, to improve coding skills, testing skills, and learn more about cryptography and all kinds of stuff around a blockchain ecosystem like Neo.
After much thoughts, and many discussions with many different people, I think that we need an execution mode on NeoVM that would allow us to disable backward jumps. This disables loops on practice, although not generating problems to ifs. This also disables recursive calls.
Just a brief back in history, Bitcoin has become famous, and one of its explicit flags has been the capability of not having Turing-complete compution in its verification engine. It also doesn't have this sort of computation on Application Engine either (since it doesn't have one joy), so I think Neo could benefit of also not having it on Verification trigger, while maintaining a full powerful Turing-complete application engine.
Verification and Application are very different things. Verification needs to be denied as quickly as possible, when necessary, without consuming too much resources from the network, while Application can be much more expensive, since it is paid in advance in Gas. If someone wants to have an infinite loop on Application, it doesn't matter, it will take time, and this time will be well paid on System Fees, no worry (and it will FAULT and be discarded in the future). But we cannot afford having this type of computation on the Verification engine, where nothing is actually paid (yet) to the network.
Disabling Turing-completeness can be done on several manners, one of each is simply removing backward loops, like done here. There even more severe strategies, like removing all sorts of computation from here (thus basically removing the Verification Trigger away). This is an attempt to keep the verification trigger, which is useful for several stuff, while keeping it well contained and fast to process.
Hope having a heated discussion here, but thanks in advance anyway ;)