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

Align core types with execution spec #733

Merged
merged 13 commits into from
Sep 29, 2024
4 changes: 2 additions & 2 deletions doc/trie.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ Additional APIs are:
that starts with the same key prefix
* rootNode() -- get root node
* rootNode(node) -- replace the root node
* getRootHash(): `KeccakHash` with `seq[byte]` type
* getRootHash(): `Hash32` with `seq[byte]` type
* getDB(): `DB` -- get flat-db pointer

Constructor API:
* initBinaryTrie(DB, rootHash[optional]) -- rootHash has `seq[byte]` or KeccakHash type
* initBinaryTrie(DB, rootHash[optional]) -- rootHash has `seq[byte]` or Hash32 type
* init(BinaryTrie, DB, rootHash[optional])

Normally you would not set the rootHash when constructing an empty Binary-trie.
Expand Down
7 changes: 1 addition & 6 deletions eth.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ requires "nim >= 1.6.0",
"nimcrypto",
"stint",
"secp256k1",
"chronos#head",
"chronos",
"chronicles",
"stew",
"nat_traversal",
Expand Down Expand Up @@ -47,9 +47,6 @@ proc run(path, outdir: string) =
task test_keyfile, "Run keyfile tests":
run "tests/keyfile/all_tests", "keyfile"

task test_keys, "Run keys tests":
run "tests/keys/all_tests", "keys"

task test_discv5, "Run discovery v5 tests":
run "tests/p2p/all_discv5_tests", "p2p"

Expand Down Expand Up @@ -78,7 +75,6 @@ task test, "Run all tests":
run "tests/test_bloom", ""

test_keyfile_task()
test_keys_task()
test_rlp_task()
test_p2p_task()
test_trie_task()
Expand All @@ -87,7 +83,6 @@ task test, "Run all tests":
test_common_task()

task test_discv5_full, "Run discovery v5 and its dependencies tests":
test_keys_task()
test_rlp_task()
test_discv5_task()

Expand Down
30 changes: 18 additions & 12 deletions eth/bloom.nim
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import stint, ./common/eth_hash
import stint, ./common/[addresses, base, hashes]

type UInt2048 = StUint[2048]

iterator chunksForBloom(h: MDigest[256]): array[2, uint8] =
iterator chunksForBloom(h: Hash32): array[2, uint8] =
yield [h.data[0], h.data[1]]
yield [h.data[2], h.data[3]]
yield [h.data[4], h.data[5]]
Expand All @@ -12,28 +12,34 @@ proc chunkToBloomBits(chunk: array[2, uint8]): UInt2048 =
let l = chunk[1].int
one(UInt2048) shl ((l + (h shl 8)) and 2047)

iterator bloomBits(h: MDigest[256]): UInt2048 =
iterator bloomBits(h: Hash32): UInt2048 =
for chunk in chunksForBloom(h):
yield chunkToBloomBits(chunk)

type BloomFilter* = object
value*: UInt2048

proc incl*(f: var BloomFilter, h: MDigest[256]) =
proc incl*(f: var BloomFilter, h: Hash32) =
for bits in bloomBits(h):
f.value = f.value or bits

proc init*(_: type BloomFilter, h: MDigest[256]): BloomFilter =
proc init*(_: type BloomFilter, h: Hash32): BloomFilter =
result.incl(h)

# TODO: The following 2 procs should be one genric, but it doesn't compile. Nim bug?
proc incl*(f: var BloomFilter, v: string) = f.incl(keccakHash(v))
proc incl*(f: var BloomFilter, v: openArray[byte]) = f.incl(keccakHash(v))
proc incl*[T: byte|char](f: var BloomFilter, v: openArray[T]) =
f.incl(keccak256(v))

proc contains*(f: BloomFilter, h: MDigest[256]): bool =
proc incl*(f: var BloomFilter, v: Address | Bytes32) =
f.incl(v.data)

proc contains*(f: BloomFilter, h: Hash32): bool =
for bits in bloomBits(h):
if (f.value and bits).isZero: return false
if (f.value and bits).isZero:
return false
return true

template contains*[T](f: BloomFilter, v: openArray[T]): bool =
f.contains(keccakHash(v))
template contains*(f: BloomFilter, v: openArray): bool =
f.contains(keccak256(v))

proc contains*(f: BloomFilter, v: Address | Bytes32): bool =
f.contains(v.data)
4 changes: 2 additions & 2 deletions eth/common.nim
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import ./common/[eth_types_rlp, utils]
export eth_types_rlp, utils
import ./common/eth_types_rlp
export eth_types_rlp
35 changes: 35 additions & 0 deletions eth/common/accounts.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# eth
# Copyright (c) 2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

{.push raises: [].}

import ./[base, hashes]

export base, hashes

type Account* = object
## Account with fields in RLP order, per `encode_account` spec function
## https://github.com/ethereum/execution-specs/blob/51fac24740e662844446439ceeb96a460aae0ba0/src/ethereum/paris/fork_types.py#L36
nonce*: AccountNonce
balance*: UInt256
storageRoot*: Root
codeHash*: Hash32

const
EMPTY_ROOT_HASH* = emptyRoot
EMPTY_CODE_HASH* = emptyKeccak256

func init*(
T: type Account,
nonce = default(AccountNonce),
balance = default(UInt256),
storageRoot = EMPTY_ROOT_HASH,
codeHash = EMPTY_CODE_HASH,
): T =
T(nonce: nonce, balance: balance, storageRoot: storageRoot, codeHash: codeHash)

const EMPTY_ACCOUNT* = Account.init()
12 changes: 12 additions & 0 deletions eth/common/accounts_rlp.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# eth
# Copyright (c) 2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

{.push raises: [].}

import ./[accounts, base_rlp, hashes_rlp], ../rlp

export accounts, base_rlp, hashes_rlp, rlp
111 changes: 111 additions & 0 deletions eth/common/addresses.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# eth
# Copyright (c) 2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

{.push raises: [].}

## 20-byte ethereum account address, as derived from the keypair controlling it
## https://ethereum.org/en/developers/docs/accounts/#account-creation

import std/[typetraits, hashes as std_hashes], "."/[base, hashes], stew/assign2

export hashes

type Address* = distinct Bytes20
## https://github.com/ethereum/execution-specs/blob/51fac24740e662844446439ceeb96a460aae0ba0/src/ethereum/paris/fork_types.py#L28

const zeroAddress* = system.default(Address)
## Address consisting of all zeroes.
## Transactions to zeroAddress are legitimate transfers to that account, not
## contract creations. They are used to "burn" Eth. People also send Eth to
## address zero by accident, unrecoverably, due to poor user interface issues.

template to*(v: array[20, byte], _: type Address): Address =
Address(v)

func to*(s: Bytes32 | Hash32, _: type Address): Address =
## Take the last 20 bytes of the given hash to form an Address - this is a
## lossy conversion which discards the first 12 bytes

assign(result.data, s.data.toOpenArray(12, 31))

func to*(a: Address, _: type Bytes32): Bytes32 =
## Place the address in the last 20 bytes of the result, filling the rest with 0

assign(result.data.toOpenArray(12, 31), a.data)

template data*(v: Address): array[20, byte] =
distinctBase(v)

template `data=`*[N: static int](a: FixedBytes[N], b: array[N, byte]) =
assign(distinctBase(a), b)

template copyFrom*(T: type Address, v: openArray[byte], start = 0): T =
## Copy up to N bytes from the given openArray, starting at `start` and
## filling any missing bytes with zero.
##
## This is a lenient function in that `v` may contain both fewer and more
## bytes than N and start might be out of bounds.
Address(Bytes20.copyFrom(v, start))

template default*(_: type Address): Address =
# Avoid bad codegen where fixed bytes are zeroed byte-by-byte at call site
zeroAddress

func `==`*(a, b: Address): bool {.borrow.}

func hash*(a: Address): Hash {.inline.} =
# Addresses are more or less random so we should not need a fancy mixing
# function
var a0 {.noinit.}, a1 {.noinit.}: uint64
var a2 {.noinit.}: uint32

copyMem(addr a0, unsafeAddr a.data[0], sizeof(a0))
copyMem(addr a1, unsafeAddr a.data[8], sizeof(a1))
copyMem(addr a2, unsafeAddr a.data[16], sizeof(a2))

cast[Hash](a0 + a1 + uint64(a2))

func toHex*(a: Address): string {.borrow.}
func to0xHex*(a: Address): string {.borrow.}
func `$`*(a: Address): string {.borrow.}

func fromHex*(_: type Address, s: openArray[char]): Address {.raises: [ValueError].} =
Address(Bytes20.fromHex(s))

template to*(s: static string, _: type Address): Address =
const hash = Address.fromHex(s)
hash

template address*(s: static string): Address =
s.to(Address)

func toChecksum0xHex*(a: Address): string =
## Convert the address to 0x-prefixed mixed-case EIP-55 format
let
# TODO avoid memory allocations here
hhash1 = a.toHex()
hhash2 = keccak256(hhash1).toHex()
result = newStringOfCap(hhash2.len + 2)
result.add "0x"

for i, c in hhash1:
if hhash2[i] >= '0' and hhash2[i] <= '7':
result.add c
else:
if c >= '0' and c <= '9':
result.add c
else:
result.add chr(ord(c) - ord('a') + ord('A'))

func hasValidChecksum*(_: type Address, a: string): bool =
## Validate checksumable mixed-case address (EIP-55).
let address =
try:
Address.fromHex(a)
except ValueError:
return false
a == address.toChecksum0xHex()
19 changes: 19 additions & 0 deletions eth/common/addresses_rlp.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# eth
# Copyright (c) 2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

{.push raises: [].}

import ./addresses, ../rlp

export addresses, rlp

proc read*[T: Address](rlp: var Rlp, _: type T): T {.raises: [RlpError].} =
T(rlp.read(type(result.data)))

proc append*(w: var RlpWriter, val: Address) =
mixin append
w.append(val.data())
Loading