-
Notifications
You must be signed in to change notification settings - Fork 773
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
Migrate opFns to typescript, modify loop/interpreter structure #506
Conversation
This pull request introduces 1 alert when merging 31e9838 into 37a4de1 - view on LGTM.com new alerts:
Comment posted by LGTM.com |
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.
Looks good.
this._runState.gasLeft = new BN(0) | ||
this._gasLeft.isub(amount) | ||
if (this._gasLeft.ltn(0)) { | ||
this._gasLeft = new BN(0) | ||
trap(ERROR.OUT_OF_GAS) | ||
} | ||
} |
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.
Doesn't need to be addressed in this PR but still have the impression, that gas handling should rather have it's own gas.ts
module since this doesn't really belong to the environment but is rather some internal state component like Stack
or Memory
. This would be likely also similarly useful for VM users if they could hook into that class and get step-by-step gas usage info or can manipulate.
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.
Also one indicator: gasLeft
doesn't really look right in the constructor here, this is also used only in very isolated places and not throughout the whole class.
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-top note: but please resist the temptation of just doing it because I repeat the idea 2-3 times if you don't think it's a good idea. 😛
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.
To be honest I'm not sure what you have in mind to put in the gas.ts
module :) The useGas
and refundGas
methods (maybe like this)?
this is also used only in very isolated places and not throughout the whole class.
It's mostly used in opFns
(and also used for instruction base fee in Loop
). On purpose I avoided doing gas metering in EEI
.
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.
Thanks for digging this out from Py-EVM, yes I was actually thinking about something like this. We can do this later though if agreed upon, don't let this distract you here. I would assume this would be useful if e.g. extended with events which could be used to real-time display gas usage in IDEs e.g..
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.
Great, I think this is doable. One possibility (for next versions) is to take inspiration from go-ethereum's gas table bring most gas calculation logic there (possibly even hardfork-specific gas costs).
const addressBuf = addressToBuffer(address) | ||
return this._state.accountIsEmpty(addressBuf) | ||
async isAccountEmpty (address: Buffer): Promise<boolean> { | ||
return this._state.accountIsEmpty(address) | ||
} | ||
} | ||
|
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.
Ok, some more signature tightening fof account functionality, good.
gas: eei._gasLeft, | ||
gasUsed, | ||
'return': result.returnValue ? result.returnValue : Buffer.alloc(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.
Can't completely judge these changes in Interpreter
but would give this a go since you're saying that this is only some intermediate step.
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.
Yeah this is mostly temporary. But I'd appreciate comments on how to change it in future (I'm not yet sure myself).
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.
Will give this some thought, maybe @alcuadrado can also join for discussion?
account: this._runState.eei!._env.contract, | ||
depth: this._eei._env.depth, | ||
address: this._eei._env.address, | ||
account: this._eei._env.contract, | ||
stateManager: this._runState.stateManager, | ||
memory: this._runState.memory._store, // Return underlying array for backwards-compatibility | ||
memoryWordCount: this._runState.memoryWordCount |
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.
Whole file: yes, this looks actually much cleaner and this state removal and just execution focus looks more fitting to the purpose this class should probably have, great!
module.exports = { | ||
STOP: function (runState) { | ||
runState.stopped = true | ||
export const handlers: {[k: string]: OpHandler} = { |
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.
Is this mainly to closely stick to the current structure of the module or does this have other benefits or - more broadly - implications?
Related question: is making opcodes sub-classable or extendable or similar a separate step? Have you this on the plan for this release/change round?
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.
Well, no major benefit, I only typed what was there, didn't make structural modifications. I think it definitely makes sense to revisit this and maybe take inspiration from Chris's comments to improve the handlers.
is making opcodes sub-classable or extendable or similar a separate step?
Once users can customize Loop
and use their customized version to run transactions, they can override getOpHandler
and replace the implementation for any of the opcodes. I imagine this should be a good first step. I'm open however to ideas on how to make this even further extensible.
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.
Ok, thanks for the explanation.
@@ -8,7 +8,8 @@ export enum ERROR { | |||
REVERT = 'revert', | |||
STATIC_STATE_CHANGE = 'static state change', | |||
INTERNAL_ERROR = 'internal error', | |||
CREATE_COLLISION = 'create collision' | |||
CREATE_COLLISION = 'create collision', | |||
STOP = 'stop' | |||
} | |||
|
|||
export class VmError { |
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.
Whole file: generally looks good, various variable initialization clean ups and naming improvements, good.
const MASK_160 = new BN(1).shln(160).subn(1) | ||
|
||
// Find Ceil(`this` / `num`) | ||
BN.prototype.divCeil = function divCeil (num) { | ||
var dm = this.divmod(num) | ||
function divCeil (a: BN, b: BN) { |
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.
Makes sense to have this less intrusive.
Loop
is now greatly simplified: it simply takes a code buffer, goes through it until an exception happens or the code finishes. The opcode handlers and the Loop itself interact with outside through EEI (there are some exceptions which still use stateManager directly, but I suggest we modify them later). The resultLoop.run
is whether there were any exceptions in running the code.Interpreter
instantiatesEEI
and passes it toLoop
, and also processes the results after each message being executed. After these changesEEI
has become more stateful (it stores results,gasLeft
and some other stuff locally). But as the name suggests, it should only be an interface, and the logic and state should be moved elsewhere (Interpreter in this case) in future.Loop
logic for detecting when to stop has been changed and everything happens through traps (droppedrunState.stopped
and checkingreturnValue
).Now I'm rather happy with
Loop
(apart from its name!! 😄), butInterpreter
andEEI
still need changes. E.g. processing results, handling calls and creates, etc.