Skip to content

Commit

Permalink
Feature/execution api spec (#69)
Browse files Browse the repository at this point in the history
* Fix problem with contract out of assets

* Add nully value json test

* Fix bug with passing null to a few functions

* Typesafe U256 parsing

* Update readme for the web3 to include runner info

* Cleanup commits

Strip out unused JSON

Update comment

Remove echo

Added DynamicBytes test

More correct naming

Remove one extra double check

* Add specific object tests

* Ensure we cover different status types

* Add header comments

* Cleanup

* Add more tests

* Cleanup

* Revert docs

* Nimpretty file

* Fix issue in base stew

* Add tests

* Sorting

* Work on tests

* Move items to the correct location

* Add TODO

* Add all test executables

* Work on generating tests

* Push latest execution API changes

* Work on eth_call test

* Fix source type

* Latest

* Add many more tests

* Add more tests

* Revert "from" to "source"

* Try fix types

* Split to types which fail and those which dont

* Cleanup

* Remove whispher types again

* re-remove whisper

* Add more eth_api signature

* Readding executions-apis submodule

* Disable test_execution_api

* Nitpick

* add handlers

* Add last line to handler

* Some handler pass

* Finally all tests pass

* Add the test to all_tests

* Consisten style

* Turn on submodule downloading in CI

* Temporary workaround of get eth_getBlockReceipts for nim 2.0

---------

Co-authored-by: jangko <[email protected]>
  • Loading branch information
tavurth and jangko authored Jan 11, 2024
1 parent 6c27c97 commit 85b3567
Show file tree
Hide file tree
Showing 11 changed files with 315 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: true

- name: Install build dependencies (Linux i386)
if: runner.os == 'Linux' && matrix.target.cpu == 'i386'
Expand Down
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,18 @@ node_modules
nohup.out
hardhat.config.js
package-lock.json

# Individual test executables
all_tests
test_contract_dsl
test_contracts
test_deposit_contract
test_execution_api
test_execution_debug_apis
test_execution_types
test_json_marshalling
test_logs
test_null_conversion
test_primitives
test_signed_tx
test_string_decoder
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "tests/execution-apis"]
path = tests/execution-apis
url = https://github.com/ethereum/execution-apis
3 changes: 2 additions & 1 deletion tests/all_tests.nim
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ import
test_signed_tx,
test_execution_types,
test_string_decoder,
test_contract_dsl
test_contract_dsl,
test_execution_api
1 change: 1 addition & 0 deletions tests/execution-apis
Submodule execution-apis added at cea7ee
159 changes: 159 additions & 0 deletions tests/helpers/handlers.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# json-rpc
# Copyright (c) 2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.

import
stint,
eth/common,
json_rpc/rpcserver,
../../web3/conversions,
../../web3/eth_api_types,
../../web3/primitives as w3

type
Hash256 = w3.Hash256

proc installHandlers*(server: RpcServer) =
server.rpc("eth_syncing") do(x: JsonString, ) -> bool:
return false

server.rpc("eth_sendRawTransaction") do(x: JsonString, data: seq[byte]) -> TxHash:
let tx = rlp.decode(data, Transaction)
let h = rlpHash(tx)
return TxHash(h.data)

server.rpc("eth_getTransactionReceipt") do(x: JsonString, data: TxHash) -> ReceiptObject:
var r: ReceiptObject
if x != "-1".JsonString:
r = JrpcConv.decode(x.string, ReceiptObject)
return r

server.rpc("eth_getTransactionByHash") do(x: JsonString, data: TxHash) -> TransactionObject:
var tx: TransactionObject
if x != "-1".JsonString:
tx = JrpcConv.decode(x.string, TransactionObject)
return tx

server.rpc("eth_getTransactionByBlockNumberAndIndex") do(x: JsonString, blockId: RtBlockIdentifier, quantity: Quantity) -> TransactionObject:
var tx: TransactionObject
if x != "-1".JsonString:
tx = JrpcConv.decode(x.string, TransactionObject)
return tx

server.rpc("eth_getTransactionByBlockHashAndIndex") do(x: JsonString, data: Hash256, quantity: Quantity) -> TransactionObject:
var tx: TransactionObject
if x != "-1".JsonString:
tx = JrpcConv.decode(x.string, TransactionObject)
return tx

server.rpc("eth_getTransactionCount") do(x: JsonString, data: Address, blockId: RtBlockIdentifier) -> Quantity:
if x != "-1".JsonString:
result = JrpcConv.decode(x.string, Quantity)

server.rpc("eth_getStorageAt") do(x: JsonString, data: Address, slot: UInt256, blockId: RtBlockIdentifier) -> FixedBytes[32]:
if x != "-1".JsonString:
result = JrpcConv.decode(x.string, FixedBytes[32])

server.rpc("eth_getProof") do(x: JsonString, address: Address, slots: seq[UInt256], blockId: RtBlockIdentifier) -> ProofResponse:
var p: ProofResponse
if x != "-1".JsonString:
p = JrpcConv.decode(x.string, ProofResponse)
return p

server.rpc("eth_getCode") do(x: JsonString, data: Address, blockId: RtBlockIdentifier) -> seq[byte]:
if x != "-1".JsonString:
result = JrpcConv.decode(x.string, seq[byte])

server.rpc("eth_getBlockTransactionCountByNumber") do(x: JsonString, blockId: RtBlockIdentifier) -> Quantity:
if x != "-1".JsonString:
result = JrpcConv.decode(x.string, Quantity)

server.rpc("eth_getBlockTransactionCountByHash") do(x: JsonString, data: BlockHash) -> Quantity:
if x != "-1".JsonString:
result = JrpcConv.decode(x.string, Quantity)

when NimMajor >= 2:
server.rpc("eth_getBlockReceipts") do(x: JsonString, blockId: RtBlockIdentifier) -> JsonString:
# TODO: cannot prove obj is not nil
return x
else:
server.rpc("eth_getBlockReceipts") do(x: JsonString, blockId: RtBlockIdentifier) -> Option[seq[ReceiptObject]]:
if x == "null".JsonString:
var n: Option[seq[ReceiptObject]]
return n
if x != "-1".JsonString:
let r = JrpcConv.decode(x.string, seq[ReceiptObject])
return some(r)

server.rpc("eth_getBlockByNumber") do(x: JsonString, blockId: RtBlockIdentifier, fullTransactions: bool) -> BlockObject:
var blk: BlockObject
if x != "-1".JsonString:
blk = JrpcConv.decode(x.string, BlockObject)
return blk

server.rpc("eth_getBlockByHash") do(x: JsonString, data: BlockHash, fullTransactions: bool) -> BlockObject:
var blk: BlockObject
if x != "-1".JsonString:
blk = JrpcConv.decode(x.string, BlockObject)
return blk

server.rpc("eth_getBalance") do(x: JsonString, data: Address, blockId: RtBlockIdentifier) -> UInt256:
if x != "-1".JsonString:
result = JrpcConv.decode(x.string, UInt256)

server.rpc("eth_feeHistory") do(x: JsonString, blockCount: Quantity, newestBlock: RtBlockIdentifier, rewardPercentiles: Option[seq[float64]]) -> FeeHistoryResult:
var fh: FeeHistoryResult
if x != "-1".JsonString:
fh = JrpcConv.decode(x.string, FeeHistoryResult)
return fh

server.rpc("eth_estimateGas") do(x: JsonString, call: EthCall) -> Quantity:
if x != "-1".JsonString:
result = JrpcConv.decode(x.string, Quantity)

server.rpc("eth_createAccessList") do(x: JsonString, call: EthCall, blockId: RtBlockIdentifier) -> AccessListResult:
var z: AccessListResult
if x != "-1".JsonString:
z = JrpcConv.decode(x.string, AccessListResult)
return z

server.rpc("eth_chainId") do(x: JsonString, ) -> Quantity:
if x != "-1".JsonString:
result = JrpcConv.decode(x.string, Quantity)

server.rpc("eth_call") do(x: JsonString, call: EthCall, blockId: RtBlockIdentifier) -> seq[byte]:
if x != "-1".JsonString:
result = JrpcConv.decode(x.string, seq[byte])

server.rpc("eth_blockNumber") do(x: JsonString) -> Quantity:
if x != "-1".JsonString:
result = JrpcConv.decode(x.string, Quantity)

server.rpc("debug_getRawTransaction") do(x: JsonString, data: TxHash) -> RlpEncodedBytes:
var res: seq[byte]
if x != "-1".JsonString:
res = JrpcConv.decode(x.string, seq[byte])
return res.RlpEncodedBytes

server.rpc("debug_getRawReceipts") do(x: JsonString, blockId: RtBlockIdentifier) -> seq[RlpEncodedBytes]:
var res: seq[RlpEncodedBytes]
if x != "-1".JsonString:
res = JrpcConv.decode(x.string, seq[RlpEncodedBytes])
return res

server.rpc("debug_getRawHeader") do(x: JsonString, blockId: RtBlockIdentifier) -> RlpEncodedBytes:
var res: seq[byte]
if x != "-1".JsonString:
res = JrpcConv.decode(x.string, seq[byte])
return res.RlpEncodedBytes

server.rpc("debug_getRawBlock") do(x: JsonString, blockId: RtBlockIdentifier) -> RlpEncodedBytes:
var res: seq[byte]
if x != "-1".JsonString:
res = JrpcConv.decode(x.string, seq[byte])
return res.RlpEncodedBytes
110 changes: 110 additions & 0 deletions tests/test_execution_api.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import
std/[os, strutils],
pkg/unittest2,
chronos,
json_rpc/[rpcclient, rpcserver],
json_rpc/private/jrpc_sys,
../web3/conversions,
./helpers/handlers

type
TestData = tuple
file: string
input: RequestTx
output: ResponseRx

const
inputPath = "tests/execution-apis/tests"

func strip(line: string): string =
return line[3..^1]

func toTx(req: RequestRx): RequestTx =
RequestTx(
id: Opt.some(req.id),
`method`: req.`method`.get(),
params: req.params.toTx,
)

proc extractTest(fileName: string): TestData =
let
lines = readFile(fileName).split("\n")
input = lines[0].strip()
output = lines[1].strip()

return (
file: fileName,
input: JrpcSys.decode(input, RequestRx).toTx,
output: JrpcSys.decode(output, ResponseRx),
)

proc extractTests(): seq[TestData] =
for fileName in walkDirRec(inputPath):
if fileName.endsWith(".io"):
result.add(fileName.extractTest())

proc callWithParams(client: RpcClient, data: TestData): Future[bool] {.async.} =
let res = data.output

try:
var params = data.input.params
if data.output.result.string.len > 0:
params.positional.insert(data.output.result, 0)
else:
params.positional.insert("-1".JsonString, 0)

let resJson = await client.call(data.input.`method`, params)

if res.result.string.len > 0:
let wantVal = JrpcConv.decode(res.result.string, JsonValueRef[string])
let getVal = JrpcConv.decode(resJson.string, JsonValueRef[string])

if wantVal != getVal:
debugEcho data.file
debugEcho "EXPECT: ", res.result
debugEcho "GET: ", resJson.string
return false

return true
except SerializationError as exc:
debugEcho data.file
debugEcho exc.formatMsg("xxx")
return false
except CatchableError as exc:
if res.error.isSome:
return true
debugEcho data.file
debugEcho exc.msg
return false

const allowedToFail = [
"fee-history.io" # float roundtrip not match
]

suite "Ethereum execution api":
let testCases = extractTests()
if testCases.len < 1:
raise newException(ValueError, "execution_api tests not found, did you clone?")

var srv = newRpcHttpServer(["127.0.0.1:0"])
srv.installHandlers()
srv.start()

for idx, item in testCases:
let input = item.input
let methodName = input.`method`

test methodName:
let (_, fileName, ext) = splitFile(item.file)
let client = newRpcHttpClient()
waitFor client.connect("http://" & $srv.localAddress()[0])
let response = waitFor client.callWithParams(item)
let source = fileName & ext
if source in allowedToFail:
check true
else:
check response
waitFor client.close()

waitFor srv.stop()
waitFor srv.closeWait()
3 changes: 1 addition & 2 deletions tests/test_logs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract(LoggerContract):
proc MyEvent(sender: Address, number: UInt256) {.event.}
proc invoke(value: UInt256)

const LoggerContractCode = "6080604052348015600f57600080fd5b5060bc8061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80632b30d2b814602d575b600080fd5b604760048036036020811015604157600080fd5b50356049565b005b604080513381526020810183905281517fdf50c7bb3b25f812aedef81bc334454040e7b27e27de95a79451d663013b7e17929181900390910190a15056fea265627a7a723058202ed7f5086297d2a49fbe359f4e489a007b69eb5077f5c76328bffdb63f164b4b64736f6c63430005090032"
const LoggerContractCode = "6080604052348015600f57600080fd5b5060fb8061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80632b30d2b814602d575b600080fd5b605660048036036020811015604157600080fd5b81019080803590602001909291905050506058565b005b7fdf50c7bb3b25f812aedef81bc334454040e7b27e27de95a79451d663013b7e173382604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a15056fea265627a7a72315820cb9980a67d78ee2e84fedf080db8463ce4a944fccf8b5512448163aaff0aea8964736f6c63430005110032"

var contractAddress = Address.fromHex("0xEA255DeA28c84F698Fa195f87fC83D1d4125ef9C")

Expand All @@ -48,7 +48,6 @@ suite "Logs":
# let q = await web3.provider.eth_blockNumber()
echo "block: ", uint64(await web3.provider.eth_blockNumber())


block: # LoggerContract
let receipt = await web3.deployContract(LoggerContractCode)
contractAddress = receipt.contractAddress.get
Expand Down
3 changes: 2 additions & 1 deletion web3/conversions.nim
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ ProofResponse.useDefaultSerializationIn JrpcConv
FilterOptions.useDefaultSerializationIn JrpcConv
EthSend.useDefaultSerializationIn JrpcConv
EthCall.useDefaultSerializationIn JrpcConv
FeeHistoryResult.useDefaultSerializationIn JrpcConv

derefType(BlockHeader).useDefaultSerializationIn JrpcConv
derefType(BlockObject).useDefaultSerializationIn JrpcConv
Expand Down Expand Up @@ -290,7 +291,7 @@ proc readValue*(r: var JsonReader[JrpcConv], val: var RtBlockIdentifier)
val = RtBlockIdentifier(kind: bidNumber, number: fromHex[uint64](hexStr))
else:
val = RtBlockIdentifier(kind: bidAlias, alias: hexStr)

proc writeValue*(w: var JsonWriter[JrpcConv], v: RtBlockIdentifier)
{.gcsafe, raises: [IOError].} =
case v.kind
Expand Down
15 changes: 13 additions & 2 deletions web3/eth_api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ createRpcSigsFromNim(RpcClient):
proc eth_accounts(): seq[Address]
proc eth_blockNumber(): Quantity
proc eth_getBalance(data: Address, blockId: BlockIdentifier): UInt256
proc eth_getStorageAt(data: Address, slot: UInt256, blockId: BlockIdentifier): UInt256
proc eth_getStorageAt(data: Address, slot: UInt256, blockId: BlockIdentifier): FixedBytes[32]
proc eth_getTransactionCount(data: Address, blockId: BlockIdentifier): Quantity
proc eth_getBlockTransactionCountByHash(data: BlockHash): Quantity
proc eth_getBlockTransactionCountByNumber(blockId: BlockIdentifier): Quantity
proc eth_getBlockReceipts(blockId: BlockIdentifier): Option[seq[ReceiptObject]]
proc eth_getUncleCountByBlockHash(data: BlockHash): Quantity
proc eth_getUncleCountByBlockNumber(blockId: BlockIdentifier): Quantity
proc eth_getCode(data: Address, blockId: BlockIdentifier): seq[byte]
Expand All @@ -47,7 +48,7 @@ createRpcSigsFromNim(RpcClient):
proc eth_sendTransaction(obj: EthSend): TxHash
proc eth_sendRawTransaction(data: seq[byte]): TxHash
proc eth_call(call: EthCall, blockId: BlockIdentifier): seq[byte]
proc eth_estimateGas(call: EthCall, blockId: BlockIdentifier): Quantity
proc eth_estimateGas(call: EthCall): Quantity
proc eth_createAccessList(call: EthCall, blockId: BlockIdentifier): AccessListResult
proc eth_getBlockByHash(data: BlockHash, fullTransactions: bool): BlockObject
proc eth_getBlockByNumber(blockId: BlockIdentifier, fullTransactions: bool): BlockObject
Expand Down Expand Up @@ -82,5 +83,15 @@ createRpcSigsFromNim(RpcClient):
slots: seq[UInt256],
blockId: BlockIdentifier): ProofResponse

proc eth_feeHistory(
blockCount: Quantity,
newestBlock: BlockIdentifier,
rewardPercentiles: Option[seq[float64]]): FeeHistoryResult

proc debug_getRawBlock(blockId: BlockIdentifier): RlpEncodedBytes
proc debug_getRawHeader(blockId: BlockIdentifier): RlpEncodedBytes
proc debug_getRawReceipts(blockId: BlockIdentifier): seq[RlpEncodedBytes]
proc debug_getRawTransaction(data: TxHash): RlpEncodedBytes

createSingleRpcSig(RpcClient, "eth_getJsonLogs"):
proc eth_getLogs(filterOptions: FilterOptions): seq[JsonString]
8 changes: 8 additions & 0 deletions web3/eth_api_types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,14 @@ type
of bidAlias:
alias*: string

FeeHistoryReward* = array[2, Quantity]

FeeHistoryResult* = object
oldestBlock*: Quantity
baseFeePerGas*: seq[Quantity]
gasUsedRatio*: seq[float64]
reward*: seq[FeeHistoryReward]

{.push raises: [].}

func blockId*(n: BlockNumber): RtBlockIdentifier =
Expand Down

0 comments on commit 85b3567

Please sign in to comment.