fix t8n tool tx and env parser

This commit is contained in:
jangko 2022-12-08 12:17:14 +07:00
parent ed518c760f
commit 490b2f8023
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
4 changed files with 233 additions and 21 deletions

View File

@ -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
@ -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"]
@ -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 =
@ -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)

View File

@ -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],
@ -11,6 +11,7 @@ import
../../nimbus/core/dao,
../../nimbus/core/executor/[process_transaction, executor_helpers]
import stew/byteutils
const
wrapExceptionEnabled* {.booldefine.} = true
stdinSelector = "stdin"
@ -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:
@ -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]()
@ -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(
@ -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:
@ -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) =
@ -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 = {
@ -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)
@ -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
@ -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):

96
tools/t8n/txpriv.nim Normal file
View File

@ -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)

View File

@ -9,7 +9,7 @@
# according to those terms.
import
std/[tables],
std/[tables, json],
eth/common,
../../nimbus/common/chain_config,
../common/types
@ -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
@ -75,6 +89,7 @@ type
rejected*: seq[RejectedTx]
currentDifficulty*: Option[DifficultyInt]
gasUsed*: GasInt
currentBaseFee*: Option[UInt256]
const
ErrorEVM* = 2.T8NExitCode