Fix EVM tracer producing wrong order of CALL family

Also fix t8n tool when given json txs with no v,r,s fields.
v,r,s field can be subtituted by "secretKey" field.
This commit is contained in:
jangko 2023-08-23 16:29:33 +07:00
parent 124ac064c6
commit 849c4bc785
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
17 changed files with 388 additions and 27 deletions

View File

@ -395,6 +395,9 @@ proc traceOpCodeStarted*(c: Computation, op: Op): int {.gcsafe, raises: [].} =
c.gasMeter.gasRemaining, c.gasMeter.gasRemaining,
c.msg.depth + 1) c.msg.depth + 1)
proc traceCallFamilyGas*(c: Computation, op: Op, gas: GasInt) {.gcsafe, raises: [].} =
c.vmState.callFamilyGas(op, gas, c.msg.depth + 1)
proc traceOpCodeEnded*(c: Computation, op: Op, opIndex: int) {.gcsafe, raises: [].} = proc traceOpCodeEnded*(c: Computation, op: Op, opIndex: int) {.gcsafe, raises: [].} =
c.vmState.captureOpEnd( c.vmState.captureOpEnd(
c.code.pc - 1, c.code.pc - 1,

View File

@ -204,7 +204,10 @@ const
StaticContextError, StaticContextError,
"Cannot modify state while inside of a STATICCALL context") "Cannot modify state while inside of a STATICCALL context")
let p = cpt.callParams let
gasAtStart = cpt.gasMeter.gasRemaining
p = cpt.callParams
cpt.asyncChainTo(ifNecessaryGetAccounts(cpt.vmState, @[p.sender])): cpt.asyncChainTo(ifNecessaryGetAccounts(cpt.vmState, @[p.sender])):
cpt.asyncChainTo(ifNecessaryGetCodeForAccounts(cpt.vmState, @[p.contractAddress, p.codeAddress])): cpt.asyncChainTo(ifNecessaryGetCodeForAccounts(cpt.vmState, @[p.contractAddress, p.codeAddress])):
var (gasCost, childGasLimit) = cpt.gasCosts[Call].c_handler( var (gasCost, childGasLimit) = cpt.gasCosts[Call].c_handler(
@ -238,6 +241,9 @@ const
raise newException( raise newException(
OutOfGas, "Gas not enough to perform calculation (call)") OutOfGas, "Gas not enough to perform calculation (call)")
gasCost = gasAtStart - cpt.gasMeter.gasRemaining
cpt.traceCallFamilyGas(Call, gasCost)
cpt.memory.extend(p.memInPos, p.memInLen) cpt.memory.extend(p.memInPos, p.memInLen)
cpt.memory.extend(p.memOutPos, p.memOutLen) cpt.memory.extend(p.memOutPos, p.memOutLen)
@ -287,6 +293,7 @@ const
## 0xf2, Message-call into this account with an alternative account's code. ## 0xf2, Message-call into this account with an alternative account's code.
let let
cpt = k.cpt cpt = k.cpt
gasAtStart = cpt.gasMeter.gasRemaining
p = cpt.callCodeParams p = cpt.callCodeParams
cpt.asyncChainTo(ifNecessaryGetAccounts(cpt.vmState, @[p.sender])): cpt.asyncChainTo(ifNecessaryGetAccounts(cpt.vmState, @[p.sender])):
@ -325,6 +332,9 @@ const
raise newException( raise newException(
OutOfGas, "Gas not enough to perform calculation (callCode)") OutOfGas, "Gas not enough to perform calculation (callCode)")
gasCost = gasAtStart - cpt.gasMeter.gasRemaining
cpt.traceCallFamilyGas(CallCode, gasCost)
cpt.memory.extend(p.memInPos, p.memInLen) cpt.memory.extend(p.memInPos, p.memInLen)
cpt.memory.extend(p.memOutPos, p.memOutLen) cpt.memory.extend(p.memOutPos, p.memOutLen)
@ -375,6 +385,7 @@ const
## code, but persisting the current values for sender and value. ## code, but persisting the current values for sender and value.
let let
cpt = k.cpt cpt = k.cpt
gasAtStart = cpt.gasMeter.gasRemaining
p = cpt.delegateCallParams p = cpt.delegateCallParams
cpt.asyncChainTo(ifNecessaryGetAccounts(cpt.vmState, @[p.sender])): cpt.asyncChainTo(ifNecessaryGetAccounts(cpt.vmState, @[p.sender])):
@ -409,6 +420,9 @@ const
raise newException( raise newException(
OutOfGas, "Gas not enough to perform calculation (delegateCall)") OutOfGas, "Gas not enough to perform calculation (delegateCall)")
gasCost = gasAtStart - cpt.gasMeter.gasRemaining
cpt.traceCallFamilyGas(DelegateCall, gasCost)
cpt.memory.extend(p.memInPos, p.memInLen) cpt.memory.extend(p.memInPos, p.memInLen)
cpt.memory.extend(p.memOutPos, p.memOutLen) cpt.memory.extend(p.memOutPos, p.memOutLen)
@ -451,6 +465,7 @@ const
let let
cpt = k.cpt cpt = k.cpt
gasAtStart = cpt.gasMeter.gasRemaining
p = cpt.staticCallParams p = cpt.staticCallParams
cpt.asyncChainTo(ifNecessaryGetAccounts(cpt.vmState, @[p.sender])): cpt.asyncChainTo(ifNecessaryGetAccounts(cpt.vmState, @[p.sender])):
@ -490,6 +505,9 @@ const
raise newException( raise newException(
OutOfGas, "Gas not enough to perform calculation (staticCall)") OutOfGas, "Gas not enough to perform calculation (staticCall)")
gasCost = gasAtStart - cpt.gasMeter.gasRemaining
cpt.traceCallFamilyGas(StaticCall, gasCost)
cpt.memory.extend(p.memInPos, p.memInLen) cpt.memory.extend(p.memInPos, p.memInLen)
cpt.memory.extend(p.memOutPos, p.memOutLen) cpt.memory.extend(p.memOutPos, p.memOutLen)

View File

@ -99,7 +99,9 @@ const
raise newException(InitcodeError, raise newException(InitcodeError,
&"CREATE: have {memLen}, max {EIP3860_MAX_INITCODE_SIZE}") &"CREATE: have {memLen}, max {EIP3860_MAX_INITCODE_SIZE}")
let gasParams = GasParams( let
gasAtStart = k.cpt.gasMeter.gasRemaining
gasParams = GasParams(
kind: Create, kind: Create,
cr_currentMemSize: k.cpt.memory.len, cr_currentMemSize: k.cpt.memory.len,
cr_memOffset: memPos, cr_memOffset: memPos,
@ -132,6 +134,9 @@ const
createMsgGas -= createMsgGas div 64 createMsgGas -= createMsgGas div 64
k.cpt.gasMeter.consumeGas(createMsgGas, reason = "CREATE") k.cpt.gasMeter.consumeGas(createMsgGas, reason = "CREATE")
gasCost = gasAtStart - k.cpt.gasMeter.gasRemaining
k.cpt.traceCallFamilyGas(Create, gasCost)
when evmc_enabled: when evmc_enabled:
let let
msg = new(nimbus_message) msg = new(nimbus_message)
@ -177,7 +182,9 @@ const
raise newException(InitcodeError, raise newException(InitcodeError,
&"CREATE2: have {memLen}, max {EIP3860_MAX_INITCODE_SIZE}") &"CREATE2: have {memLen}, max {EIP3860_MAX_INITCODE_SIZE}")
let gasParams = GasParams( let
gasAtStart = k.cpt.gasMeter.gasRemaining
gasParams = GasParams(
kind: Create, kind: Create,
cr_currentMemSize: k.cpt.memory.len, cr_currentMemSize: k.cpt.memory.len,
cr_memOffset: memPos, cr_memOffset: memPos,
@ -212,6 +219,9 @@ const
createMsgGas -= createMsgGas div 64 createMsgGas -= createMsgGas div 64
k.cpt.gasMeter.consumeGas(createMsgGas, reason = "CREATE2") k.cpt.gasMeter.consumeGas(createMsgGas, reason = "CREATE2")
gasCost = gasAtStart - k.cpt.gasMeter.gasRemaining
k.cpt.traceCallFamilyGas(Create2, gasCost)
when evmc_enabled: when evmc_enabled:
let let
msg = new(nimbus_message) msg = new(nimbus_message)

View File

@ -257,8 +257,8 @@ proc executeOpcodes*(c: Computation, shouldPrepareTracer: bool = true)
except CatchableError as e: except CatchableError as e:
let let
msg = e.msg msg = e.msg
depth = $c.msg.depth depth = $(c.msg.depth + 1) # plus one to match tracer depth, and avoid confusion
c.setError("Opcode Dispatch Error msg=" & msg & ", depth=" & depth, true) c.setError("Opcode Dispatch Error: " & msg & ", depth=" & depth, true)
if c.isError() and c.continuation.isNil: if c.isError() and c.continuation.isNil:
if c.tracingEnabled: c.traceError() if c.tracingEnabled: c.traceError()

View File

@ -58,7 +58,7 @@ proc ensurePop(elements: Stack, a: int) =
let expected = a let expected = a
if num < expected: if num < expected:
raise newException(InsufficientStack, raise newException(InsufficientStack,
&"Stack underflow: expected {expected} elements, got {num} instead.") &"Stack underflow, expect {expected}, got {num}")
proc popAux[T](stack: var Stack, value: var T) = proc popAux[T](stack: var Stack, value: var T) =
ensurePop(stack, 1) ensurePop(stack, 1)
@ -103,7 +103,7 @@ proc swap*(stack: var Stack, position: int) =
(stack.values[^1], stack.values[^idx]) = (stack.values[^idx], stack.values[^1]) (stack.values[^1], stack.values[^idx]) = (stack.values[^idx], stack.values[^1])
else: else:
raise newException(InsufficientStack, raise newException(InsufficientStack,
&"Insufficient stack items for SWAP{position}") "Stack underflow for SWAP" & $position)
template getInt(x: int): int = x template getInt(x: int): int = x
@ -114,7 +114,7 @@ proc dup*(stack: var Stack, position: int | UInt256) =
stack.push(stack.values[^position]) stack.push(stack.values[^position])
else: else:
raise newException(InsufficientStack, raise newException(InsufficientStack,
&"Insufficient stack items for DUP{position}") "Stack underflow for DUP" & $position)
proc peek*(stack: Stack): UInt256 = proc peek*(stack: Stack): UInt256 =
# This should be used only for testing purposes! # This should be used only for testing purposes!

View File

@ -393,6 +393,12 @@ proc captureOpStart*(vmState: BaseVMState, pc: int,
if vmState.tracingEnabled: if vmState.tracingEnabled:
result = vmState.tracer.captureOpStart(pc, op, gas, depth) result = vmState.tracer.captureOpStart(pc, op, gas, depth)
proc callFamilyGas*(vmState: BaseVMState,
op: Op, gas: GasInt,
depth: int) =
if vmState.tracingEnabled:
vmState.tracer.callFamilyGas(op, gas, depth)
proc captureOpEnd*(vmState: BaseVMState, pc: int, proc captureOpEnd*(vmState: BaseVMState, pc: int,
op: Op, gas: GasInt, refund: GasInt, op: Op, gas: GasInt, refund: GasInt,
rData: openArray[byte], rData: openArray[byte],

View File

@ -29,6 +29,17 @@ type
stack: JsonNode stack: JsonNode
storageKeys: seq[HashSet[UInt256]] storageKeys: seq[HashSet[UInt256]]
index: int index: int
callFamilyNode: JsonNode
const
callFamily = [
Create,
Create2,
Call,
CallCode,
DelegateCall,
StaticCall,
]
template stripLeadingZeros(value: string): string = template stripLeadingZeros(value: string): string =
var cidx = 0 var cidx = 0
@ -41,6 +52,9 @@ proc encodeHexInt(x: SomeInteger): JsonNode =
%("0x" & x.toHex.stripLeadingZeros.toLowerAscii) %("0x" & x.toHex.stripLeadingZeros.toLowerAscii)
proc `%`(x: openArray[byte]): JsonNode = proc `%`(x: openArray[byte]): JsonNode =
if x.len == 0:
%("")
else:
%("0x" & x.toHex) %("0x" & x.toHex)
proc writeJson(ctx: JsonTracer, res: JsonNode) = proc writeJson(ctx: JsonTracer, res: JsonNode) =
@ -113,6 +127,9 @@ proc captureOpImpl(ctx: JsonTracer, pc: int,
if error.isSome: if error.isSome:
res["error"] = %(error.get) res["error"] = %(error.get)
if op in callFamily:
ctx.callFamilyNode = res
else:
ctx.writeJson(res) ctx.writeJson(res)
proc newJsonTracer*(stream: Stream, flags: set[TracerFlags], pretty: bool): JsonTracer = proc newJsonTracer*(stream: Stream, flags: set[TracerFlags], pretty: bool): JsonTracer =
@ -171,13 +188,34 @@ method captureOpStart*(ctx: JsonTracer, pc: int,
except ValueError as ex: except ValueError as ex:
error "JsonTracer captureOpStart", msg=ex.msg error "JsonTracer captureOpStart", msg=ex.msg
if op in callFamily:
try:
ctx.captureOpImpl(pc, op, 0, 0, [], depth, none(string))
except RlpError as ex:
error "JsonTracer captureOpEnd", msg=ex.msg
# make sure captureOpEnd get the right opIndex
result = ctx.index result = ctx.index
inc ctx.index inc ctx.index
method callFamilyGas*(ctx: JsonTracer,
op: Op, gas: GasInt,
depth: int) {.gcsafe.} =
doAssert(op in callFamily)
doAssert(ctx.callFamilyNode.isNil.not)
let res = ctx.callFamilyNode
res["gasCost"] = encodeHexInt(gas)
ctx.writeJson(res)
method captureOpEnd*(ctx: JsonTracer, pc: int, method captureOpEnd*(ctx: JsonTracer, pc: int,
op: Op, gas: GasInt, refund: GasInt, op: Op, gas: GasInt, refund: GasInt,
rData: openArray[byte], rData: openArray[byte],
depth: int, opIndex: int) {.gcsafe.} = depth: int, opIndex: int) {.gcsafe.} =
if op in callFamily:
# call family opcode is processed in captureOpStart
return
try: try:
ctx.captureOpImpl(pc, op, gas, refund, rData, depth, none(string)) ctx.captureOpImpl(pc, op, gas, refund, rData, depth, none(string))
except RlpError as ex: except RlpError as ex:

View File

@ -173,6 +173,11 @@ method captureOpStart*(ctx: TracerRef, pc: int,
depth: int): int {.base, gcsafe.} = depth: int): int {.base, gcsafe.} =
discard discard
method callFamilyGas*(ctx: TracerRef,
op: Op, gas: GasInt,
depth: int) {.base, gcsafe.} =
discard
method captureOpEnd*(ctx: TracerRef, pc: int, method captureOpEnd*(ctx: TracerRef, pc: int,
op: Op, gas: GasInt, refund: GasInt, op: Op, gas: GasInt, refund: GasInt,
rData: openArray[byte], rData: openArray[byte],
@ -188,4 +193,3 @@ method captureFault*(ctx: TracerRef, pc: int,
method capturePrepare*(ctx: TracerRef, depth: int) {.base, gcsafe.} = method capturePrepare*(ctx: TracerRef, depth: int) {.base, gcsafe.} =
discard discard

View File

@ -43,6 +43,7 @@ export
vms.captureEnter, vms.captureEnter,
vms.captureExit, vms.captureExit,
vms.captureOpStart, vms.captureOpStart,
vms.callFamilyGas,
vms.captureOpEnd, vms.captureOpEnd,
vms.captureFault, vms.captureFault,
vms.capturePrepare vms.capturePrepare

View File

@ -283,7 +283,7 @@
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000060" "0000000000000000000000000000000000000000000000000000000000000060"
], ],
"error": "Opcode Dispatch Error msg=Out of gas: Needed 20000 - Remaining 412 - Reason: SSTORE, depth=0", "error": "Opcode Dispatch Error: Out of gas: Needed 20000 - Remaining 412 - Reason: SSTORE, depth=1",
"gasCost": 0 "gasCost": 0
} }
], ],
@ -705,7 +705,7 @@
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000060" "0000000000000000000000000000000000000000000000000000000000000060"
], ],
"error": "Opcode Dispatch Error msg=Out of gas: Needed 20000 - Remaining 412 - Reason: SSTORE, depth=0", "error": "Opcode Dispatch Error: Out of gas: Needed 20000 - Remaining 412 - Reason: SSTORE, depth=1",
"gasCost": 0 "gasCost": 0
} }
] ]

View File

@ -212,9 +212,6 @@ proc parseTx(n: JsonNode, chainId: ChainID): Transaction =
required(tx, GasInt, gas) required(tx, GasInt, gas)
required(tx, UInt256, value) required(tx, UInt256, value)
required(tx, Blob, input) required(tx, Blob, input)
required(tx, int64, v)
required(tx, UInt256, r)
required(tx, UInt256, s)
if n.hasKey("to"): if n.hasKey("to"):
tx.to = some(EthAddress.fromJson(n, "to")) tx.to = some(EthAddress.fromJson(n, "to"))
@ -248,6 +245,9 @@ proc parseTx(n: JsonNode, chainId: ChainID): Transaction =
let secretKey = PrivateKey.fromRaw(data).tryGet let secretKey = PrivateKey.fromRaw(data).tryGet
signTransaction(tx, secretKey, chainId, eip155) signTransaction(tx, secretKey, chainId, eip155)
else: else:
required(tx, int64, v)
required(tx, UInt256, r)
required(tx, UInt256, s)
tx tx
proc parseTxLegacy(item: var Rlp): Result[Transaction, string] = proc parseTxLegacy(item: var Rlp): Result[Transaction, string] =

View File

@ -498,7 +498,7 @@ const
expOut: "exp.json", expOut: "exp.json",
), ),
TestSpec( TestSpec(
name : "EVM tracer crash bug", name : "EVM tracer nil stack crash bug",
base : "testdata/00-519", base : "testdata/00-519",
input : t8nInput( input : t8nInput(
"alloc.json", "txs.json", "env.json", "Shanghai", "0", "alloc.json", "txs.json", "env.json", "Shanghai", "0",
@ -506,6 +506,15 @@ const
output: T8nOutput(trace: true), output: T8nOutput(trace: true),
expOut: "exp.txt", expOut: "exp.txt",
), ),
TestSpec(
name : "EVM tracer wrong order for CALL family opcodes",
base : "testdata/00-520",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Merge", "0",
),
output: T8nOutput(trace: true, result: true),
expOut: "exp.txt",
),
] ]
proc main() = proc main() =

View File

@ -1,2 +1,2 @@
{"pc":0,"op":0,"gas":"0x0","gasCost":"0xfffffffffffecb68","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP","error":"Blake2b F function invalid input"} {"pc":0,"op":0,"gas":"0x0","gasCost":"0xfffffffffffecb68","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP","error":"Blake2b F function invalid input"}
{"output":"0x","gasUsed":"0x13498","error":"Blake2b F function invalid input"} {"output":"","gasUsed":"0x13498","error":"Blake2b F function invalid input"}

237
tools/t8n/testdata/00-520/alloc.json vendored Normal file

File diff suppressed because one or more lines are too long

9
tools/t8n/testdata/00-520/env.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"currentBaseFee": "0x10",
"currentCoinbase": "b94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"currentGasLimit": "0x26e1f476fe1e22",
"currentNumber": "0x1",
"currentTimestamp": "0x3e8",
"currentRandom": "0x0000000000000000000000000000000000000000000000000000000000020000",
"currentDifficulty": "0x0",
}

12
tools/t8n/testdata/00-520/exp.txt vendored Normal file
View File

@ -0,0 +1,12 @@
{"pc":0,"op":96,"gas":"0x79bf88","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":2,"op":96,"gas":"0x79bf85","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":4,"op":96,"gas":"0x79bf82","gasCost":"0x3","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":6,"op":96,"gas":"0x79bf7f","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":8,"op":96,"gas":"0x79bf7c","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":10,"op":96,"gas":"0x79bf79","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":12,"op":90,"gas":"0x79bf76","gasCost":"0x2","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xf3"],"depth":1,"refund":0,"opName":"GAS"}
{"pc":13,"op":241,"gas":"0x79bf74","gasCost":"0x77d89f","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xf3","0x79bf74"],"depth":1,"refund":0,"opName":"CALL"}
{"pc":0,"op":11,"gas":"0x77ce77","gasCost":"0x5","memSize":0,"stack":["0x0"],"depth":2,"refund":0,"opName":"SIGNEXTEND","error":"Opcode Dispatch Error: Stack underflow, expect 2, got 0, depth=2"}
{"pc":14,"op":80,"gas":"0x1e6d5","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"POP"}
{"pc":15,"op":152,"gas":"0x1e6d3","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"SWAP9","error":"Opcode Dispatch Error: Stack underflow for SWAP9, depth=1"}
{"output":"","gasUsed":"0x79bf88","error":"Opcode Dispatch Error: Stack underflow for SWAP9, depth=1"}

14
tools/t8n/testdata/00-520/txs.json vendored Normal file
View File

@ -0,0 +1,14 @@
[
{
"type": "0x0",
"chainId": "0x1",
"nonce": "0x0",
"gasPrice": "0x10",
"gas": "0x7a1200",
"to": "0x00000000000000000000000000000000000000f1",
"value": "0x54afed",
"input": "0xf9ddd3baf78a80",
"secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8",
"sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
}
]