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

Migrate opFns to typescript, modify loop/interpreter structure #506

Merged
merged 5 commits into from
May 13, 2019
Merged

Conversation

s1na
Copy link
Contributor

@s1na s1na commented May 9, 2019

  • Migrates opFns to typescript, which necessitated some changes (that were also suggested by @holgerd77 in V4 tracker #479) in the interaction between Interpreter, Loop and the EEI:
  • 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 result Loop.run is whether there were any exceptions in running the code.
  • Interpreter instantiates EEI and passes it to Loop, and also processes the results after each message being executed. After these changes EEI 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 (dropped runState.stopped and checking returnValue).

Now I'm rather happy with Loop (apart from its name!! 😄), but Interpreter and EEI still need changes. E.g. processing results, handling calls and creates, etc.

@lgtm-com
Copy link

lgtm-com bot commented May 9, 2019

This pull request introduces 1 alert when merging 31e9838 into 37a4de1 - view on LGTM.com

new alerts:

  • 1 for Unused variable, import, function or class

Comment posted by LGTM.com

Copy link
Member

@holgerd77 holgerd77 left a 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)
}
}
Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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. 😛

Copy link
Contributor Author

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.

Copy link
Member

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..

Copy link
Contributor Author

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)
}
}

Copy link
Member

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)
})
}

Copy link
Member

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.

Copy link
Contributor Author

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).

Copy link
Member

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
Copy link
Member

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} = {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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 {
Copy link
Member

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) {
Copy link
Member

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.

@s1na s1na merged commit a53d432 into v4 May 13, 2019
@s1na s1na deleted the ts/opFns branch May 13, 2019 07:38
@s1na s1na mentioned this pull request May 13, 2019
23 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants