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:
andri lim 2024-09-12 10:51:13 +07:00 committed by jangko
parent 8e8258e460
commit 6503d51b44
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
7 changed files with 148 additions and 10 deletions

View File

@ -108,4 +108,5 @@ const
initAddress(3) initAddress(3)
HISTORY_STORAGE_ADDRESS* = hexToByteArray[20]("0x0aae40965e6800cd9b1f4b05ff21581047e3f91e") HISTORY_STORAGE_ADDRESS* = hexToByteArray[20]("0x0aae40965e6800cd9b1f4b05ff21581047e3f91e")
DEPOSIT_CONTRACT_ADDRESS* = hexToByteArray[20]("0x00000000219ab540356cbb839cbe05303d7705fa")
# End # End

76
nimbus/core/eip6110.nim Normal file
View File

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

View File

@ -17,6 +17,7 @@ import
../../evm/state, ../../evm/state,
../../evm/types, ../../evm/types,
../dao, ../dao,
../eip6110,
./calculate_reward, ./calculate_reward,
./executor_helpers, ./executor_helpers,
./process_transaction, ./process_transaction,
@ -32,9 +33,11 @@ proc processTransactions*(
header: BlockHeader, header: BlockHeader,
transactions: seq[Transaction], transactions: seq[Transaction],
skipReceipts = false, skipReceipts = false,
collectLogs = false
): Result[void, string] = ): Result[void, string] =
vmState.receipts.setLen(if skipReceipts: 0 else: transactions.len) vmState.receipts.setLen(if skipReceipts: 0 else: transactions.len)
vmState.cumulativeGasUsed = 0 vmState.cumulativeGasUsed = 0
vmState.allLogs = @[]
for txIndex, tx in transactions: for txIndex, tx in transactions:
var sender: EthAddress var sender: EthAddress
@ -46,9 +49,14 @@ proc processTransactions*(
if skipReceipts: if skipReceipts:
# TODO don't generate logs at all if we're not going to put them in # TODO don't generate logs at all if we're not going to put them in
# receipts # receipts
if collectLogs:
vmState.allLogs.add vmState.getAndClearLogEntries()
else:
discard vmState.getAndClearLogEntries() discard vmState.getAndClearLogEntries()
else: else:
vmState.receipts[txIndex] = vmState.makeReceipt(tx.txType) vmState.receipts[txIndex] = vmState.makeReceipt(tx.txType)
if collectLogs:
vmState.allLogs.add vmState.receipts[txIndex].logs
ok() ok()
proc procBlkPreamble( proc procBlkPreamble(
@ -67,7 +75,13 @@ proc procBlkPreamble(
return err("Mismatched txRoot") return err("Mismatched txRoot")
if com.isPragueOrLater(header.timestamp): 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) ?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 com.isCancunOrLater(header.timestamp):
if header.parentBeaconBlockRoot.isNone: if header.parentBeaconBlockRoot.isNone:
@ -82,7 +96,8 @@ proc procBlkPreamble(
if blk.transactions.len == 0: if blk.transactions.len == 0:
return err("Transactions missing from body") 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: elif blk.transactions.len > 0:
return err("Transactions in block with empty txRoot") return err("Transactions in block with empty txRoot")
@ -121,8 +136,11 @@ proc procBlkPreamble(
ok() ok()
proc procBlkEpilogue( proc procBlkEpilogue(
vmState: BaseVMState, header: BlockHeader, skipValidation: bool, skipReceipts: bool vmState: BaseVMState, blk: EthBlock, skipValidation: bool, skipReceipts: bool
): Result[void, string] = ): Result[void, string] =
template header(): BlockHeader =
blk.header
# Reward beneficiary # Reward beneficiary
vmState.mutateStateDB: vmState.mutateStateDB:
if vmState.collectWitnessData: if vmState.collectWitnessData:
@ -160,6 +178,21 @@ proc procBlkEpilogue(
expected = header.receiptsRoot expected = header.receiptsRoot
return err("receiptRoot mismatch") 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() ok()
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -180,7 +213,7 @@ proc processBlock*(
if vmState.com.consensus == ConsensusType.POW: if vmState.com.consensus == ConsensusType.POW:
vmState.calculateReward(blk.header, blk.uncles) vmState.calculateReward(blk.header, blk.uncles)
?vmState.procBlkEpilogue(blk.header, skipValidation, skipReceipts) ?vmState.procBlkEpilogue(blk, skipValidation, skipReceipts)
ok() ok()

View File

@ -67,6 +67,7 @@ type
cumulativeGasUsed*: GasInt cumulativeGasUsed*: GasInt
gasCosts* : GasCosts gasCosts* : GasCosts
blobGasUsed* : uint64 blobGasUsed* : uint64
allLogs* : seq[Log] # EIP-6110
Computation* = ref object Computation* = ref object
# The execution computation # The execution computation

View File

@ -346,10 +346,7 @@ proc `@@`*(x: Blob): JsonNode =
proc `@@`(x: bool): JsonNode = proc `@@`(x: bool): JsonNode =
%(if x: "0x1" else: "0x0") %(if x: "0x1" else: "0x0")
proc `@@`(x: EthAddress): JsonNode = proc `@@`(x: openArray[byte]): JsonNode =
%("0x" & x.toHex)
proc `@@`(x: Topic): JsonNode =
%("0x" & x.toHex) %("0x" & x.toHex)
proc toJson(x: Table[UInt256, UInt256]): JsonNode = proc toJson(x: Table[UInt256, UInt256]): JsonNode =
@ -378,7 +375,7 @@ proc `@@`(x: BloomFilter): JsonNode =
%("0x" & toHex[256](x)) %("0x" & toHex[256](x))
proc `@@`(x: Log): JsonNode = proc `@@`(x: Log): JsonNode =
result = %{ %{
"address": @@(x.address), "address": @@(x.address),
"topics" : @@(x.topics), "topics" : @@(x.topics),
"data" : @@(x.data) "data" : @@(x.data)
@ -401,11 +398,20 @@ proc `@@`(x: TxReceipt): JsonNode =
result["type"] = %("0x" & toHex(x.txType.int, 1)) result["type"] = %("0x" & toHex(x.txType.int, 1))
proc `@@`(x: RejectedTx): JsonNode = proc `@@`(x: RejectedTx): JsonNode =
result = %{ %{
"index": %(x.index), "index": %(x.index),
"error": %(x.error) "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 = proc `@@`[T](x: seq[T]): JsonNode =
result = newJArray() result = newJArray()
for c in x: for c in x:
@ -438,3 +444,7 @@ proc `@@`*(x: ExecutionResult): JsonNode =
result["currentExcessBlobGas"] = @@(x.currentExcessBlobGas) result["currentExcessBlobGas"] = @@(x.currentExcessBlobGas)
if x.blobGasUsed.isSome: if x.blobGasUsed.isSome:
result["blobGasUsed"] = @@(x.blobGasUsed) result["blobGasUsed"] = @@(x.blobGasUsed)
if x.requestsRoot.isSome:
result["requestsRoot"] = @@(x.requestsRoot)
if x.depositRequests.isSome:
result["depositRequests"] = @@(x.depositRequests)

View File

@ -22,6 +22,7 @@ import
../../nimbus/core/dao, ../../nimbus/core/dao,
../../nimbus/core/executor/[process_transaction, executor_helpers], ../../nimbus/core/executor/[process_transaction, executor_helpers],
../../nimbus/core/eip4844, ../../nimbus/core/eip4844,
../../nimbus/core/eip6110,
../../nimbus/evm/tracer/json_tracer ../../nimbus/evm/tracer/json_tracer
const const
@ -338,6 +339,20 @@ proc exec(ctx: var TransContext,
elif ctx.env.parentExcessBlobGas.isSome and ctx.env.parentBlobGasUsed.isSome: elif ctx.env.parentExcessBlobGas.isSome and ctx.env.parentBlobGasUsed.isSome:
result.result.currentExcessBlobGas = Opt.some calcExcessBlobGas(vmState.parent) 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) = template wrapException(body: untyped) =
when wrapExceptionEnabled: when wrapExceptionEnabled:
try: try:

View File

@ -99,6 +99,8 @@ type
withdrawalsRoot*: Opt[Hash256] withdrawalsRoot*: Opt[Hash256]
blobGasUsed*: Opt[uint64] blobGasUsed*: Opt[uint64]
currentExcessBlobGas*: Opt[uint64] currentExcessBlobGas*: Opt[uint64]
requestsRoot*: Opt[Hash256]
depositRequests*: Opt[seq[DepositRequest]]
const const
ErrorEVM* = 2.T8NExitCode ErrorEVM* = 2.T8NExitCode