mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-03-01 12:20:49 +00:00
* Refactor TxPool: leaner and simpler * Rewrite test_txpool Reduce number of tables used, from 5 to 2. Reduce number of files. If need to modify the price rule or other filters, now is far more easier because only one table to work with(sender/nonce). And the other table is just a map from txHash to TxItemRef. Removing transactions from txPool either because of producing new block or syncing became much easier. Removing expired transactions also simple. Explicit Tx Pending, Staged, or Packed status is removed. The status of the transactions can be inferred implicitly. Developer new to TxPool can easily follow the logic. But the most important is we can revive the test_txpool without dirty trick and remove usage of getCanonicalHead furthermore to prepare for better integration with ForkedChain.
1441 lines
45 KiB
Nim
1441 lines
45 KiB
Nim
# nim-graphql
|
|
# Copyright (c) 2021-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
|
|
std/[strutils],
|
|
stew/byteutils, stint,
|
|
results,
|
|
eth/common/transaction_utils,
|
|
chronos,
|
|
graphql, graphql/graphql as context,
|
|
graphql/common/types, graphql/httpserver,
|
|
graphql/instruments/query_complexity,
|
|
../db/[ledger],
|
|
../rpc/rpc_utils,
|
|
".."/[transaction, evm/state, config, constants],
|
|
../transaction/call_evm,
|
|
../core/[tx_pool, tx_pool/tx_item],
|
|
../core/chain/forked_chain,
|
|
../common/common,
|
|
web3/eth_api_types
|
|
|
|
from eth/p2p import EthereumNode
|
|
export httpserver
|
|
|
|
type
|
|
EthTypes = enum
|
|
ethAccount = "Account"
|
|
ethLog = "Log"
|
|
ethTransaction = "Transaction"
|
|
ethBlock = "Block"
|
|
ethCallResult = "CallResult"
|
|
ethSyncState = "SyncState"
|
|
ethPending = "Pending"
|
|
ethQuery = "Query"
|
|
ethMutation = "Mutation"
|
|
ethAccessTuple = "AccessTuple"
|
|
ethWithdrawal = "Withdrawal"
|
|
|
|
HeaderNode = ref object of Node
|
|
header: Header
|
|
|
|
AccountNode = ref object of Node
|
|
address: Address
|
|
account: Account
|
|
db: LedgerRef
|
|
|
|
TxNode = ref object of Node
|
|
tx: Transaction
|
|
index: uint64
|
|
blockNumber: base.BlockNumber
|
|
receipt: Receipt
|
|
gasUsed: GasInt
|
|
baseFee: Opt[UInt256]
|
|
|
|
LogNode = ref object of Node
|
|
log: Log
|
|
index: int
|
|
tx: TxNode
|
|
|
|
AclNode = ref object of Node
|
|
acl: AccessPair
|
|
|
|
WdNode = ref object of Node
|
|
wd: Withdrawal
|
|
|
|
GraphqlContextRef = ref GraphqlContextObj
|
|
GraphqlContextObj = object of Graphql
|
|
ids: array[EthTypes, Name]
|
|
com: CommonRef
|
|
chainDB: CoreDbRef
|
|
ethNode: EthereumNode
|
|
txPool: TxPoolRef
|
|
chain: ForkedChainRef
|
|
|
|
{.push gcsafe, raises: [].}
|
|
{.pragma: apiRaises, raises: [].}
|
|
{.pragma: apiPragma, cdecl, gcsafe, apiRaises.}
|
|
|
|
proc toHash(n: Node): Hash32 {.gcsafe, raises: [ValueError].} =
|
|
Hash32.fromHex(n.stringVal)
|
|
|
|
proc toBlockNumber(n: Node): base.BlockNumber {.gcsafe, raises: [ValueError].} =
|
|
if n.kind == nkInt:
|
|
result = parse(n.intVal, UInt256, radix = 10).truncate(base.BlockNumber)
|
|
elif n.kind == nkString:
|
|
result = parse(n.stringVal, UInt256, radix = 16).truncate(base.BlockNumber)
|
|
else:
|
|
doAssert(false, "unknown node type: " & $n.kind)
|
|
|
|
proc headerNode(ctx: GraphqlContextRef, header: Header): Node =
|
|
HeaderNode(
|
|
kind: nkMap,
|
|
typeName: ctx.ids[ethBlock],
|
|
pos: Pos(),
|
|
header: header
|
|
)
|
|
|
|
proc accountNode(ctx: GraphqlContextRef, acc: Account, address: Address, db: LedgerRef): Node =
|
|
AccountNode(
|
|
kind: nkMap,
|
|
typeName: ctx.ids[ethAccount],
|
|
pos: Pos(),
|
|
account: acc,
|
|
address: address,
|
|
db: db
|
|
)
|
|
|
|
proc txNode(ctx: GraphqlContextRef, tx: Transaction, index: uint64, blockNumber: base.BlockNumber, baseFee: Opt[UInt256]): Node =
|
|
TxNode(
|
|
kind: nkMap,
|
|
typeName: ctx.ids[ethTransaction],
|
|
pos: Pos(),
|
|
tx: tx,
|
|
index: index,
|
|
blockNumber: blockNumber,
|
|
baseFee: baseFee
|
|
)
|
|
|
|
proc logNode(ctx: GraphqlContextRef, log: Log, index: int, tx: TxNode): Node =
|
|
LogNode(
|
|
kind: nkMap,
|
|
typeName: ctx.ids[ethLog],
|
|
pos: Pos(),
|
|
log: log,
|
|
index: index,
|
|
tx: tx
|
|
)
|
|
|
|
proc aclNode(ctx: GraphqlContextRef, accessPair: AccessPair): Node =
|
|
AclNode(
|
|
kind: nkMap,
|
|
typeName: ctx.ids[ethAccessTuple],
|
|
pos: Pos(),
|
|
acl: accessPair
|
|
)
|
|
|
|
proc wdNode(ctx: GraphqlContextRef, wd: Withdrawal): Node =
|
|
WdNode(
|
|
kind: nkMap,
|
|
typeName: ctx.ids[ethWithdrawal],
|
|
pos: Pos(),
|
|
wd: wd
|
|
)
|
|
|
|
proc getLedger(com: CommonRef, header: Header): LedgerRef {.deprecated: "LedgerRef does not support loading a particular state".} =
|
|
## Retrieves the account db from canonical head
|
|
## we don't use accounst_cache here because it's read only operations
|
|
# TODO the ledger initialized here refers to the base, not the given header!
|
|
LedgerRef.init(com.db)
|
|
|
|
proc getBlockByNumber(ctx: GraphqlContextRef, number: Node): RespResult =
|
|
try:
|
|
let header = ?ctx.chain.headerByNumber(toBlockNumber(number))
|
|
ok(headerNode(ctx, header))
|
|
except ValueError as exc:
|
|
err(exc.msg)
|
|
|
|
proc getBlockByNumber(ctx: GraphqlContextRef, number: base.BlockNumber): RespResult =
|
|
let header = ?ctx.chain.headerByNumber(number)
|
|
ok(headerNode(ctx, header))
|
|
|
|
proc getBlockByHash(ctx: GraphqlContextRef, hash: Node): RespResult =
|
|
try:
|
|
let header = ?ctx.chain.headerByHash(toHash(hash))
|
|
ok(headerNode(ctx, header))
|
|
except ValueError as exc:
|
|
err(exc.msg)
|
|
|
|
proc getBlockByHash(ctx: GraphqlContextRef, hash: Hash32): RespResult =
|
|
let header = ?ctx.chain.headerByHash(hash)
|
|
ok(headerNode(ctx, header))
|
|
|
|
proc getLatestBlock(ctx: GraphqlContextRef): RespResult =
|
|
let header = ctx.chain.latestHeader
|
|
ok(headerNode(ctx, header))
|
|
|
|
proc getTxCount(ctx: GraphqlContextRef, txRoot: Hash32): RespResult =
|
|
let txCount = ctx.chainDB.getTransactionCount(txRoot)
|
|
ok(resp(txCount))
|
|
|
|
proc longNode(val: uint64 | int64): RespResult =
|
|
ok(Node(kind: nkInt, intVal: $val, pos: Pos()))
|
|
|
|
proc longNode(val: UInt256): RespResult =
|
|
ok(Node(kind: nkInt, intVal: val.toString, pos: Pos()))
|
|
|
|
proc stripLeadingZeros(x: string): string =
|
|
strip(x, leading = true, trailing = false, chars = {'0'})
|
|
|
|
proc bigIntNode(val: UInt256): RespResult =
|
|
if val == 0.u256:
|
|
ok(Node(kind: nkString, stringVal: "0x0", pos: Pos()))
|
|
else:
|
|
let hex = stripLeadingZeros(val.toHex)
|
|
ok(Node(kind: nkString, stringVal: "0x" & hex, pos: Pos()))
|
|
|
|
proc bigIntNode(x: uint64 | int64): RespResult =
|
|
# stdlib toHex is not suitable for hive
|
|
const
|
|
HexChars = "0123456789abcdef"
|
|
|
|
if x == 0:
|
|
return ok(Node(kind: nkString, stringVal: "0x0", pos: Pos()))
|
|
|
|
var
|
|
n = cast[uint64](x)
|
|
r: array[2*sizeof(uint64), char]
|
|
i = 0
|
|
while n > 0:
|
|
r[i] = HexChars[int(n and 0xF)]
|
|
n = n shr 4
|
|
inc i
|
|
var hex = newString(i+2)
|
|
hex[0] = '0'
|
|
hex[1] = 'x'
|
|
while i > 0:
|
|
hex[hex.len-i] = r[i-1]
|
|
dec i
|
|
ok(Node(kind: nkString, stringVal: hex, pos: Pos()))
|
|
|
|
proc byte32Node(val: UInt256): RespResult =
|
|
ok(Node(kind: nkString, stringVal: "0x" & val.dumpHex, pos: Pos()))
|
|
|
|
proc resp(hash: Hash32 | Bytes32): RespResult =
|
|
ok(resp(hash.to0xHex))
|
|
|
|
proc resp(data: openArray[byte]): RespResult =
|
|
ok(resp("0x" & data.toHex))
|
|
|
|
proc getTotalDifficulty(ctx: GraphqlContextRef, blockHash: Hash32): RespResult =
|
|
let score = getScore(ctx.chainDB, blockHash).valueOr:
|
|
return err("can't get total difficulty")
|
|
|
|
bigIntNode(score)
|
|
|
|
proc getOmmerCount(ctx: GraphqlContextRef, ommersHash: Hash32): RespResult =
|
|
let ommers = ?ctx.chainDB.getUnclesCount(ommersHash)
|
|
ok(resp(ommers))
|
|
|
|
proc getOmmers(ctx: GraphqlContextRef, ommersHash: Hash32): RespResult =
|
|
let uncles = ?ctx.chainDB.getUncles(ommersHash)
|
|
when false:
|
|
# EIP 1767 says no ommers == null
|
|
# but hive test case want empty array []
|
|
if uncles.len == 0:
|
|
return ok(respNull())
|
|
var list = respList()
|
|
for n in uncles:
|
|
list.add headerNode(ctx, n)
|
|
ok(list)
|
|
|
|
proc getOmmerAt(ctx: GraphqlContextRef, ommersHash: Hash32, index: int): RespResult =
|
|
let uncles = ?ctx.chainDB.getUncles(ommersHash)
|
|
if uncles.len == 0:
|
|
return ok(respNull())
|
|
if index < 0 or index >= uncles.len:
|
|
return ok(respNull())
|
|
ok(headerNode(ctx, uncles[index]))
|
|
|
|
proc getTxs(ctx: GraphqlContextRef, header: Header): RespResult =
|
|
let txCount = getTransactionCount(ctx.chainDB, header.txRoot)
|
|
if txCount == 0:
|
|
return ok(respNull())
|
|
var list = respList()
|
|
var index = 0'u64
|
|
|
|
let txList = ?ctx.chainDB.getTransactions(header.txRoot)
|
|
for tx in txList:
|
|
list.add txNode(ctx, tx, index, header.number, header.baseFeePerGas)
|
|
inc index
|
|
|
|
index = 0'u64
|
|
var prevUsed = 0.GasInt
|
|
let receiptList = ?ctx.chainDB.getReceipts(header.receiptsRoot)
|
|
for r in receiptList:
|
|
let tx = TxNode(list.sons[index])
|
|
tx.receipt = r
|
|
tx.gasUsed = r.cumulativeGasUsed - prevUsed
|
|
prevUsed = r.cumulativeGasUsed
|
|
inc index
|
|
|
|
ok(list)
|
|
|
|
proc getWithdrawals(ctx: GraphqlContextRef, header: Header): RespResult =
|
|
if header.withdrawalsRoot.isNone:
|
|
return ok(respNull())
|
|
|
|
let wds = ?ctx.chainDB.getWithdrawals(header.withdrawalsRoot.get)
|
|
var list = respList()
|
|
for wd in wds:
|
|
list.add wdNode(ctx, wd)
|
|
ok(list)
|
|
|
|
proc getTxAt(ctx: GraphqlContextRef, header: Header, index: uint64): RespResult =
|
|
let tx = ctx.chainDB.getTransactionByIndex(header.txRoot, index.uint16).valueOr:
|
|
return ok(respNull())
|
|
|
|
let txn = txNode(ctx, tx, index, header.number, header.baseFeePerGas)
|
|
var i = 0'u64
|
|
var prevUsed = 0.GasInt
|
|
let receiptList = ?ctx.chainDB.getReceipts(header.receiptsRoot)
|
|
for r in receiptList:
|
|
if i == index:
|
|
let tx = TxNode(txn)
|
|
tx.receipt = r
|
|
tx.gasUsed = r.cumulativeGasUsed - prevUsed
|
|
prevUsed = r.cumulativeGasUsed
|
|
inc i
|
|
ok(txn)
|
|
|
|
proc getTxByHash(ctx: GraphqlContextRef, hash: Hash32): RespResult =
|
|
let
|
|
txKey = ?ctx.chainDB.getTransactionKey(hash)
|
|
header = ?ctx.chainDB.getBlockHeader(txKey.blockNumber)
|
|
getTxAt(ctx, header, txKey.index)
|
|
|
|
proc accountNode(ctx: GraphqlContextRef, header: Header, address: Address): RespResult =
|
|
try:
|
|
let db = getLedger(ctx.com, header)
|
|
when false:
|
|
# EIP 1767 unclear about non existent account
|
|
# but hive test case demand something
|
|
if not db.accountExists(address):
|
|
return ok(respNull())
|
|
let acc = db.getEthAccount(address)
|
|
ok(accountNode(ctx, acc, address, db))
|
|
except RlpError as ex:
|
|
err(ex.msg)
|
|
|
|
func hexCharToInt(c: char): uint64 =
|
|
case c
|
|
of 'a'..'f': return c.uint64 - 'a'.uint64 + 10'u64
|
|
of 'A'..'F': return c.uint64 - 'A'.uint64 + 10'u64
|
|
of '0'..'9': return c.uint64 - '0'.uint64
|
|
else: doAssert(false, "invalid hex digit: " & $c)
|
|
|
|
proc parseU64(node: Node): uint64 =
|
|
if node.kind == nkString:
|
|
if node.stringVal.len > 2 and node.stringVal[1] == 'x':
|
|
for i in 2..<node.stringVal.len:
|
|
let c = node.stringVal[i]
|
|
result = result * 16 + hexCharToInt(c)
|
|
else:
|
|
for c in node.stringVal:
|
|
result = result * 10 + (c.uint64 - '0'.uint64)
|
|
else:
|
|
for c in node.intVal:
|
|
result = result * 10 + (c.uint64 - '0'.uint64)
|
|
|
|
proc validateHex(x: Node, minLen = 0): NodeResult =
|
|
if x.stringVal.len < 2:
|
|
return err("hex is too short")
|
|
if x.stringVal.len != 2 + minLen * 2 and minLen != 0:
|
|
return err("expect hex with len '" &
|
|
$(2 * minLen + 2) & "', got '" & $x.stringVal.len & "'")
|
|
if x.stringVal.len mod 2 != 0:
|
|
return err("hex must have even number of nibbles")
|
|
if x.stringVal[0] != '0' or x.stringVal[1] != 'x':
|
|
return err("hex should be prefixed by '0x'")
|
|
for i in 2..<x.stringVal.len:
|
|
if x.stringVal[i] notin HexDigits:
|
|
return err("invalid chars in hex")
|
|
ok(x)
|
|
|
|
proc padBytes(n: Node, prefixLen, minLen: int) =
|
|
let tmp = n.stringVal
|
|
if prefixLen != 0:
|
|
n.stringVal = newString(minLen + prefixLen)
|
|
for i in 0..<prefixLen:
|
|
n.stringVal[i] = tmp[i]
|
|
let zeros = minLen - tmp.len + prefixLen
|
|
for i in 0..<zeros:
|
|
n.stringVal[i + prefixLen] = '0'
|
|
for i in 2..<tmp.len:
|
|
n.stringVal[i+zeros] = tmp[i]
|
|
else:
|
|
n.stringVal = newString(minLen)
|
|
let zeros = minLen - tmp.len
|
|
for i in 0..<zeros:
|
|
n.stringVal[i] = '0'
|
|
for i in 0..<tmp.len:
|
|
n.stringVal[i+zeros] = tmp[i]
|
|
|
|
proc validateFixedLenHex(x: Node, minLen: int, kind: string, padding = false): NodeResult =
|
|
if x.stringVal.len < 2:
|
|
return err(kind & " hex is too short")
|
|
|
|
try:
|
|
var prefixLen = 0
|
|
if x.stringVal[0] == '0' and x.stringVal[1] == 'x':
|
|
prefixLen = 2
|
|
|
|
let expectedLen = minLen * 2 + prefixLen
|
|
if x.stringVal.len < expectedLen:
|
|
if not padding:
|
|
return err("$1 len is too short: expect $2 got $3" %
|
|
[kind, $expectedLen, $x.stringVal.len])
|
|
else:
|
|
padBytes(x, prefixLen, minLen * 2)
|
|
elif x.stringVal.len > expectedLen:
|
|
return err("$1 len is too long: expect $2 got $3" %
|
|
[kind, $expectedLen, $x.stringVal.len])
|
|
|
|
for i in prefixLen..<x.stringVal.len:
|
|
if x.stringVal[i] notin HexDigits:
|
|
return err("invalid chars in $1 hex" % [kind])
|
|
|
|
ok(x)
|
|
except ValueError as exc:
|
|
err(exc.msg)
|
|
|
|
proc scalarBytes32(ctx: GraphqlRef, typeNode, node: Node): NodeResult {.cdecl, gcsafe, noSideEffect, raises:[].} =
|
|
## Bytes32 is a 32 byte binary string,
|
|
## represented as 0x-prefixed hexadecimal.
|
|
if node.kind != nkString:
|
|
return err("expect hex string, but got '" & $node.kind & "'")
|
|
validateFixedLenHex(node, 32, "Bytes32", padding = true)
|
|
|
|
proc scalarAddress(ctx: GraphqlRef, typeNode, node: Node): NodeResult {.cdecl, gcsafe, noSideEffect, raises:[].} =
|
|
## Address is a 20 byte Ethereum address,
|
|
## represented as 0x-prefixed hexadecimal.
|
|
if node.kind != nkString:
|
|
return err("expect hex string, but got '" & $node.kind & "'")
|
|
validateFixedLenHex(node, 20, "Address")
|
|
|
|
proc scalarBytes(ctx: GraphqlRef, typeNode, node: Node): NodeResult {.cdecl, gcsafe, noSideEffect, raises:[].} =
|
|
## Bytes is an arbitrary length binary string,
|
|
## represented as 0x-prefixed hexadecimal.
|
|
## An empty byte string is represented as '0x'.
|
|
## Byte strings must have an even number of hexadecimal nybbles.
|
|
if node.kind != nkString:
|
|
return err("expect hex string, but got '" & $node.kind & "'")
|
|
validateHex(node)
|
|
|
|
proc scalarBigInt(ctx: GraphqlRef, typeNode, node: Node): NodeResult {.cdecl, gcsafe, noSideEffect, raises:[].} =
|
|
## BigInt is a large integer. Input is accepted as
|
|
## either a JSON number or as a string.
|
|
## Strings may be either decimal or 0x-prefixed hexadecimal.
|
|
## Output values are all 0x-prefixed hexadecimal.
|
|
try:
|
|
if node.kind == nkInt:
|
|
# convert it into hex nkString node
|
|
let val = parse(node.intVal, UInt256, radix = 10)
|
|
ok(Node(kind: nkString, stringVal: "0x" & val.toHex, pos: node.pos))
|
|
elif node.kind == nkString:
|
|
if node.stringVal.len > 2 and node.stringVal[1] == 'x':
|
|
if node.stringVal[0] != '0':
|
|
return err("Big Int hex malformed")
|
|
if node.stringVal.len > 66:
|
|
# 256 bits = 32 bytes = 64 hex nibbles
|
|
# 64 hex nibbles + '0x' prefix = 66 bytes
|
|
return err("Big Int hex should not exceed 66 bytes")
|
|
for i in 2..<node.stringVal.len:
|
|
if node.stringVal[i] notin HexDigits:
|
|
return err("invalid chars in BigInt hex")
|
|
ok(node)
|
|
elif HexDigits in node.stringVal:
|
|
if node.stringVal.len > 64:
|
|
return err("Big Int hex should not exceed 64 bytes")
|
|
for i in 0..<node.stringVal.len:
|
|
if node.stringVal[i] notin HexDigits:
|
|
return err("invalid chars in BigInt hex")
|
|
ok(node)
|
|
else:
|
|
# convert it into hex nkString node
|
|
let val = parse(node.stringVal, UInt256, radix = 10)
|
|
node.stringVal = "0x" & val.toHex
|
|
ok(node)
|
|
else:
|
|
return err("expect hex/dec string or int, but got '" & $node.kind & "'")
|
|
except CatchableError as e:
|
|
err("scalar BigInt error: " & e.msg)
|
|
|
|
proc scalarLong(ctx: GraphqlRef, typeNode, node: Node): NodeResult {.cdecl, gcsafe, noSideEffect.} =
|
|
## Long is a 64 bit unsigned integer.
|
|
const maxU64 = uint64.high.u256
|
|
try:
|
|
case node.kind
|
|
of nkString:
|
|
if node.stringVal.len > 2 and node.stringVal[1] == 'x':
|
|
let val = parse(node.stringVal, UInt256, radix = 16)
|
|
if val > maxU64:
|
|
return err("long value overflow")
|
|
ok(node)
|
|
else:
|
|
let val = parse(node.stringVal, UInt256, radix = 10)
|
|
if val > maxU64:
|
|
return err("long value overflow")
|
|
ok(Node(kind: nkString, pos: node.pos, stringVal: "0x" & val.toHex))
|
|
of nkInt:
|
|
let val = parse(node.intVal, UInt256, radix = 10)
|
|
if val > maxU64:
|
|
return err("long value overflow")
|
|
ok(Node(kind: nkString, pos: node.pos, stringVal: "0x" & val.toHex))
|
|
else:
|
|
err("expect int, but got '" & $node.kind & "'")
|
|
except CatchableError as e:
|
|
err("scalar Long error: " & e.msg)
|
|
|
|
proc accountAddress(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let acc = AccountNode(parent)
|
|
resp(acc.address.data)
|
|
|
|
proc accountBalance(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let acc = AccountNode(parent)
|
|
bigIntNode(acc.account.balance)
|
|
|
|
proc accountTxCount(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let acc = AccountNode(parent)
|
|
longNode(acc.account.nonce)
|
|
|
|
proc accountCode(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let acc = AccountNode(parent)
|
|
try:
|
|
let code = acc.db.getCode(acc.address)
|
|
resp(code.bytes())
|
|
except RlpError as ex:
|
|
err(ex.msg)
|
|
|
|
proc accountStorage(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let acc = AccountNode(parent)
|
|
try:
|
|
let slot = parse(params[0].val.stringVal, UInt256, radix = 16)
|
|
let val = acc.db.getStorage(acc.address, slot)
|
|
byte32Node(val)
|
|
except RlpError as ex:
|
|
err(ex.msg)
|
|
except ValueError as ex:
|
|
err(ex.msg)
|
|
|
|
const accountProcs = {
|
|
# Note: Need to define it as ResolverProc else a proc with noSideEffect is
|
|
# assumed and this fails for accountCode and accountStorage.
|
|
"address": ResolverProc accountAddress,
|
|
"balance": accountBalance,
|
|
"transactionCount": accountTxCount,
|
|
"code": accountCode,
|
|
"storage": accountStorage
|
|
}
|
|
|
|
proc logIndex(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let log = LogNode(parent)
|
|
ok(resp(log.index))
|
|
|
|
proc logAccount(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# TODO: with block param
|
|
let ctx = GraphqlContextRef(ud)
|
|
let log = LogNode(parent)
|
|
|
|
let hres = ctx.getBlockByNumber(log.tx.blockNumber)
|
|
if hres.isErr:
|
|
return hres
|
|
let h = HeaderNode(hres.get())
|
|
ctx.accountNode(h.header, log.log.address)
|
|
|
|
proc logTopics(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let log = LogNode(parent)
|
|
var list = respList()
|
|
for n in log.log.topics:
|
|
list.add resp("0x" & n.toHex)
|
|
ok(list)
|
|
|
|
proc logData(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let log = LogNode(parent)
|
|
resp(log.log.data)
|
|
|
|
proc logTransaction(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let log = LogNode(parent)
|
|
ok(cast[Node](log.tx))
|
|
|
|
const logProcs = {
|
|
"account": logAccount,
|
|
"index": logIndex,
|
|
"topics": logTopics,
|
|
"data": logData,
|
|
"transaction": logTransaction
|
|
}
|
|
|
|
proc txHash(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let
|
|
tx = TxNode(parent)
|
|
txHash = rlpHash(tx.tx) # beware EIP-4844
|
|
resp(txHash)
|
|
|
|
proc txNonce(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
longNode(tx.tx.nonce)
|
|
|
|
proc txIndex(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
ok(resp(tx.index.int))
|
|
|
|
proc txFrom(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let tx = TxNode(parent)
|
|
|
|
let blockNumber = if params[0].val.kind != nkEmpty:
|
|
parseU64(params[0].val)
|
|
else:
|
|
tx.blockNumber
|
|
|
|
let sender = tx.tx.recoverSender.valueOr:
|
|
return ok(respNull())
|
|
let hres = ctx.getBlockByNumber(blockNumber)
|
|
if hres.isErr:
|
|
return hres
|
|
let h = HeaderNode(hres.get())
|
|
ctx.accountNode(h.header, sender)
|
|
|
|
proc txTo(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let tx = TxNode(parent)
|
|
|
|
let blockNumber = if params[0].val.kind != nkEmpty:
|
|
parseU64(params[0].val)
|
|
else:
|
|
tx.blockNumber
|
|
|
|
if tx.tx.contractCreation:
|
|
return ok(respNull())
|
|
let hres = ctx.getBlockByNumber(blockNumber)
|
|
if hres.isErr:
|
|
return hres
|
|
let h = HeaderNode(hres.get())
|
|
ctx.accountNode(h.header, tx.tx.to.get())
|
|
|
|
proc txValue(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
bigIntNode(tx.tx.value)
|
|
|
|
proc txGasPrice(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
if tx.tx.txType >= TxEip1559:
|
|
if tx.baseFee.isNone:
|
|
return bigIntNode(tx.tx.gasPrice)
|
|
|
|
let baseFee = tx.baseFee.get().truncate(GasInt)
|
|
let priorityFee = min(tx.tx.maxPriorityFeePerGas, tx.tx.maxFeePerGas - baseFee)
|
|
bigIntNode(priorityFee + baseFee)
|
|
else:
|
|
bigIntNode(tx.tx.gasPrice)
|
|
|
|
proc txMaxFeePerGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
if tx.tx.txType >= TxEip1559:
|
|
bigIntNode(tx.tx.maxFeePerGas)
|
|
else:
|
|
ok(respNull())
|
|
|
|
proc txMaxPriorityFeePerGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
if tx.tx.txType >= TxEip1559:
|
|
bigIntNode(tx.tx.maxPriorityFeePerGas)
|
|
else:
|
|
ok(respNull())
|
|
|
|
proc txEffectiveGasPrice(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
if tx.baseFee.isNone:
|
|
return bigIntNode(tx.tx.gasPrice)
|
|
|
|
let baseFee = tx.baseFee.get().truncate(GasInt)
|
|
let priorityFee = min(tx.tx.maxPriorityFeePerGas, tx.tx.maxFeePerGas - baseFee)
|
|
bigIntNode(priorityFee + baseFee)
|
|
|
|
proc txChainId(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
if tx.tx.txType == TxLegacy:
|
|
ok(respNull())
|
|
else:
|
|
longNode(tx.tx.chainId.uint64)
|
|
|
|
proc txGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
longNode(tx.tx.gasLimit)
|
|
|
|
proc txInputData(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
resp(tx.tx.payload)
|
|
|
|
proc txBlock(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let tx = TxNode(parent)
|
|
ctx.getBlockByNumber(tx.blockNumber)
|
|
|
|
proc txStatus(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
if tx.receipt.hasStatus:
|
|
longNode(tx.receipt.status.uint64)
|
|
else:
|
|
ok(respNull())
|
|
|
|
proc txGasUsed(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
longNode(tx.gasUsed)
|
|
|
|
proc txCumulativeGasUsed(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
longNode(tx.receipt.cumulativeGasUsed)
|
|
|
|
proc txCreatedContract(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let tx = TxNode(parent)
|
|
let sender = tx.tx.recoverSender.valueOr:
|
|
return err("can't calculate sender")
|
|
|
|
if not tx.tx.contractCreation:
|
|
return ok(respNull())
|
|
|
|
let hres = getBlockByNumber(ctx, tx.blockNumber)
|
|
if hres.isErr:
|
|
return hres
|
|
let h = HeaderNode(hres.get())
|
|
let contractAddress = generateAddress(sender, tx.tx.nonce)
|
|
ctx.accountNode(h.header, contractAddress)
|
|
|
|
proc txLogs(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let tx = TxNode(parent)
|
|
var list = respList()
|
|
for i, n in tx.receipt.logs:
|
|
list.add logNode(ctx, n, i, tx)
|
|
ok(list)
|
|
|
|
proc txR(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
bigIntNode(tx.tx.R)
|
|
|
|
proc txS(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
bigIntNode(tx.tx.S)
|
|
|
|
proc txV(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
bigIntNode(tx.tx.V)
|
|
|
|
proc txType(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
let typ = resp(ord(tx.tx.txType))
|
|
ok(typ)
|
|
|
|
proc txAccessList(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let tx = TxNode(parent)
|
|
if tx.tx.txType == TxLegacy:
|
|
ok(respNull())
|
|
else:
|
|
var list = respList()
|
|
for x in tx.tx.accessList:
|
|
list.add aclNode(ctx, x)
|
|
ok(list)
|
|
|
|
proc txMaxFeePerBlobGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
if tx.tx.txType < TxEip4844:
|
|
ok(respNull())
|
|
else:
|
|
longNode(tx.tx.maxFeePerBlobGas)
|
|
|
|
proc txVersionedHashes(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
if tx.tx.txType < TxEip4844:
|
|
ok(respNull())
|
|
else:
|
|
var list = respList()
|
|
for hs in tx.tx.versionedHashes:
|
|
list.add resp("0x" & hs.data.toHex)
|
|
ok(list)
|
|
|
|
proc txRaw(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
let txBytes = rlp.encode(tx.tx)
|
|
resp(txBytes)
|
|
|
|
proc txRawReceipt(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let tx = TxNode(parent)
|
|
let recBytes = rlp.encode(tx.receipt)
|
|
resp(recBytes)
|
|
|
|
const txProcs = {
|
|
"from": txFrom,
|
|
"hash": txHash,
|
|
"nonce": txNonce,
|
|
"index": txIndex,
|
|
"to": txTo,
|
|
"value": txValue,
|
|
"gasPrice": txGasPrice,
|
|
"gas": txGas,
|
|
"inputData": txInputData,
|
|
"block": txBlock,
|
|
"status": txStatus,
|
|
"gasUsed": txGasUsed,
|
|
"cumulativeGasUsed": txCumulativeGasUsed,
|
|
"createdContract": txCreatedContract,
|
|
"logs": txLogs,
|
|
"r": txR,
|
|
"s": txS,
|
|
"v": txV,
|
|
"type": txType,
|
|
"accessList": txAccessList,
|
|
"maxFeePerGas": txMaxFeePerGas,
|
|
"maxPriorityFeePerGas": txMaxPriorityFeePerGas,
|
|
"effectiveGasPrice": txEffectiveGasPrice,
|
|
"chainID": txChainId,
|
|
"maxFeePerBlobGas": txMaxFeePerBlobGas,
|
|
"versionedHashes": txVersionedHashes,
|
|
"raw": txRaw,
|
|
"rawReceipt": txRawReceipt
|
|
}
|
|
|
|
proc aclAddress(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let acl = AclNode(parent)
|
|
resp(acl.acl.address.data)
|
|
|
|
proc aclStorageKeys(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let acl = AclNode(parent)
|
|
if acl.acl.storageKeys.len == 0:
|
|
ok(respNull())
|
|
else:
|
|
var list = respList()
|
|
for n in acl.acl.storageKeys:
|
|
list.add resp(n.data).get()
|
|
ok(list)
|
|
|
|
const aclProcs = {
|
|
"address": aclAddress,
|
|
"storageKeys": aclStorageKeys
|
|
}
|
|
|
|
proc wdIndex(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let w = WdNode(parent)
|
|
longNode(w.wd.index)
|
|
|
|
proc wdValidator(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let w = WdNode(parent)
|
|
longNode(w.wd.validatorIndex)
|
|
|
|
proc wdAddress(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let w = WdNode(parent)
|
|
resp(w.wd.address.data)
|
|
|
|
proc wdAmount(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let w = WdNode(parent)
|
|
longNode(w.wd.amount)
|
|
|
|
const wdProcs = {
|
|
"index": wdIndex,
|
|
"validator": wdValidator,
|
|
"address": wdAddress,
|
|
"amount": wdAmount
|
|
}
|
|
|
|
proc blockNumberImpl(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
longNode(h.header.number)
|
|
|
|
proc blockHashImpl(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
let hash = blockHash(h.header)
|
|
resp(hash)
|
|
|
|
proc blockParent(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
getBlockByHash(ctx, h.header.parentHash)
|
|
|
|
proc blockNonce(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
ok(resp("0x" & h.header.nonce.toHex))
|
|
|
|
proc blockTransactionsRoot(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
resp(h.header.txRoot)
|
|
|
|
proc blockTransactionCount(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
ctx.getTxCount(h.header.txRoot)
|
|
|
|
proc blockStateRoot(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
resp(h.header.stateRoot)
|
|
|
|
proc blockReceiptsRoot(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
resp(h.header.receiptsRoot)
|
|
|
|
proc blockMiner(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
ctx.accountNode(h.header, h.header.coinbase)
|
|
|
|
proc blockExtraData(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
resp(h.header.extraData)
|
|
|
|
proc blockGasLimit(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
longNode(h.header.gasLimit)
|
|
|
|
proc blockGasUsed(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
longNode(h.header.gasUsed)
|
|
|
|
proc blockTimestamp(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
bigIntNode(h.header.timestamp.uint64)
|
|
|
|
proc blockLogsBloom(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
resp(h.header.logsBloom.data)
|
|
|
|
proc blockMixHash(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
resp(h.header.mixHash)
|
|
|
|
proc blockDifficulty(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
bigIntNode(h.header.difficulty)
|
|
|
|
proc blockTotalDifficulty(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
let hash = blockHash(h.header)
|
|
getTotalDifficulty(ctx, hash)
|
|
|
|
proc blockOmmerCount(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
getOmmerCount(ctx, h.header.ommersHash)
|
|
|
|
proc blockOmmers(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
getOmmers(ctx, h.header.ommersHash)
|
|
|
|
proc blockOmmerAt(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
let index = parseU64(params[0].val)
|
|
getOmmerAt(ctx, h.header.ommersHash, index.int)
|
|
|
|
proc blockOmmerHash(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
resp(h.header.ommersHash)
|
|
|
|
proc blockTransactions(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
{.cast(noSideEffect).}:
|
|
getTxs(ctx, h.header)
|
|
|
|
proc blockTransactionAt(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
try:
|
|
let index = parseU64(params[0].val)
|
|
{.cast(noSideEffect).}:
|
|
getTxAt(ctx, h.header, index)
|
|
except ValueError as ex:
|
|
err(ex.msg)
|
|
|
|
proc blockLogs(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# TODO: stub, missing impl
|
|
err("not implemented")
|
|
|
|
proc blockAccount(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
try:
|
|
let address = Address.fromHex(params[0].val.stringVal)
|
|
ctx.accountNode(h.header, address)
|
|
except ValueError as ex:
|
|
err(ex.msg)
|
|
|
|
const
|
|
fFrom = 0
|
|
fTo = 1
|
|
fGasLimit = 2
|
|
fGasPrice = 3
|
|
fMaxFee = 4
|
|
fMaxPriorityFee = 5
|
|
fValue = 6
|
|
fData = 7
|
|
|
|
template isSome(n: Node, field: int): bool =
|
|
# [0] is the field's name node
|
|
# [1] is the field's value node
|
|
n[field][1].kind != nkEmpty
|
|
|
|
template fieldString(n: Node, field: int): string =
|
|
n[field][1].stringVal
|
|
|
|
template optionalAddress(dstField: untyped, n: Node, field: int) =
|
|
if isSome(n, field):
|
|
let address = addresses.Address.fromHex(fieldString(n, field))
|
|
dstField = Opt.some(primitives.Address address.data)
|
|
|
|
template optionalGasInt(dstField: untyped, n: Node, field: int) =
|
|
if isSome(n, field):
|
|
dstField = Opt.some(parseU64(n[field][1]).Quantity)
|
|
|
|
template optionalGasHex(dstField: untyped, n: Node, field: int) =
|
|
if isSome(n, field):
|
|
let gas = parse(fieldString(n, field), UInt256, radix = 16)
|
|
dstField = Opt.some(gas.truncate(uint64).Quantity)
|
|
|
|
template optionalHexU256(dstField: untyped, n: Node, field: int) =
|
|
if isSome(n, field):
|
|
dstField = Opt.some(parse(fieldString(n, field), UInt256, radix = 16))
|
|
|
|
template optionalBytes(dstField: untyped, n: Node, field: int) =
|
|
if isSome(n, field):
|
|
dstField = Opt.some(hexToSeqByte(fieldString(n, field)))
|
|
|
|
proc toTxArgs(n: Node): TransactionArgs {.gcsafe, raises: [ValueError].} =
|
|
optionalAddress(result.`from`, n, fFrom)
|
|
optionalAddress(result.to, n, fTo)
|
|
optionalGasInt(result.gas, n, fGasLimit)
|
|
optionalGasHex(result.gasPrice, n, fGasPrice)
|
|
optionalGasHex(result.maxFeePerGas, n, fMaxFee)
|
|
optionalGasHex(result.maxPriorityFeePerGas, n, fMaxPriorityFee)
|
|
optionalHexU256(result.value, n, fValue)
|
|
optionalBytes(result.data, n, fData)
|
|
|
|
proc makeCall(ctx: GraphqlContextRef, args: TransactionArgs,
|
|
header: Header): RespResult =
|
|
let res = rpcCallEvm(args, header, ctx.com).valueOr:
|
|
return err("Failed to call rpcCallEvm")
|
|
var map = respMap(ctx.ids[ethCallResult])
|
|
map["data"] = resp("0x" & res.output.toHex)
|
|
map["gasUsed"] = longNode(res.gasUsed).get()
|
|
map["status"] = longNode(if res.isError: 0 else: 1).get()
|
|
ok(map)
|
|
|
|
proc blockCall(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
let param = params[0].val
|
|
try:
|
|
let args = toTxArgs(param)
|
|
{.cast(noSideEffect).}:
|
|
ctx.makeCall(args, h.header)
|
|
except CatchableError as em:
|
|
err("call error: " & em.msg)
|
|
|
|
proc blockEstimateGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
let param = params[0].val
|
|
try:
|
|
let args = toTxArgs(param)
|
|
# TODO: DEFAULT_RPC_GAS_CAP should configurable
|
|
{.cast(noSideEffect).}:
|
|
let gasUsed = rpcEstimateGas(args, h.header, ctx.com, DEFAULT_RPC_GAS_CAP).valueOr:
|
|
return err("Failed to call rpcEstimateGas")
|
|
longNode(gasUsed)
|
|
except CatchableError as em:
|
|
err("estimateGas error: " & em.msg)
|
|
|
|
proc blockBaseFeePerGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
if h.header.baseFeePerGas.isSome:
|
|
bigIntNode(h.header.baseFeePerGas.get)
|
|
else:
|
|
ok(respNull())
|
|
|
|
proc blockWithdrawalsRoot(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
if h.header.withdrawalsRoot.isSome:
|
|
resp(h.header.withdrawalsRoot.get)
|
|
else:
|
|
ok(respNull())
|
|
|
|
proc blockWithdrawals(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let h = HeaderNode(parent)
|
|
getWithdrawals(ctx, h.header)
|
|
|
|
proc blockBlobGasUsed(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
if h.header.blobGasUsed.isSome:
|
|
longNode(h.header.blobGasUsed.get)
|
|
else:
|
|
ok(respNull())
|
|
|
|
proc blockExcessBlobGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
if h.header.excessBlobGas.isSome:
|
|
longNode(h.header.excessBlobGas.get)
|
|
else:
|
|
ok(respNull())
|
|
|
|
proc blockParentBeaconBlockRoot(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let h = HeaderNode(parent)
|
|
if h.header.parentBeaconBlockRoot.isSome:
|
|
resp(h.header.parentBeaconBlockRoot.get)
|
|
else:
|
|
ok(respNull())
|
|
|
|
|
|
const blockProcs = {
|
|
"parent": blockParent,
|
|
"number": blockNumberImpl,
|
|
"hash": blockHashImpl,
|
|
"nonce": blockNonce,
|
|
"transactionsRoot": blockTransactionsRoot,
|
|
"transactionCount": blockTransactionCount,
|
|
"stateRoot": blockStateRoot,
|
|
"receiptsRoot": blockReceiptsRoot,
|
|
"miner": blockMiner,
|
|
"extraData": blockExtraData,
|
|
"gasLimit": blockGasLimit,
|
|
"gasUsed": blockGasUsed,
|
|
"timestamp": blockTimestamp,
|
|
"logsBloom": blockLogsBloom,
|
|
"mixHash": blockMixHash,
|
|
"difficulty": blockDifficulty,
|
|
"totalDifficulty": blockTotalDifficulty,
|
|
"ommerCount": blockOmmerCount,
|
|
"ommers": blockOmmers,
|
|
"ommerAt": blockOmmerAt,
|
|
"ommerHash": blockOmmerHash,
|
|
"transactions": blockTransactions,
|
|
"transactionAt": blockTransactionAt,
|
|
"logs": blockLogs,
|
|
"account": blockAccount,
|
|
"call": blockCall,
|
|
"estimateGas": blockEstimateGas,
|
|
"baseFeePerGas": blockBaseFeePerGas,
|
|
"withdrawalsRoot": blockWithdrawalsRoot,
|
|
"withdrawals": blockWithdrawals,
|
|
"blobGasUsed": blockBlobGasUsed,
|
|
"excessBlobGas": blockExcessBlobGas,
|
|
"parentBeaconBlockRoot": blockParentBeaconBlockRoot,
|
|
}
|
|
|
|
proc callResultData(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
ok(parent.map[0].val)
|
|
|
|
proc callResultGasUsed(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
ok(parent.map[1].val)
|
|
|
|
proc callResultStatus(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
ok(parent.map[2].val)
|
|
|
|
const callResultProcs = {
|
|
"data": callResultData,
|
|
"gasUsed": callResultGasUsed,
|
|
"status": callResultStatus
|
|
}
|
|
|
|
proc syncStateStartingBlock(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
longNode(ctx.com.syncStart)
|
|
|
|
proc syncStateCurrentBlock(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
longNode(ctx.com.syncCurrent)
|
|
|
|
proc syncStateHighestBlock(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
longNode(ctx.com.syncHighest)
|
|
|
|
proc syncStatePulledStates(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# TODO: what is this ?
|
|
ok(respNull())
|
|
|
|
proc syncStateKnownStates(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# TODO: what is this ?
|
|
ok(respNull())
|
|
|
|
const syncStateProcs = {
|
|
"startingBlock": syncStateStartingBlock,
|
|
"currentBlock": syncStateCurrentBlock,
|
|
"highestBlock": syncStateHighestBlock,
|
|
"pulledStates": syncStatePulledStates,
|
|
"knownStates": syncStateKnownStates
|
|
}
|
|
|
|
proc pendingTransactionCount(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# TODO: stub, missing impl
|
|
err("not implemented")
|
|
|
|
proc pendingTransactions(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# TODO: stub, missing impl
|
|
err("not implemented")
|
|
|
|
proc pendingAccount(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# TODO: stub, missing impl
|
|
err("not implemented")
|
|
|
|
proc pendingCall(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# TODO: stub, missing impl
|
|
err("not implemented")
|
|
|
|
proc pendingEstimateGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# TODO: stub, missing impl
|
|
err("not implemented")
|
|
|
|
const pendingProcs = {
|
|
"transactionCount": pendingTransactionCount,
|
|
"transactions": pendingTransactions,
|
|
"account": pendingAccount,
|
|
"call": pendingCall,
|
|
"estimateGas": pendingEstimateGas
|
|
}
|
|
|
|
proc pickBlockNumber(ctx: GraphqlContextRef, number: Node): base.BlockNumber =
|
|
if number.kind == nkEmpty:
|
|
ctx.com.syncCurrent
|
|
else:
|
|
parseU64(number)
|
|
|
|
proc queryAccount(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
try:
|
|
let address = Address.fromHex(params[0].val.stringVal)
|
|
let blockNumber = pickBlockNumber(ctx, params[1].val)
|
|
let hres = getBlockByNumber(ctx, blockNumber)
|
|
if hres.isErr:
|
|
return hres
|
|
let h = HeaderNode(hres.get())
|
|
accountNode(ctx, h.header, address)
|
|
except ValueError as ex:
|
|
err(ex.msg)
|
|
|
|
proc queryBlock(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let number = params[0].val
|
|
let hash = params[1].val
|
|
if number.kind != nkEmpty and hash.kind != nkEmpty:
|
|
err("only one param allowed, number or hash, not both")
|
|
elif number.kind == nkInt:
|
|
getBlockByNumber(ctx, number)
|
|
elif number.kind == nkString:
|
|
try:
|
|
let blockNumber = toBlockNumber(number)
|
|
getBlockByNumber(ctx, blockNumber)
|
|
except ValueError as ex:
|
|
err(ex.msg)
|
|
elif hash.kind == nkString:
|
|
getBlockByHash(ctx, hash)
|
|
else:
|
|
getLatestBlock(ctx)
|
|
|
|
proc queryBlocks(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
let fromNumber = parseU64(params[0].val)
|
|
|
|
let to = params[1].val
|
|
let toNumber = pickBlockNumber(ctx, to)
|
|
|
|
if fromNumber > toNumber:
|
|
return err("from(" & $fromNumber &
|
|
") is bigger than to(" & $toNumber & ")")
|
|
|
|
# TODO: what is the maximum number here?
|
|
if toNumber - fromNumber > 32'u64:
|
|
return err("can't get more than 32 blocks at once")
|
|
|
|
var list = respList()
|
|
var number = fromNumber
|
|
while number <= toNumber:
|
|
let n = getBlockByNumber(ctx, number)
|
|
if n.isErr:
|
|
list.add respNull()
|
|
else:
|
|
list.add n.get()
|
|
number += 1'u64
|
|
|
|
ok(list)
|
|
|
|
proc queryPending(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# TODO: stub, missing impl
|
|
err("not implemented")
|
|
|
|
proc queryTransaction(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
try:
|
|
let hash = toHash(params[0].val)
|
|
{.cast(noSideEffect).}:
|
|
getTxByHash(ctx, hash)
|
|
except ValueError as ex:
|
|
err(ex.msg)
|
|
|
|
proc queryLogs(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# TODO: stub, missing impl
|
|
err("not implemented")
|
|
|
|
proc queryGasPrice(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
try:
|
|
bigIntNode(calculateMedianGasPrice(ctx.chain))
|
|
except CatchableError as em:
|
|
err("can't get gasPrice: " & em.msg)
|
|
|
|
proc queryProtocolVersion(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
for n in ctx.ethNode.capabilities:
|
|
if n.name == "eth":
|
|
return bigIntNode(n.version)
|
|
err("can't get protocol version")
|
|
|
|
proc querySyncing(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
ok(respMap(ctx.ids[ethSyncState]))
|
|
|
|
proc queryMaxPriorityFeePerGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# TODO: stub, missing impl
|
|
err("not implemented")
|
|
|
|
proc queryChainId(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
let ctx = GraphqlContextRef(ud)
|
|
longNode(ctx.com.chainId.uint64)
|
|
|
|
const queryProcs = {
|
|
"account": queryAccount,
|
|
"block": queryBlock,
|
|
"blocks": queryBlocks,
|
|
"pending": queryPending,
|
|
"transaction": queryTransaction,
|
|
"logs": queryLogs,
|
|
"gasPrice": queryGasPrice,
|
|
"protocolVersion": queryProtocolVersion,
|
|
"syncing": querySyncing,
|
|
"maxPriorityFeePerGas": queryMaxPriorityFeePerGas,
|
|
"chainID": queryChainId
|
|
}
|
|
|
|
proc sendRawTransaction(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
|
# if tx validation failed, the result will be null
|
|
let ctx = GraphqlContextRef(ud)
|
|
try:
|
|
let data = hexToSeqByte(params[0].val.stringVal)
|
|
let tx = decodePooledTx(data) # we want to know if it is a valid tx blob
|
|
let txHash = rlpHash(tx)
|
|
|
|
ctx.txPool.addTx(tx).isOkOr:
|
|
return err($error)
|
|
return resp(txHash)
|
|
except CatchableError as em:
|
|
return err("failed to process raw transaction: " & em.msg)
|
|
|
|
const mutationProcs = {
|
|
"sendRawTransaction": sendRawTransaction
|
|
}
|
|
|
|
const
|
|
ethSchema = staticRead("ethapi.ql")
|
|
|
|
type
|
|
QcNames = enum
|
|
qcType = "__Type"
|
|
qcFields = "fields"
|
|
qcBlock = "block"
|
|
qcTransaction = "Transaction"
|
|
|
|
EthQueryComplexity = ref object of QueryComplexity
|
|
names: array[QcNames, Name]
|
|
|
|
proc calcQC(qc: QueryComplexity, field: FieldRef): int {.cdecl,
|
|
gcsafe, apiRaises.} =
|
|
let qc = EthQueryComplexity(qc)
|
|
if field.parentType.sym.name == qc.names[qcType] and
|
|
field.field.name.name == qc.names[qcFields]:
|
|
return 100
|
|
elif field.parentType.sym.name == qc.names[qcTransaction] and
|
|
field.field.name.name == qc.names[qcBlock]:
|
|
return 100
|
|
else:
|
|
return 1
|
|
|
|
proc newQC(ctx: GraphqlContextRef): EthQueryComplexity =
|
|
const MaxQueryComplexity = 200
|
|
var qc = EthQueryComplexity()
|
|
qc.init(calcQC, MaxQueryComplexity)
|
|
for n in QcNames:
|
|
let name = ctx.createName($n)
|
|
qc.names[n] = name
|
|
qc
|
|
|
|
proc initEthApi(ctx: GraphqlContextRef) =
|
|
ctx.customScalar("Bytes32", scalarBytes32)
|
|
ctx.customScalar("Address", scalarAddress)
|
|
ctx.customScalar("Bytes", scalarBytes)
|
|
ctx.customScalar("BigInt", scalarBigInt)
|
|
ctx.customScalar("Long", scalarLong)
|
|
|
|
for n in EthTypes:
|
|
let name = ctx.createName($n)
|
|
ctx.ids[n] = name
|
|
|
|
ctx.addResolvers(ctx, ctx.ids[ethAccount ], accountProcs)
|
|
ctx.addResolvers(ctx, ctx.ids[ethLog ], logProcs)
|
|
ctx.addResolvers(ctx, ctx.ids[ethTransaction], txProcs)
|
|
ctx.addResolvers(ctx, ctx.ids[ethBlock ], blockProcs)
|
|
ctx.addResolvers(ctx, ctx.ids[ethCallResult ], callResultProcs)
|
|
ctx.addResolvers(ctx, ctx.ids[ethSyncState ], syncStateProcs)
|
|
ctx.addResolvers(ctx, ctx.ids[ethPending ], pendingProcs)
|
|
ctx.addResolvers(ctx, ctx.ids[ethQuery ], queryProcs)
|
|
ctx.addResolvers(ctx, ctx.ids[ethMutation ], mutationProcs)
|
|
ctx.addResolvers(ctx, ctx.ids[ethAccessTuple], aclProcs)
|
|
ctx.addResolvers(ctx, ctx.ids[ethWithdrawal ], wdProcs)
|
|
|
|
var qc = newQC(ctx)
|
|
ctx.addInstrument(qc)
|
|
|
|
let res = ctx.parseSchema(ethSchema)
|
|
if res.isErr:
|
|
echo res.error
|
|
quit(QuitFailure)
|
|
|
|
proc setupGraphqlContext*(chain: ForkedChainRef,
|
|
ethNode: EthereumNode,
|
|
txPool: TxPoolRef): GraphqlContextRef =
|
|
let ctx = GraphqlContextRef(
|
|
chainDB: chain.com.db,
|
|
com : chain.com,
|
|
ethNode: ethNode,
|
|
txPool : txPool,
|
|
chain : chain,
|
|
)
|
|
graphql.init(ctx)
|
|
ctx.initEthApi()
|
|
ctx
|
|
|
|
proc setupGraphqlHttpHandler*(chain: ForkedChainRef,
|
|
ethNode: EthereumNode,
|
|
txPool: TxPoolRef): GraphqlHttpHandlerRef =
|
|
let ctx = setupGraphqlContext(chain, ethNode, txPool)
|
|
GraphqlHttpHandlerRef.new(ctx)
|
|
|
|
{.pop.}
|