Implement EIP-6110: Execution layer triggered deposits (#2612)
* Implement EIP-6110: Execution layer triggered deposits * Implement EIP-6110 of t8n tool * Avoid unnecessary DepositRequestType check * Avoid using 'result' in t8n helpers * Fix logs collection and deposits validation
This commit is contained in:
parent
8e8258e460
commit
6503d51b44
|
@ -108,4 +108,5 @@ const
|
|||
initAddress(3)
|
||||
|
||||
HISTORY_STORAGE_ADDRESS* = hexToByteArray[20]("0x0aae40965e6800cd9b1f4b05ff21581047e3f91e")
|
||||
DEPOSIT_CONTRACT_ADDRESS* = hexToByteArray[20]("0x00000000219ab540356cbb839cbe05303d7705fa")
|
||||
# End
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||
# http://opensource.org/licenses/MIT)
|
||||
# at your option. This file may not be copied, modified, or distributed except
|
||||
# according to those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
eth/common,
|
||||
stew/arrayops,
|
||||
stew/endians2,
|
||||
results,
|
||||
../constants
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Private helpers
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# UnpackIntoDeposit unpacks a serialized DepositEvent.
|
||||
func unpackIntoDeposit(data: openArray[byte]): Result[Request, string] =
|
||||
if data.len != 576:
|
||||
return err("deposit wrong length: want 576, have " & $data.len)
|
||||
|
||||
# The ABI encodes the position of dynamic elements first. Since there
|
||||
# are 5 elements, skip over the positional data. The first 32 bytes of
|
||||
# dynamic elements also encode their actual length. Skip over that value too.
|
||||
const
|
||||
b = 32*5 + 32
|
||||
c = b + 48 + 16 + 32
|
||||
d = c + 32 + 32
|
||||
e = d + 8 + 24 + 32
|
||||
f = e + 96 + 32
|
||||
|
||||
template copyFrom(T: type, input, a, b): auto =
|
||||
T.initCopyFrom(input.toOpenArray(a, b))
|
||||
|
||||
let res = Request(
|
||||
requestType: DepositRequestType,
|
||||
deposit: DepositRequest(
|
||||
# PublicKey is the first element. ABI encoding pads values to 32 bytes,
|
||||
# so despite BLS public keys being length 48, the value length
|
||||
# here is 64. Then skip over the next length value.
|
||||
pubkey: array[48, byte].copyFrom(data, b, b+47),
|
||||
|
||||
# WithdrawalCredentials is 32 bytes. Read that value then skip over next
|
||||
# length.
|
||||
withdrawalCredentials: array[32, byte].copyFrom(data, c, c+31),
|
||||
|
||||
# Amount is 8 bytes, but it is padded to 32. Skip over it and the next
|
||||
# length.
|
||||
amount: uint64.fromBytesLE(data.toOpenArray(d, d+7)),
|
||||
|
||||
# Signature is 96 bytes. Skip over it and the next length.
|
||||
signature: array[96, byte].copyFrom(data, e, e+95),
|
||||
|
||||
# Amount is 8 bytes.
|
||||
index: uint64.fromBytesLE(data.toOpenArray(f, f+7)),
|
||||
)
|
||||
)
|
||||
ok(res)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
func parseDepositLogs*(logs: openArray[Log]): Result[seq[Request], string] =
|
||||
var res: seq[Request]
|
||||
for log in logs:
|
||||
if log.address == DEPOSIT_CONTRACT_ADDRESS:
|
||||
res.add ?unpackIntoDeposit(log.data)
|
||||
ok(res)
|
|
@ -17,6 +17,7 @@ import
|
|||
../../evm/state,
|
||||
../../evm/types,
|
||||
../dao,
|
||||
../eip6110,
|
||||
./calculate_reward,
|
||||
./executor_helpers,
|
||||
./process_transaction,
|
||||
|
@ -32,9 +33,11 @@ proc processTransactions*(
|
|||
header: BlockHeader,
|
||||
transactions: seq[Transaction],
|
||||
skipReceipts = false,
|
||||
collectLogs = false
|
||||
): Result[void, string] =
|
||||
vmState.receipts.setLen(if skipReceipts: 0 else: transactions.len)
|
||||
vmState.cumulativeGasUsed = 0
|
||||
vmState.allLogs = @[]
|
||||
|
||||
for txIndex, tx in transactions:
|
||||
var sender: EthAddress
|
||||
|
@ -46,9 +49,14 @@ proc processTransactions*(
|
|||
if skipReceipts:
|
||||
# TODO don't generate logs at all if we're not going to put them in
|
||||
# receipts
|
||||
discard vmState.getAndClearLogEntries()
|
||||
if collectLogs:
|
||||
vmState.allLogs.add vmState.getAndClearLogEntries()
|
||||
else:
|
||||
discard vmState.getAndClearLogEntries()
|
||||
else:
|
||||
vmState.receipts[txIndex] = vmState.makeReceipt(tx.txType)
|
||||
if collectLogs:
|
||||
vmState.allLogs.add vmState.receipts[txIndex].logs
|
||||
ok()
|
||||
|
||||
proc procBlkPreamble(
|
||||
|
@ -67,7 +75,13 @@ proc procBlkPreamble(
|
|||
return err("Mismatched txRoot")
|
||||
|
||||
if com.isPragueOrLater(header.timestamp):
|
||||
if header.requestsRoot.isNone or blk.requests.isNone:
|
||||
return err("Post-Prague block header must have requestsRoot/requests")
|
||||
|
||||
?vmState.processParentBlockHash(header.parentHash)
|
||||
else:
|
||||
if header.requestsRoot.isSome or blk.requests.isSome:
|
||||
return err("Pre-Prague block header must not have requestsRoot/requests")
|
||||
|
||||
if com.isCancunOrLater(header.timestamp):
|
||||
if header.parentBeaconBlockRoot.isNone:
|
||||
|
@ -82,7 +96,8 @@ proc procBlkPreamble(
|
|||
if blk.transactions.len == 0:
|
||||
return err("Transactions missing from body")
|
||||
|
||||
?processTransactions(vmState, header, blk.transactions, skipReceipts)
|
||||
let collectLogs = header.requestsRoot.isSome and not skipValidation
|
||||
?processTransactions(vmState, header, blk.transactions, skipReceipts, collectLogs)
|
||||
elif blk.transactions.len > 0:
|
||||
return err("Transactions in block with empty txRoot")
|
||||
|
||||
|
@ -121,8 +136,11 @@ proc procBlkPreamble(
|
|||
ok()
|
||||
|
||||
proc procBlkEpilogue(
|
||||
vmState: BaseVMState, header: BlockHeader, skipValidation: bool, skipReceipts: bool
|
||||
vmState: BaseVMState, blk: EthBlock, skipValidation: bool, skipReceipts: bool
|
||||
): Result[void, string] =
|
||||
template header(): BlockHeader =
|
||||
blk.header
|
||||
|
||||
# Reward beneficiary
|
||||
vmState.mutateStateDB:
|
||||
if vmState.collectWitnessData:
|
||||
|
@ -160,6 +178,21 @@ proc procBlkEpilogue(
|
|||
expected = header.receiptsRoot
|
||||
return err("receiptRoot mismatch")
|
||||
|
||||
if header.requestsRoot.isSome:
|
||||
let requestsRoot = calcRequestsRoot(blk.requests.get)
|
||||
if header.requestsRoot.get != requestsRoot:
|
||||
debug "wrong requestsRoot in block",
|
||||
blockNumber = header.number,
|
||||
actual = requestsRoot,
|
||||
expected = header.requestsRoot.get
|
||||
return err("requestsRoot mismatch")
|
||||
let depositReqs = ?parseDepositLogs(vmState.allLogs)
|
||||
var expectedDeposits: seq[Request]
|
||||
for req in blk.requests.get:
|
||||
if req.requestType == DepositRequestType:
|
||||
expectedDeposits.add req
|
||||
if depositReqs != expectedDeposits:
|
||||
return err("EIP-6110 deposit requests mismatch")
|
||||
ok()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
@ -180,7 +213,7 @@ proc processBlock*(
|
|||
if vmState.com.consensus == ConsensusType.POW:
|
||||
vmState.calculateReward(blk.header, blk.uncles)
|
||||
|
||||
?vmState.procBlkEpilogue(blk.header, skipValidation, skipReceipts)
|
||||
?vmState.procBlkEpilogue(blk, skipValidation, skipReceipts)
|
||||
|
||||
ok()
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ type
|
|||
cumulativeGasUsed*: GasInt
|
||||
gasCosts* : GasCosts
|
||||
blobGasUsed* : uint64
|
||||
allLogs* : seq[Log] # EIP-6110
|
||||
|
||||
Computation* = ref object
|
||||
# The execution computation
|
||||
|
|
|
@ -346,10 +346,7 @@ proc `@@`*(x: Blob): JsonNode =
|
|||
proc `@@`(x: bool): JsonNode =
|
||||
%(if x: "0x1" else: "0x0")
|
||||
|
||||
proc `@@`(x: EthAddress): JsonNode =
|
||||
%("0x" & x.toHex)
|
||||
|
||||
proc `@@`(x: Topic): JsonNode =
|
||||
proc `@@`(x: openArray[byte]): JsonNode =
|
||||
%("0x" & x.toHex)
|
||||
|
||||
proc toJson(x: Table[UInt256, UInt256]): JsonNode =
|
||||
|
@ -378,7 +375,7 @@ proc `@@`(x: BloomFilter): JsonNode =
|
|||
%("0x" & toHex[256](x))
|
||||
|
||||
proc `@@`(x: Log): JsonNode =
|
||||
result = %{
|
||||
%{
|
||||
"address": @@(x.address),
|
||||
"topics" : @@(x.topics),
|
||||
"data" : @@(x.data)
|
||||
|
@ -401,11 +398,20 @@ proc `@@`(x: TxReceipt): JsonNode =
|
|||
result["type"] = %("0x" & toHex(x.txType.int, 1))
|
||||
|
||||
proc `@@`(x: RejectedTx): JsonNode =
|
||||
result = %{
|
||||
%{
|
||||
"index": %(x.index),
|
||||
"error": %(x.error)
|
||||
}
|
||||
|
||||
proc `@@`(x: DepositRequest): JsonNode =
|
||||
%{
|
||||
"pubkey": @@(x.pubkey),
|
||||
"withdrawalCredentials": @@(x.withdrawalCredentials),
|
||||
"amount": @@(x.amount),
|
||||
"signature": @@(x.signature),
|
||||
"index": @@(x.index),
|
||||
}
|
||||
|
||||
proc `@@`[T](x: seq[T]): JsonNode =
|
||||
result = newJArray()
|
||||
for c in x:
|
||||
|
@ -438,3 +444,7 @@ proc `@@`*(x: ExecutionResult): JsonNode =
|
|||
result["currentExcessBlobGas"] = @@(x.currentExcessBlobGas)
|
||||
if x.blobGasUsed.isSome:
|
||||
result["blobGasUsed"] = @@(x.blobGasUsed)
|
||||
if x.requestsRoot.isSome:
|
||||
result["requestsRoot"] = @@(x.requestsRoot)
|
||||
if x.depositRequests.isSome:
|
||||
result["depositRequests"] = @@(x.depositRequests)
|
||||
|
|
|
@ -22,6 +22,7 @@ import
|
|||
../../nimbus/core/dao,
|
||||
../../nimbus/core/executor/[process_transaction, executor_helpers],
|
||||
../../nimbus/core/eip4844,
|
||||
../../nimbus/core/eip6110,
|
||||
../../nimbus/evm/tracer/json_tracer
|
||||
|
||||
const
|
||||
|
@ -338,6 +339,20 @@ proc exec(ctx: var TransContext,
|
|||
elif ctx.env.parentExcessBlobGas.isSome and ctx.env.parentBlobGasUsed.isSome:
|
||||
result.result.currentExcessBlobGas = Opt.some calcExcessBlobGas(vmState.parent)
|
||||
|
||||
if vmState.com.isPragueOrLater(ctx.env.currentTimestamp):
|
||||
var allLogs: seq[Log]
|
||||
for rec in result.result.receipts:
|
||||
allLogs.add rec.logs
|
||||
let reqs = parseDepositLogs(allLogs).valueOr:
|
||||
raise newError(ErrorEVM, error)
|
||||
result.result.requestsRoot = Opt.some(calcRequestsRoot(reqs))
|
||||
var deposits: seq[DepositRequest]
|
||||
for req in reqs:
|
||||
# all requests produced by parseDepositLogs
|
||||
# should be DepositRequest
|
||||
deposits.add req.deposit
|
||||
result.result.depositRequests = Opt.some(deposits)
|
||||
|
||||
template wrapException(body: untyped) =
|
||||
when wrapExceptionEnabled:
|
||||
try:
|
||||
|
|
|
@ -99,6 +99,8 @@ type
|
|||
withdrawalsRoot*: Opt[Hash256]
|
||||
blobGasUsed*: Opt[uint64]
|
||||
currentExcessBlobGas*: Opt[uint64]
|
||||
requestsRoot*: Opt[Hash256]
|
||||
depositRequests*: Opt[seq[DepositRequest]]
|
||||
|
||||
const
|
||||
ErrorEVM* = 2.T8NExitCode
|
||||
|
|
Loading…
Reference in New Issue