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

Fix t8n tool #1354

Merged
merged 6 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Prev Previous commit
Next Next commit
fix t8n tool tx and env parser
  • Loading branch information
jangko committed Dec 8, 2022
commit 490b2f80233bc57b132b931a14fd4fa3f2632537
81 changes: 71 additions & 10 deletions tools/t8n/helpers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@

import
std/[json, strutils, tables],
stew/byteutils,
stew/[byteutils, results],
stint,
eth/[common, rlp, keys],
../../nimbus/transaction,
../../nimbus/common/chain_config,
../common/helpers,
./types
./types,
./txpriv

export
helpers
Expand Down Expand Up @@ -150,6 +151,9 @@ proc parseEnv*(ctx: var TransContext, n: JsonNode) =
omitZero(ctx.env, EthTime, parentTimestamp)
optional(ctx.env, UInt256, currentBaseFee)
omitZero(ctx.env, Hash256, parentUncleHash)
optional(ctx.env, UInt256, parentBaseFee)
optional(ctx.env, GasInt, parentGasUsed)
optional(ctx.env, GasInt, parentGasLimit)

if n.hasKey("blockHashes"):
let w = n["blockHashes"]
Expand Down Expand Up @@ -203,24 +207,79 @@ proc parseTx(n: JsonNode, chainId: ChainID): Transaction =
else:
tx

proc parseTxs*(ctx: var TransContext, txs: JsonNode, chainId: ChainID) =
proc parseTxLegacy(item: var Rlp): Result[Transaction, string] =
try:
var tx: Transaction
item.readTxLegacy(tx)
return ok(tx)
except RlpError as x:
return err(x.msg)

proc parseTxTyped(item: var Rlp): Result[Transaction, string] =
try:
var tx: Transaction
var rr = rlpFromBytes(item.read(Blob))
rr.readTxTyped(tx)
return ok(tx)
except RlpError as x:
return err(x.msg)

proc parseTxJson(ctx: TransContext, i: int, chainId: ChainId): Result[Transaction, string] =
try:
let n = ctx.txs.n[i]
return ok(parseTx(n, chainId))
except Exception as x:
return err(x.msg)

proc parseTxs*(ctx: TransContext, chainId: ChainId): seq[Result[Transaction, string]] =
if ctx.txs.txsType == TxsJson:
let len = ctx.txs.n.len
result = newSeqOfCap[Result[Transaction, string]](len)
for i in 0 ..< len:
result.add ctx.parseTxJson(i, chainId)
return

if ctx.txs.txsType == TxsRlp:
result = newSeqOfCap[Result[Transaction, string]](ctx.txs.r.listLen)
var rlp = ctx.txs.r
for item in rlp:
if item.isList:
result.add parseTxLegacy(item)
else:
result.add parseTxTyped(item)
return

proc txList*(ctx: TransContext, chainId: ChainId): seq[Transaction] =
let list = ctx.parseTxs(chainId)
for txRes in list:
if txRes.isOk:
result.add txRes.get

proc parseTxs*(ctx: var TransContext, txs: JsonNode) =
if txs.kind == JNull:
return
if txs.kind != JArray:
raise newError(ErrorJson, "Transaction list should be a JSON array, got=" & $txs.kind)
for n in txs:
ctx.txs.add parseTx(n, chainId)
raise newError(ErrorJson,
"Transaction list should be a JSON array, got=" & $txs.kind)
ctx.txs = TxsList(
txsType: TxsJson,
n: txs)

proc parseTxsRlp*(ctx: var TransContext, hexData: string) =
let data = hexToSeqByte(hexData)
ctx.txs = rlp.decode(data, seq[Transaction])
let bytes = hexToSeqByte(hexData)
ctx.txs = TxsList(
txsType: TxsRlp,
r: rlpFromBytes(bytes)
)
if ctx.txs.r.isList.not:
raise newError(ErrorRlp, "RLP Transaction list should be a list")

proc parseInputFromStdin*(ctx: var TransContext, chainId: ChainId) =
proc parseInputFromStdin*(ctx: var TransContext) =
let data = stdin.readAll()
let n = json.parseJson(data)
if n.hasKey("alloc"): ctx.parseAlloc(n["alloc"])
if n.hasKey("env"): ctx.parseEnv(n["env"])
if n.hasKey("txs"): ctx.parseTxs(n["txs"], chainId)
if n.hasKey("txs"): ctx.parseTxs(n["txs"])
if n.hasKey("txsRlp"): ctx.parseTxsRlp(n["txsRlp"].getStr())

template stripLeadingZeros(value: string): string =
Expand Down Expand Up @@ -336,3 +395,5 @@ proc `@@`*(x: ExecutionResult): JsonNode =
}
if x.rejected.len > 0:
result["rejected"] = @@(x.rejected)
if x.currentBaseFee.isSome:
result["currentBaseFee"] = @@(x.currentBaseFee)
58 changes: 49 additions & 9 deletions tools/t8n/transition.nim
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import
std/[json, strutils, times, tables, os, sets],
eth/[rlp, trie],
eth/[rlp, trie, eip1559],
stint, chronicles, stew/results,
"."/[config, types, helpers],
../../nimbus/[vm_types, vm_state, transaction],
Expand All @@ -11,6 +11,7 @@ import
../../nimbus/core/dao,
../../nimbus/core/executor/[process_transaction, executor_helpers]

import stew/byteutils
const
wrapExceptionEnabled* {.booldefine.} = true
stdinSelector = "stdin"
Expand Down Expand Up @@ -56,7 +57,10 @@ proc dispatchOutput(ctx: var TransContext, conf: T8NConf, res: ExecOutput) =
dis.dispatch(conf.outputBaseDir, conf.outputAlloc, "alloc", @@(res.alloc))
dis.dispatch(conf.outputBaseDir, conf.outputResult, "result", @@(res.result))

let body = @@(rlp.encode(ctx.txs))
let chainId = conf.stateChainId.ChainId
let txList = ctx.txList(chainId)

let body = @@(rlp.encode(txList))
dis.dispatch(conf.outputBaseDir, conf.outputBody, "body", body)

if dis.stdout.len > 0:
Expand Down Expand Up @@ -133,8 +137,10 @@ proc exec(ctx: var TransContext,
stateReward: Option[UInt256],
header: BlockHeader): ExecOutput =

let txList = ctx.parseTxs(vmState.com.chainId)

var
receipts = newSeqOfCap[TxReceipt](ctx.txs.len)
receipts = newSeqOfCap[TxReceipt](txList.len)
rejected = newSeq[RejectedTx]()
includedTx = newSeq[Transaction]()

Expand All @@ -143,10 +149,18 @@ proc exec(ctx: var TransContext,
vmState.mutateStateDB:
db.applyDAOHardFork()

vmState.receipts = newSeqOfCap[Receipt](ctx.txs.len)
vmState.receipts = newSeqOfCap[Receipt](txList.len)
vmState.cumulativeGasUsed = 0

for txIndex, tx in ctx.txs:
for txIndex, txRes in txList:
if txRes.isErr:
rejected.add RejectedTx(
index: txIndex,
error: txRes.error
)
continue

let tx = txRes.get
var sender: EthAddress
if not tx.getSender(sender):
rejected.add RejectedTx(
Expand Down Expand Up @@ -176,7 +190,13 @@ proc exec(ctx: var TransContext,
)
includedTx.add tx

# Add mining reward? (-1 means rewards are disabled)
if stateReward.isSome and stateReward.get >= 0:
# Add mining reward. The mining reward may be `0`, which only makes a difference in the cases
# where
# - the coinbase suicided, or
# - there are only 'bad' transactions, which aren't executed. In those cases,
# the coinbase gets no txfee, so isn't created, and thus needs to be touched
let blockReward = stateReward.get()
var mainReward = blockReward
for uncle in ctx.env.ommers:
Expand Down Expand Up @@ -216,7 +236,8 @@ proc exec(ctx: var TransContext,
# geth using both vmContext.Difficulty and vmContext.Random
# therefore we cannot use vmState.difficulty
currentDifficulty: ctx.env.currentDifficulty,
gasUsed : vmState.cumulativeGasUsed
gasUsed : vmState.cumulativeGasUsed,
currentBaseFee: ctx.env.currentBaseFee
)

template wrapException(body: untyped) =
Expand Down Expand Up @@ -261,6 +282,20 @@ proc parseChainConfig(network: string): ChainConfig =
except ValueError as e:
raise newError(ErrorConfig, e.msg)

proc calcBaseFee(env: EnvStruct): UInt256 =
if env.parentGasUsed.isNone:
raise newError(ErrorConfig,
"'parentBaseFee' exists but missing 'parentGasUsed' in env section")

if env.parentGasLimit.isNone:
raise newError(ErrorConfig,
"'parentBaseFee' exists but missing 'parentGasLimit' in env section")

calcEip1599BaseFee(
env.parentGasLimit.get,
env.parentGasUsed.get,
env.parentBaseFee.get)

proc transitionAction*(ctx: var TransContext, conf: T8NConf) =
wrapException:
var tracerFlags = {
Expand Down Expand Up @@ -291,7 +326,7 @@ proc transitionAction*(ctx: var TransContext, conf: T8NConf) =
if conf.inputAlloc == stdinSelector or
conf.inputEnv == stdinSelector or
conf.inputTxs == stdinSelector:
ctx.parseInputFromStdin(com.chainId)
ctx.parseInputFromStdin()

if conf.inputAlloc != stdinSelector and conf.inputAlloc.len > 0:
let n = json.parseFile(conf.inputAlloc)
Expand All @@ -307,7 +342,7 @@ proc transitionAction*(ctx: var TransContext, conf: T8NConf) =
ctx.parseTxsRlp(data.strip(chars={'"'}))
else:
let n = json.parseFile(conf.inputTxs)
ctx.parseTxs(n, com.chainId)
ctx.parseTxs(n)

let uncleHash = if ctx.env.parentUncleHash == Hash256():
EMPTY_UNCLE_HASH
Expand All @@ -324,7 +359,12 @@ proc transitionAction*(ctx: var TransContext, conf: T8NConf) =

# Sanity check, to not `panic` in state_transition
if com.isLondon(ctx.env.currentNumber):
if ctx.env.currentBaseFee.isNone:
if ctx.env.currentBaseFee.isSome:
# Already set, currentBaseFee has precedent over parentBaseFee.
discard
elif ctx.env.parentBaseFee.isSome:
ctx.env.currentBaseFee = some(calcBaseFee(ctx.env))
else:
raise newError(ErrorConfig, "EIP-1559 config but missing 'currentBaseFee' in env section")

if com.forkGTE(MergeFork):
Expand Down
96 changes: 96 additions & 0 deletions tools/t8n/txpriv.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import
eth/common

from stew/objects
import checkedEnumAssign

# these procs are duplicates of nim-eth/eth_types_rlp.nim
# both `readTxLegacy` and `readTxTyped` are exported here

template read[T](rlp: var Rlp, val: var T)=
val = rlp.read(type val)

proc read[T](rlp: var Rlp, val: var Option[T])=
if rlp.blobLen != 0:
val = some(rlp.read(T))
else:
rlp.skipElem

proc readTxLegacy*(rlp: var Rlp, tx: var Transaction)=
tx.txType = TxLegacy
rlp.tryEnterList()
rlp.read(tx.nonce)
rlp.read(tx.gasPrice)
rlp.read(tx.gasLimit)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.payload)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)

proc readTxEip2930(rlp: var Rlp, tx: var Transaction)=
tx.txType = TxEip2930
rlp.tryEnterList()
tx.chainId = rlp.read(uint64).ChainId
rlp.read(tx.nonce)
rlp.read(tx.gasPrice)
rlp.read(tx.gasLimit)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.payload)
rlp.read(tx.accessList)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)

proc readTxEip1559(rlp: var Rlp, tx: var Transaction)=
tx.txType = TxEip1559
rlp.tryEnterList()
tx.chainId = rlp.read(uint64).ChainId
rlp.read(tx.nonce)
rlp.read(tx.maxPriorityFee)
rlp.read(tx.maxFee)
rlp.read(tx.gasLimit)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.payload)
rlp.read(tx.accessList)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)

proc readTxTyped*(rlp: var Rlp, tx: var Transaction) {.inline.} =
# EIP-2718: We MUST decode the first byte as a byte, not `rlp.read(int)`.
# If decoded with `rlp.read(int)`, bad transaction data (from the network)
# or even just incorrectly framed data for other reasons fails with
# any of these misleading error messages:
# - "Message too large to fit in memory"
# - "Number encoded with a leading zero"
# - "Read past the end of the RLP stream"
# - "Small number encoded in a non-canonical way"
# - "Attempt to read an Int value past the RLP end"
# - "The RLP contains a larger than expected Int value"
if not rlp.isSingleByte:
if not rlp.hasData:
raise newException(MalformedRlpError,
"Transaction expected but source RLP is empty")
raise newException(MalformedRlpError,
"TypedTransaction type byte is out of range, must be 0x00 to 0x7f")
let txType = rlp.getByteValue
rlp.position += 1

var txVal: TxType
if checkedEnumAssign(txVal, txType):
case txVal:
of TxEip2930:
rlp.readTxEip2930(tx)
return
of TxEip1559:
rlp.readTxEip1559(tx)
return
else:
discard

raise newException(UnsupportedRlpError,
"TypedTransaction type must be 1 or 2 in this version, got " & $txType)
19 changes: 17 additions & 2 deletions tools/t8n/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# according to those terms.

import
std/[tables],
std/[tables, json],
eth/common,
../../nimbus/common/chain_config,
../common/types
Expand Down Expand Up @@ -40,10 +40,24 @@ type
ommers*: seq[Ommer]
currentBaseFee*: Option[UInt256]
parentUncleHash*: Hash256
parentBaseFee*: Option[UInt256]
parentGasUsed*: Option[GasInt]
parentGasLimit*: Option[GasInt]

TxsType* = enum
TxsNone
TxsRlp
TxsJson

TxsList* = object
case txsType*: TxsType
of TxsRlp: r*: Rlp
of TxsJson: n*: JsonNode
else: discard

TransContext* = object
alloc*: GenesisAlloc
txs*: seq[Transaction]
txs*: TxsList
env*: EnvStruct

RejectedTx* = object
Expand Down Expand Up @@ -75,6 +89,7 @@ type
rejected*: seq[RejectedTx]
currentDifficulty*: Option[DifficultyInt]
gasUsed*: GasInt
currentBaseFee*: Option[UInt256]

const
ErrorEVM* = 2.T8NExitCode
Expand Down