-
Notifications
You must be signed in to change notification settings - Fork 116
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
Implemented most of the stubbed-out state handling instructions #59
Conversation
14f0407
to
026f1d6
Compare
The code compiles, but still fails at the moment due to incorrect initialization of the VM. Don't merge yet. More commits will be pushed in the coming days.
026f1d6
to
c8287a9
Compare
This is in a relatively good shape for merging now. Please note that quite a lot of tests are failing now, but this seems to be the result of properly implementing the "post" state checker. Previously, a test was considered successful just because it managed to execute to the end without crashing. |
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 to me. I prefer the new syntax to the old db(readOnly = true) macro
A few remarks:
-
I feel like the code will get littered by toOpenArray (which I like), I hope that move/lent types and destructors will alleviate this because the compiler should infer that copy is not needed.
-
I think TODO is better than XXX due to github highlight, have to check if FIXME works also so that we have granularity if needed
-
due to quasiBoolean and https://github.com/status-im/nimbus/issues/63, the code doesn't compile on latest devel. I see a partial fix for and/or/xor code, if you have time I'd like a complete fix. Otherwise we can merge as is, as the "fix" will be removed anyway when I merge my branch.
-
writePaddedResult is complex and inefficient (2 nested if, plus zero-padding that is actually unneeded). I use a much simpler version in my branch. In fact I noticed that Py-EVM overengineered when implementing memory ops:
- https://github.com/ethereum/py-evm/blob/090b29141d1d80c4b216cfa7ab889115df3c0da0/evm/vm/logic/context.py#L96-L97
- https://github.com/paritytech/parity/blob/98b7c07171cd320f32877dfa5aa528f585dc9a72/ethcore/evm/src/interpreter/mod.rs#L581-L582
- https://github.com/ethereum/go-ethereum/blob/947e0afeb3bce9c52548979daddd1e00aa0d7ba8/core/vm/instructions.go#L478-L479
|
||
proc getCanonicalHead*(self: BaseChainDB): BlockHeader = | ||
let k = canonicalHeadHashKey() | ||
if k notin self.db: | ||
if k.toOpenArray notin self.db: | ||
raise newException(CanonicalHeadNotFound, | ||
"No canonical head set for this chain") | ||
return self.getBlockHeaderByHash(self.getHash(k)) |
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 there really a cost from passing a simple k
to notin or getHash?
If so shouldn't it be getHash(k.toOpenarray)
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.
Passing just k
wouldn't compile. notin
is expanded to a contains
call, which is part of the abstract interface of the database.
# db.trie[address] = rlp.encode[Account](account) | ||
discard # TODO | ||
|
||
db.trie.put createRangeFromAddress(address), rlp.encode(account) |
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.
rlp.encode(account).toOpenArray here?
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.
The Trie currently requires ByteRanges
keys and values, because it needs to create alternative views over the passed in range (it needs to examine the range bit by bit or nibble by nibble). The Nim type system must be improved before it will be possible to create such views over openarray
inputs. I'll create an RFC about it soon.
# XXX: This is too expensive. Similar to `createRangeFromAddress` | ||
# Converts a number to hex big-endian representation including | ||
# prefix and leading zeros: | ||
@(keccak256.digest(slot.toByteArrayBE).data).toRange |
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 use toByteRange_Unnecessary
defined below.
let last = min(c.pc + n, c.bytes.len) | ||
let toWrite = last - c.pc | ||
for i in 0 ..< toWrite : result_bytes[i] = c.bytes[last - i - 1] | ||
for j in toWrite ..< 32: result_bytes[j] = 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.
This is unnecessary, Uint256/stack objects are initialized with all 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.
Fair enough, but we can add your trick about using noinit
on the result here perhaps.
|
||
let last = min(c.pc + n, c.bytes.len) | ||
let toWrite = last - c.pc | ||
for i in 0 ..< toWrite : result_bytes[i] = c.bytes[last - i - 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.
Are there cases where endianness is important?
If yes, it should at least have a # TODO: implement endianness aware
or be implemented right now because that might lead to hard to debug issues otherwise.
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.
Yes, I wanted to add a static assert about this actually (wanted to see how you are detecting the endianness in stint).
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 just use when system.cpuEndian == littleEndian
@@ -18,11 +18,17 @@ quasiBoolean(sgt, `>`, signed=true) # Signed Greater Comparison | |||
|
|||
quasiBoolean(eq, `==`) # Equality |
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.
Unfortunately, the quasiBoolean macro is also broken for < and == on latest devel
presentBytes = 0 | ||
|
||
for i in presentBytes ..< 32: bytes[i] = 0 | ||
computation.stack.push(bytes) |
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 seems very complex. In my own implementation rewrite I use the following:
op callDataLoad, inline = false, startPos:
## 0x35, Get input data of current environment
let start = startPos.toInt
# If the data does not take 32 bytes, pad with zeros
let endRange = min(computation.msg.data.len - 1, start + 31)
let padding = start + 31 - endRange
var value: array[32, byte] # We rely on value being initialized with 0 by default
value[padding ..< 32] = computation.msg.data.toOpenArray(start, endRange)
push: 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.
This is not safe. The original code was written in convoluted way, because startPos
may be larger than INT_MAX
.
And again, noinit
can speed up the original code to avoid double writes to the same bytes.
let endPos = startPos + numberOfBytes | ||
assert endPos < memory.len | ||
for i in startPos ..< endPos: | ||
memory.bytes[i] = paddingValue |
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 unnecessary if we use openarray because memory.extend already extends with 0.
mem.writePaddingBytes(memPos + presentElements, | ||
len - presentElements, | ||
paddingValue) | ||
|
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.
Since extend already pads with 0, this can be simplified, the following is extracted from my implementation of codecopy and avoids 2 if-else nesting and the need of writePaddingBytes
# If the data does not take 32 bytes, pad with zeros
let lim = min(computation.code.bytes.len, copyPos + len)
let padding = copyPos + len - lim
# Note: when extending, extended memory is zero-ed, we only need to offset with padding value
computation.memory.write(memPos):
computation.code.bytes.toOpenArray(copyPos+padding, copyPos+lim)
Full implementation with comments, I noticed that Py-EVM is overengineering here:
op codecopy, inline = false, memStartPos, copyStartPos, size:
## 0x39, Copy code running in current environment to memory.
let (memPos, copyPos, len) = (memStartPos.toInt, copyStartPos.toInt, size.toInt)
computation.gasMeter.consumeGas(
computation.gasCosts[CodeCopy].m_handler(memPos, copyPos, len),
reason="CodeCopy fee")
computation.memory.extend(memPos, len)
# TODO: here Py-EVM is doing something very complex, increasing a program counter in the "CodeStream" type.
# while Geth, Parity and the Yellow paper are just copying bytes?
# https://github.com/ethereum/py-evm/blob/090b29141d1d80c4b216cfa7ab889115df3c0da0/evm/vm/logic/context.py#L96-L97
# https://github.com/paritytech/parity/blob/98b7c07171cd320f32877dfa5aa528f585dc9a72/ethcore/evm/src/interpreter/mod.rs#L581-L582
# https://github.com/ethereum/go-ethereum/blob/947e0afeb3bce9c52548979daddd1e00aa0d7ba8/core/vm/instructions.go#L478-L479
# If the data does not take 32 bytes, pad with zeros
let lim = min(computation.code.bytes.len, copyPos + len)
let padding = copyPos + len - lim
# Note: when extending, extended memory is zero-ed, we only need to offset with padding value
computation.memory.write(memPos):
computation.code.bytes.toOpenArray(copyPos+padding, copyPos+lim)
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.
Fair enough again, but perhaps expand
shouldn't pad with zero. This way, there will be a single write over the expanded range.
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 there is an off-by-one error in your code. The second index passed to toOpenArray
is inclusive, so there should be lim - 1
somewhere in there.
Also, this is seductively simple, but the getting the old code right was quite tricky. Notice how there is a check if presentElements > 0
. This was because dataPos
may be outside the bounds of code.bytes
. You code is not handling this case properly as far as I can tell.
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've extracted the discussion to #67. That can be tackled later and might make a good first issue for someone new to Nimbus. (We would need an easier way to test mem opcodes probably)
if found: | ||
computation.stack.push value | ||
else: | ||
# XXX: raise exception? |
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 prefer TODO to XXX due to Github highlight
let paddedValue = `value`.padRight(`size`, 0.byte) | ||
`computation`.stack.push(paddedValue) | ||
|
||
`computation`.stack.push `computation`.code.readVmWord(`size`) |
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 not ported yet in the other branch.
* move forks constants, rename errors * Move vm/utils to vm/interpreter/utils * initial opcodes refactoring * Add refactored Comparison & Bitwise Logic Operations * Add sha3 and address, simplify macro, support pop 0 * balance, origin, caller, callValue * fix gas copy opcodes gas costs, add callDataLoad/Size/Copy, CodeSize/Copy and gas price opcode * Update with 30s, 40s, 50s opcodes + impl of balance + stack improvement * add push, dup, swap, log, create and call operations * finish opcode implementation * Add the new dispatching logic * Pass the opcode test * Make test_vm_json compile * halt execution without exceptions for Return, Revert, selfdestruct (fix #62) * Properly catch and recover from EVM exceptions (stack underflow ...) * Fix byte op * Fix jump regressions * Update for latest devel, don't import old dispatch code as quasiBoolean macro is broken by latest devel * Fix sha3 regression on empty memory slice and until end of range slice * Fix padding / range error on expXY_success (gas computation left) * update logging procs * Add tracing - expXY_success is not a regression, sload stub was accidentally passing the test * Reuse the same stub as OO implementation * Delete previous opcode implementation * Delete object oriented fork code * Delete exceptions that were used as control flows * delete base.nim 🔥, yet another OO remnants * Delete opcode table * Enable omputed gotos and compile-time gas fees * Revert const gasCosts -> generates SIGSEGV * inline push, swap and dup opcodes * loggers are now template again, why does this pass new tests? * Trigger CI rebuild after rocksdb fix status-im/nim-rocksdb#5 * Address review comment on "push" + VMTests in debug mode (not release) * Address review comment: don't tag fork by default, make opcode impl grepable * Static compilation fixes after rebasing * fix the initialization of the VM database * add a missing import * Deactivate balance and sload test following #59 * Reactivate stack check (deactivated in #59, necessary to pass tests) * Merge remaining opcodes implementation from #59 * Merge callDataLoad and codeCopy fixes, todo simplify see #67
The code compiles, but still fails at the moment due to incorrect
initialization of the VM. Don't merge yet. More commits will be
pushed in the coming days.