144 lines
5.5 KiB
Nim
144 lines
5.5 KiB
Nim
# Nimbus
|
|
# Copyright (c) 2018 Status Research & Development GmbH
|
|
# Licensed under either of
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
# at your option.
|
|
# This file may not be copied, modified, or distributed except according to
|
|
# those terms.
|
|
|
|
import
|
|
std/[typetraits, times],
|
|
stew/[objects, results],
|
|
json_rpc/[rpcserver, errors],
|
|
web3/[conversions, engine_api_types],
|
|
eth/[trie, rlp, common, trie/db],
|
|
".."/db/db_chain,
|
|
".."/p2p/chain/[chain_desc, persist_blocks],
|
|
".."/[sealer, utils, constants]
|
|
|
|
import eth/common/eth_types except BlockHeader
|
|
type EthBlockHeader = eth_types.BlockHeader
|
|
|
|
# TODO move this to stew/objects
|
|
template newClone*[T: not ref](x: T): ref T =
|
|
# TODO not nil in return type: https://github.com/nim-lang/Nim/issues/14146
|
|
# TODO use only when x is a function call that returns a new instance!
|
|
let res = new typeof(x) # TODO safe to do noinit here?
|
|
res[] = x
|
|
res
|
|
|
|
template asEthHash*(hash: engine_api_types.BlockHash): Hash256 =
|
|
Hash256(data: distinctBase(hash))
|
|
|
|
template unsafeQuantityToInt64(q: Quantity): int64 =
|
|
int64 q
|
|
|
|
proc calcRootHashRlp*(items: openArray[seq[byte]]): Hash256 =
|
|
var tr = initHexaryTrie(newMemoryDB())
|
|
for i, t in items:
|
|
tr.put(rlp.encode(i), t)
|
|
return tr.rootHash()
|
|
|
|
proc toBlockHeader(payload: ExecutionPayloadV1): eth_types.BlockHeader =
|
|
discard payload.random # TODO: What should this be used for?
|
|
|
|
let transactions = seq[seq[byte]](payload.transactions)
|
|
let txRoot = calcRootHashRlp(transactions)
|
|
|
|
EthBlockHeader(
|
|
parentHash : payload.parentHash.asEthHash,
|
|
ommersHash : EMPTY_UNCLE_HASH,
|
|
coinbase : EthAddress payload.feeRecipient,
|
|
stateRoot : payload.stateRoot.asEthHash,
|
|
txRoot : txRoot,
|
|
receiptRoot : payload.receiptsRoot.asEthHash,
|
|
bloom : distinctBase(payload.logsBloom),
|
|
difficulty : default(DifficultyInt),
|
|
blockNumber : payload.blockNumber.distinctBase.u256,
|
|
gasLimit : payload.gasLimit.unsafeQuantityToInt64,
|
|
gasUsed : payload.gasUsed.unsafeQuantityToInt64,
|
|
timestamp : fromUnix payload.timestamp.unsafeQuantityToInt64,
|
|
extraData : distinctBase payload.extraData,
|
|
mixDigest : default(Hash256),
|
|
nonce : default(BlockNonce),
|
|
fee : some payload.baseFeePerGas
|
|
)
|
|
|
|
proc toBlockBody(payload: ExecutionPayloadV1): BlockBody =
|
|
# TODO the transactions from the payload have to be converted here
|
|
discard payload.transactions
|
|
|
|
proc setupEngineAPI*(
|
|
sealingEngine: SealingEngineRef,
|
|
server: RpcServer) =
|
|
|
|
var payloadsInstance = newClone(newSeq[ExecutionPayloadV1]())
|
|
template payloads: auto = payloadsInstance[]
|
|
|
|
# https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.5/src/engine/specification.md#engine_getpayloadv1
|
|
server.rpc("engine_getPayloadV1") do(payloadIdBytes: FixedBytes[8]) -> ExecutionPayloadV1:
|
|
let payloadId = uint64.fromBytesBE(distinctBase payloadIdBytes)
|
|
if payloadId > payloads.len.uint64:
|
|
raise (ref InvalidRequest)(code: engineApiUnknownPayload, msg: "Unknown payload")
|
|
return payloads[int payloadId]
|
|
|
|
# https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.5/src/engine/specification.md#engine_executepayloadv1
|
|
server.rpc("engine_executePayloadV1") do(payload: ExecutionPayloadV1) -> ExecutePayloadResponse:
|
|
# TODO
|
|
if payload.transactions.len > 0:
|
|
# Give us a break, a block with transcations? instructions to execute?
|
|
# Nah, we are syncing!
|
|
return ExecutePayloadResponse(status: PayloadExecutionStatus.syncing)
|
|
|
|
# TODO check whether we are syncing
|
|
|
|
let
|
|
headers = [payload.toBlockHeader]
|
|
bodies = [payload.toBlockBody]
|
|
|
|
if rlpHash(headers[0]) != payload.blockHash.asEthHash:
|
|
return ExecutePayloadResponse(status: PayloadExecutionStatus.invalid,
|
|
validationError: some "payload root doesn't match its contents")
|
|
|
|
if sealingEngine.chain.persistBlocks(headers, bodies) != ValidationResult.OK:
|
|
# TODO Provide validationError and latestValidHash
|
|
return ExecutePayloadResponse(status: PayloadExecutionStatus.invalid)
|
|
|
|
return ExecutePayloadResponse(status: PayloadExecutionStatus.valid,
|
|
latestValidHash: some payload.blockHash)
|
|
|
|
# https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.5/src/engine/specification.md#engine_forkchoiceupdatedv1
|
|
server.rpc("engine_forkchoiceUpdatedV1") do(
|
|
update: ForkchoiceStateV1,
|
|
payloadAttributes: Option[PayloadAttributesV1]) -> ForkchoiceUpdatedResponse:
|
|
let
|
|
db = sealingEngine.chain.db
|
|
newHead = update.headBlockHash.asEthHash
|
|
|
|
# TODO Use the finalized block information to prune any alterantive
|
|
# histories that are no longer relevant
|
|
discard update.finalizedBlockHash
|
|
|
|
# TODO Check whether we are syncing
|
|
|
|
if not db.setHead(newHead):
|
|
return ForkchoiceUpdatedResponse(status: ForkchoiceUpdatedStatus.syncing)
|
|
|
|
if payloadAttributes.isSome:
|
|
let payloadId = uint64 payloads.len
|
|
|
|
var payload: ExecutionPayloadV1
|
|
let generatePayloadRes = sealingEngine.generateExecutionPayload(
|
|
payloadAttributes.get,
|
|
payload)
|
|
if generatePayloadRes.isErr:
|
|
raise newException(CatchableError, generatePayloadRes.error)
|
|
|
|
payloads.add payload
|
|
|
|
return ForkchoiceUpdatedResponse(status: ForkchoiceUpdatedStatus.success,
|
|
payloadId: some payloadId.toBytesBE.PayloadID)
|
|
else:
|
|
return ForkchoiceUpdatedResponse(status: ForkchoiceUpdatedStatus.success)
|