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)
|
initAddress(3)
|
||||||
|
|
||||||
HISTORY_STORAGE_ADDRESS* = hexToByteArray[20]("0x0aae40965e6800cd9b1f4b05ff21581047e3f91e")
|
HISTORY_STORAGE_ADDRESS* = hexToByteArray[20]("0x0aae40965e6800cd9b1f4b05ff21581047e3f91e")
|
||||||
|
DEPOSIT_CONTRACT_ADDRESS* = hexToByteArray[20]("0x00000000219ab540356cbb839cbe05303d7705fa")
|
||||||
# End
|
# 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/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
|
||||||
discard vmState.getAndClearLogEntries()
|
if collectLogs:
|
||||||
|
vmState.allLogs.add vmState.getAndClearLogEntries()
|
||||||
|
else:
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue