Refactor engine api and cleanup web3 types conversion
This commit is contained in:
parent
7d113b839f
commit
7a1fe5707c
|
@ -3,6 +3,7 @@ import
|
|||
test_env,
|
||||
chronicles,
|
||||
nimcrypto/[hmac],
|
||||
web3/engine_api_types,
|
||||
json_rpc/[rpcclient],
|
||||
./types
|
||||
|
||||
|
|
|
@ -5,8 +5,9 @@ import
|
|||
stew/[byteutils, endians2],
|
||||
eth/common, chronos,
|
||||
json_rpc/rpcclient,
|
||||
../../../nimbus/rpc/merge/mergeutils,
|
||||
../../../nimbus/rpc/execution_types,
|
||||
../../../nimbus/beacon/execution_types,
|
||||
../../../nimbus/beacon/web3_eth_conv,
|
||||
../../../nimbus/beacon/payload_conv,
|
||||
../../../nimbus/[constants],
|
||||
../../../nimbus/common as nimbus_common,
|
||||
./engine_client
|
||||
|
@ -322,11 +323,12 @@ proc getNextPayload*(cl: CLMocker): bool =
|
|||
cl.latestBlockValue = x.blockValue
|
||||
cl.latestBlobsBundle = x.blobsBundle
|
||||
|
||||
let header = toBlockHeader(cl.latestPayloadBuilt)
|
||||
let blockHash = BlockHash header.blockHash.data
|
||||
let beaconRoot = ethHash cl.latestPayloadAttributes.parentBeaconblockRoot
|
||||
let header = blockHeader(cl.latestPayloadBuilt, beaconRoot)
|
||||
let blockHash = w3Hash header.blockHash
|
||||
if blockHash != cl.latestPayloadBuilt.blockHash:
|
||||
error "CLMocker: getNextPayload blockHash mismatch",
|
||||
expected=cl.latestPayloadBuilt.blockHash.toHex,
|
||||
expected=cl.latestPayloadBuilt.blockHash,
|
||||
get=blockHash.toHex
|
||||
return false
|
||||
|
||||
|
@ -338,19 +340,19 @@ proc getNextPayload*(cl: CLMocker): bool =
|
|||
|
||||
if cl.latestPayloadBuilt.feeRecipient != cl.latestPayloadAttributes.suggestedFeeRecipient:
|
||||
error "CLMocker: Incorrect SuggestedFeeRecipient on payload built",
|
||||
expect=cl.latestPayloadBuilt.feeRecipient.toHex,
|
||||
get=cl.latestPayloadAttributes.suggestedFeeRecipient.toHex
|
||||
expect=cl.latestPayloadBuilt.feeRecipient,
|
||||
get=cl.latestPayloadAttributes.suggestedFeeRecipient
|
||||
return false
|
||||
|
||||
if cl.latestPayloadBuilt.prevRandao != cl.latestPayloadAttributes.prevRandao:
|
||||
error "CLMocker: Incorrect PrevRandao on payload built",
|
||||
expect=cl.latestPayloadBuilt.prevRandao.toHex,
|
||||
get=cl.latestPayloadAttributes.prevRandao.toHex
|
||||
expect=cl.latestPayloadBuilt.prevRandao,
|
||||
get=cl.latestPayloadAttributes.prevRandao
|
||||
return false
|
||||
|
||||
if cl.latestPayloadBuilt.parentHash != BlockHash cl.latestHeader.blockHash.data:
|
||||
error "CLMocker: Incorrect ParentHash on payload built",
|
||||
expect=cl.latestPayloadBuilt.parentHash.toHex,
|
||||
expect=cl.latestPayloadBuilt.parentHash,
|
||||
get=cl.latestHeader.blockHash
|
||||
return false
|
||||
|
||||
|
|
|
@ -2,15 +2,14 @@ import
|
|||
std/tables,
|
||||
stew/byteutils,
|
||||
chronicles,
|
||||
eth/common,
|
||||
nimcrypto/sysrand,
|
||||
chronos,
|
||||
".."/[test_env, helper, types],
|
||||
../../../nimbus/transaction,
|
||||
../../../nimbus/rpc/rpc_types,
|
||||
../../../nimbus/rpc/merge/mergeutils
|
||||
|
||||
import eth/common/eth_types as common_eth_types
|
||||
type Hash256 = common_eth_types.Hash256
|
||||
../../../nimbus/beacon/web3_eth_conv,
|
||||
../../../nimbus/beacon/execution_types
|
||||
|
||||
type
|
||||
EngineSpec* = ref object of BaseSpec
|
||||
|
@ -23,7 +22,7 @@ type
|
|||
const
|
||||
prevRandaoContractAddr = hexToByteArray[20]("0000000000000000000000000000000000000316")
|
||||
|
||||
template testNP(res, cond: untyped, validHash = none(Hash256)) =
|
||||
template testNP(res, cond: untyped, validHash = none(common.Hash256)) =
|
||||
testCond res.isOk
|
||||
let s = res.get()
|
||||
testCond s.status == PayloadExecutionStatus.cond:
|
||||
|
@ -31,7 +30,7 @@ template testNP(res, cond: untyped, validHash = none(Hash256)) =
|
|||
testCond s.latestValidHash == validHash:
|
||||
error "Unexpected NewPayload latestValidHash", expect=validHash, get=s.latestValidHash
|
||||
|
||||
template testNPEither(res, cond: untyped, validHash = none(Hash256)) =
|
||||
template testNPEither(res, cond: untyped, validHash = none(common.Hash256)) =
|
||||
testCond res.isOk
|
||||
let s = res.get()
|
||||
testCond s.status in cond:
|
||||
|
@ -39,13 +38,13 @@ template testNPEither(res, cond: untyped, validHash = none(Hash256)) =
|
|||
testCond s.latestValidHash == validHash:
|
||||
error "Unexpected NewPayload latestValidHash", expect=validHash, get=s.latestValidHash
|
||||
|
||||
template testLatestHeader(client: untyped, expectedHash: BlockHash) =
|
||||
var lastHeader: EthBlockHeader
|
||||
template testLatestHeader(client: untyped, expectedHash: Web3Hash) =
|
||||
var lastHeader: common.BlockHeader
|
||||
var hRes = client.latestHeader(lastHeader)
|
||||
testCond hRes.isOk:
|
||||
error "unable to get latest header", msg=hRes.error
|
||||
|
||||
let lastHash = BlockHash lastHeader.blockHash.data
|
||||
let lastHash = w3Hash lastHeader.blockHash
|
||||
# Latest block header available via Eth RPC should not have changed at this point
|
||||
testCond lastHash == expectedHash:
|
||||
error "latest block header incorrect",
|
||||
|
@ -80,7 +79,7 @@ proc invalidTerminalBlockForkchoiceUpdated*(t: TestEnv): bool =
|
|||
# either obtained from the Payload validation process or as a result of
|
||||
# validating a PoW block referenced by forkchoiceState.headBlockHash
|
||||
|
||||
testFCU(res, invalid, some(Hash256()))
|
||||
testFCU(res, invalid, some(common.Hash256()))
|
||||
# ValidationError is not validated since it can be either null or a string message
|
||||
|
||||
# Check that PoW chain progresses
|
||||
|
@ -122,7 +121,7 @@ proc invalidTerminalBlockNewPayload(t: TestEnv): TestStatus =
|
|||
# Execution specification:
|
||||
# {status: INVALID, latestValidHash=0x00..00}
|
||||
# if terminal block conditions are not satisfied
|
||||
testNP(res, invalid, some(Hash256()))
|
||||
testNP(res, invalid, some(common.Hash256()))
|
||||
|
||||
# Check that PoW chain progresses
|
||||
testCond t.verifyPoWProgress(t.gHeader.blockHash)
|
||||
|
@ -133,7 +132,7 @@ proc unknownHeadBlockHash(t: TestEnv): TestStatus =
|
|||
let ok = waitFor t.clMock.waitForTTD()
|
||||
testCond ok
|
||||
|
||||
var randomHash: Hash256
|
||||
var randomHash: common.Hash256
|
||||
testCond randomBytes(randomHash.data) == 32
|
||||
|
||||
let clMock = t.clMock
|
||||
|
@ -180,7 +179,7 @@ proc unknownSafeBlockHash(t: TestEnv): TestStatus =
|
|||
# Run test after a new payload has been broadcast
|
||||
onNewPayloadBroadcast: proc(): bool =
|
||||
# Generate a random SafeBlock hash
|
||||
var randomSafeBlockHash: Hash256
|
||||
var randomSafeBlockHash: common.Hash256
|
||||
doAssert randomBytes(randomSafeBlockHash.data) == 32
|
||||
|
||||
# Send forkchoiceUpdated with random SafeBlockHash
|
||||
|
@ -213,7 +212,7 @@ proc unknownFinalizedBlockHash(t: TestEnv): TestStatus =
|
|||
# Run test after a new payload has been broadcast
|
||||
onNewPayloadBroadcast: proc(): bool =
|
||||
# Generate a random SafeBlock hash
|
||||
var randomFinalBlockHash: Hash256
|
||||
var randomFinalBlockHash: common.Hash256
|
||||
doAssert randomBytes(randomFinalBlockHash.data) == 32
|
||||
|
||||
# Send forkchoiceUpdated with random SafeBlockHash
|
||||
|
@ -339,13 +338,13 @@ template invalidPayloadAttributesGen(procname: untyped, syncingCond: bool) =
|
|||
produceBlockRes = clMock.produceSingleBlock(BlockProcessCallbacks(
|
||||
onNewPayloadBroadcast: proc(): bool =
|
||||
# Try to apply the new payload with invalid attributes
|
||||
var blockHash: Hash256
|
||||
var blockHash: common.Hash256
|
||||
when syncingCond:
|
||||
# Setting a random hash will put the client into `SYNCING`
|
||||
doAssert randomBytes(blockHash.data) == 32
|
||||
else:
|
||||
# Set the block hash to the next payload that was broadcasted
|
||||
blockHash = hash256(clMock.latestPayloadBuilt.blockHash)
|
||||
blockHash = common.Hash256(clMock.latestPayloadBuilt.blockHash)
|
||||
|
||||
let fcu = ForkchoiceStateV1(
|
||||
headBlockHash: Web3BlockHash blockHash.data,
|
||||
|
@ -400,7 +399,7 @@ proc preTTDFinalizedBlockHash(t: TestEnv): TestStatus =
|
|||
clMock = t.clMock
|
||||
|
||||
var res = client.forkchoiceUpdatedV1(forkchoiceState)
|
||||
testFCU(res, invalid, some(Hash256()))
|
||||
testFCU(res, invalid, some(common.Hash256()))
|
||||
|
||||
res = client.forkchoiceUpdatedV1(clMock.latestForkchoice)
|
||||
testFCU(res, valid)
|
||||
|
@ -433,7 +432,7 @@ proc preTTDFinalizedBlockHash(t: TestEnv): TestStatus =
|
|||
|
||||
type
|
||||
Shadow = ref object
|
||||
hash: Hash256
|
||||
hash: common.Hash256
|
||||
|
||||
template badHashOnNewPayloadGen(procname: untyped, syncingCond: bool, sideChain: bool) =
|
||||
proc procName(t: TestEnv): TestStatus =
|
||||
|
@ -455,7 +454,7 @@ template badHashOnNewPayloadGen(procname: untyped, syncingCond: bool, sideChain:
|
|||
onGetPayload: proc(): bool =
|
||||
# Alter hash on the payload and send it to client, should produce an error
|
||||
var alteredPayload = clMock.latestPayloadBuilt
|
||||
var invalidPayloadHash = hash256(alteredPayload.blockHash)
|
||||
var invalidPayloadHash = common.Hash256(alteredPayload.blockHash)
|
||||
let lastByte = int invalidPayloadHash.data[^1]
|
||||
invalidPayloadHash.data[^1] = byte(not lastByte)
|
||||
shadow.hash = invalidPayloadHash
|
||||
|
@ -468,7 +467,7 @@ template badHashOnNewPayloadGen(procname: untyped, syncingCond: bool, sideChain:
|
|||
alteredPayload.parentHash = Web3BlockHash clMock.latestHeader.parentHash.data
|
||||
elif syncingCond:
|
||||
# We need to send an fcU to put the client in SYNCING state.
|
||||
var randomHeadBlock: Hash256
|
||||
var randomHeadBlock: common.Hash256
|
||||
doAssert randomBytes(randomHeadBlock.data) == 32
|
||||
|
||||
let latestHeaderHash = clMock.latestHeader.blockHash
|
||||
|
@ -590,12 +589,12 @@ proc invalidTransitionPayload(t: TestEnv): TestStatus =
|
|||
|
||||
let res = client.newPayloadV1(alteredPayload)
|
||||
let cond = {PayloadExecutionStatus.invalid, PayloadExecutionStatus.accepted}
|
||||
testNPEither(res, cond, some(Hash256()))
|
||||
testNPEither(res, cond, some(common.Hash256()))
|
||||
|
||||
let rr = client.forkchoiceUpdatedV1(
|
||||
ForkchoiceStateV1(headBlockHash: alteredPayload.blockHash)
|
||||
)
|
||||
testFCU(rr, invalid, some(Hash256()))
|
||||
testFCU(rr, invalid, some(common.Hash256()))
|
||||
|
||||
testLatestHeader(client, clMock.latestExecutedPayload.blockHash)
|
||||
return true
|
||||
|
@ -648,7 +647,7 @@ template invalidPayloadTestCaseGen(procName: untyped, payloadField: InvalidPaylo
|
|||
return false
|
||||
|
||||
let alteredPayload = generateInvalidPayload(clMock.latestPayloadBuilt, payloadField, t.vaultKey)
|
||||
invalidPayload.hash = hash256(alteredPayload.blockHash)
|
||||
invalidPayload.hash = common.Hash256(alteredPayload.blockHash)
|
||||
|
||||
# Depending on the field we modified, we expect a different status
|
||||
let rr = client.newPayloadV1(alteredPayload)
|
||||
|
@ -715,7 +714,7 @@ template invalidPayloadTestCaseGen(procName: untyped, payloadField: InvalidPaylo
|
|||
|
||||
# Finally, attempt to fetch the invalid payload using the JSON-RPC endpoint
|
||||
var header: rpc_types.BlockHeader
|
||||
let rp = client.headerByHash(alteredPayload.blockHash.hash256, header)
|
||||
let rp = client.headerByHash(alteredPayload.blockHash.common.Hash256, header)
|
||||
rp.isErr
|
||||
))
|
||||
|
||||
|
@ -872,7 +871,7 @@ template invalidMissingAncestorReOrgGen(procName: untyped,
|
|||
onGetPayload: proc(): bool =
|
||||
# Insert extraData to ensure we deviate from the main payload, which contains empty extradata
|
||||
var alternatePayload = customizePayload(clMock.latestPayloadBuilt, CustomPayload(
|
||||
parentHash: some(shadow.altChainPayloads[^1].blockHash.hash256),
|
||||
parentHash: some(shadow.altChainPayloads[^1].blockHash.common.Hash256),
|
||||
extraData: some(@[1.byte]),
|
||||
))
|
||||
|
||||
|
@ -913,7 +912,7 @@ template invalidMissingAncestorReOrgGen(procName: untyped,
|
|||
if i == invalid_index:
|
||||
# If this is the first payload after the common ancestor, and this is the payload we invalidated,
|
||||
# then we have all the information to determine that this payload is invalid.
|
||||
testNP(rr, invalid, some(shadow.altChainPayloads[i-1].blockHash.hash256))
|
||||
testNP(rr, invalid, some(shadow.altChainPayloads[i-1].blockHash.common.Hash256))
|
||||
elif i > invalid_index:
|
||||
# We have already sent the invalid payload, but the client could've discarded it.
|
||||
# In reality the CL will not get to this point because it will have already received the `INVALID`
|
||||
|
@ -922,7 +921,7 @@ template invalidMissingAncestorReOrgGen(procName: untyped,
|
|||
testNPEither(rr, cond)
|
||||
else:
|
||||
# This is one of the payloads before the invalid one, therefore is valid.
|
||||
let latestValidHash = some(shadow.altChainPayloads[i].blockHash.hash256)
|
||||
let latestValidHash = some(shadow.altChainPayloads[i].blockHash.common.Hash256)
|
||||
testNP(rr, valid, latestValidHash)
|
||||
testFCU(rs, valid, latestValidHash)
|
||||
|
||||
|
@ -989,7 +988,7 @@ proc blockStatusSafeBlock(t: TestEnv): TestStatus =
|
|||
let client = t.rpcClient
|
||||
|
||||
# On PoW mode, `safe` tag shall return error.
|
||||
var header: EthBlockHeader
|
||||
var header: common.BlockHeader
|
||||
var rr = client.namedHeader("safe", header)
|
||||
testCond rr.isErr
|
||||
|
||||
|
@ -1004,10 +1003,10 @@ proc blockStatusSafeBlock(t: TestEnv): TestStatus =
|
|||
let pbres = clMock.produceBlocks(3, BlockProcessCallbacks(
|
||||
# Run test after a forkchoice with new SafeBlockHash has been broadcasted
|
||||
onSafeBlockChange: proc(): bool =
|
||||
var header: EthBlockHeader
|
||||
var header: common.BlockHeader
|
||||
let rr = client.namedHeader("safe", header)
|
||||
testCond rr.isOk
|
||||
let safeBlockHash = hash256(clMock.latestForkchoice.safeBlockHash)
|
||||
let safeBlockHash = common.Hash256(clMock.latestForkchoice.safeBlockHash)
|
||||
header.blockHash == safeBlockHash
|
||||
))
|
||||
|
||||
|
@ -1020,7 +1019,7 @@ proc blockStatusFinalizedBlock(t: TestEnv): TestStatus =
|
|||
let client = t.rpcClient
|
||||
|
||||
# On PoW mode, `finalized` tag shall return error.
|
||||
var header: EthBlockHeader
|
||||
var header: common.BlockHeader
|
||||
var rr = client.namedHeader("finalized", header)
|
||||
testCond rr.isErr
|
||||
|
||||
|
@ -1035,10 +1034,10 @@ proc blockStatusFinalizedBlock(t: TestEnv): TestStatus =
|
|||
let pbres = clMock.produceBlocks(3, BlockProcessCallbacks(
|
||||
# Run test after a forkchoice with new FinalizedBlockHash has been broadcasted
|
||||
onFinalizedBlockChange: proc(): bool =
|
||||
var header: EthBlockHeader
|
||||
var header: common.BlockHeader
|
||||
let rr = client.namedHeader("finalized", header)
|
||||
testCond rr.isOk
|
||||
let finalizedBlockHash = hash256(clMock.latestForkchoice.finalizedBlockHash)
|
||||
let finalizedBlockHash = common.Hash256(clMock.latestForkchoice.finalizedBlockHash)
|
||||
header.blockHash == finalizedBlockHash
|
||||
))
|
||||
|
||||
|
@ -1061,7 +1060,7 @@ proc blockStatusReorg(t: TestEnv): TestStatus =
|
|||
# Run test after a forkchoice with new HeadBlockHash has been broadcasted
|
||||
onForkchoiceBroadcast: proc(): bool =
|
||||
# Verify the client is serving the latest HeadBlock
|
||||
var currHeader: EthBlockHeader
|
||||
var currHeader: common.BlockHeader
|
||||
var hRes = client.latestHeader(currHeader)
|
||||
if hRes.isErr:
|
||||
error "unable to get latest header", msg=hRes.error
|
||||
|
@ -1190,7 +1189,7 @@ proc multipleNewCanonicalPayloads(t: TestEnv): TestStatus =
|
|||
onGetPayload: proc(): bool =
|
||||
let payloadCount = 80
|
||||
let basePayload = toExecutableData(clMock.latestPayloadBuilt)
|
||||
var newPrevRandao: Hash256
|
||||
var newPrevRandao: common.Hash256
|
||||
|
||||
# Fabricate and send multiple new payloads by changing the PrevRandao field
|
||||
for i in 0..<payloadCount:
|
||||
|
@ -1322,7 +1321,7 @@ proc reorgBackFromSyncing(t: TestEnv): TestStatus =
|
|||
let executableData = toExecutableData(clMock.latestPayloadBuilt)
|
||||
let altPayload = customizePayload(executableData,
|
||||
CustomPayload(
|
||||
parentHash: some(altParentHash.hash256),
|
||||
parentHash: some(altParentHash.common.Hash256),
|
||||
extraData: some(@[0x01.byte]),
|
||||
))
|
||||
|
||||
|
@ -1370,7 +1369,7 @@ proc reorgBackFromSyncing(t: TestEnv): TestStatus =
|
|||
type
|
||||
TxReorgShadow = ref object
|
||||
noTxnPayload: ExecutionPayloadV1
|
||||
txHash: Hash256
|
||||
txHash: common.Hash256
|
||||
|
||||
proc transactionReorg(t: TestEnv): TestStatus =
|
||||
result = TestStatus.OK
|
||||
|
@ -1446,7 +1445,7 @@ proc transactionReorg(t: TestEnv): TestStatus =
|
|||
return false
|
||||
|
||||
let rz = client.newPayloadV1(shadow.noTxnPayload)
|
||||
testNP(rz, valid, some(hash256(shadow.noTxnPayload.blockHash)))
|
||||
testNP(rz, valid, some(common.Hash256(shadow.noTxnPayload.blockHash)))
|
||||
|
||||
let rx = client.forkchoiceUpdatedV1(ForkchoiceStateV1(
|
||||
headBlockHash: shadow.noTxnPayload.blockHash,
|
||||
|
@ -1469,7 +1468,7 @@ proc transactionReorg(t: TestEnv): TestStatus =
|
|||
|
||||
testCond pbres
|
||||
|
||||
proc testCondPrevRandaoValue(t: TestEnv, expectedPrevRandao: Hash256, blockNumber: uint64): bool =
|
||||
proc testCondPrevRandaoValue(t: TestEnv, expectedPrevRandao: common.Hash256, blockNumber: uint64): bool =
|
||||
let storageKey = blockNumber.u256
|
||||
let client = t.rpcClient
|
||||
|
||||
|
@ -1478,7 +1477,7 @@ proc testCondPrevRandaoValue(t: TestEnv, expectedPrevRandao: Hash256, blockNumbe
|
|||
error "Unable to get storage", msg=res.error
|
||||
return false
|
||||
|
||||
let opcodeValueAtBlock = Hash256(data: res.get().toBytesBE)
|
||||
let opcodeValueAtBlock = common.Hash256(data: res.get().toBytesBE)
|
||||
if opcodeValueAtBlock != expectedPrevRandao:
|
||||
error "Storage does not match prevRandao",
|
||||
expected=expectedPrevRandao.data,
|
||||
|
@ -1509,7 +1508,7 @@ proc sidechainReorg(t: TestEnv): TestStatus =
|
|||
onNewPayloadBroadcast: proc(): bool =
|
||||
# At this point the clMocker has a payload that will result in a specific outcome,
|
||||
# we can produce an alternative payload, send it, fcU to it, and verify the changes
|
||||
var alternativePrevRandao: Hash256
|
||||
var alternativePrevRandao: common.Hash256
|
||||
doAssert randomBytes(alternativePrevRandao.data) == 32
|
||||
|
||||
let timestamp = Quantity toUnix(clMock.latestHeader.timestamp + 1.seconds)
|
||||
|
|
|
@ -7,7 +7,7 @@ import
|
|||
../../../tests/rpcclient/eth_api,
|
||||
../../../premix/parser,
|
||||
../../../nimbus/rpc/hexstrings,
|
||||
../../../nimbus/rpc/execution_types
|
||||
../../../nimbus/beacon/execution_types
|
||||
|
||||
import web3/engine_api as web3_engine_api
|
||||
|
||||
|
|
|
@ -5,53 +5,48 @@ import
|
|||
json_rpc/[rpcclient],
|
||||
../../../nimbus/transaction,
|
||||
../../../nimbus/utils/utils,
|
||||
../../../nimbus/rpc/execution_types,
|
||||
./types
|
||||
|
||||
import eth/common/eth_types as common_eth_types
|
||||
type
|
||||
Hash256 = common_eth_types.Hash256
|
||||
EthBlockHeader = common_eth_types.BlockHeader
|
||||
../../../nimbus/beacon/execution_types,
|
||||
../../../nimbus/beacon/web3_eth_conv
|
||||
|
||||
type
|
||||
ExecutableData* = object
|
||||
parentHash* : Hash256
|
||||
parentHash* : common.Hash256
|
||||
feeRecipient* : EthAddress
|
||||
stateRoot* : Hash256
|
||||
receiptsRoot* : Hash256
|
||||
stateRoot* : common.Hash256
|
||||
receiptsRoot* : common.Hash256
|
||||
logsBloom* : BloomFilter
|
||||
prevRandao* : Hash256
|
||||
prevRandao* : common.Hash256
|
||||
number* : uint64
|
||||
gasLimit* : GasInt
|
||||
gasUsed* : GasInt
|
||||
timestamp* : EthTime
|
||||
extraData* : common.Blob
|
||||
baseFeePerGas*: UInt256
|
||||
blockHash* : Hash256
|
||||
blockHash* : common.Hash256
|
||||
transactions* : seq[Transaction]
|
||||
withdrawals* : Option[seq[Withdrawal]]
|
||||
blobGasUsed* : Option[uint64]
|
||||
excessBlobGas*: Option[uint64]
|
||||
|
||||
CustomPayload* = object
|
||||
parentHash* : Option[Hash256]
|
||||
parentHash* : Option[common.Hash256]
|
||||
feeRecipient* : Option[EthAddress]
|
||||
stateRoot* : Option[Hash256]
|
||||
receiptsRoot* : Option[Hash256]
|
||||
stateRoot* : Option[common.Hash256]
|
||||
receiptsRoot* : Option[common.Hash256]
|
||||
logsBloom* : Option[BloomFilter]
|
||||
prevRandao* : Option[Hash256]
|
||||
prevRandao* : Option[common.Hash256]
|
||||
number* : Option[uint64]
|
||||
gasLimit* : Option[GasInt]
|
||||
gasUsed* : Option[GasInt]
|
||||
timestamp* : Option[EthTime]
|
||||
extraData* : Option[common.Blob]
|
||||
baseFeePerGas*: Option[UInt256]
|
||||
blockHash* : Option[Hash256]
|
||||
blockHash* : Option[common.Hash256]
|
||||
transactions* : Option[seq[Transaction]]
|
||||
withdrawals* : Option[seq[Withdrawal]]
|
||||
blobGasUsed* : Option[uint64]
|
||||
excessBlobGas*: Option[uint64]
|
||||
beaconRoot* : Option[Hash256]
|
||||
beaconRoot* : Option[common.Hash256]
|
||||
removeWithdrawals*: bool
|
||||
|
||||
InvalidPayloadField* = enum
|
||||
|
@ -96,9 +91,9 @@ proc customizePayload*(basePayload: ExecutableData, customData: CustomPayload):
|
|||
elif basePayload.withdrawals.isSome:
|
||||
some(calcWithdrawalsRoot(basePayload.withdrawals.get))
|
||||
else:
|
||||
none(Hash256)
|
||||
none(common.Hash256)
|
||||
|
||||
var customHeader = EthBlockHeader(
|
||||
var customHeader = common.BlockHeader(
|
||||
parentHash: basePayload.parentHash,
|
||||
ommersHash: EMPTY_UNCLE_HASH,
|
||||
coinbase: basePayload.feeRecipient,
|
||||
|
@ -169,16 +164,16 @@ proc customizePayload*(basePayload: ExecutableData, customData: CustomPayload):
|
|||
# Return the new payload
|
||||
result = ExecutionPayload(
|
||||
parentHash: w3Hash customHeader.parentHash,
|
||||
feeRecipient: Web3Address customHeader.coinbase,
|
||||
feeRecipient: w3Addr customHeader.coinbase,
|
||||
stateRoot: w3Hash customHeader.stateRoot,
|
||||
receiptsRoot: w3Hash customHeader.receiptRoot,
|
||||
logsBloom: Web3Bloom customHeader.bloom,
|
||||
prevRandao: Web3PrevRandao customHeader.mixDigest.data,
|
||||
blockNumber: Web3Quantity customHeader.blockNumber.truncate(uint64),
|
||||
gasLimit: Web3Quantity customHeader.gasLimit,
|
||||
gasUsed: Web3Quantity customHeader.gasUsed,
|
||||
timestamp: Web3Quantity toUnix(customHeader.timestamp),
|
||||
extraData: Web3ExtraData customHeader.extraData,
|
||||
logsBloom: w3Bloom customHeader.bloom,
|
||||
prevRandao: w3PrevRandao customHeader.mixDigest,
|
||||
blockNumber: w3Qty customHeader.blockNumber,
|
||||
gasLimit: w3Qty customHeader.gasLimit,
|
||||
gasUsed: w3Qty customHeader.gasUsed,
|
||||
timestamp: w3Qty customHeader.timestamp,
|
||||
extraData: w3ExtraData customHeader.extraData,
|
||||
baseFeePerGas: customHeader.baseFee,
|
||||
blockHash: w3Hash customHeader.blockHash,
|
||||
blobGasUsed: w3Qty customHeader.blobGasUsed,
|
||||
|
@ -201,30 +196,25 @@ proc customizePayload*(basePayload: ExecutableData, customData: CustomPayload):
|
|||
|
||||
proc toExecutableData*(payload: ExecutionPayload): ExecutableData =
|
||||
result = ExecutableData(
|
||||
parentHash : hash256(payload.parentHash),
|
||||
parentHash : ethHash payload.parentHash,
|
||||
feeRecipient : distinctBase payload.feeRecipient,
|
||||
stateRoot : hash256(payload.stateRoot),
|
||||
receiptsRoot : hash256(payload.receiptsRoot),
|
||||
stateRoot : ethHash payload.stateRoot,
|
||||
receiptsRoot : ethHash payload.receiptsRoot,
|
||||
logsBloom : distinctBase payload.logsBloom,
|
||||
prevRandao : hash256(payload.prevRandao),
|
||||
prevRandao : ethHash payload.prevRandao,
|
||||
number : uint64 payload.blockNumber,
|
||||
gasLimit : GasInt payload.gasLimit,
|
||||
gasUsed : GasInt payload.gasUsed,
|
||||
timestamp : fromUnix(int64 payload.timestamp),
|
||||
timestamp : ethTime payload.timestamp,
|
||||
extraData : distinctBase payload.extraData,
|
||||
baseFeePerGas : payload.baseFeePerGas,
|
||||
blockHash : hash256(payload.blockHash),
|
||||
blockHash : ethHash payload.blockHash,
|
||||
blobGasUsed : u64 payload.blobGasUsed,
|
||||
excessBlobGas : u64 payload.excessBlobGas,
|
||||
transactions : ethTxs payload.transactions,
|
||||
withdrawals : ethWithdrawals payload.withdrawals,
|
||||
)
|
||||
|
||||
for data in payload.transactions:
|
||||
let tx = rlp.decode(distinctBase data, Transaction)
|
||||
result.transactions.add tx
|
||||
|
||||
if payload.withdrawals.isSome:
|
||||
result.withdrawals = some(withdrawals(payload.withdrawals.get))
|
||||
|
||||
proc customizePayload*(basePayload: ExecutionPayload, customData: CustomPayload): ExecutionPayload =
|
||||
customizePayload(basePayload.toExecutableData, customData)
|
||||
|
||||
|
@ -269,7 +259,7 @@ proc customizeTx(baseTx: Transaction, vaultKey: PrivateKey, customTx: CustomTx):
|
|||
let chainId = baseTx.chainId
|
||||
signTransaction(modTx, vaultKey, chainId, eip155 = true)
|
||||
|
||||
proc modifyHash(x: Hash256): Hash256 =
|
||||
proc modifyHash(x: common.Hash256): common.Hash256 =
|
||||
result = x
|
||||
result.data[^1] = byte(255 - x.data[^1].int)
|
||||
|
||||
|
@ -297,7 +287,7 @@ proc generateInvalidPayload*(basePayload: ExecutableData,
|
|||
of InvalidPrevRandao:
|
||||
# This option potentially requires a transaction that uses the PREVRANDAO opcode.
|
||||
# Otherwise the payload will still be valid.
|
||||
var randomHash: Hash256
|
||||
var randomHash: common.Hash256
|
||||
doAssert randomBytes(randomHash.data) == 32
|
||||
customPayload.prevRandao = some(randomHash)
|
||||
of RemoveTransaction:
|
||||
|
@ -343,7 +333,7 @@ proc generateInvalidPayload*(basePayload: ExecutionPayload,
|
|||
vaultKey = default(PrivateKey)): ExecutionPayload =
|
||||
generateInvalidPayload(basePayload.toExecutableData, payloadField, vaultKey)
|
||||
|
||||
proc txInPayload*(payload: ExecutionPayload, txHash: Hash256): bool =
|
||||
proc txInPayload*(payload: ExecutionPayload, txHash: common.Hash256): bool =
|
||||
for txBytes in payload.transactions:
|
||||
let currTx = rlp.decode(common.Blob txBytes, Transaction)
|
||||
if rlpHash(currTx) == txHash:
|
||||
|
|
|
@ -14,7 +14,7 @@ import
|
|||
core/block_import,
|
||||
rpc,
|
||||
sync/protocol,
|
||||
rpc/merge/merger,
|
||||
beacon/beacon_engine,
|
||||
common
|
||||
],
|
||||
../../../tests/test_helpers,
|
||||
|
@ -112,9 +112,9 @@ proc setupELClient*(t: TestEnv, chainFile: string, enableAuth: bool) =
|
|||
txPool, EngineStopped
|
||||
)
|
||||
|
||||
let merger = MergerRef.new(t.com.db)
|
||||
let beaconEngine = BeaconEngineRef.new(txPool, t.chainRef)
|
||||
setupEthRpc(t.ethNode, t.ctx, t.com, txPool, t.rpcServer)
|
||||
setupEngineAPI(t.sealingEngine, t.rpcServer, merger)
|
||||
setupEngineAPI(beaconEngine, t.rpcServer)
|
||||
setupDebugRpc(t.com, t.rpcServer)
|
||||
|
||||
# Do not start clique sealing engine if we are using a Proof of Work chain file
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
import
|
||||
std/[options, times, strutils, typetraits],
|
||||
std/[options, typetraits, strutils],
|
||||
eth/common,
|
||||
web3/ethtypes,
|
||||
../../../nimbus/rpc/merge/mergeutils,
|
||||
../../../nimbus/rpc/execution_types,
|
||||
web3/engine_api_types,
|
||||
eth/common/eth_types_rlp
|
||||
|
||||
from web3/ethtypes as web3types import nil
|
||||
|
||||
export
|
||||
ethtypes,
|
||||
engine_api_types
|
||||
|
||||
import eth/common/eth_types as common
|
||||
../../../nimbus/beacon/execution_types,
|
||||
../../../nimbus/beacon/web3_eth_conv
|
||||
|
||||
type
|
||||
BaseSpec* = ref object of RootObj
|
||||
|
@ -24,13 +16,6 @@ type
|
|||
run* : proc(spec: BaseSpec): bool
|
||||
spec* : BaseSpec
|
||||
|
||||
Web3Hash256* = web3types.Hash256
|
||||
Web3Address* = web3types.Address
|
||||
Web3Bloom* = web3types.FixedBytes[256]
|
||||
Web3Quantity* = web3types.Quantity
|
||||
Web3PrevRandao* = web3types.FixedBytes[32]
|
||||
Web3ExtraData* = web3types.DynamicBytes[0, 32]
|
||||
|
||||
template testCond*(expr: untyped) =
|
||||
if not (expr):
|
||||
return false
|
||||
|
@ -40,88 +25,6 @@ template testCond*(expr, body: untyped) =
|
|||
body
|
||||
return false
|
||||
|
||||
proc `$`*(x: Option[common.Hash256]): string =
|
||||
if x.isNone:
|
||||
"none"
|
||||
else:
|
||||
$x.get()
|
||||
|
||||
proc `$`*(x: Option[BlockHash]): string =
|
||||
if x.isNone:
|
||||
"none"
|
||||
else:
|
||||
$x.get()
|
||||
|
||||
proc `$`*(x: Option[PayloadID]): string =
|
||||
if x.isNone:
|
||||
"none"
|
||||
else:
|
||||
x.get().toHex
|
||||
|
||||
func w3Hash*(x: common.Hash256): Web3Hash256 =
|
||||
Web3Hash256 x.data
|
||||
|
||||
func w3Hash*(x: Option[common.Hash256]): Option[BlockHash] =
|
||||
if x.isNone:
|
||||
return none(BlockHash)
|
||||
some(BlockHash x.get.data)
|
||||
|
||||
proc w3Hash*(x: common.BlockHeader): BlockHash =
|
||||
BlockHash x.blockHash.data
|
||||
|
||||
func w3Qty*(a: EthTime, b: int): Quantity =
|
||||
Quantity(a.toUnix + b.int64)
|
||||
|
||||
func w3Qty*(x: Option[uint64]): Option[Quantity] =
|
||||
if x.isNone:
|
||||
return none(Quantity)
|
||||
return some(Quantity x.get)
|
||||
|
||||
func u64*(x: Option[Quantity]): Option[uint64] =
|
||||
if x.isNone:
|
||||
return none(uint64)
|
||||
return some(uint64 x.get)
|
||||
|
||||
func w3PrevRandao*(): Web3PrevRandao =
|
||||
discard
|
||||
|
||||
func w3Address*(): Web3Address =
|
||||
discard
|
||||
|
||||
proc hash256*(h: Web3Hash256): common.Hash256 =
|
||||
common.Hash256(data: distinctBase h)
|
||||
|
||||
proc hash256*(h: Option[Web3Hash256]): Option[common.Hash256] =
|
||||
if h.isNone:
|
||||
return none(common.Hash256)
|
||||
some(hash256(h.get))
|
||||
|
||||
proc w3Withdrawal*(w: Withdrawal): WithdrawalV1 =
|
||||
WithdrawalV1(
|
||||
index: Quantity(w.index),
|
||||
validatorIndex: Quantity(w.validatorIndex),
|
||||
address: Address(w.address),
|
||||
amount: Quantity(w.amount)
|
||||
)
|
||||
|
||||
proc w3Withdrawals*(list: openArray[Withdrawal]): seq[WithdrawalV1] =
|
||||
result = newSeqOfCap[WithdrawalV1](list.len)
|
||||
for x in list:
|
||||
result.add w3Withdrawal(x)
|
||||
|
||||
proc withdrawal*(w: WithdrawalV1): Withdrawal =
|
||||
Withdrawal(
|
||||
index: uint64(w.index),
|
||||
validatorIndex: uint64(w.validatorIndex),
|
||||
address: distinctBase(w.address),
|
||||
amount: uint64(w.amount)
|
||||
)
|
||||
|
||||
proc withdrawals*(list: openArray[WithdrawalV1]): seq[Withdrawal] =
|
||||
result = newSeqOfCap[Withdrawal](list.len)
|
||||
for x in list:
|
||||
result.add withdrawal(x)
|
||||
|
||||
proc `==`*(a: Option[BlockHash], b: Option[common.Hash256]): bool =
|
||||
if a.isNone and b.isNone:
|
||||
return true
|
||||
|
@ -190,7 +93,7 @@ template expectBalanceEqual*(res: untyped, expectedBalance: UInt256) =
|
|||
testCond res.get == expectedBalance:
|
||||
error "balance mismatch", expect=expectedBalance, get=res.get
|
||||
|
||||
template expectLatestValidHash*(res: untyped, expectedHash: Web3Hash256) =
|
||||
template expectLatestValidHash*(res: untyped, expectedHash: Web3Hash) =
|
||||
testCond res.isOk:
|
||||
error "Unexpected error", msg=res.error
|
||||
let s = res.get
|
||||
|
|
|
@ -15,7 +15,8 @@ import
|
|||
../../../nimbus/common/common,
|
||||
../../../nimbus/utils/utils,
|
||||
../../../nimbus/common/chain_config,
|
||||
../../../nimbus/rpc/execution_types
|
||||
../../../nimbus/beacon/execution_types,
|
||||
../../../nimbus/beacon/web3_eth_conv
|
||||
|
||||
type
|
||||
WDBaseSpec* = ref object of BaseSpec
|
||||
|
@ -321,7 +322,7 @@ proc execute*(ws: WDBaseSpec, t: TestEnv): bool =
|
|||
let emptyWithdrawalsList = newSeq[Withdrawal]()
|
||||
let customizer = CustomPayload(
|
||||
withdrawals: some(emptyWithdrawalsList),
|
||||
beaconRoot: hash256 t.clMock.latestPayloadAttributes.parentBeaconBlockRoot
|
||||
beaconRoot: ethHash t.clMock.latestPayloadAttributes.parentBeaconBlockRoot
|
||||
)
|
||||
let payloadPlusWithdrawals = customizePayload(t.clMock.latestPayloadBuilt, customizer)
|
||||
var r = t.rpcClient.newPayloadV2(payloadPlusWithdrawals.V1V2)
|
||||
|
@ -410,7 +411,7 @@ proc execute*(ws: WDBaseSpec, t: TestEnv): bool =
|
|||
# be checked first instead of responding `INVALID`
|
||||
let customizer = CustomPayload(
|
||||
removeWithdrawals: true,
|
||||
beaconRoot: hash256 t.clMock.latestPayloadAttributes.parentBeaconBlockRoot
|
||||
beaconRoot: ethHash t.clMock.latestPayloadAttributes.parentBeaconBlockRoot
|
||||
)
|
||||
let nilWithdrawalsPayload = customizePayload(t.clMock.latestPayloadBuilt, customizer)
|
||||
let r = t.rpcClient.newPayloadV2(nilWithdrawalsPayload.V1V2)
|
||||
|
@ -431,7 +432,7 @@ proc execute*(ws: WDBaseSpec, t: TestEnv): bool =
|
|||
get=wdList.len
|
||||
|
||||
for i, x in sentList:
|
||||
let z = withdrawal wdList[i]
|
||||
let z = ethWithdrawal wdList[i]
|
||||
testCond z == x:
|
||||
error "Incorrect withdrawal", index=i
|
||||
return true
|
||||
|
|
|
@ -9,7 +9,8 @@ import
|
|||
../types,
|
||||
../helper,
|
||||
../../../nimbus/constants,
|
||||
../../../nimbus/rpc/execution_types
|
||||
../../../nimbus/beacon/execution_types,
|
||||
../../../nimbus/beacon/web3_eth_conv
|
||||
|
||||
# EIP-3860 Shanghai Tests:
|
||||
# Send transactions overflowing the MAX_INITCODE_SIZE
|
||||
|
@ -100,7 +101,7 @@ proc execute*(ws: MaxInitcodeSizeSpec, t: TestEnv): bool =
|
|||
|
||||
# Customize the payload to include a tx with an invalid initcode
|
||||
let customData = CustomPayload(
|
||||
beaconRoot: hash256 t.clMock.latestPayloadAttributes.parentBeaconBlockRoot,
|
||||
beaconRoot: ethHash t.clMock.latestPayloadAttributes.parentBeaconBlockRoot,
|
||||
transactions: some( @[invalidTx] ),
|
||||
)
|
||||
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
import
|
||||
eth/[common],
|
||||
json_rpc/[rpcclient],
|
||||
web3/ethtypes,
|
||||
../../../nimbus/transaction
|
||||
|
||||
import eth/common/eth_types as common_eth_types
|
||||
type Hash256 = common_eth_types.Hash256
|
||||
|
||||
import web3/engine_api_types
|
||||
from web3/ethtypes as web3types import nil
|
||||
|
||||
type
|
||||
Web3BlockHash* = web3types.BlockHash
|
||||
Web3Address* = web3types.Address
|
||||
Web3Bloom* = web3types.FixedBytes[256]
|
||||
Web3Quantity* = web3types.Quantity
|
||||
Web3PrevRandao* = web3types.FixedBytes[32]
|
||||
Web3ExtraData* = web3types.DynamicBytes[0, 32]
|
||||
|
||||
func toWdV1(wd: Withdrawal): WithdrawalV1 =
|
||||
result = WithdrawalV1(
|
||||
index: Web3Quantity wd.index,
|
||||
validatorIndex: Web3Quantity wd.validatorIndex,
|
||||
address: Web3Address wd.address,
|
||||
amount: Web3Quantity wd.amount
|
||||
)
|
||||
|
||||
func toPayloadV1OrV2*(blk: EthBlock): ExecutionPayloadV1OrV2 =
|
||||
let header = blk.header
|
||||
|
||||
# Return the new payload
|
||||
result = ExecutionPayloadV1OrV2(
|
||||
parentHash: Web3BlockHash header.parentHash.data,
|
||||
feeRecipient: Web3Address header.coinbase,
|
||||
stateRoot: Web3BlockHash header.stateRoot.data,
|
||||
receiptsRoot: Web3BlockHash header.receiptRoot.data,
|
||||
logsBloom: Web3Bloom header.bloom,
|
||||
prevRandao: Web3PrevRandao header.mixDigest.data,
|
||||
blockNumber: Web3Quantity header.blockNumber.truncate(uint64),
|
||||
gasLimit: Web3Quantity header.gasLimit,
|
||||
gasUsed: Web3Quantity header.gasUsed,
|
||||
timestamp: Web3Quantity toUnix(header.timestamp),
|
||||
extraData: Web3ExtraData header.extraData,
|
||||
baseFeePerGas: header.baseFee,
|
||||
blockHash: Web3BlockHash header.blockHash.data
|
||||
)
|
||||
|
||||
for tx in blk.txs:
|
||||
let txData = rlp.encode(tx)
|
||||
result.transactions.add TypedTransaction(txData)
|
||||
|
||||
if blk.withdrawals.isSome:
|
||||
let withdrawals = blk.withdrawals.get
|
||||
var wds = newSeqOfCap[WithdrawalV1](withdrawals.len)
|
||||
for wd in withdrawals:
|
||||
wds.add toWdV1(wd)
|
||||
result.withdrawals = some(wds)
|
|
@ -11,29 +11,24 @@ import
|
|||
std/[os, json, strutils, times, typetraits, options],
|
||||
stew/[byteutils, results],
|
||||
eth/common,
|
||||
web3/engine_api_types,
|
||||
../sim_utils,
|
||||
../../../tools/common/helpers as chp,
|
||||
../../../tools/evmstate/helpers as ehp,
|
||||
../../../tests/test_helpers,
|
||||
../../../nimbus/beacon/web3_eth_conv,
|
||||
../../../nimbus/beacon/execution_types,
|
||||
../../../nimbus/beacon/payload_conv,
|
||||
../engine/engine_client,
|
||||
./test_env,
|
||||
./helpers
|
||||
./test_env
|
||||
|
||||
const
|
||||
baseFolder = "hive_integration/nodocker/pyspec"
|
||||
caseFolder = baseFolder & "/testcases"
|
||||
supportedNetwork = ["Merge", "Shanghai", "MergeToShanghaiAtTime15k"]
|
||||
|
||||
type
|
||||
Hash256 = common.Hash256
|
||||
|
||||
proc getPayload(node: JsonNode): ExecutionPayloadV1OrV2 =
|
||||
let rlpBytes = hexToSeqByte(node.getStr)
|
||||
toPayloadV1OrV2(rlp.decode(rlpBytes, EthBlock))
|
||||
|
||||
proc hash256(h: Web3BlockHash): Hash256 =
|
||||
Hash256(data: distinctBase h)
|
||||
executionPayloadV1V2(rlp.decode(rlpBytes, EthBlock))
|
||||
|
||||
proc validatePostState(node: JsonNode, t: TestEnv): bool =
|
||||
# check nonce, balance & storage of accounts in final block against fixture values
|
||||
|
@ -101,7 +96,7 @@ proc runTest(node: JsonNode, network: string): TestStatus =
|
|||
t.setupELClient(conf, node)
|
||||
|
||||
let blks = node["blocks"]
|
||||
var latestValidHash = Hash256()
|
||||
var latestValidHash = common.Hash256()
|
||||
result = TestStatus.OK
|
||||
for blkNode in blks:
|
||||
let expectedStatus = if "expectException" in blkNode:
|
||||
|
@ -117,7 +112,7 @@ proc runTest(node: JsonNode, network: string): TestStatus =
|
|||
|
||||
let pStatus = res.value
|
||||
if pStatus.status == PayloadExecutionStatus.valid:
|
||||
latestValidHash = hash256(pStatus.latestValidHash.get)
|
||||
latestValidHash = ethHash pStatus.latestValidHash.get
|
||||
|
||||
if pStatus.status != expectedStatus:
|
||||
result = TestStatus.Failed
|
||||
|
@ -128,7 +123,7 @@ proc runTest(node: JsonNode, network: string): TestStatus =
|
|||
|
||||
block:
|
||||
# only update head of beacon chain if valid response occurred
|
||||
if latestValidHash != Hash256():
|
||||
if latestValidHash != common.Hash256():
|
||||
# update with latest valid response
|
||||
let fcState = ForkchoiceStateV1(headBlockHash: BlockHash latestValidHash.data)
|
||||
let res = t.rpcClient.forkchoiceUpdatedV2(fcState)
|
||||
|
|
|
@ -14,7 +14,7 @@ import
|
|||
core/tx_pool,
|
||||
rpc,
|
||||
sync/protocol,
|
||||
rpc/merge/merger,
|
||||
beacon/beacon_engine,
|
||||
common
|
||||
],
|
||||
../../../tests/test_helpers,
|
||||
|
@ -68,10 +68,9 @@ proc setupELClient*(t: TestEnv, conf: ChainConfig, node: JsonNode) =
|
|||
txPool, EngineStopped
|
||||
)
|
||||
|
||||
let merger = MergerRef.new(t.com.db)
|
||||
let beaconEngine = BeaconEngineRef.new(txPool, t.chainRef)
|
||||
setupEthRpc(t.ethNode, t.ctx, t.com, txPool, t.rpcServer)
|
||||
setupEngineAPI(t.sealingEngine, t.rpcServer, merger)
|
||||
#setupDebugRpc(t.com, t.rpcServer)
|
||||
setupEngineAPI(beaconEngine, t.rpcServer)
|
||||
|
||||
t.rpcServer.start()
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2023 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/[options, typetraits],
|
||||
eth/common,
|
||||
./web3_eth_conv,
|
||||
./beacon_engine,
|
||||
./execution_types,
|
||||
./api_handler/api_utils,
|
||||
./api_handler/api_getpayload,
|
||||
./api_handler/api_getbodies,
|
||||
./api_handler/api_exchangeconf,
|
||||
./api_handler/api_newpayload,
|
||||
./api_handler/api_forkchoice
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
{.push gcsafe, raises:[CatchableError].}
|
||||
|
||||
func validateVersionedHashed*(payload: ExecutionPayload,
|
||||
expected: openArray[Web3Hash]): bool =
|
||||
var versionedHashes: seq[common.Hash256]
|
||||
for x in payload.transactions:
|
||||
let tx = rlp.decode(distinctBase(x), Transaction)
|
||||
versionedHashes.add tx.versionedHashes
|
||||
for i, x in expected:
|
||||
if distinctBase(x) != versionedHashes[i].data:
|
||||
return false
|
||||
true
|
||||
|
||||
{.pop.}
|
||||
|
||||
export
|
||||
invalidStatus,
|
||||
getPayload,
|
||||
getPayloadV3,
|
||||
getPayloadBodiesByHash,
|
||||
getPayloadBodiesByRange,
|
||||
exchangeConf,
|
||||
newPayload,
|
||||
forkchoiceUpdated
|
|
@ -0,0 +1,75 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2023 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/[strutils],
|
||||
eth/common,
|
||||
../web3_eth_conv,
|
||||
../beacon_engine,
|
||||
../execution_types,
|
||||
chronicles
|
||||
|
||||
{.push gcsafe, raises:[CatchableError].}
|
||||
|
||||
proc exchangeConf*(ben: BeaconEngineRef,
|
||||
conf: TransitionConfigurationV1):
|
||||
TransitionConfigurationV1 =
|
||||
trace "Engine API request received",
|
||||
meth = "exchangeTransitionConfigurationV1",
|
||||
ttd = conf.terminalTotalDifficulty,
|
||||
number = uint64(conf.terminalBlockNumber),
|
||||
blockHash = conf.terminalBlockHash
|
||||
|
||||
let
|
||||
com = ben.com
|
||||
db = com.db
|
||||
ttd = com.ttd
|
||||
|
||||
if ttd.isNone:
|
||||
raise newException(ValueError, "invalid ttd: EL (none) CL ($1)" % [
|
||||
$conf.terminalTotalDifficulty])
|
||||
|
||||
if conf.terminalTotalDifficulty != ttd.get:
|
||||
raise newException(ValueError, "invalid ttd: EL ($1) CL ($2)" % [
|
||||
$ttd.get, $conf.terminalTotalDifficulty])
|
||||
|
||||
let
|
||||
terminalBlockNumber = u256 conf.terminalBlockNumber
|
||||
terminalBlockHash = ethHash conf.terminalBlockHash
|
||||
|
||||
if terminalBlockHash != common.Hash256():
|
||||
var headerHash: common.Hash256
|
||||
|
||||
if not db.getBlockHash(terminalBlockNumber, headerHash):
|
||||
raise newException(ValueError, "cannot get terminal block hash, number $1" %
|
||||
[$terminalBlockNumber])
|
||||
|
||||
if terminalBlockHash != headerHash:
|
||||
raise newException(ValueError, "invalid terminal block hash, got $1 want $2" %
|
||||
[$terminalBlockHash, $headerHash])
|
||||
|
||||
var header: common.BlockHeader
|
||||
if not db.getBlockHeader(headerHash, header):
|
||||
raise newException(ValueError, "cannot get terminal block header, hash $1" %
|
||||
[$terminalBlockHash])
|
||||
|
||||
return TransitionConfigurationV1(
|
||||
terminalTotalDifficulty: ttd.get,
|
||||
terminalBlockHash : w3Hash headerHash,
|
||||
terminalBlockNumber : w3Qty header.blockNumber
|
||||
)
|
||||
|
||||
if terminalBlockNumber.isZero.not:
|
||||
raise newException(ValueError, "invalid terminal block number: $1" % [
|
||||
$terminalBlockNumber])
|
||||
|
||||
if terminalBlockHash != common.Hash256():
|
||||
raise newException(ValueError, "invalid terminal block hash, no terminal header set")
|
||||
|
||||
TransitionConfigurationV1(terminalTotalDifficulty: ttd.get)
|
|
@ -0,0 +1,192 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2023 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],
|
||||
eth/common,
|
||||
stew/results,
|
||||
../web3_eth_conv,
|
||||
../beacon_engine,
|
||||
../execution_types,
|
||||
./api_utils,
|
||||
chronicles
|
||||
|
||||
{.push gcsafe, raises:[CatchableError].}
|
||||
|
||||
template validateVersion(attrsOpt, com) =
|
||||
if attrsOpt.isSome:
|
||||
let
|
||||
attr = attrsOpt.get
|
||||
version = attr.version
|
||||
timestamp = ethTime attr.timestamp
|
||||
|
||||
if com.isCancunOrLater(timestamp):
|
||||
if version != Version.V3:
|
||||
raise invalidParams("if timestamp is Cancun or later," &
|
||||
" payloadAttributes must be PayloadAttributesV3")
|
||||
elif com.isShanghaiOrLater(timestamp):
|
||||
if version != Version.V2:
|
||||
raise invalidParams("if timestamp is Shanghai or later," &
|
||||
" payloadAttributes must be PayloadAttributesV2")
|
||||
else:
|
||||
if version != Version.V1:
|
||||
raise invalidParams("if timestamp is earlier than Shanghai," &
|
||||
" payloadAttributes must be PayloadAttributesV1")
|
||||
|
||||
proc forkchoiceUpdated*(ben: BeaconEngineRef,
|
||||
update: ForkchoiceStateV1,
|
||||
attrsOpt: Option[PayloadAttributes]):
|
||||
ForkchoiceUpdatedResponse =
|
||||
let
|
||||
com = ben.com
|
||||
db = com.db
|
||||
chain = ben.chain
|
||||
blockHash = ethHash update.headBlockHash
|
||||
|
||||
validateVersion(attrsOpt, com)
|
||||
|
||||
if blockHash == common.Hash256():
|
||||
warn "Forkchoice requested update to zero hash"
|
||||
return simpleFCU(PayloadExecutionStatus.invalid)
|
||||
|
||||
# Check whether we have the block yet in our database or not. If not, we'll
|
||||
# need to either trigger a sync, or to reject this forkchoice update for a
|
||||
# reason.
|
||||
var header: common.BlockHeader
|
||||
if not db.getBlockHeader(blockHash, header):
|
||||
# If the head hash is unknown (was not given to us in a newPayload request),
|
||||
# we cannot resolve the header, so not much to do. This could be extended in
|
||||
# the future to resolve from the `eth` network, but it's an unexpected case
|
||||
# that should be fixed, not papered over.
|
||||
if not ben.get(blockHash, header):
|
||||
warn "Forkchoice requested unknown head",
|
||||
hash = blockHash
|
||||
return simpleFCU(PayloadExecutionStatus.syncing)
|
||||
|
||||
# Header advertised via a past newPayload request. Start syncing to it.
|
||||
# Before we do however, make sure any legacy sync in switched off so we
|
||||
# don't accidentally have 2 cycles running.
|
||||
if not ben.ttdReached():
|
||||
ben.reachTTD()
|
||||
# TODO: cancel downloader
|
||||
|
||||
info "Forkchoice requested sync to new head",
|
||||
number = header.blockNumber,
|
||||
hash = blockHash
|
||||
|
||||
# Update sync header (if any)
|
||||
com.syncReqNewHead(header)
|
||||
return simpleFCU(PayloadExecutionStatus.syncing)
|
||||
|
||||
# Block is known locally, just sanity check that the beacon client does not
|
||||
# attempt to push us back to before the merge.
|
||||
let blockNumber = header.blockNumber.truncate(uint64)
|
||||
if header.difficulty > 0.u256 or blockNumber == 0'u64:
|
||||
var
|
||||
td, ptd: DifficultyInt
|
||||
ttd = com.ttd.get(high(common.BlockNumber))
|
||||
|
||||
if not db.getTd(blockHash, td) or (blockNumber > 0'u64 and not db.getTd(header.parentHash, ptd)):
|
||||
error "TDs unavailable for TTD check",
|
||||
number = blockNumber,
|
||||
hash = blockHash,
|
||||
td = td,
|
||||
parent = header.parentHash,
|
||||
ptd = ptd
|
||||
return simpleFCU(PayloadExecutionStatus.invalid, "TDs unavailable for TDD check")
|
||||
|
||||
if td < ttd or (blockNumber > 0'u64 and ptd > ttd):
|
||||
error "Refusing beacon update to pre-merge",
|
||||
number = blockNumber,
|
||||
hash = blockHash,
|
||||
diff = header.difficulty,
|
||||
ptd = ptd,
|
||||
ttd = ttd
|
||||
|
||||
return invalidFCU()
|
||||
|
||||
# If the head block is already in our canonical chain, the beacon client is
|
||||
# probably resyncing. Ignore the update.
|
||||
var canonHash: common.Hash256
|
||||
if db.getBlockHash(header.blockNumber, canonHash) and canonHash == blockHash:
|
||||
# TODO should this be possible?
|
||||
# If we allow these types of reorgs, we will do lots and lots of reorgs during sync
|
||||
warn "Reorg to previous block"
|
||||
if chain.setCanonical(header) != ValidationResult.OK:
|
||||
return invalidFCU(com, header)
|
||||
elif chain.setCanonical(header) != ValidationResult.OK:
|
||||
return invalidFCU(com, header)
|
||||
|
||||
# If the beacon client also advertised a finalized block, mark the local
|
||||
# chain final and completely in PoS mode.
|
||||
let finalizedBlockHash = ethHash update.finalizedBlockHash
|
||||
if finalizedBlockHash != common.Hash256():
|
||||
if not ben.posFinalized:
|
||||
ben.finalizePoS()
|
||||
|
||||
# TODO: If the finalized block is not in our canonical tree, somethings wrong
|
||||
var finalBlock: common.BlockHeader
|
||||
if not db.getBlockHeader(finalizedBlockHash, finalBlock):
|
||||
warn "Final block not available in database",
|
||||
hash=finalizedBlockHash
|
||||
raise invalidParams("finalized block header not available")
|
||||
var finalHash: common.Hash256
|
||||
if not db.getBlockHash(finalBlock.blockNumber, finalHash):
|
||||
warn "Final block not in canonical chain",
|
||||
number=finalBlock.blockNumber,
|
||||
hash=finalizedBlockHash
|
||||
raise invalidParams("finalized block hash not available")
|
||||
if finalHash != finalizedBlockHash:
|
||||
warn "Final block not in canonical chain",
|
||||
number=finalBlock.blockNumber,
|
||||
expect=finalizedBlockHash,
|
||||
get=finalHash
|
||||
raise invalidParams("finalilized block not canonical")
|
||||
db.finalizedHeaderHash(finalizedBlockHash)
|
||||
|
||||
let safeBlockHash = ethHash update.safeBlockHash
|
||||
if safeBlockHash != common.Hash256():
|
||||
var safeBlock: common.BlockHeader
|
||||
if not db.getBlockHeader(safeBlockHash, safeBlock):
|
||||
warn "Safe block not available in database",
|
||||
hash = safeBlockHash
|
||||
raise invalidParams("safe head not available")
|
||||
var safeHash: common.Hash256
|
||||
if not db.getBlockHash(safeBlock.blockNumber, safeHash):
|
||||
warn "Safe block hash not available in database",
|
||||
hash = safeHash
|
||||
raise invalidParams("safe block hash not available")
|
||||
if safeHash != safeBlockHash:
|
||||
warn "Safe block not in canonical chain",
|
||||
blockNumber=safeBlock.blockNumber,
|
||||
expect=safeBlockHash,
|
||||
get=safeHash
|
||||
raise invalidParams("safe head not canonical")
|
||||
db.safeHeaderHash(safeBlockHash)
|
||||
|
||||
# If payload generation was requested, create a new block to be potentially
|
||||
# sealed by the beacon client. The payload will be requested later, and we
|
||||
# might replace it arbitrarilly many times in between.
|
||||
if attrsOpt.isSome:
|
||||
let attrs = attrsOpt.get()
|
||||
let payload = ben.generatePayload(attrs).valueOr:
|
||||
error "Failed to create sealing payload", err = error
|
||||
raise invalidAttr(error)
|
||||
|
||||
let id = computePayloadId(blockHash, attrs)
|
||||
ben.put(id, ben.blockValue, payload)
|
||||
|
||||
info "Created payload for sealing",
|
||||
id = id.toHex,
|
||||
hash = payload.blockHash,
|
||||
number = payload.blockNumber
|
||||
|
||||
return validFCU(some(id), blockHash)
|
||||
|
||||
return validFCU(none(PayloadID), blockHash)
|
|
@ -0,0 +1,93 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2023 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/[options, typetraits],
|
||||
eth/common,
|
||||
../web3_eth_conv,
|
||||
../beacon_engine,
|
||||
../execution_types,
|
||||
../../db/core_db,
|
||||
./api_utils
|
||||
|
||||
{.push gcsafe, raises:[CatchableError].}
|
||||
|
||||
const
|
||||
maxBodyRequest = 32
|
||||
|
||||
proc getPayloadBodyByHeader(db: CoreDbRef,
|
||||
header: common.BlockHeader,
|
||||
output: var seq[Option[ExecutionPayloadBodyV1]]) =
|
||||
|
||||
var body: common.BlockBody
|
||||
if not db.getBlockBody(header, body):
|
||||
output.add none(ExecutionPayloadBodyV1)
|
||||
return
|
||||
|
||||
let txs = w3Txs body.transactions
|
||||
var wds: seq[WithdrawalV1]
|
||||
if body.withdrawals.isSome:
|
||||
for w in body.withdrawals.get:
|
||||
wds.add w3Withdrawal(w)
|
||||
|
||||
output.add(
|
||||
some(ExecutionPayloadBodyV1(
|
||||
transactions: txs,
|
||||
# pre Shanghai block return null withdrawals
|
||||
# post Shanghai block return at least empty slice
|
||||
withdrawals: if header.withdrawalsRoot.isSome:
|
||||
some(wds)
|
||||
else:
|
||||
none(seq[WithdrawalV1])
|
||||
))
|
||||
)
|
||||
|
||||
proc getPayloadBodiesByHash*(ben: BeaconEngineRef,
|
||||
hashes: seq[Web3Hash]):
|
||||
seq[Option[ExecutionPayloadBodyV1]] =
|
||||
if hashes.len > maxBodyRequest:
|
||||
raise tooLargeRequest("request exceeds max allowed " & $maxBodyRequest)
|
||||
|
||||
let db = ben.com.db
|
||||
var header: common.BlockHeader
|
||||
for h in hashes:
|
||||
if not db.getBlockHeader(ethHash h, header):
|
||||
result.add none(ExecutionPayloadBodyV1)
|
||||
continue
|
||||
db.getPayloadBodyByHeader(header, result)
|
||||
|
||||
proc getPayloadBodiesByRange*(ben: BeaconEngineRef,
|
||||
start: uint64, count: uint64):
|
||||
seq[Option[ExecutionPayloadBodyV1]] =
|
||||
if start == 0:
|
||||
raise invalidParams("start block should greater than zero")
|
||||
|
||||
if count == 0:
|
||||
raise invalidParams("blocks count should greater than zero")
|
||||
|
||||
if count > maxBodyRequest:
|
||||
raise tooLargeRequest("request exceeds max allowed " & $maxBodyRequest)
|
||||
|
||||
let
|
||||
com = ben.com
|
||||
db = com.db
|
||||
current = com.syncCurrent.truncate(uint64)
|
||||
|
||||
var
|
||||
header: common.BlockHeader
|
||||
last = start+count-1
|
||||
|
||||
if last > current:
|
||||
last = current
|
||||
|
||||
for bn in start..last:
|
||||
if not db.getBlockHeader(bn.toBlockNumber, header):
|
||||
result.add none(ExecutionPayloadBodyV1)
|
||||
continue
|
||||
db.getPayloadBodyByHeader(header, result)
|
|
@ -0,0 +1,68 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2023 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],
|
||||
eth/common,
|
||||
../web3_eth_conv,
|
||||
../beacon_engine,
|
||||
../execution_types,
|
||||
./api_utils,
|
||||
chronicles
|
||||
|
||||
{.push gcsafe, raises:[CatchableError].}
|
||||
|
||||
proc getPayload*(ben: BeaconEngineRef, id: PayloadID): GetPayloadV2Response =
|
||||
trace "Engine API request received",
|
||||
meth = "GetPayload", id
|
||||
|
||||
var payload: ExecutionPayloadV1OrV2
|
||||
var blockValue: UInt256
|
||||
if not ben.get(id, blockValue, payload):
|
||||
raise unknownPayload("Unknown payload")
|
||||
|
||||
GetPayloadV2Response(
|
||||
executionPayload: payload,
|
||||
blockValue: blockValue
|
||||
)
|
||||
|
||||
proc getPayloadV3*(ben: BeaconEngineRef, id: PayloadID): GetPayloadV3Response =
|
||||
trace "Engine API request received",
|
||||
meth = "GetPayload", id
|
||||
|
||||
var payload: ExecutionPayloadV3
|
||||
var blockValue: UInt256
|
||||
if not ben.get(id, blockValue, payload):
|
||||
raise unknownPayload("Unknown payload")
|
||||
|
||||
let com = ben.com
|
||||
if not com.isCancunOrLater(ethTime payload.timestamp):
|
||||
raise unsupportedFork("payload timestamp is less than Cancun activation")
|
||||
|
||||
var
|
||||
blobsBundle: BlobsBundleV1
|
||||
|
||||
try:
|
||||
for ttx in payload.transactions:
|
||||
let tx = rlp.decode(distinctBase(ttx), Transaction)
|
||||
if tx.networkPayload.isNil.not:
|
||||
for blob in tx.networkPayload.blobs:
|
||||
blobsBundle.blobs.add Web3Blob(blob)
|
||||
for p in tx.networkPayload.proofs:
|
||||
blobsBundle.proofs.add Web3KZGProof(p)
|
||||
for k in tx.networkPayload.commitments:
|
||||
blobsBundle.commitments.add Web3KZGCommitment(k)
|
||||
except RlpError:
|
||||
doAssert(false, "found TypedTransaction that RLP failed to decode")
|
||||
|
||||
GetPayloadV3Response(
|
||||
executionPayload: payload,
|
||||
blockValue: blockValue,
|
||||
blobsBundle: blobsBundle
|
||||
)
|
|
@ -0,0 +1,140 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2023 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],
|
||||
eth/common,
|
||||
stew/results,
|
||||
../web3_eth_conv,
|
||||
../beacon_engine,
|
||||
../execution_types,
|
||||
../payload_conv,
|
||||
./api_utils,
|
||||
chronicles
|
||||
|
||||
{.push gcsafe, raises:[CatchableError].}
|
||||
|
||||
template validateVersion(com, timestamp, version) =
|
||||
if com.isCancunOrLater(timestamp):
|
||||
if version != Version.V3:
|
||||
raise invalidParams("if timestamp is Cancun or later, " &
|
||||
"payload must be ExecutionPayloadV3")
|
||||
|
||||
elif com.isShanghaiOrLater(timestamp):
|
||||
if version != Version.V2:
|
||||
raise invalidParams("if timestamp is Shanghai or later, " &
|
||||
"payload must be ExecutionPayloadV2")
|
||||
|
||||
elif version != Version.V1:
|
||||
if com.syncReqRelaxV2:
|
||||
trace "Relaxed mode, treating payload as V1"
|
||||
discard
|
||||
else:
|
||||
raise invalidParams("if timestamp is earlier than Shanghai, " &
|
||||
"payload must be ExecutionPayloadV1")
|
||||
|
||||
proc newPayload*(ben: BeaconEngineRef,
|
||||
payload: ExecutionPayload,
|
||||
beaconRoot = none(Web3Hash)): PayloadStatusV1 =
|
||||
|
||||
trace "Engine API request received",
|
||||
meth = "newPayload",
|
||||
number = payload.blockNumber,
|
||||
hash = payload.blockHash
|
||||
|
||||
let
|
||||
com = ben.com
|
||||
db = com.db
|
||||
timestamp = ethTime payload.timestamp
|
||||
version = payload.version
|
||||
|
||||
validateVersion(com, timestamp, version)
|
||||
|
||||
var header = blockHeader(payload, ethHash beaconRoot)
|
||||
let blockHash = ethHash payload.blockHash
|
||||
header.validateBlockHash(blockHash, version).isOkOr:
|
||||
return error
|
||||
|
||||
|
||||
# If we already have the block locally, ignore the entire execution and just
|
||||
# return a fake success.
|
||||
if db.getBlockHeader(blockHash, header):
|
||||
warn "Ignoring already known beacon payload",
|
||||
number = header.blockNumber, hash = blockHash
|
||||
return validStatus(blockHash)
|
||||
|
||||
# If the parent is missing, we - in theory - could trigger a sync, but that
|
||||
# would also entail a reorg. That is problematic if multiple sibling blocks
|
||||
# are being fed to us, and even moreso, if some semi-distant uncle shortens
|
||||
# our live chain. As such, payload execution will not permit reorgs and thus
|
||||
# will not trigger a sync cycle. That is fine though, if we get a fork choice
|
||||
# update after legit payload executions.
|
||||
var parent: common.BlockHeader
|
||||
if not db.getBlockHeader(header.parentHash, parent):
|
||||
# Stash the block away for a potential forced forckchoice update to it
|
||||
# at a later time.
|
||||
ben.put(blockHash, header)
|
||||
|
||||
# Although we don't want to trigger a sync, if there is one already in
|
||||
# progress, try to extend if with the current payload request to relieve
|
||||
# some strain from the forkchoice update.
|
||||
#if err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header()); err == nil {
|
||||
# log.Debug("Payload accepted for sync extension", "number", params.Number, "hash", params.BlockHash)
|
||||
# return beacon.PayloadStatusV1{Status: beacon.SYNCING}, nil
|
||||
|
||||
# Either no beacon sync was started yet, or it rejected the delivered
|
||||
# payload as non-integratable on top of the existing sync. We'll just
|
||||
# have to rely on the beacon client to forcefully update the head with
|
||||
# a forkchoice update request.
|
||||
warn "Ignoring payload with missing parent",
|
||||
number = header.blockNumber,
|
||||
hash = blockHash,
|
||||
parent = header.parentHash
|
||||
return acceptedStatus()
|
||||
|
||||
# We have an existing parent, do some sanity checks to avoid the beacon client
|
||||
# triggering too early
|
||||
let ttd = com.ttd.get(high(common.BlockNumber))
|
||||
|
||||
if version == Version.V1:
|
||||
let td = db.getScore(header.parentHash)
|
||||
if (not com.forkGTE(MergeFork)) and td < ttd:
|
||||
warn "Ignoring pre-merge payload",
|
||||
number = header.blockNumber, hash = blockHash, td, ttd
|
||||
return invalidStatus()
|
||||
|
||||
if header.timestamp <= parent.timestamp:
|
||||
warn "Invalid timestamp",
|
||||
parent = header.timestamp, header = header.timestamp
|
||||
return invalidStatus(db.getHeadBlockHash(), "Invalid timestamp")
|
||||
|
||||
if not db.haveBlockAndState(header.parentHash):
|
||||
ben.put(blockHash, header)
|
||||
warn "State not available, ignoring new payload",
|
||||
hash = blockHash,
|
||||
number = header.blockNumber
|
||||
let blockHash = latestValidHash(db, parent, ttd)
|
||||
return acceptedStatus(blockHash)
|
||||
|
||||
trace "Inserting block without sethead",
|
||||
hash = blockHash, number = header.blockNumber
|
||||
let body = blockBody(payload)
|
||||
let vres = ben.chain.insertBlockWithoutSetHead(header, body)
|
||||
if vres != ValidationResult.OK:
|
||||
let blockHash = latestValidHash(db, parent, ttd)
|
||||
return invalidStatus(blockHash, "Failed to insert block")
|
||||
|
||||
# We've accepted a valid payload from the beacon client. Mark the local
|
||||
# chain transitions to notify other subsystems (e.g. downloader) of the
|
||||
# behavioral change.
|
||||
if not ben.ttdReached():
|
||||
ben.reachTTD()
|
||||
# TODO: cancel downloader
|
||||
|
||||
return validStatus(blockHash)
|
|
@ -0,0 +1,183 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2022-2023 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, strutils],
|
||||
eth/[rlp],
|
||||
json_rpc/errors,
|
||||
nimcrypto/[hash, sha2],
|
||||
stew/[results],
|
||||
../../constants,
|
||||
../../db/core_db,
|
||||
../../utils/utils,
|
||||
../../common/common,
|
||||
../execution_types,
|
||||
../web3_eth_conv
|
||||
|
||||
{.push gcsafe, raises:[].}
|
||||
|
||||
proc update(ctx: var sha256, wd: WithdrawalV1) =
|
||||
ctx.update(toBytesBE distinctBase wd.index)
|
||||
ctx.update(toBytesBE distinctBase wd.validatorIndex)
|
||||
ctx.update(distinctBase wd.address)
|
||||
ctx.update(toBytesBE distinctBase wd.amount)
|
||||
|
||||
proc computePayloadId*(blockHash: common.Hash256,
|
||||
params: PayloadAttributes): PayloadID =
|
||||
var dest: common.Hash256
|
||||
var ctx: sha256
|
||||
ctx.init()
|
||||
ctx.update(blockHash.data)
|
||||
ctx.update(toBytesBE distinctBase params.timestamp)
|
||||
ctx.update(distinctBase params.prevRandao)
|
||||
ctx.update(distinctBase params.suggestedFeeRecipient)
|
||||
if params.withdrawals.isSome:
|
||||
for wd in params.withdrawals.get:
|
||||
ctx.update(wd)
|
||||
if params.parentBeaconBlockRoot.isSome:
|
||||
ctx.update(distinctBase params.parentBeaconBlockRoot.get)
|
||||
ctx.finish dest.data
|
||||
ctx.clear()
|
||||
(distinctBase result)[0..7] = dest.data[0..7]
|
||||
|
||||
proc validateBlockHash*(header: common.BlockHeader,
|
||||
gotHash: common.Hash256,
|
||||
version: Version): Result[void, PayloadStatusV1]
|
||||
{.gcsafe, raises: [ValueError].} =
|
||||
let wantHash = header.blockHash
|
||||
if wantHash != gotHash:
|
||||
let status = if version == Version.V1:
|
||||
PayloadExecutionStatus.invalid_block_hash
|
||||
else:
|
||||
PayloadExecutionStatus.invalid
|
||||
|
||||
let res = PayloadStatusV1(
|
||||
status: status,
|
||||
validationError: some("blockhash mismatch, want $1, got $2" % [
|
||||
$wantHash, $gotHash])
|
||||
)
|
||||
return err(res)
|
||||
|
||||
return ok()
|
||||
|
||||
template toValidHash*(x: common.Hash256): Option[Web3Hash] =
|
||||
some(w3Hash x)
|
||||
|
||||
proc simpleFCU*(status: PayloadExecutionStatus): ForkchoiceUpdatedResponse =
|
||||
ForkchoiceUpdatedResponse(payloadStatus: PayloadStatusV1(status: status))
|
||||
|
||||
proc simpleFCU*(status: PayloadExecutionStatus,
|
||||
msg: string): ForkchoiceUpdatedResponse =
|
||||
ForkchoiceUpdatedResponse(
|
||||
payloadStatus: PayloadStatusV1(
|
||||
status: status,
|
||||
validationError: some(msg)
|
||||
)
|
||||
)
|
||||
|
||||
proc invalidFCU*(hash = common.Hash256()): ForkchoiceUpdatedResponse =
|
||||
ForkchoiceUpdatedResponse(payloadStatus:
|
||||
PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.invalid,
|
||||
latestValidHash: toValidHash(hash)
|
||||
)
|
||||
)
|
||||
|
||||
proc validFCU*(id: Option[PayloadID],
|
||||
validHash: common.Hash256): ForkchoiceUpdatedResponse =
|
||||
ForkchoiceUpdatedResponse(
|
||||
payloadStatus: PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.valid,
|
||||
latestValidHash: toValidHash(validHash)
|
||||
),
|
||||
payloadId: id
|
||||
)
|
||||
|
||||
proc invalidStatus*(validHash: common.Hash256, msg: string): PayloadStatusV1 =
|
||||
PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.invalid,
|
||||
latestValidHash: toValidHash(validHash),
|
||||
validationError: some(msg)
|
||||
)
|
||||
|
||||
proc invalidStatus*(validHash = common.Hash256()): PayloadStatusV1 =
|
||||
PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.invalid,
|
||||
latestValidHash: toValidHash(validHash)
|
||||
)
|
||||
|
||||
proc acceptedStatus*(validHash: common.Hash256): PayloadStatusV1 =
|
||||
PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.accepted,
|
||||
latestValidHash: toValidHash(validHash)
|
||||
)
|
||||
|
||||
proc acceptedStatus*(): PayloadStatusV1 =
|
||||
PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.accepted
|
||||
)
|
||||
|
||||
proc validStatus*(validHash: common.Hash256): PayloadStatusV1 =
|
||||
PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.valid,
|
||||
latestValidHash: toValidHash(validHash)
|
||||
)
|
||||
|
||||
proc invalidParams*(msg: string): ref InvalidRequest =
|
||||
(ref InvalidRequest)(
|
||||
code: engineApiInvalidParams,
|
||||
msg: msg
|
||||
)
|
||||
|
||||
proc unknownPayload*(msg: string): ref InvalidRequest =
|
||||
(ref InvalidRequest)(
|
||||
code: engineApiUnknownPayload,
|
||||
msg: msg
|
||||
)
|
||||
|
||||
proc invalidAttr*(msg: string): ref InvalidRequest =
|
||||
(ref InvalidRequest)(
|
||||
code: engineApiInvalidPayloadAttributes,
|
||||
msg: msg
|
||||
)
|
||||
|
||||
proc unsupportedFork*(msg: string): ref InvalidRequest =
|
||||
(ref InvalidRequest)(
|
||||
code: engineApiUnsupportedFork,
|
||||
msg: msg
|
||||
)
|
||||
|
||||
proc tooLargeRequest*(msg: string): ref InvalidRequest =
|
||||
(ref InvalidRequest)(
|
||||
code: engineApiTooLargeRequest,
|
||||
msg: msg
|
||||
)
|
||||
|
||||
proc latestValidHash*(db: CoreDbRef,
|
||||
parent: common.BlockHeader,
|
||||
ttd: DifficultyInt): common.Hash256
|
||||
{.gcsafe, raises: [RlpError].} =
|
||||
let ptd = db.getScore(parent.parentHash)
|
||||
if ptd >= ttd:
|
||||
parent.blockHash
|
||||
else:
|
||||
# If the most recent valid ancestor is a PoW block,
|
||||
# latestValidHash MUST be set to ZERO
|
||||
common.Hash256()
|
||||
|
||||
proc invalidFCU*(com: CommonRef,
|
||||
header: common.BlockHeader): ForkchoiceUpdatedResponse
|
||||
{.gcsafe, raises: [RlpError].} =
|
||||
var parent: common.BlockHeader
|
||||
if not com.db.getBlockHeader(header.parentHash, parent):
|
||||
return invalidFCU(common.Hash256())
|
||||
|
||||
let blockHash = latestValidHash(com.db, parent,
|
||||
com.ttd.get(high(common.BlockNumber)))
|
||||
invalidFCU(blockHash)
|
|
@ -0,0 +1,171 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2023 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
|
||||
./web3_eth_conv,
|
||||
./payload_conv,
|
||||
./execution_types,
|
||||
./merge_tracker,
|
||||
./payload_queue,
|
||||
../db/core_db,
|
||||
../core/[tx_pool, casper, chain],
|
||||
../common/common
|
||||
|
||||
export
|
||||
common,
|
||||
chain
|
||||
|
||||
type
|
||||
BeaconEngineRef* = ref object
|
||||
txPool: TxPoolRef
|
||||
merge : MergeTrackerRef
|
||||
queue : PayloadQueue
|
||||
chain : ChainRef
|
||||
|
||||
{.push gcsafe, raises:[].}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc setWithdrawals(xp: TxPoolRef, attrs: PayloadAttributes) =
|
||||
case attrs.version
|
||||
of Version.V2, Version.V3:
|
||||
xp.withdrawals = ethWithdrawals attrs.withdrawals.get
|
||||
else:
|
||||
xp.withdrawals = @[]
|
||||
|
||||
template wrapException(body: untyped): auto =
|
||||
try:
|
||||
body
|
||||
except CatchableError as ex:
|
||||
err(ex.msg)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Constructors
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc new*(_: type BeaconEngineRef,
|
||||
txPool: TxPoolRef,
|
||||
chain: ChainRef): BeaconEngineRef =
|
||||
BeaconEngineRef(
|
||||
txPool: txPool,
|
||||
merge : MergeTrackerRef.new(txPool.com.db),
|
||||
queue : PayloadQueue(),
|
||||
chain : chain,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, setters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc reachTTD*(ben: BeaconEngineRef) =
|
||||
## ReachTTD is called whenever the first NewHead message received
|
||||
## from the consensus-layer.
|
||||
ben.merge.reachTTD()
|
||||
|
||||
proc finalizePoS*(ben: BeaconEngineRef) =
|
||||
## FinalizePoS is called whenever the first FinalisedBlock message received
|
||||
## from the consensus-layer.
|
||||
ben.merge.finalizePos()
|
||||
|
||||
proc put*(ben: BeaconEngineRef,
|
||||
hash: common.Hash256, header: common.BlockHeader) =
|
||||
ben.queue.put(hash, header)
|
||||
|
||||
proc put*(ben: BeaconEngineRef, id: PayloadID,
|
||||
blockValue: UInt256, payload: ExecutionPayload) =
|
||||
ben.queue.put(id, blockValue, payload)
|
||||
|
||||
proc put*(ben: BeaconEngineRef, id: PayloadID,
|
||||
blockValue: UInt256, payload: SomeExecutionPayload) =
|
||||
ben.queue.put(id, blockValue, payload)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, getters
|
||||
# ------------------------------------------------------------------------------
|
||||
func com*(ben: BeaconEngineRef): CommonRef =
|
||||
ben.txPool.com
|
||||
|
||||
func chain*(ben: BeaconEngineRef): ChainRef =
|
||||
ben.chain
|
||||
|
||||
func ttdReached*(ben: BeaconEngineRef): bool =
|
||||
## TTDReached reports whether the chain has left the PoW stage.
|
||||
ben.merge.ttdReached
|
||||
|
||||
func posFinalized*(ben: BeaconEngineRef): bool =
|
||||
## PoSFinalized reports whether the chain has entered the PoS stage.
|
||||
ben.merge.posFinalized
|
||||
|
||||
func blockValue*(ben: BeaconEngineRef): UInt256 =
|
||||
## return sum of reward for feeRecipient for each
|
||||
## tx included in a block
|
||||
ben.txPool.blockValue
|
||||
|
||||
proc get*(ben: BeaconEngineRef, hash: common.Hash256,
|
||||
header: var common.BlockHeader): bool =
|
||||
ben.queue.get(hash, header)
|
||||
|
||||
proc get*(ben: BeaconEngineRef, id: PayloadID,
|
||||
blockValue: var UInt256,
|
||||
payload: var ExecutionPayload): bool =
|
||||
ben.queue.get(id, blockValue, payload)
|
||||
|
||||
proc get*(ben: BeaconEngineRef, id: PayloadID,
|
||||
blockValue: var UInt256,
|
||||
payload: var ExecutionPayloadV1): bool =
|
||||
ben.queue.get(id, blockValue, payload)
|
||||
|
||||
proc get*(ben: BeaconEngineRef, id: PayloadID,
|
||||
blockValue: var UInt256,
|
||||
payload: var ExecutionPayloadV2): bool =
|
||||
ben.queue.get(id, blockValue, payload)
|
||||
|
||||
proc get*(ben: BeaconEngineRef, id: PayloadID,
|
||||
blockValue: var UInt256,
|
||||
payload: var ExecutionPayloadV3): bool =
|
||||
ben.queue.get(id, blockValue, payload)
|
||||
|
||||
proc get*(ben: BeaconEngineRef, id: PayloadID,
|
||||
blockValue: var UInt256,
|
||||
payload: var ExecutionPayloadV1OrV2): bool =
|
||||
ben.queue.get(id, blockValue, payload)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc generatePayload*(ben: BeaconEngineRef,
|
||||
attrs: PayloadAttributes):
|
||||
Result[ExecutionPayload, string] =
|
||||
wrapException:
|
||||
let
|
||||
xp = ben.txPool
|
||||
db = xp.com.db
|
||||
pos = xp.com.pos
|
||||
headBlock = db.getCanonicalHead()
|
||||
|
||||
pos.prevRandao = ethHash attrs.prevRandao
|
||||
pos.timestamp = ethTime attrs.timestamp
|
||||
pos.feeRecipient = ethAddr attrs.suggestedFeeRecipient
|
||||
|
||||
xp.setWithdrawals(attrs)
|
||||
|
||||
if headBlock.blockHash != xp.head.blockHash:
|
||||
# reorg
|
||||
discard xp.smartHead(headBlock)
|
||||
|
||||
# someBaseFee = true: make sure blk.header
|
||||
# have the same blockHash with generated payload
|
||||
let blk = xp.ethBlock(someBaseFee = true)
|
||||
if blk.header.extraData.len > 32:
|
||||
return err "extraData length should not exceed 32 bytes"
|
||||
|
||||
ok(executionPayload(blk))
|
|
@ -1,3 +1,12 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2023 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
|
||||
stint,
|
||||
web3/ethtypes,
|
||||
|
@ -98,6 +107,17 @@ func V3*(attr: PayloadAttributes): PayloadAttributesV3 =
|
|||
parentBeaconBlockRoot: attr.parentBeaconBlockRoot.get
|
||||
)
|
||||
|
||||
func payloadAttributes*(attr: PayloadAttributesV1): PayloadAttributes =
|
||||
PayloadAttributes(
|
||||
timestamp: attr.timestamp,
|
||||
prevRandao: attr.prevRandao,
|
||||
suggestedFeeRecipient: attr.suggestedFeeRecipient
|
||||
)
|
||||
|
||||
func payloadAttributes*(x: Option[PayloadAttributesV1]): Option[PayloadAttributes] =
|
||||
if x.isNone: none(PayloadAttributes)
|
||||
else: some(payloadAttributes x.get)
|
||||
|
||||
func V1V2*(p: ExecutionPayload): ExecutionPayloadV1OrV2 =
|
||||
ExecutionPayloadV1OrV2(
|
||||
parentHash: p.parentHash,
|
||||
|
@ -175,6 +195,43 @@ func V3*(p: ExecutionPayload): ExecutionPayloadV3 =
|
|||
excessBlobGas: p.excessBlobGas.get
|
||||
)
|
||||
|
||||
func V1*(p: ExecutionPayloadV1OrV2): ExecutionPayloadV1 =
|
||||
ExecutionPayloadV1(
|
||||
parentHash: p.parentHash,
|
||||
feeRecipient: p.feeRecipient,
|
||||
stateRoot: p.stateRoot,
|
||||
receiptsRoot: p.receiptsRoot,
|
||||
logsBloom: p.logsBloom,
|
||||
prevRandao: p.prevRandao,
|
||||
blockNumber: p.blockNumber,
|
||||
gasLimit: p.gasLimit,
|
||||
gasUsed: p.gasUsed,
|
||||
timestamp: p.timestamp,
|
||||
extraData: p.extraData,
|
||||
baseFeePerGas: p.baseFeePerGas,
|
||||
blockHash: p.blockHash,
|
||||
transactions: p.transactions
|
||||
)
|
||||
|
||||
func V2*(p: ExecutionPayloadV1OrV2): ExecutionPayloadV2 =
|
||||
ExecutionPayloadV2(
|
||||
parentHash: p.parentHash,
|
||||
feeRecipient: p.feeRecipient,
|
||||
stateRoot: p.stateRoot,
|
||||
receiptsRoot: p.receiptsRoot,
|
||||
logsBloom: p.logsBloom,
|
||||
prevRandao: p.prevRandao,
|
||||
blockNumber: p.blockNumber,
|
||||
gasLimit: p.gasLimit,
|
||||
gasUsed: p.gasUsed,
|
||||
timestamp: p.timestamp,
|
||||
extraData: p.extraData,
|
||||
baseFeePerGas: p.baseFeePerGas,
|
||||
blockHash: p.blockHash,
|
||||
transactions: p.transactions,
|
||||
withdrawals: p.withdrawals.get
|
||||
)
|
||||
|
||||
func executionPayload*(p: ExecutionPayloadV1): ExecutionPayload =
|
||||
ExecutionPayload(
|
||||
parentHash: p.parentHash,
|
|
@ -0,0 +1,97 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2022-2023 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.
|
||||
|
||||
{.push gcsafe, raises: [].}
|
||||
|
||||
import
|
||||
chronicles,
|
||||
eth/rlp,
|
||||
../db/[core_db, storage_types]
|
||||
|
||||
type
|
||||
# transitionStatus describes the status of eth1/2 transition. This switch
|
||||
# between modes is a one-way action which is triggered by corresponding
|
||||
# consensus-layer message.
|
||||
TransitionStatus = object
|
||||
# The flag is set when the first NewHead message received
|
||||
leftPoW : bool
|
||||
|
||||
# The flag is set when the first FinalisedBlock message received
|
||||
enteredPoS: bool
|
||||
|
||||
# Merger is an internal help structure used to track the eth1/2
|
||||
# transition status. It's a common structure can be used in both full node
|
||||
# and light client.
|
||||
MergeTrackerRef* = ref object
|
||||
db : CoreDbRef
|
||||
status: TransitionStatus
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc writeStatus(db: CoreDbRef, status: TransitionStatus) =
|
||||
db.kvt.put(transitionStatusKey().toOpenArray(), rlp.encode(status))
|
||||
|
||||
proc readStatus(db: CoreDbRef): TransitionStatus =
|
||||
var bytes = db.kvt.get(transitionStatusKey().toOpenArray())
|
||||
if bytes.len > 0:
|
||||
try:
|
||||
result = rlp.decode(bytes, typeof result)
|
||||
except CatchableError:
|
||||
error "Failed to decode POS transition status"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Constructors
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc new*(_: type MergeTrackerRef, db: CoreDbRef): MergeTrackerRef =
|
||||
MergeTrackerRef(
|
||||
db: db,
|
||||
status: db.readStatus()
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, setters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc reachTTD*(m: MergeTrackerRef) =
|
||||
## ReachTTD is called whenever the first NewHead message received
|
||||
## from the consensus-layer.
|
||||
if m.status.leftPoW:
|
||||
return
|
||||
|
||||
m.status = TransitionStatus(leftPoW: true)
|
||||
m.db.writeStatus(m.status)
|
||||
|
||||
info "Left PoW stage"
|
||||
|
||||
proc finalizePoS*(m: MergeTrackerRef) =
|
||||
## FinalizePoS is called whenever the first FinalisedBlock message received
|
||||
## from the consensus-layer.
|
||||
|
||||
if m.status.enteredPoS:
|
||||
return
|
||||
|
||||
m.status = TransitionStatus(leftPoW: true, enteredPoS: true)
|
||||
m.db.writeStatus(m.status)
|
||||
|
||||
info "Entered PoS stage"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, getters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
func ttdReached*(m: MergeTrackerRef): bool =
|
||||
## TTDReached reports whether the chain has left the PoW stage.
|
||||
m.status.leftPoW
|
||||
|
||||
func posFinalized*(m: MergeTrackerRef): bool =
|
||||
## PoSFinalized reports whether the chain has entered the PoS stage.
|
||||
m.status.enteredPoS
|
|
@ -0,0 +1,124 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2023 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
|
||||
./web3_eth_conv,
|
||||
./execution_types,
|
||||
../utils/utils,
|
||||
eth/common
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
func wdRoot(list: openArray[WithdrawalV1]): common.Hash256
|
||||
{.gcsafe, raises:[RlpError].} =
|
||||
{.nosideEffect.}:
|
||||
calcWithdrawalsRoot(ethWithdrawals list)
|
||||
|
||||
func wdRoot(x: Option[seq[WithdrawalV1]]): Option[common.Hash256]
|
||||
{.gcsafe, raises:[RlpError].} =
|
||||
{.nosideEffect.}:
|
||||
if x.isNone: none(common.Hash256)
|
||||
else: some(wdRoot x.get)
|
||||
|
||||
func txRoot(list: openArray[Web3Tx]): common.Hash256
|
||||
{.gcsafe, raises:[RlpError].} =
|
||||
{.nosideEffect.}:
|
||||
calcTxRoot(ethTxs list)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
{.push gcsafe, raises:[].}
|
||||
|
||||
func executionPayload*(blk: EthBlock): ExecutionPayload =
|
||||
ExecutionPayload(
|
||||
parentHash : w3Hash blk.header.parentHash,
|
||||
feeRecipient : w3Addr blk.header.coinbase,
|
||||
stateRoot : w3Hash blk.header.stateRoot,
|
||||
receiptsRoot : w3Hash blk.header.receiptRoot,
|
||||
logsBloom : w3Bloom blk.header.bloom,
|
||||
prevRandao : w3PrevRandao blk.header.prevRandao,
|
||||
blockNumber : w3Qty blk.header.blockNumber,
|
||||
gasLimit : w3Qty blk.header.gasLimit,
|
||||
gasUsed : w3Qty blk.header.gasUsed,
|
||||
timestamp : w3Qty blk.header.timestamp,
|
||||
extraData : w3ExtraData blk.header.extraData,
|
||||
baseFeePerGas: blk.header.fee.get(0.u256),
|
||||
blockHash : w3Hash blk.header,
|
||||
transactions : w3Txs blk.txs,
|
||||
withdrawals : w3Withdrawals blk.withdrawals,
|
||||
blobGasUsed : w3Qty blk.header.blobGasUsed,
|
||||
excessBlobGas: w3Qty blk.header.excessBlobGas
|
||||
)
|
||||
|
||||
func executionPayloadV1V2*(blk: EthBlock): ExecutionPayloadV1OrV2 =
|
||||
ExecutionPayloadV1OrV2(
|
||||
parentHash : w3Hash blk.header.parentHash,
|
||||
feeRecipient : w3Addr blk.header.coinbase,
|
||||
stateRoot : w3Hash blk.header.stateRoot,
|
||||
receiptsRoot : w3Hash blk.header.receiptRoot,
|
||||
logsBloom : w3Bloom blk.header.bloom,
|
||||
prevRandao : w3PrevRandao blk.header.prevRandao,
|
||||
blockNumber : w3Qty blk.header.blockNumber,
|
||||
gasLimit : w3Qty blk.header.gasLimit,
|
||||
gasUsed : w3Qty blk.header.gasUsed,
|
||||
timestamp : w3Qty blk.header.timestamp,
|
||||
extraData : w3ExtraData blk.header.extraData,
|
||||
baseFeePerGas: blk.header.fee.get(0.u256),
|
||||
blockHash : w3Hash blk.header,
|
||||
transactions : w3Txs blk.txs,
|
||||
withdrawals : w3Withdrawals blk.withdrawals,
|
||||
)
|
||||
|
||||
func blockHeader*(p: ExecutionPayload,
|
||||
beaconRoot: Option[common.Hash256]):
|
||||
common.BlockHeader {.gcsafe, raises:[RlpError].} =
|
||||
common.BlockHeader(
|
||||
parentHash : ethHash p.parentHash,
|
||||
ommersHash : EMPTY_UNCLE_HASH,
|
||||
coinbase : ethAddr p.feeRecipient,
|
||||
stateRoot : ethHash p.stateRoot,
|
||||
txRoot : txRoot p.transactions,
|
||||
receiptRoot : ethHash p.receiptsRoot,
|
||||
bloom : ethBloom p.logsBloom,
|
||||
difficulty : 0.u256,
|
||||
blockNumber : u256 p.blockNumber,
|
||||
gasLimit : ethGasInt p.gasLimit,
|
||||
gasUsed : ethGasInt p.gasUsed,
|
||||
timestamp : ethTime p.timestamp,
|
||||
extraData : ethBlob p.extraData,
|
||||
mixDigest : ethHash p.prevRandao,
|
||||
nonce : default(BlockNonce),
|
||||
fee : some(p.baseFeePerGas),
|
||||
withdrawalsRoot: wdRoot p.withdrawals,
|
||||
blobGasUsed : u64(p.blobGasUsed),
|
||||
excessBlobGas : u64(p.excessBlobGas),
|
||||
parentBeaconBlockRoot: beaconRoot
|
||||
)
|
||||
|
||||
func blockBody*(p: ExecutionPayload):
|
||||
common.BlockBody {.gcsafe, raises:[RlpError].} =
|
||||
common.BlockBody(
|
||||
uncles : @[],
|
||||
transactions: ethTxs p.transactions,
|
||||
withdrawals : ethWithdrawals p.withdrawals,
|
||||
)
|
||||
|
||||
func ethBlock*(p: ExecutionPayload,
|
||||
beaconRoot: Option[common.Hash256]):
|
||||
common.EthBlock {.gcsafe, raises:[RlpError].} =
|
||||
common.Ethblock(
|
||||
header : blockHeader(p, beaconRoot),
|
||||
uncles : @[],
|
||||
txs : ethTxs p.transactions,
|
||||
withdrawals: ethWithdrawals p.withdrawals,
|
||||
)
|
|
@ -8,28 +8,19 @@
|
|||
# those terms.
|
||||
|
||||
import
|
||||
eth/common,
|
||||
web3/engine_api_types,
|
||||
./merger,
|
||||
../execution_types
|
||||
|
||||
|
||||
import eth/common/eth_types except BlockHeader
|
||||
|
||||
export merger, eth_types
|
||||
|
||||
type
|
||||
EthBlockHeader* = eth_types.BlockHeader
|
||||
Hash256 = eth_types.Hash256
|
||||
./execution_types
|
||||
|
||||
const
|
||||
# maxTrackedPayloads is the maximum number of prepared payloads the execution
|
||||
# engine tracks before evicting old ones. Ideally we should only ever track the
|
||||
# latest one; but have a slight wiggle room for non-ideal conditions.
|
||||
# engine tracks before evicting old ones. Ideally we should only ever track
|
||||
# the latest one; but have a slight wiggle room for non-ideal conditions.
|
||||
MaxTrackedPayloads = 10
|
||||
|
||||
# maxTrackedHeaders is the maximum number of executed payloads the execution
|
||||
# engine tracks before evicting old ones. Ideally we should only ever track the
|
||||
# latest one; but have a slight wiggle room for non-ideal conditions.
|
||||
# engine tracks before evicting old ones. Ideally we should only ever track
|
||||
# the latest one; but have a slight wiggle room for non-ideal conditions.
|
||||
MaxTrackedHeaders = 10
|
||||
|
||||
type
|
||||
|
@ -46,14 +37,19 @@ type
|
|||
blockValue: UInt256
|
||||
|
||||
HeaderItem = object
|
||||
hash: Hash256
|
||||
header: EthBlockHeader
|
||||
hash: common.Hash256
|
||||
header: common.BlockHeader
|
||||
|
||||
EngineApiRef* = ref object
|
||||
merger: MergerRef
|
||||
PayloadQueue* = ref object
|
||||
payloadQueue: SimpleQueue[MaxTrackedPayloads, PayloadItem]
|
||||
headerQueue: SimpleQueue[MaxTrackedHeaders, HeaderItem]
|
||||
|
||||
{.push gcsafe, raises:[].}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
template shiftRight[M, T](x: var SimpleQueue[M, T]) =
|
||||
x.list[1..^1] = x.list[0..^2]
|
||||
|
||||
|
@ -66,34 +62,36 @@ iterator items[M, T](x: SimpleQueue[M, T]): T =
|
|||
if z.used:
|
||||
yield z.data
|
||||
|
||||
template new*(_: type EngineApiRef): EngineApiRef =
|
||||
{.error: "EngineApiRef should be created with merger param " & $instantiationInfo().}
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, setters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc new*(_: type EngineApiRef, merger: MergerRef): EngineApiRef =
|
||||
EngineApiRef(
|
||||
merger: merger
|
||||
)
|
||||
|
||||
proc put*(api: EngineApiRef, hash: Hash256, header: EthBlockHeader) =
|
||||
proc put*(api: var PayloadQueue,
|
||||
hash: common.Hash256, header: common.BlockHeader) =
|
||||
api.headerQueue.put(HeaderItem(hash: hash, header: header))
|
||||
|
||||
proc get*(api: EngineApiRef, hash: Hash256, header: var EthBlockHeader): bool =
|
||||
proc put*(api: var PayloadQueue, id: PayloadID,
|
||||
blockValue: UInt256, payload: ExecutionPayload) =
|
||||
api.payloadQueue.put(PayloadItem(id: id,
|
||||
payload: payload, blockValue: blockValue))
|
||||
|
||||
proc put*(api: var PayloadQueue, id: PayloadID,
|
||||
blockValue: UInt256, payload: SomeExecutionPayload) =
|
||||
api.put(id, blockValue, payload.executionPayload)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, getters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc get*(api: PayloadQueue, hash: common.Hash256,
|
||||
header: var common.BlockHeader): bool =
|
||||
for x in api.headerQueue:
|
||||
if x.hash == hash:
|
||||
header = x.header
|
||||
return true
|
||||
false
|
||||
|
||||
proc put*(api: EngineApiRef, id: PayloadID,
|
||||
blockValue: UInt256, payload: ExecutionPayload) =
|
||||
api.payloadQueue.put(PayloadItem(id: id,
|
||||
payload: payload, blockValue: blockValue))
|
||||
|
||||
proc put*(api: EngineApiRef, id: PayloadID,
|
||||
blockValue: UInt256, payload: SomeExecutionPayload) =
|
||||
api.put(id, blockValue, payload.executionPayload)
|
||||
|
||||
proc get*(api: EngineApiRef, id: PayloadID,
|
||||
proc get*(api: PayloadQueue, id: PayloadID,
|
||||
blockValue: var UInt256,
|
||||
payload: var ExecutionPayload): bool =
|
||||
for x in api.payloadQueue:
|
||||
|
@ -103,7 +101,7 @@ proc get*(api: EngineApiRef, id: PayloadID,
|
|||
return true
|
||||
false
|
||||
|
||||
proc get*(api: EngineApiRef, id: PayloadID,
|
||||
proc get*(api: PayloadQueue, id: PayloadID,
|
||||
blockValue: var UInt256,
|
||||
payload: var ExecutionPayloadV1): bool =
|
||||
var p: ExecutionPayload
|
||||
|
@ -112,7 +110,7 @@ proc get*(api: EngineApiRef, id: PayloadID,
|
|||
payload = p.V1
|
||||
return found
|
||||
|
||||
proc get*(api: EngineApiRef, id: PayloadID,
|
||||
proc get*(api: PayloadQueue, id: PayloadID,
|
||||
blockValue: var UInt256,
|
||||
payload: var ExecutionPayloadV2): bool =
|
||||
var p: ExecutionPayload
|
||||
|
@ -121,7 +119,7 @@ proc get*(api: EngineApiRef, id: PayloadID,
|
|||
payload = p.V2
|
||||
return found
|
||||
|
||||
proc get*(api: EngineApiRef, id: PayloadID,
|
||||
proc get*(api: PayloadQueue, id: PayloadID,
|
||||
blockValue: var UInt256,
|
||||
payload: var ExecutionPayloadV3): bool =
|
||||
var p: ExecutionPayload
|
||||
|
@ -130,7 +128,7 @@ proc get*(api: EngineApiRef, id: PayloadID,
|
|||
payload = p.V3
|
||||
return found
|
||||
|
||||
proc get*(api: EngineApiRef, id: PayloadID,
|
||||
proc get*(api: PayloadQueue, id: PayloadID,
|
||||
blockValue: var UInt256,
|
||||
payload: var ExecutionPayloadV1OrV2): bool =
|
||||
var p: ExecutionPayload
|
||||
|
@ -138,6 +136,3 @@ proc get*(api: EngineApiRef, id: PayloadID,
|
|||
doAssert(p.version in {Version.V1, Version.V2})
|
||||
payload = p.V1V2
|
||||
return found
|
||||
|
||||
proc merger*(api: EngineApiRef): MergerRef =
|
||||
api.merger
|
|
@ -0,0 +1,197 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2023 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/[options, times, typetraits],
|
||||
web3/ethtypes,
|
||||
web3/engine_api_types,
|
||||
eth/common/eth_types_rlp,
|
||||
stew/byteutils
|
||||
|
||||
from web3/ethtypes as web3types import nil
|
||||
import eth/common/eth_types as common
|
||||
|
||||
type
|
||||
Web3Hash* = web3types.Hash256
|
||||
Web3Address* = web3types.Address
|
||||
Web3Bloom* = web3types.FixedBytes[256]
|
||||
Web3Quantity* = web3types.Quantity
|
||||
Web3PrevRandao* = web3types.FixedBytes[32]
|
||||
Web3ExtraData* = web3types.DynamicBytes[0, 32]
|
||||
Web3Tx* = web3types.TypedTransaction
|
||||
Web3Blob* = web3types.Blob
|
||||
Web3KZGProof* = web3types.KZGProof
|
||||
Web3KZGCommitment* = web3types.KZGCommitment
|
||||
|
||||
{.push gcsafe, raises:[].}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Pretty printers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `$`*(x: Option[common.Hash256]): string =
|
||||
if x.isNone: "none"
|
||||
else: x.get().data.toHex
|
||||
|
||||
proc `$`*(x: Option[Web3Hash]): string =
|
||||
if x.isNone: "none"
|
||||
else: x.get().toHex
|
||||
|
||||
proc `$`*(x: Option[PayloadID]): string =
|
||||
if x.isNone: "none"
|
||||
else: x.get().toHex
|
||||
|
||||
proc `$`*[N](x: FixedBytes[N]): string =
|
||||
x.toHex
|
||||
|
||||
proc `$`*(x: Web3Quantity): string =
|
||||
$distinctBase(x)
|
||||
|
||||
proc `$`*(x: Web3Address): string =
|
||||
distinctBase(x).toHex
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Web3 defaults
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
func w3PrevRandao*(): Web3PrevRandao =
|
||||
discard
|
||||
|
||||
func w3Address*(): Web3Address =
|
||||
discard
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Web3 types to Eth types
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
template unsafeQuantityToInt64*(q: Web3Quantity): int64 =
|
||||
int64 q
|
||||
|
||||
func u64*(x: Option[Web3Quantity]): Option[uint64] =
|
||||
if x.isNone: none(uint64)
|
||||
else: some(uint64 x.get)
|
||||
|
||||
func u256*(x: Web3Quantity): UInt256 =
|
||||
u256(x.uint64)
|
||||
|
||||
func ethTime*(x: Web3Quantity): common.EthTime =
|
||||
fromUnix(x.unsafeQuantityToInt64)
|
||||
|
||||
func ethHash*(x: Web3PrevRandao): common.Hash256 =
|
||||
common.Hash256(data: distinctBase x)
|
||||
|
||||
func ethHash*(x: Option[Web3Hash]): Option[common.Hash256] =
|
||||
if x.isNone: none(common.Hash256)
|
||||
else: some(ethHash x.get)
|
||||
|
||||
func ethAddr*(x: Web3Address): common.EthAddress =
|
||||
EthAddress x
|
||||
|
||||
func ethBloom*(x: Web3Bloom): common.BloomFilter =
|
||||
common.BloomFilter distinctBase x
|
||||
|
||||
func ethGasInt*(x: Web3Quantity): common.GasInt =
|
||||
common.GasInt x
|
||||
|
||||
func ethBlob*(x: Web3ExtraData): common.Blob =
|
||||
common.Blob distinctBase x
|
||||
|
||||
func ethWithdrawal*(x: WithdrawalV1): common.Withdrawal =
|
||||
result.index = x.index.uint64
|
||||
result.validatorIndex = x.validatorIndex.uint64
|
||||
result.address = x.address.EthAddress
|
||||
result.amount = x.amount.uint64
|
||||
|
||||
func ethWithdrawals*(list: openArray[WithdrawalV1]):
|
||||
seq[common.Withdrawal] =
|
||||
result = newSeqOfCap[common.Withdrawal](list.len)
|
||||
for x in list:
|
||||
result.add ethWithdrawal(x)
|
||||
|
||||
func ethWithdrawals*(x: Option[seq[WithdrawalV1]]):
|
||||
Option[seq[common.Withdrawal]] =
|
||||
if x.isNone: none(seq[common.Withdrawal])
|
||||
else: some(ethWithdrawals x.get)
|
||||
|
||||
func ethTx*(x: Web3Tx): common.Transaction {.gcsafe, raises:[RlpError].} =
|
||||
result = rlp.decode(distinctBase x, common.Transaction)
|
||||
|
||||
func ethTxs*(list: openArray[Web3Tx]):
|
||||
seq[common.Transaction] {.gcsafe, raises:[RlpError].} =
|
||||
result = newSeqOfCap[common.Transaction](list.len)
|
||||
for x in list:
|
||||
result.add ethTx(x)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Eth types to Web3 types
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
func w3Hash*(x: common.Hash256): Web3Hash =
|
||||
Web3Hash x.data
|
||||
|
||||
func w3Hash*(x: Option[common.Hash256]): Option[BlockHash] =
|
||||
if x.isNone: none(BlockHash)
|
||||
else: some(BlockHash x.get.data)
|
||||
|
||||
func w3Hash*(x: common.BlockHeader): BlockHash =
|
||||
BlockHash rlpHash(x).data
|
||||
|
||||
func w3Addr*(x: common.EthAddress): Web3Address =
|
||||
Web3Address x
|
||||
|
||||
func w3Bloom*(x: common.BloomFilter): Web3Bloom =
|
||||
Web3Bloom x
|
||||
|
||||
func w3PrevRandao*(x: common.Hash256): Web3PrevRandao =
|
||||
Web3PrevRandao x.data
|
||||
|
||||
func w3Qty*(x: UInt256): Web3Quantity =
|
||||
Web3Quantity x.truncate(uint64)
|
||||
|
||||
func w3Qty*(x: common.GasInt): Web3Quantity =
|
||||
Web3Quantity x.uint64
|
||||
|
||||
func w3Qty*(x: common.EthTime): Web3Quantity =
|
||||
Web3Quantity x.toUnix
|
||||
|
||||
func w3Qty*(x: common.EthTime, y: int): Web3Quantity =
|
||||
Web3Quantity(x.toUnix + y.int64)
|
||||
|
||||
func w3Qty*(x: Option[uint64]): Option[Web3Quantity] =
|
||||
if x.isNone: none(Web3Quantity)
|
||||
else: some(Web3Quantity x.get)
|
||||
|
||||
func w3ExtraData*(x: common.Blob): Web3ExtraData =
|
||||
Web3ExtraData x
|
||||
|
||||
func w3Withdrawal*(w: common.Withdrawal): WithdrawalV1 =
|
||||
WithdrawalV1(
|
||||
index : Web3Quantity w.index,
|
||||
validatorIndex: Web3Quantity w.validatorIndex,
|
||||
address : Web3Address w.address,
|
||||
amount : Web3Quantity w.amount
|
||||
)
|
||||
|
||||
func w3Withdrawals*(list: openArray[common.Withdrawal]): seq[WithdrawalV1] =
|
||||
result = newSeqOfCap[WithdrawalV1](list.len)
|
||||
for x in list:
|
||||
result.add w3Withdrawal(x)
|
||||
|
||||
func w3Withdrawals*(x: Option[seq[common.Withdrawal]]):
|
||||
Option[seq[WithdrawalV1]] =
|
||||
if x.isNone: none(seq[WithdrawalV1])
|
||||
else: some(w3Withdrawals x.get)
|
||||
|
||||
func w3Tx*(tx: common.Transaction): Web3Tx =
|
||||
Web3Tx rlp.encode(tx.removeNetworkPayload)
|
||||
|
||||
func w3Txs*(list: openArray[common.Transaction]): seq[Web3Tx] =
|
||||
result = newSeqOfCap[Web3Tx](list.len)
|
||||
for tx in list:
|
||||
result.add w3Tx(tx)
|
|
@ -102,7 +102,7 @@ proc prepare*(c: Clique; parent: BlockHeader, header: var BlockHeader): CliqueOk
|
|||
header.coinbase.reset
|
||||
|
||||
let modEpoch = (parent.blockNumber+1) mod c.cfg.epoch
|
||||
if modEpoch != 0:
|
||||
if modEpoch.isZero.not:
|
||||
# Gather all the proposals that make sense voting on
|
||||
var addresses: seq[EthAddress]
|
||||
for (address,authorize) in c.proposals.pairs:
|
||||
|
@ -120,7 +120,7 @@ proc prepare*(c: Clique; parent: BlockHeader, header: var BlockHeader): CliqueOk
|
|||
|
||||
# Ensure the extra data has all its components
|
||||
header.extraData.setLen(EXTRA_VANITY)
|
||||
if modEpoch == 0:
|
||||
if modEpoch.isZero:
|
||||
header.extraData.add c.snapshot.ballot.authSigners.mapIt(toSeq(it)).concat
|
||||
header.extraData.add 0.byte.repeat(EXTRA_SEAL)
|
||||
|
||||
|
|
|
@ -128,10 +128,10 @@ proc maxCheckPointLe(d: var LocalSnaps; number: BlockNumber): BlockNumber =
|
|||
0.u256
|
||||
|
||||
proc isCheckPoint(d: var LocalSnaps; number: BlockNumber): bool =
|
||||
(number mod d.c.cfg.ckpInterval) == 0
|
||||
(number mod d.c.cfg.ckpInterval).isZero
|
||||
|
||||
proc isEpoch(d: var LocalSnaps; number: BlockNumber): bool =
|
||||
(number mod d.c.cfg.epoch) == 0
|
||||
(number mod d.c.cfg.epoch).isZero
|
||||
|
||||
proc isSnapshotPosition(d: var LocalSnaps; number: BlockNumber): bool =
|
||||
# clique/clique.go(394): if number == 0 || (number%c.config.Epoch [..]
|
||||
|
|
|
@ -209,7 +209,7 @@ proc verifyHeaderFields(c: Clique; header: BlockHeader): CliqueOkResult =
|
|||
return err((errFutureBlock,""))
|
||||
|
||||
# Checkpoint blocks need to enforce zero beneficiary
|
||||
let isCheckPoint = (header.blockNumber mod c.cfg.epoch.u256) == 0
|
||||
let isCheckPoint = (header.blockNumber mod c.cfg.epoch.u256).isZero
|
||||
if isCheckPoint and not header.coinbase.isZero:
|
||||
return err((errInvalidCheckpointBeneficiary,""))
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ proc snapshotApplySeq*(s: Snapshot; headers: var seq[BlockHeader],
|
|||
number = header.blockNumber
|
||||
|
||||
# Remove any votes on checkpoint blocks
|
||||
if (number mod s.cfg.epoch) == 0:
|
||||
if (number mod s.cfg.epoch).isZero:
|
||||
# Note that the correctness of the authorised accounts list is verified in
|
||||
# clique/clique.verifyCascadingFields(),
|
||||
# see clique/clique.go(355): if number%c.config.Epoch == 0 {
|
||||
|
|
|
@ -1,42 +1,30 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2021 Status Research & Development GmbH
|
||||
# Copyright (c) 2018-2023 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.
|
||||
# * 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/[sequtils, times, typetraits],
|
||||
std/[times],
|
||||
pkg/[chronos,
|
||||
stew/results,
|
||||
chronicles,
|
||||
eth/keys,
|
||||
eth/rlp],
|
||||
eth/keys],
|
||||
".."/[config,
|
||||
constants],
|
||||
"."/[
|
||||
chain,
|
||||
tx_pool,
|
||||
casper,
|
||||
validate],
|
||||
"."/clique/[
|
||||
clique_desc,
|
||||
clique_cfg,
|
||||
clique_sealer],
|
||||
../utils/utils,
|
||||
../common/[common, context],
|
||||
../rpc/execution_types
|
||||
|
||||
|
||||
from web3/ethtypes as web3types import nil, TypedTransaction, WithdrawalV1, ExecutionPayloadV1OrV2, toExecutionPayloadV1OrV2, toExecutionPayloadV1
|
||||
from web3/engine_api_types import PayloadAttributesV1, ExecutionPayloadV1, PayloadAttributesV2, ExecutionPayloadV2
|
||||
|
||||
export
|
||||
# generateExecutionPayload caller will need this
|
||||
casper
|
||||
../common/[common, context]
|
||||
|
||||
type
|
||||
EngineState* = enum
|
||||
|
@ -44,11 +32,6 @@ type
|
|||
EngineRunning,
|
||||
EnginePostMerge
|
||||
|
||||
Web3BlockHash = web3types.BlockHash
|
||||
Web3Address = web3types.Address
|
||||
Web3Bloom = web3types.FixedBytes[256]
|
||||
Web3Quantity = web3types.Quantity
|
||||
|
||||
SealingEngineRef* = ref SealingEngineObj
|
||||
SealingEngineObj = object of RootObj
|
||||
state: EngineState
|
||||
|
@ -141,102 +124,6 @@ proc sealingLoop(engine: SealingEngineRef): Future[void] {.async.} =
|
|||
discard engine.txPool.smartHead(blk.header) # add transactions update jobs
|
||||
info "block generated", number=blk.header.blockNumber
|
||||
|
||||
template unsafeQuantityToInt64(q: web3types.Quantity): int64 =
|
||||
int64 q
|
||||
|
||||
proc toTypedTransaction(tx: Transaction): TypedTransaction =
|
||||
web3types.TypedTransaction(rlp.encode(tx.removeNetworkPayload))
|
||||
|
||||
func toWithdrawal(x: WithdrawalV1): Withdrawal =
|
||||
result.index = x.index.uint64
|
||||
result.validatorIndex = x.validatorIndex.uint64
|
||||
result.address = x.address.EthAddress
|
||||
result.amount = x.amount.uint64
|
||||
|
||||
func toWithdrawals(list: openArray[WithdrawalV1]): seq[Withdrawal] =
|
||||
result = newSeqOfCap[Withdrawal](list.len)
|
||||
for x in list:
|
||||
result.add toWithdrawal(x)
|
||||
|
||||
proc generateExecutionPayload*(engine: SealingEngineRef,
|
||||
payloadAttrs: SomePayloadAttributes): Result[ExecutionPayload, string] =
|
||||
let
|
||||
headBlock = try: engine.chain.db.getCanonicalHead()
|
||||
except CatchableError: return err "No head block in database"
|
||||
pos = engine.chain.com.pos
|
||||
|
||||
pos.prevRandao = Hash256(data: distinctBase payloadAttrs.prevRandao)
|
||||
pos.timestamp = fromUnix(payloadAttrs.timestamp.unsafeQuantityToInt64)
|
||||
pos.feeRecipient = EthAddress payloadAttrs.suggestedFeeRecipient
|
||||
|
||||
when payloadAttrs is PayloadAttributesV2:
|
||||
engine.txPool.withdrawals = payloadAttrs.withdrawals.toWithdrawals
|
||||
elif payloadAttrs is PayloadAttributesV3:
|
||||
engine.txPool.withdrawals = payloadAttrs.withdrawals.toWithdrawals
|
||||
else:
|
||||
engine.txPool.withdrawals = @[]
|
||||
|
||||
if headBlock.blockHash != engine.txPool.head.blockHash:
|
||||
# reorg
|
||||
discard engine.txPool.smartHead(headBlock)
|
||||
|
||||
var blk: EthBlock
|
||||
let res = engine.generateBlock(blk)
|
||||
if res.isErr:
|
||||
error "sealing engine generateBlock error", msg = res.error
|
||||
return err(res.error)
|
||||
|
||||
# make sure both generated block header and payloadRes(ExecutionPayloadV2)
|
||||
# produce the same blockHash
|
||||
blk.header.fee = some(blk.header.fee.get(UInt256.zero)) # force it with some(UInt256)
|
||||
|
||||
let blockHash = rlpHash(blk.header)
|
||||
if blk.header.extraData.len > 32:
|
||||
return err "extraData length should not exceed 32 bytes"
|
||||
|
||||
let transactions = blk.txs.map(toTypedTransaction)
|
||||
|
||||
let withdrawals =
|
||||
when payloadAttrs is PayloadAttributesV2:
|
||||
some(payloadAttrs.withdrawals)
|
||||
else:
|
||||
none[seq[WithdrawalV1]]()
|
||||
|
||||
let blobGasUsed = if blk.header.blobGasUsed.isSome:
|
||||
some(blk.header.blobGasUsed.get.Quantity)
|
||||
else:
|
||||
none(Quantity)
|
||||
|
||||
let excessBlobGas = if blk.header.excessBlobGas.isSome:
|
||||
some(blk.header.excessBlobGas.get.Quantity)
|
||||
else:
|
||||
none(Quantity)
|
||||
|
||||
return ok(ExecutionPayload(
|
||||
parentHash: Web3BlockHash blk.header.parentHash.data,
|
||||
feeRecipient: Web3Address blk.header.coinbase,
|
||||
stateRoot: Web3BlockHash blk.header.stateRoot.data,
|
||||
receiptsRoot: Web3BlockHash blk.header.receiptRoot.data,
|
||||
logsBloom: Web3Bloom blk.header.bloom,
|
||||
prevRandao: payloadAttrs.prevRandao,
|
||||
blockNumber: Web3Quantity blk.header.blockNumber.truncate(uint64),
|
||||
gasLimit: Web3Quantity blk.header.gasLimit,
|
||||
gasUsed: Web3Quantity blk.header.gasUsed,
|
||||
timestamp: payloadAttrs.timestamp,
|
||||
extraData: web3types.DynamicBytes[0, 32] blk.header.extraData,
|
||||
baseFeePerGas: blk.header.fee.get(UInt256.zero),
|
||||
blockHash: Web3BlockHash blockHash.data,
|
||||
transactions: transactions,
|
||||
withdrawals: withdrawals,
|
||||
blobGasUsed: blobGasUsed,
|
||||
excessBlobGas: excessBlobGas
|
||||
))
|
||||
|
||||
proc blockValue*(engine: SealingEngineRef): UInt256 =
|
||||
# return sum of reward for feeRecipient for each
|
||||
# tx included in a block
|
||||
engine.txPool.blockValue
|
||||
|
||||
proc new*(_: type SealingEngineRef,
|
||||
chain: ChainRef,
|
||||
ctx: EthContext,
|
||||
|
|
|
@ -593,6 +593,10 @@ proc triggerReorg*(xp: TxPoolRef)
|
|||
# Public functions, getters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc com*(xp: TxPoolRef): CommonRef =
|
||||
## Getter
|
||||
xp.chain.com
|
||||
|
||||
proc baseFee*(xp: TxPoolRef): GasPrice =
|
||||
## Getter, this parameter modifies/determines the expected gain when packing
|
||||
xp.chain.baseFee
|
||||
|
@ -602,7 +606,7 @@ proc dirtyBuckets*(xp: TxPoolRef): bool =
|
|||
## flag is also set.
|
||||
xp.pDirtyBuckets
|
||||
|
||||
proc ethBlock*(xp: TxPoolRef): EthBlock
|
||||
proc ethBlock*(xp: TxPoolRef, someBaseFee: bool = false): EthBlock
|
||||
{.gcsafe,raises: [CatchableError].} =
|
||||
## Getter, retrieves a packed block ready for mining and signing depending
|
||||
## on the internally cached block chain head, the txs in the pool and some
|
||||
|
@ -625,6 +629,10 @@ proc ethBlock*(xp: TxPoolRef): EthBlock
|
|||
if com.forkGTE(Shanghai):
|
||||
result.withdrawals = some(xp.chain.withdrawals)
|
||||
|
||||
if someBaseFee:
|
||||
# make sure baseFee always has something
|
||||
result.header.fee = some(result.header.fee.get(0.u256))
|
||||
|
||||
proc gasCumulative*(xp: TxPoolRef): GasInt =
|
||||
## Getter, retrieves the gas that will be burned in the block after
|
||||
## retrieving it via `ethBlock`.
|
||||
|
|
|
@ -12,7 +12,10 @@
|
|||
## ============================================
|
||||
##
|
||||
|
||||
{.push raises: [CatchableError].} # basically the annotation type of a `Vm2OpFn`
|
||||
when defined(evmc_enabled):
|
||||
{.push raises: [CatchableError].} # basically the annotation type of a `Vm2OpFn`
|
||||
else:
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
../../../errors,
|
||||
|
|
|
@ -262,7 +262,7 @@ const
|
|||
jumpIOp: Vm2OpFn = proc (k: var Vm2Ctx) =
|
||||
## 0x57, Conditionally alter the program counter.
|
||||
let (jumpTarget, testedValue) = k.cpt.stack.popInt(2)
|
||||
if testedValue != 0:
|
||||
if testedValue.isZero.not:
|
||||
jumpImpl(k.cpt, jumpTarget)
|
||||
|
||||
pcOp: Vm2OpFn = proc (k: var Vm2Ctx) =
|
||||
|
|
|
@ -26,7 +26,7 @@ import
|
|||
./graphql/ethapi,
|
||||
./core/[chain, sealer, clique/clique_desc,
|
||||
clique/clique_sealer, tx_pool, block_import],
|
||||
./rpc/merge/merger,
|
||||
./beacon/beacon_engine,
|
||||
./sync/[legacy, full, protocol, snap, stateless,
|
||||
protocol/les_protocol, handlers, peers],
|
||||
./evm/async/data_sources/json_rpc_data_source
|
||||
|
@ -61,7 +61,7 @@ type
|
|||
snapSyncRef: SnapSyncRef
|
||||
fullSyncRef: FullSyncRef
|
||||
statelessSyncRef: StatelessSyncRef
|
||||
merger: MergerRef
|
||||
beaconEngine: BeaconEngineRef
|
||||
|
||||
proc importBlocks(conf: NimbusConf, com: CommonRef) =
|
||||
if string(conf.blocksFile).len > 0:
|
||||
|
@ -74,9 +74,6 @@ proc importBlocks(conf: NimbusConf, com: CommonRef) =
|
|||
proc basicServices(nimbus: NimbusNode,
|
||||
conf: NimbusConf,
|
||||
com: CommonRef) =
|
||||
# app wide TxPool singleton
|
||||
# TODO: disable some of txPool internal mechanism if
|
||||
# the engineSigner is zero.
|
||||
nimbus.txPool = TxPoolRef.new(com, conf.engineSigner)
|
||||
|
||||
# txPool must be informed of active head
|
||||
|
@ -92,10 +89,7 @@ proc basicServices(nimbus: NimbusNode,
|
|||
nimbus.chainRef.extraValidation = 0 < verifyFrom
|
||||
nimbus.chainRef.verifyFrom = verifyFrom
|
||||
|
||||
# this is temporary workaround to track POS transition
|
||||
# until we have proper chain config and hard fork module
|
||||
# see issue #640
|
||||
nimbus.merger = MergerRef.new(com.db)
|
||||
nimbus.beaconEngine = BeaconEngineRef.new(nimbus.txPool, nimbus.chainRef)
|
||||
|
||||
proc manageAccounts(nimbus: NimbusNode, conf: NimbusConf) =
|
||||
if string(conf.keyStore).len > 0:
|
||||
|
@ -371,32 +365,32 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf,
|
|||
nimbus.sealingEngine.start()
|
||||
|
||||
if conf.engineApiEnabled:
|
||||
let maybeAsyncDataSource = maybeStatelessAsyncDataSource(nimbus, conf)
|
||||
#let maybeAsyncDataSource = maybeStatelessAsyncDataSource(nimbus, conf)
|
||||
if conf.engineApiPort != conf.rpcPort:
|
||||
nimbus.engineApiServer = newRpcHttpServer(
|
||||
[initTAddress(conf.engineApiAddress, conf.engineApiPort)],
|
||||
authHooks = @[httpJwtAuthHook, httpCorsHook]
|
||||
)
|
||||
setupEngineAPI(nimbus.sealingEngine, nimbus.engineApiServer, nimbus.merger, maybeAsyncDataSource)
|
||||
setupEngineAPI(nimbus.beaconEngine, nimbus.engineApiServer)
|
||||
setupEthRpc(nimbus.ethNode, nimbus.ctx, com, nimbus.txPool, nimbus.engineApiServer)
|
||||
nimbus.engineApiServer.start()
|
||||
else:
|
||||
setupEngineAPI(nimbus.sealingEngine, nimbus.rpcServer, nimbus.merger, maybeAsyncDataSource)
|
||||
setupEngineAPI(nimbus.beaconEngine, nimbus.rpcServer)
|
||||
|
||||
info "Starting engine API server", port = conf.engineApiPort
|
||||
|
||||
if conf.engineApiWsEnabled:
|
||||
let maybeAsyncDataSource = maybeStatelessAsyncDataSource(nimbus, conf)
|
||||
#let maybeAsyncDataSource = maybeStatelessAsyncDataSource(nimbus, conf)
|
||||
if conf.engineApiWsPort != conf.wsPort:
|
||||
nimbus.engineApiWsServer = newRpcWebSocketServer(
|
||||
initTAddress(conf.engineApiWsAddress, conf.engineApiWsPort),
|
||||
authHooks = @[wsJwtAuthHook, wsCorsHook]
|
||||
)
|
||||
setupEngineAPI(nimbus.sealingEngine, nimbus.engineApiWsServer, nimbus.merger, maybeAsyncDataSource)
|
||||
setupEngineAPI(nimbus.beaconEngine, nimbus.engineApiWsServer)
|
||||
setupEthRpc(nimbus.ethNode, nimbus.ctx, com, nimbus.txPool, nimbus.engineApiWsServer)
|
||||
nimbus.engineApiWsServer.start()
|
||||
else:
|
||||
setupEngineAPI(nimbus.sealingEngine, nimbus.wsRpcServer, nimbus.merger, maybeAsyncDataSource)
|
||||
setupEngineAPI(nimbus.beaconEngine, nimbus.wsRpcServer)
|
||||
|
||||
info "Starting WebSocket engine API server", port = conf.engineApiWsPort
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2018 Status Research & Development GmbH
|
||||
# Copyright (c) 202-2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
|
@ -8,533 +8,17 @@
|
|||
# those terms.
|
||||
|
||||
import
|
||||
std/[typetraits, times, strutils, sequtils, sets],
|
||||
stew/[results, byteutils],
|
||||
std/[typetraits, sequtils, sets],
|
||||
stew/[byteutils],
|
||||
json_rpc/rpcserver,
|
||||
web3/[conversions],
|
||||
web3/engine_api_types as web3types,
|
||||
eth/rlp,
|
||||
eth/common/eth_types,
|
||||
eth/common/eth_types_rlp,
|
||||
../common/common,
|
||||
".."/core/chain/[chain_desc, persist_blocks],
|
||||
".."/stateless_runner,
|
||||
../constants,
|
||||
../core/[tx_pool, sealer],
|
||||
../evm/async/data_sources,
|
||||
./merge/[mergetypes, mergeutils],
|
||||
./execution_types,
|
||||
# put chronicles import last because Nim
|
||||
# compiler resolve `$` for logging
|
||||
# arguments differently on Windows vs posix
|
||||
# if chronicles import is in the middle
|
||||
chronicles
|
||||
../beacon/api_handler,
|
||||
../beacon/beacon_engine,
|
||||
../beacon/web3_eth_conv,
|
||||
../beacon/execution_types
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
type
|
||||
BlockHeader = eth_types.BlockHeader
|
||||
Hash256 = eth_types.Hash256
|
||||
Web3Blob = web3types.Blob
|
||||
Web3KZGProof = web3types.KZGProof
|
||||
Web3KZGCommitment = web3types.KZGCommitment
|
||||
|
||||
proc latestValidHash(db: CoreDbRef, parent: EthBlockHeader, ttd: DifficultyInt): Hash256
|
||||
{.gcsafe, raises: [RlpError].} =
|
||||
let ptd = db.getScore(parent.parentHash)
|
||||
if ptd >= ttd:
|
||||
parent.blockHash
|
||||
else:
|
||||
# If the most recent valid ancestor is a PoW block,
|
||||
# latestValidHash MUST be set to ZERO
|
||||
Hash256()
|
||||
|
||||
proc invalidFCU(com: CommonRef, header: EthBlockHeader): ForkchoiceUpdatedResponse
|
||||
{.gcsafe, raises: [RlpError].} =
|
||||
var parent: EthBlockHeader
|
||||
if not com.db.getBlockHeader(header.parentHash, parent):
|
||||
return invalidFCU(Hash256())
|
||||
|
||||
let blockHash = latestValidHash(com.db, parent, com.ttd.get(high(common.BlockNumber)))
|
||||
invalidFCU(blockHash)
|
||||
|
||||
template unsafeQuantityToInt64(q: Quantity): int64 =
|
||||
int64 q
|
||||
|
||||
# I created these handle_whatever procs to eliminate duplicated code
|
||||
# between the V1 and V2 RPC endpoint implementations. (I believe
|
||||
# they're meant to be implementable in that way. e.g. The V2 specs
|
||||
# explicitly say "here's what to do if the `withdrawals` field is
|
||||
# null.) --Adam
|
||||
|
||||
# https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#engine_newpayloadv1
|
||||
proc handle_newPayload(sealingEngine: SealingEngineRef,
|
||||
api: EngineApiRef,
|
||||
com: CommonRef, maybeAsyncDataSource: Option[AsyncDataSource],
|
||||
payload: SomeExecutionPayload): PayloadStatusV1 {.raises: [CatchableError].} =
|
||||
trace "Engine API request received",
|
||||
meth = "newPayload", number = $(distinctBase payload.blockNumber), hash = payload.blockHash
|
||||
|
||||
if com.isShanghaiOrLater(fromUnix(payload.timestamp.unsafeQuantityToInt64)):
|
||||
when not(payload is ExecutionPayloadV2):
|
||||
raise invalidParams("if timestamp is Shanghai or later, " &
|
||||
"payload must be ExecutionPayloadV2")
|
||||
else:
|
||||
when not(payload is ExecutionPayloadV1):
|
||||
if com.syncReqRelaxV2:
|
||||
trace "Relaxed mode, treating payload as V1"
|
||||
discard
|
||||
else:
|
||||
raise invalidParams("if timestamp is earlier than Shanghai, " &
|
||||
"payload must be ExecutionPayloadV1")
|
||||
|
||||
var header = toBlockHeader(payload)
|
||||
let blockHash = payload.blockHash.asEthHash
|
||||
var res = header.validateBlockHash(blockHash)
|
||||
if res.isErr:
|
||||
return res.error
|
||||
|
||||
let db = sealingEngine.chain.db
|
||||
|
||||
# If we already have the block locally, ignore the entire execution and just
|
||||
# return a fake success.
|
||||
if db.getBlockHeader(blockHash, header):
|
||||
warn "Ignoring already known beacon payload",
|
||||
number = header.blockNumber, hash = blockHash
|
||||
return validStatus(blockHash)
|
||||
|
||||
# FIXME-Adam - I'm adding this here, but I don't actually think this is the right place.
|
||||
# For one thing, it won't even persist the new block. But let's worry about persisting
|
||||
# after I've gotten a block to come out actually correct. --Adam
|
||||
if maybeAsyncDataSource.isSome:
|
||||
let r = statelesslyRunBlock(maybeAsyncDataSource.get, com, header, toBlockBody(payload))
|
||||
if r.isErr:
|
||||
error "Stateless execution failed", error=r.error
|
||||
return invalidStatus()
|
||||
else:
|
||||
return validStatus(r.get)
|
||||
|
||||
# If the parent is missing, we - in theory - could trigger a sync, but that
|
||||
# would also entail a reorg. That is problematic if multiple sibling blocks
|
||||
# are being fed to us, and even moreso, if some semi-distant uncle shortens
|
||||
# our live chain. As such, payload execution will not permit reorgs and thus
|
||||
# will not trigger a sync cycle. That is fine though, if we get a fork choice
|
||||
# update after legit payload executions.
|
||||
var parent: EthBlockHeader
|
||||
if not db.getBlockHeader(header.parentHash, parent):
|
||||
# Stash the block away for a potential forced forckchoice update to it
|
||||
# at a later time.
|
||||
api.put(blockHash, header)
|
||||
|
||||
# Although we don't want to trigger a sync, if there is one already in
|
||||
# progress, try to extend if with the current payload request to relieve
|
||||
# some strain from the forkchoice update.
|
||||
#if err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header()); err == nil {
|
||||
# log.Debug("Payload accepted for sync extension", "number", params.Number, "hash", params.BlockHash)
|
||||
# return beacon.PayloadStatusV1{Status: beacon.SYNCING}, nil
|
||||
|
||||
# Either no beacon sync was started yet, or it rejected the delivered
|
||||
# payload as non-integratable on top of the existing sync. We'll just
|
||||
# have to rely on the beacon client to forcefully update the head with
|
||||
# a forkchoice update request.
|
||||
warn "Ignoring payload with missing parent",
|
||||
number = header.blockNumber,
|
||||
hash = blockHash,
|
||||
parent = header.parentHash
|
||||
return acceptedStatus()
|
||||
|
||||
# We have an existing parent, do some sanity checks to avoid the beacon client
|
||||
# triggering too early
|
||||
let ttd = com.ttd.get(high(common.BlockNumber))
|
||||
|
||||
when payload is ExecutionPayloadV1:
|
||||
let td = db.getScore(header.parentHash)
|
||||
if (not com.forkGTE(MergeFork)) and td < ttd:
|
||||
warn "Ignoring pre-merge payload",
|
||||
number = header.blockNumber, hash = blockHash, td, ttd
|
||||
return invalidStatus()
|
||||
|
||||
if header.timestamp <= parent.timestamp:
|
||||
warn "Invalid timestamp",
|
||||
parent = header.timestamp, header = header.timestamp
|
||||
return invalidStatus(db.getHeadBlockHash(), "Invalid timestamp")
|
||||
|
||||
if not db.haveBlockAndState(header.parentHash):
|
||||
api.put(blockHash, header)
|
||||
warn "State not available, ignoring new payload",
|
||||
hash = blockHash,
|
||||
number = header.blockNumber
|
||||
let blockHash = latestValidHash(db, parent, ttd)
|
||||
return acceptedStatus(blockHash)
|
||||
|
||||
trace "Inserting block without sethead",
|
||||
hash = blockHash, number = header.blockNumber
|
||||
let body = toBlockBody(payload)
|
||||
let vres = sealingEngine.chain.insertBlockWithoutSetHead(header, body)
|
||||
if vres != ValidationResult.OK:
|
||||
let blockHash = latestValidHash(db, parent, ttd)
|
||||
return invalidStatus(blockHash, "Failed to insert block")
|
||||
|
||||
# We've accepted a valid payload from the beacon client. Mark the local
|
||||
# chain transitions to notify other subsystems (e.g. downloader) of the
|
||||
# behavioral change.
|
||||
if not api.merger.ttdReached():
|
||||
api.merger.reachTTD()
|
||||
# TODO: cancel downloader
|
||||
|
||||
return validStatus(blockHash)
|
||||
|
||||
# https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#engine_getpayloadv1
|
||||
proc handle_getPayload(api: EngineApiRef, payloadId: PayloadID): GetPayloadV2Response {.raises: [CatchableError].} =
|
||||
trace "Engine API request received",
|
||||
meth = "GetPayload", id = payloadId.toHex
|
||||
|
||||
var payload: ExecutionPayloadV1OrV2
|
||||
var blockValue: UInt256
|
||||
if not api.get(payloadId, blockValue, payload):
|
||||
raise unknownPayload("Unknown payload")
|
||||
|
||||
return GetPayloadV2Response(
|
||||
executionPayload: payload,
|
||||
blockValue: blockValue
|
||||
)
|
||||
|
||||
proc handle_getPayloadV3(api: EngineApiRef, com: CommonRef, payloadId: PayloadID): GetPayloadV3Response {.raises: [CatchableError].} =
|
||||
trace "Engine API request received",
|
||||
meth = "GetPayload", id = payloadId.toHex
|
||||
|
||||
var payload: ExecutionPayloadV3
|
||||
var blockValue: UInt256
|
||||
if not api.get(payloadId, blockValue, payload):
|
||||
raise unknownPayload("Unknown payload")
|
||||
|
||||
if not com.isCancunOrLater(fromUnix(payload.timestamp.unsafeQuantityToInt64)):
|
||||
raise unsupportedFork("payload timestamp is less than Cancun activation")
|
||||
|
||||
var
|
||||
blobsBundle: BlobsBundleV1
|
||||
|
||||
try:
|
||||
for ttx in payload.transactions:
|
||||
let tx = rlp.decode(distinctBase(ttx), Transaction)
|
||||
if tx.networkPayload.isNil.not:
|
||||
for blob in tx.networkPayload.blobs:
|
||||
blobsBundle.blobs.add Web3Blob(blob)
|
||||
for p in tx.networkPayload.proofs:
|
||||
blobsBundle.proofs.add Web3KZGProof(p)
|
||||
for k in tx.networkPayload.commitments:
|
||||
blobsBundle.commitments.add Web3KZGCommitment(k)
|
||||
except RlpError:
|
||||
doAssert(false, "found TypedTransaction that RLP failed to decode")
|
||||
|
||||
return GetPayloadV3Response(
|
||||
executionPayload: payload,
|
||||
blockValue: blockValue,
|
||||
blobsBundle: blobsBundle
|
||||
)
|
||||
|
||||
# https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#engine_exchangetransitionconfigurationv1
|
||||
proc handle_exchangeTransitionConfiguration(sealingEngine: SealingEngineRef, com: CommonRef, conf: TransitionConfigurationV1): TransitionConfigurationV1 {.raises: [CatchableError].} =
|
||||
trace "Engine API request received",
|
||||
meth = "exchangeTransitionConfigurationV1",
|
||||
ttd = conf.terminalTotalDifficulty,
|
||||
number = uint64(conf.terminalBlockNumber),
|
||||
blockHash = conf.terminalBlockHash
|
||||
let db = sealingEngine.chain.db
|
||||
let ttd = com.ttd
|
||||
|
||||
if ttd.isNone:
|
||||
raise newException(ValueError, "invalid ttd: EL (none) CL ($1)" % [$conf.terminalTotalDifficulty])
|
||||
|
||||
if conf.terminalTotalDifficulty != ttd.get:
|
||||
raise newException(ValueError, "invalid ttd: EL ($1) CL ($2)" % [$ttd.get, $conf.terminalTotalDifficulty])
|
||||
|
||||
let terminalBlockNumber = uint64(conf.terminalBlockNumber).toBlockNumber
|
||||
let terminalBlockHash = conf.terminalBlockHash.asEthHash
|
||||
|
||||
if terminalBlockHash != Hash256():
|
||||
var headerHash: Hash256
|
||||
|
||||
if not db.getBlockHash(terminalBlockNumber, headerHash):
|
||||
raise newException(ValueError, "cannot get terminal block hash, number $1" %
|
||||
[$terminalBlockNumber])
|
||||
|
||||
if terminalBlockHash != headerHash:
|
||||
raise newException(ValueError, "invalid terminal block hash, got $1 want $2" %
|
||||
[$terminalBlockHash, $headerHash])
|
||||
|
||||
var header: EthBlockHeader
|
||||
if not db.getBlockHeader(headerHash, header):
|
||||
raise newException(ValueError, "cannot get terminal block header, hash $1" %
|
||||
[$terminalBlockHash])
|
||||
|
||||
return TransitionConfigurationV1(
|
||||
terminalTotalDifficulty: ttd.get,
|
||||
terminalBlockHash : BlockHash headerHash.data,
|
||||
terminalBlockNumber : Quantity header.blockNumber.truncate(uint64)
|
||||
)
|
||||
|
||||
if terminalBlockNumber != 0:
|
||||
raise newException(ValueError, "invalid terminal block number: $1" % [$terminalBlockNumber])
|
||||
|
||||
if terminalBlockHash != Hash256():
|
||||
raise newException(ValueError, "invalid terminal block hash, no terminal header set")
|
||||
|
||||
return TransitionConfigurationV1(terminalTotalDifficulty: ttd.get)
|
||||
|
||||
# ForkchoiceUpdated has several responsibilities:
|
||||
# If the method is called with an empty head block:
|
||||
# we return success, which can be used to check if the catalyst mode is enabled
|
||||
# If the total difficulty was not reached:
|
||||
# we return INVALID
|
||||
# If the finalizedBlockHash is set:
|
||||
# we check if we have the finalizedBlockHash in our db, if not we start a sync
|
||||
# We try to set our blockchain to the headBlock
|
||||
# If there are payloadAttributes:
|
||||
# we try to assemble a block with the payloadAttributes and return its payloadID
|
||||
# https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_forkchoiceupdatedv2
|
||||
proc handle_forkchoiceUpdated(sealingEngine: SealingEngineRef,
|
||||
com: CommonRef, api: EngineApiRef,
|
||||
update: ForkchoiceStateV1,
|
||||
payloadAttributes: SomeOptionalPayloadAttributes): ForkchoiceUpdatedResponse {.raises: [CatchableError].} =
|
||||
|
||||
if payloadAttributes.isSome:
|
||||
let attr = payloadAttributes.get
|
||||
if com.isCancunOrLater(fromUnix(attr.timestamp.unsafeQuantityToInt64)):
|
||||
when not(payloadAttributes is Option[PayloadAttributesV3]):
|
||||
raise invalidParams("if timestamp is Cancun or later, payloadAttributes must be PayloadAttributesV3")
|
||||
elif com.isShanghaiOrLater(fromUnix(attr.timestamp.unsafeQuantityToInt64)):
|
||||
when not(payloadAttributes is Option[PayloadAttributesV2]):
|
||||
raise invalidParams("if timestamp is Shanghai or later, payloadAttributes must be PayloadAttributesV2")
|
||||
else:
|
||||
when not(payloadAttributes is Option[PayloadAttributesV1]):
|
||||
raise invalidParams("if timestamp is earlier than Shanghai, payloadAttributes must be PayloadAttributesV1")
|
||||
|
||||
let
|
||||
chain = sealingEngine.chain
|
||||
db = chain.db
|
||||
blockHash = update.headBlockHash.asEthHash
|
||||
|
||||
if blockHash == Hash256():
|
||||
warn "Forkchoice requested update to zero hash"
|
||||
return simpleFCU(PayloadExecutionStatus.invalid)
|
||||
|
||||
# Check whether we have the block yet in our database or not. If not, we'll
|
||||
# need to either trigger a sync, or to reject this forkchoice update for a
|
||||
# reason.
|
||||
var header: EthBlockHeader
|
||||
if not db.getBlockHeader(blockHash, header):
|
||||
# If the head hash is unknown (was not given to us in a newPayload request),
|
||||
# we cannot resolve the header, so not much to do. This could be extended in
|
||||
# the future to resolve from the `eth` network, but it's an unexpected case
|
||||
# that should be fixed, not papered over.
|
||||
if not api.get(blockHash, header):
|
||||
warn "Forkchoice requested unknown head",
|
||||
hash = blockHash
|
||||
return simpleFCU(PayloadExecutionStatus.syncing)
|
||||
|
||||
# Header advertised via a past newPayload request. Start syncing to it.
|
||||
# Before we do however, make sure any legacy sync in switched off so we
|
||||
# don't accidentally have 2 cycles running.
|
||||
if not api.merger.ttdReached():
|
||||
api.merger.reachTTD()
|
||||
# TODO: cancel downloader
|
||||
|
||||
info "Forkchoice requested sync to new head",
|
||||
number = header.blockNumber,
|
||||
hash = blockHash
|
||||
|
||||
# Update sync header (if any)
|
||||
com.syncReqNewHead(header)
|
||||
|
||||
return simpleFCU(PayloadExecutionStatus.syncing)
|
||||
|
||||
# Block is known locally, just sanity check that the beacon client does not
|
||||
# attempt to push us back to before the merge.
|
||||
let blockNumber = header.blockNumber.truncate(uint64)
|
||||
if header.difficulty > 0.u256 or blockNumber == 0'u64:
|
||||
var
|
||||
td, ptd: DifficultyInt
|
||||
ttd = com.ttd.get(high(common.BlockNumber))
|
||||
|
||||
if not db.getTd(blockHash, td) or (blockNumber > 0'u64 and not db.getTd(header.parentHash, ptd)):
|
||||
error "TDs unavailable for TTD check",
|
||||
number = blockNumber,
|
||||
hash = blockHash,
|
||||
td = td,
|
||||
parent = header.parentHash,
|
||||
ptd = ptd
|
||||
return simpleFCU(PayloadExecutionStatus.invalid, "TDs unavailable for TDD check")
|
||||
|
||||
if td < ttd or (blockNumber > 0'u64 and ptd > ttd):
|
||||
error "Refusing beacon update to pre-merge",
|
||||
number = blockNumber,
|
||||
hash = blockHash,
|
||||
diff = header.difficulty,
|
||||
ptd = ptd,
|
||||
ttd = ttd
|
||||
|
||||
return invalidFCU()
|
||||
|
||||
# If the head block is already in our canonical chain, the beacon client is
|
||||
# probably resyncing. Ignore the update.
|
||||
var canonHash: Hash256
|
||||
if db.getBlockHash(header.blockNumber, canonHash) and canonHash == blockHash:
|
||||
# TODO should this be possible?
|
||||
# If we allow these types of reorgs, we will do lots and lots of reorgs during sync
|
||||
warn "Reorg to previous block"
|
||||
if chain.setCanonical(header) != ValidationResult.OK:
|
||||
return invalidFCU(com, header)
|
||||
elif chain.setCanonical(header) != ValidationResult.OK:
|
||||
return invalidFCU(com, header)
|
||||
|
||||
# If the beacon client also advertised a finalized block, mark the local
|
||||
# chain final and completely in PoS mode.
|
||||
let finalizedBlockHash = update.finalizedBlockHash.asEthHash
|
||||
if finalizedBlockHash != Hash256():
|
||||
if not api.merger.posFinalized:
|
||||
api.merger.finalizePoS()
|
||||
|
||||
# TODO: If the finalized block is not in our canonical tree, somethings wrong
|
||||
var finalBlock: EthBlockHeader
|
||||
if not db.getBlockHeader(finalizedBlockHash, finalBlock):
|
||||
warn "Final block not available in database",
|
||||
hash=finalizedBlockHash
|
||||
raise invalidParams("finalized block header not available")
|
||||
var finalHash: Hash256
|
||||
if not db.getBlockHash(finalBlock.blockNumber, finalHash):
|
||||
warn "Final block not in canonical chain",
|
||||
number=finalBlock.blockNumber,
|
||||
hash=finalizedBlockHash
|
||||
raise invalidParams("finalized block hash not available")
|
||||
if finalHash != finalizedBlockHash:
|
||||
warn "Final block not in canonical chain",
|
||||
number=finalBlock.blockNumber,
|
||||
expect=finalizedBlockHash,
|
||||
get=finalHash
|
||||
raise invalidParams("finalilized block not canonical")
|
||||
db.finalizedHeaderHash(finalizedBlockHash)
|
||||
|
||||
let safeBlockHash = update.safeBlockHash.asEthHash
|
||||
if safeBlockHash != Hash256():
|
||||
var safeBlock: EthBlockHeader
|
||||
if not db.getBlockHeader(safeBlockHash, safeBlock):
|
||||
warn "Safe block not available in database",
|
||||
hash = safeBlockHash
|
||||
raise invalidParams("safe head not available")
|
||||
var safeHash: Hash256
|
||||
if not db.getBlockHash(safeBlock.blockNumber, safeHash):
|
||||
warn "Safe block hash not available in database",
|
||||
hash = safeHash
|
||||
raise invalidParams("safe block hash not available")
|
||||
if safeHash != safeBlockHash:
|
||||
warn "Safe block not in canonical chain",
|
||||
blockNumber=safeBlock.blockNumber,
|
||||
expect=safeBlockHash,
|
||||
get=safeHash
|
||||
raise invalidParams("safe head not canonical")
|
||||
db.safeHeaderHash(safeBlockHash)
|
||||
|
||||
# If payload generation was requested, create a new block to be potentially
|
||||
# sealed by the beacon client. The payload will be requested later, and we
|
||||
# might replace it arbitrarilly many times in between.
|
||||
if payloadAttributes.isSome:
|
||||
let payloadAttrs = payloadAttributes.get()
|
||||
let res = sealingEngine.generateExecutionPayload(payloadAttrs)
|
||||
|
||||
if res.isErr:
|
||||
error "Failed to create sealing payload", err = res.error
|
||||
raise invalidAttr(res.error)
|
||||
|
||||
let payload = res.get
|
||||
|
||||
let id = computePayloadId(blockHash, payloadAttrs)
|
||||
api.put(id, sealingEngine.blockValue, payload)
|
||||
|
||||
info "Created payload for sealing",
|
||||
id = id.toHex,
|
||||
hash = payload.blockHash,
|
||||
number = payload.blockNumber.uint64
|
||||
|
||||
return validFCU(some(id), blockHash)
|
||||
|
||||
return validFCU(none(PayloadID), blockHash)
|
||||
|
||||
func toHash(value: array[32, byte]): Hash256 =
|
||||
result.data = value
|
||||
|
||||
const
|
||||
maxBodyRequest = 32
|
||||
|
||||
proc getPayloadBodyByHeader(db: CoreDbRef,
|
||||
header: BlockHeader,
|
||||
output: var seq[Option[ExecutionPayloadBodyV1]]) {.raises: [CatchableError].} =
|
||||
|
||||
var body: BlockBody
|
||||
if not db.getBlockBody(header, body):
|
||||
output.add none(ExecutionPayloadBodyV1)
|
||||
return
|
||||
|
||||
var typedTransactions: seq[TypedTransaction]
|
||||
for tx in body.transactions:
|
||||
typedTransactions.add(tx.toTypedTransaction)
|
||||
|
||||
var withdrawals: seq[WithdrawalV1]
|
||||
if body.withdrawals.isSome:
|
||||
for w in body.withdrawals.get:
|
||||
withdrawals.add(w.toWithdrawalV1)
|
||||
|
||||
output.add(
|
||||
some(ExecutionPayloadBodyV1(
|
||||
transactions: typedTransactions,
|
||||
# pre Shanghai block return null withdrawals
|
||||
# post Shanghai block return at least empty slice
|
||||
withdrawals: if header.withdrawalsRoot.isSome:
|
||||
some(withdrawals)
|
||||
else:
|
||||
none(seq[WithdrawalV1])
|
||||
))
|
||||
)
|
||||
|
||||
proc handle_getPayloadBodiesByHash(com: CommonRef,
|
||||
hashes: seq[BlockHash]):
|
||||
seq[Option[ExecutionPayloadBodyV1]] {.raises: [CatchableError].} =
|
||||
if hashes.len > maxBodyRequest:
|
||||
raise tooLargeRequest("request exceeds max allowed " & $maxBodyRequest)
|
||||
|
||||
let db = com.db
|
||||
var header: BlockHeader
|
||||
for h in hashes:
|
||||
if not db.getBlockHeader(toHash(distinctBase(h)), header):
|
||||
result.add none(ExecutionPayloadBodyV1)
|
||||
continue
|
||||
db.getPayloadBodyByHeader(header, result)
|
||||
|
||||
proc handle_getPayloadBodiesByRange(com: CommonRef,
|
||||
start: uint64, count: uint64):
|
||||
seq[Option[ExecutionPayloadBodyV1]] {.raises: [CatchableError].} =
|
||||
if start == 0:
|
||||
raise invalidParams("start block should greater than zero")
|
||||
|
||||
if count == 0:
|
||||
raise invalidParams("blocks count should greater than zero")
|
||||
|
||||
if count > maxBodyRequest:
|
||||
raise tooLargeRequest("request exceeds max allowed " & $maxBodyRequest)
|
||||
|
||||
let db = com.db
|
||||
var header: BlockHeader
|
||||
var last = start+count-1
|
||||
let current = com.syncCurrent.truncate(uint64)
|
||||
if last > current:
|
||||
last = current
|
||||
|
||||
for bn in start..last:
|
||||
if not db.getBlockHeader(bn.toBlockNumber, header):
|
||||
result.add none(ExecutionPayloadBodyV1)
|
||||
continue
|
||||
db.getPayloadBodyByHeader(header, result)
|
||||
|
||||
const supportedMethods: HashSet[string] =
|
||||
toHashSet([
|
||||
"engine_newPayloadV1",
|
||||
|
@ -554,98 +38,53 @@ const supportedMethods: HashSet[string] =
|
|||
# I'm trying to keep the handlers below very thin, and move the
|
||||
# bodies up to the various procs above. Once we have multiple
|
||||
# versions, they'll need to be able to share code.
|
||||
proc setupEngineAPI*(
|
||||
sealingEngine: SealingEngineRef,
|
||||
server: RpcServer,
|
||||
merger: MergerRef,
|
||||
maybeAsyncDataSource: Option[AsyncDataSource] = none[AsyncDataSource]()) =
|
||||
|
||||
let
|
||||
api = EngineApiRef.new(merger)
|
||||
com = sealingEngine.chain.com
|
||||
proc setupEngineAPI*(engine: BeaconEngineRef, server: RpcServer) =
|
||||
|
||||
server.rpc("engine_exchangeCapabilities") do(methods: seq[string]) -> seq[string]:
|
||||
return methods.filterIt(supportedMethods.contains(it))
|
||||
|
||||
# cannot use `params` as param name. see https:#github.com/status-im/nim-json-rpc/issues/128
|
||||
server.rpc("engine_newPayloadV1") do(payload: ExecutionPayloadV1) -> PayloadStatusV1:
|
||||
return handle_newPayload(sealingEngine, api, com, maybeAsyncDataSource, payload)
|
||||
return engine.newPayload(payload.executionPayload)
|
||||
|
||||
server.rpc("engine_newPayloadV2") do(payload: ExecutionPayload) -> PayloadStatusV1:
|
||||
if payload.version == Version.V1:
|
||||
return handle_newPayload(sealingEngine, api, com, maybeAsyncDataSource, payload.V1)
|
||||
else:
|
||||
return handle_newPayload(sealingEngine, api, com, maybeAsyncDataSource, payload.V2)
|
||||
return engine.newPayload(payload)
|
||||
|
||||
server.rpc("engine_newPayloadV3") do(payload: ExecutionPayload,
|
||||
expectedBlobVersionedHashes: seq[FixedBytes[32]],
|
||||
parentBeaconBlockRoot: FixedBytes[32]) -> PayloadStatusV1:
|
||||
case payload.version:
|
||||
of Version.V1:
|
||||
return handle_newPayload(sealingEngine, api, com, maybeAsyncDataSource, payload.V1)
|
||||
of Version.V2:
|
||||
return handle_newPayload(sealingEngine, api, com, maybeAsyncDataSource, payload.V2)
|
||||
of Version.V3:
|
||||
if not com.isCancunOrLater(fromUnix(payload.timestamp.unsafeQuantityToInt64)):
|
||||
raise unsupportedFork("payload timestamp is less than Cancun activation")
|
||||
var versionedHashes: seq[Hash256]
|
||||
for x in payload.transactions:
|
||||
let tx = rlp.decode(distinctBase(x), Transaction)
|
||||
versionedHashes.add tx.versionedHashes
|
||||
for i, x in expectedBlobVersionedHashes:
|
||||
if distinctBase(x) != versionedHashes[i].data:
|
||||
expectedBlobVersionedHashes: seq[Web3Hash],
|
||||
parentBeaconBlockRoot: Web3Hash) -> PayloadStatusV1:
|
||||
if not validateVersionedHashed(payload, expectedBlobVersionedHashes):
|
||||
return invalidStatus()
|
||||
return handle_newPayload(sealingEngine, api, com, maybeAsyncDataSource, payload.V3)
|
||||
return engine.newPayload(payload, some(parentBeaconBlockRoot))
|
||||
|
||||
server.rpc("engine_getPayloadV1") do(payloadId: PayloadID) -> ExecutionPayloadV1:
|
||||
let r = handle_getPayload(api, payloadId)
|
||||
return r.executionPayload.toExecutionPayloadV1
|
||||
return engine.getPayload(payloadId).executionPayload.V1
|
||||
|
||||
server.rpc("engine_getPayloadV2") do(payloadId: PayloadID) -> GetPayloadV2Response:
|
||||
return handle_getPayload(api, payloadId)
|
||||
return engine.getPayload(payloadId)
|
||||
|
||||
server.rpc("engine_getPayloadV3") do(payloadId: PayloadID) -> GetPayloadV3Response:
|
||||
return handle_getPayloadV3(api, com, payloadId)
|
||||
return engine.getPayloadV3(payloadId)
|
||||
|
||||
server.rpc("engine_exchangeTransitionConfigurationV1") do(conf: TransitionConfigurationV1) -> TransitionConfigurationV1:
|
||||
return handle_exchangeTransitionConfiguration(sealingEngine, com, conf)
|
||||
server.rpc("engine_exchangeTransitionConfigurationV1") do(
|
||||
conf: TransitionConfigurationV1) -> TransitionConfigurationV1:
|
||||
return engine.exchangeConf(conf)
|
||||
|
||||
server.rpc("engine_forkchoiceUpdatedV1") do(
|
||||
update: ForkchoiceStateV1,
|
||||
payloadAttributes: Option[PayloadAttributesV1]) -> ForkchoiceUpdatedResponse:
|
||||
return handle_forkchoiceUpdated(sealingEngine, com, api, update, payloadAttributes)
|
||||
server.rpc("engine_forkchoiceUpdatedV1") do(update: ForkchoiceStateV1,
|
||||
attrs: Option[PayloadAttributesV1]) -> ForkchoiceUpdatedResponse:
|
||||
return engine.forkchoiceUpdated(update, attrs.payloadAttributes)
|
||||
|
||||
server.rpc("engine_forkchoiceUpdatedV2") do(
|
||||
update: ForkchoiceStateV1,
|
||||
payloadAttributes: Option[PayloadAttributes]) -> ForkchoiceUpdatedResponse:
|
||||
if payloadAttributes.isNone:
|
||||
return handle_forkchoiceUpdated(sealingEngine, com, api, update, none[PayloadAttributesV2]())
|
||||
else:
|
||||
let attr = payloadAttributes.get
|
||||
if attr.version == Version.V1:
|
||||
return handle_forkchoiceUpdated(sealingEngine, com, api, update, some(attr.V1))
|
||||
else:
|
||||
return handle_forkchoiceUpdated(sealingEngine, com, api, update, some(attr.V2))
|
||||
server.rpc("engine_forkchoiceUpdatedV2") do(update: ForkchoiceStateV1,
|
||||
attrs: Option[PayloadAttributes]) -> ForkchoiceUpdatedResponse:
|
||||
return engine.forkchoiceUpdated(update, attrs)
|
||||
|
||||
server.rpc("engine_forkchoiceUpdatedV3") do(
|
||||
update: ForkchoiceStateV1,
|
||||
payloadAttributes: Option[PayloadAttributes]) -> ForkchoiceUpdatedResponse:
|
||||
if payloadAttributes.isNone:
|
||||
return handle_forkchoiceUpdated(sealingEngine, com, api, update, none[PayloadAttributesV3]())
|
||||
else:
|
||||
let attr = payloadAttributes.get
|
||||
case attr.version
|
||||
of Version.V1:
|
||||
return handle_forkchoiceUpdated(sealingEngine, com, api, update, some(attr.V1))
|
||||
of Version.V2:
|
||||
return handle_forkchoiceUpdated(sealingEngine, com, api, update, some(attr.V2))
|
||||
of Version.V3:
|
||||
return handle_forkchoiceUpdated(sealingEngine, com, api, update, some(attr.V3))
|
||||
server.rpc("engine_forkchoiceUpdatedV3") do(update: ForkchoiceStateV1,
|
||||
attrs: Option[PayloadAttributes]) -> ForkchoiceUpdatedResponse:
|
||||
return engine.forkchoiceUpdated(update, attrs)
|
||||
|
||||
server.rpc("engine_getPayloadBodiesByHashV1") do(
|
||||
hashes: seq[BlockHash]) -> seq[Option[ExecutionPayloadBodyV1]]:
|
||||
return handle_getPayloadBodiesByHash(com, hashes)
|
||||
server.rpc("engine_getPayloadBodiesByHashV1") do(hashes: seq[Web3Hash]) ->
|
||||
seq[Option[ExecutionPayloadBodyV1]]:
|
||||
return engine.getPayloadBodiesByHash(hashes)
|
||||
|
||||
server.rpc("engine_getPayloadBodiesByRangeV1") do(
|
||||
start: Quantity, count: Quantity) -> seq[Option[ExecutionPayloadBodyV1]]:
|
||||
return handle_getPayloadBodiesByRange(com, start.uint64, count.uint64)
|
||||
return engine.getPayloadBodiesByRange(start.uint64, count.uint64)
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2022-2023 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.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
chronicles,
|
||||
eth/rlp,
|
||||
../../db/[core_db, storage_types]
|
||||
|
||||
type
|
||||
# transitionStatus describes the status of eth1/2 transition. This switch
|
||||
# between modes is a one-way action which is triggered by corresponding
|
||||
# consensus-layer message.
|
||||
TransitionStatus = object
|
||||
leftPoW : bool # The flag is set when the first NewHead message received
|
||||
enteredPoS: bool # The flag is set when the first FinalisedBlock message received
|
||||
|
||||
# Merger is an internal help structure used to track the eth1/2 transition status.
|
||||
# It's a common structure can be used in both full node and light client.
|
||||
MergerRef* = ref object
|
||||
db : CoreDbRef
|
||||
status: TransitionStatus
|
||||
|
||||
proc writeStatus(db: CoreDbRef, status: TransitionStatus) {.gcsafe, raises: [].} =
|
||||
db.kvt.put(transitionStatusKey().toOpenArray(), rlp.encode(status))
|
||||
|
||||
proc readStatus(db: CoreDbRef): TransitionStatus {.gcsafe, raises: [].} =
|
||||
var bytes = db.kvt.get(transitionStatusKey().toOpenArray())
|
||||
if bytes.len > 0:
|
||||
try:
|
||||
result = rlp.decode(bytes, typeof result)
|
||||
except CatchableError:
|
||||
error "Failed to decode POS transition status"
|
||||
|
||||
proc new*(_: type MergerRef, db: CoreDbRef): MergerRef {.gcsafe, raises: [].} =
|
||||
MergerRef(
|
||||
db: db,
|
||||
status: db.readStatus()
|
||||
)
|
||||
|
||||
# ReachTTD is called whenever the first NewHead message received
|
||||
# from the consensus-layer.
|
||||
proc reachTTD*(m: MergerRef) {.gcsafe, raises: [].} =
|
||||
if m.status.leftPoW:
|
||||
return
|
||||
|
||||
m.status = TransitionStatus(leftPoW: true)
|
||||
m.db.writeStatus(m.status)
|
||||
|
||||
info "Left PoW stage"
|
||||
|
||||
# FinalizePoS is called whenever the first FinalisedBlock message received
|
||||
# from the consensus-layer.
|
||||
proc finalizePoS*(m: MergerRef) {.gcsafe, raises: [].} =
|
||||
if m.status.enteredPoS:
|
||||
return
|
||||
|
||||
m.status = TransitionStatus(leftPoW: true, enteredPoS: true)
|
||||
m.db.writeStatus(m.status)
|
||||
|
||||
info "Entered PoS stage"
|
||||
|
||||
# TTDReached reports whether the chain has left the PoW stage.
|
||||
proc ttdReached*(m: MergerRef): bool =
|
||||
m.status.leftPoW
|
||||
|
||||
# PoSFinalized reports whether the chain has entered the PoS stage.
|
||||
proc posFinalized*(m: MergerRef): bool =
|
||||
m.status.enteredPoS
|
|
@ -1,253 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2022-2023 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, strutils],
|
||||
eth/[rlp, common],
|
||||
json_rpc/errors,
|
||||
nimcrypto/[hash, sha2],
|
||||
stew/[results, byteutils],
|
||||
web3/engine_api_types,
|
||||
../../constants,
|
||||
../../db/core_db,
|
||||
../../utils/utils,
|
||||
../../rpc/execution_types,
|
||||
./mergetypes
|
||||
|
||||
type Hash256 = eth_types.Hash256
|
||||
|
||||
proc computePayloadId*(headBlockHash: Hash256, params: SomePayloadAttributes): PayloadID =
|
||||
var dest: Hash256
|
||||
var ctx: sha256
|
||||
ctx.init()
|
||||
ctx.update(headBlockHash.data)
|
||||
ctx.update(toBytesBE distinctBase params.timestamp)
|
||||
ctx.update(distinctBase params.prevRandao)
|
||||
ctx.update(distinctBase params.suggestedFeeRecipient)
|
||||
# FIXME-Adam: Do we need to include the withdrawals in this calculation?
|
||||
# https://github.com/ethereum/go-ethereum/pull/25838#discussion_r1024340383
|
||||
# "The execution api specs define that this ID can be completely random. It
|
||||
# used to be derived from payload attributes in the past, but maybe it's
|
||||
# time to use a randomized ID to not break it with any changes to the
|
||||
# attributes?"
|
||||
ctx.finish dest.data
|
||||
ctx.clear()
|
||||
(distinctBase result)[0..7] = dest.data[0..7]
|
||||
|
||||
proc append*(w: var RlpWriter, q: Quantity) =
|
||||
w.append(uint64(q))
|
||||
|
||||
proc append*(w: var RlpWriter, a: Address) =
|
||||
w.append(distinctBase(a))
|
||||
|
||||
template unsafeQuantityToInt64(q: Quantity): int64 =
|
||||
int64 q
|
||||
|
||||
template asEthHash*(hash: engine_api_types.BlockHash): Hash256 =
|
||||
Hash256(data: distinctBase(hash))
|
||||
|
||||
proc calcRootHashRlp*(items: openArray[seq[byte]]): Hash256 =
|
||||
var tr = newCoreDbRef(LegacyDbMemory).mptPrune
|
||||
for i, t in items:
|
||||
tr.put(rlp.encode(i), t)
|
||||
return tr.rootHash()
|
||||
|
||||
proc toWithdrawal*(w: WithdrawalV1): Withdrawal =
|
||||
Withdrawal(
|
||||
index: uint64(w.index),
|
||||
validatorIndex: uint64(w.validatorIndex),
|
||||
address: distinctBase(w.address),
|
||||
amount: uint64(w.amount) # AARDVARK: is this wei or gwei or what?
|
||||
)
|
||||
|
||||
proc toWithdrawalV1*(w: Withdrawal): WithdrawalV1 =
|
||||
WithdrawalV1(
|
||||
index: Quantity(w.index),
|
||||
validatorIndex: Quantity(w.validatorIndex),
|
||||
address: Address(w.address),
|
||||
amount: Quantity(w.amount) # AARDVARK: is this wei or gwei or what?
|
||||
)
|
||||
|
||||
proc maybeWithdrawalsRoot(payload: SomeExecutionPayload): Option[Hash256] =
|
||||
when payload is ExecutionPayloadV1:
|
||||
none(Hash256)
|
||||
else:
|
||||
var wds = newSeqOfCap[Withdrawal](payload.withdrawals.len)
|
||||
for wd in payload.withdrawals:
|
||||
wds.add toWithdrawal(wd)
|
||||
some(utils.calcWithdrawalsRoot(wds))
|
||||
|
||||
proc toWithdrawals(withdrawals: openArray[WithdrawalV1]): seq[WithDrawal] =
|
||||
result = newSeqOfCap[Withdrawal](withdrawals.len)
|
||||
for wd in withdrawals:
|
||||
result.add toWithdrawal(wd)
|
||||
|
||||
proc maybeBlobGasUsed(payload: SomeExecutionPayload): Option[uint64] =
|
||||
when payload is ExecutionPayloadV3:
|
||||
some(payload.blobGasUsed.uint64)
|
||||
else:
|
||||
none(uint64)
|
||||
|
||||
proc maybeExcessBlobGas(payload: SomeExecutionPayload): Option[uint64] =
|
||||
when payload is ExecutionPayloadV3:
|
||||
some(payload.excessBlobGas.uint64)
|
||||
else:
|
||||
none(uint64)
|
||||
|
||||
proc toBlockHeader*(payload: SomeExecutionPayload): EthBlockHeader =
|
||||
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 : bytes payload.extraData,
|
||||
mixDigest : payload.prevRandao.asEthHash, # EIP-4399 redefine `mixDigest` -> `prevRandao`
|
||||
nonce : default(BlockNonce),
|
||||
fee : some payload.baseFeePerGas,
|
||||
withdrawalsRoot: payload.maybeWithdrawalsRoot, # EIP-4895
|
||||
blobGasUsed : payload.maybeBlobGasUsed, # EIP-4844
|
||||
excessBlobGas : payload.maybeExcessBlobGas, # EIP-4844
|
||||
)
|
||||
|
||||
proc toBlockHeader*(payload: ExecutionPayload): EthBlockHeader =
|
||||
case payload.version
|
||||
of Version.V1: toBlockHeader(payload.V1)
|
||||
of Version.V2: toBlockHeader(payload.V2)
|
||||
of Version.V3: toBlockHeader(payload.V3)
|
||||
|
||||
proc toTypedTransaction*(tx: Transaction): TypedTransaction =
|
||||
TypedTransaction(rlp.encode(tx))
|
||||
|
||||
proc toBlockBody*(payload: SomeExecutionPayload): BlockBody =
|
||||
result.transactions.setLen(payload.transactions.len)
|
||||
for i, tx in payload.transactions:
|
||||
result.transactions[i] = rlp.decode(distinctBase tx, Transaction)
|
||||
when payload is ExecutionPayloadV2:
|
||||
result.withdrawals = some(payload.withdrawals.toWithdrawals)
|
||||
when payload is ExecutionPayloadV3:
|
||||
result.withdrawals = some(payload.withdrawals.toWithdrawals)
|
||||
|
||||
proc `$`*(x: BlockHash): string =
|
||||
toHex(x)
|
||||
|
||||
template toValidHash*(x: Hash256): Option[BlockHash] =
|
||||
some(BlockHash(x.data))
|
||||
|
||||
proc validateBlockHash*(header: EthBlockHeader, gotHash: Hash256): Result[void, PayloadStatusV1] =
|
||||
let wantHash = header.blockHash
|
||||
if wantHash != gotHash:
|
||||
let status = PayloadStatusV1(
|
||||
# This used to say invalid_block_hash, but see here:
|
||||
# https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_newpayloadv2
|
||||
# "INVALID_BLOCK_HASH status value is supplanted by INVALID."
|
||||
status: PayloadExecutionStatus.invalid,
|
||||
validationError: some("blockhash mismatch, want $1, got $2" % [$wantHash, $gotHash])
|
||||
)
|
||||
return err(status)
|
||||
|
||||
return ok()
|
||||
|
||||
proc simpleFCU*(status: PayloadExecutionStatus): ForkchoiceUpdatedResponse =
|
||||
ForkchoiceUpdatedResponse(payloadStatus: PayloadStatusV1(status: status))
|
||||
|
||||
proc simpleFCU*(status: PayloadExecutionStatus, msg: string): ForkchoiceUpdatedResponse =
|
||||
ForkchoiceUpdatedResponse(
|
||||
payloadStatus: PayloadStatusV1(
|
||||
status: status,
|
||||
validationError: some(msg)
|
||||
)
|
||||
)
|
||||
|
||||
proc invalidFCU*(hash: Hash256 = Hash256()): ForkchoiceUpdatedResponse =
|
||||
ForkchoiceUpdatedResponse(payloadStatus:
|
||||
PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.invalid,
|
||||
latestValidHash: toValidHash(hash)
|
||||
)
|
||||
)
|
||||
|
||||
proc validFCU*(id: Option[PayloadID], validHash: Hash256): ForkchoiceUpdatedResponse =
|
||||
ForkchoiceUpdatedResponse(
|
||||
payloadStatus: PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.valid,
|
||||
latestValidHash: toValidHash(validHash)
|
||||
),
|
||||
payloadId: id
|
||||
)
|
||||
|
||||
proc invalidStatus*(validHash: Hash256, msg: string): PayloadStatusV1 =
|
||||
PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.invalid,
|
||||
latestValidHash: toValidHash(validHash),
|
||||
validationError: some(msg)
|
||||
)
|
||||
|
||||
proc invalidStatus*(validHash: Hash256 = Hash256()): PayloadStatusV1 =
|
||||
PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.invalid,
|
||||
latestValidHash: toValidHash(validHash)
|
||||
)
|
||||
|
||||
proc acceptedStatus*(validHash: Hash256): PayloadStatusV1 =
|
||||
PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.accepted,
|
||||
latestValidHash: toValidHash(validHash)
|
||||
)
|
||||
|
||||
proc acceptedStatus*(): PayloadStatusV1 =
|
||||
PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.accepted
|
||||
)
|
||||
|
||||
proc validStatus*(validHash: Hash256): PayloadStatusV1 =
|
||||
PayloadStatusV1(
|
||||
status: PayloadExecutionStatus.valid,
|
||||
latestValidHash: toValidHash(validHash)
|
||||
)
|
||||
|
||||
proc invalidParams*(msg: string): ref InvalidRequest =
|
||||
(ref InvalidRequest)(
|
||||
code: engineApiInvalidParams,
|
||||
msg: msg
|
||||
)
|
||||
|
||||
proc unknownPayload*(msg: string): ref InvalidRequest =
|
||||
(ref InvalidRequest)(
|
||||
code: engineApiUnknownPayload,
|
||||
msg: msg
|
||||
)
|
||||
|
||||
proc invalidAttr*(msg: string): ref InvalidRequest =
|
||||
(ref InvalidRequest)(
|
||||
code: engineApiInvalidPayloadAttributes,
|
||||
msg: msg
|
||||
)
|
||||
|
||||
proc unsupportedFork*(msg: string): ref InvalidRequest =
|
||||
(ref InvalidRequest)(
|
||||
code: engineApiUnsupportedFork,
|
||||
msg: msg
|
||||
)
|
||||
|
||||
proc tooLargeRequest*(msg: string): ref InvalidRequest =
|
||||
(ref InvalidRequest)(
|
||||
code: engineApiTooLargeRequest,
|
||||
msg: msg
|
||||
)
|
|
@ -17,7 +17,7 @@ import
|
|||
../nimbus/common,
|
||||
../nimbus/config,
|
||||
../nimbus/core/[sealer, tx_pool, chain],
|
||||
../nimbus/rpc/merge/[mergetypes, merger],
|
||||
../nimbus/beacon/[beacon_engine, payload_queue],
|
||||
./test_helpers
|
||||
|
||||
const
|
||||
|
@ -67,7 +67,7 @@ proc getPayload(step: Step, client: RpcClient, testStatusIMPL: var TestStatus) =
|
|||
try:
|
||||
let res = waitFor client.call(step.meth, step.params)
|
||||
check toLowerAscii($res) == toLowerAscii($step.expect)
|
||||
except:
|
||||
except CatchableError:
|
||||
check step.error == true
|
||||
|
||||
proc newPayload(step: Step, client: RpcClient, testStatusIMPL: var TestStatus) =
|
||||
|
@ -97,10 +97,10 @@ proc runTest(steps: Steps) =
|
|||
chainRef, ctx, conf.engineSigner,
|
||||
txPool, EnginePostMerge
|
||||
)
|
||||
merger = MergerRef.new(com.db)
|
||||
beaconEngine = BeaconEngineRef.new(txPool, chainRef)
|
||||
|
||||
setupEthRpc(ethNode, ctx, com, txPool, rpcServer)
|
||||
setupEngineAPI(sealingEngine, rpcServer, merger)
|
||||
setupEngineAPI(beaconEngine, rpcServer)
|
||||
|
||||
sealingEngine.start()
|
||||
rpcServer.start()
|
||||
|
@ -138,14 +138,14 @@ proc `==`(a, b: Quantity): bool =
|
|||
uint64(a) == uint64(b)
|
||||
|
||||
proc testEngineApiSupport() =
|
||||
var api = EngineAPIRef.new(nil)
|
||||
var api = PayloadQueue()
|
||||
let
|
||||
id1 = toId(1)
|
||||
id2 = toId(2)
|
||||
ep1 = ExecutionPayloadV1(gasLimit: Quantity 100)
|
||||
ep2 = ExecutionPayloadV1(gasLimit: Quantity 101)
|
||||
hdr1 = EthBlockHeader(gasLimit: 100)
|
||||
hdr2 = EthBlockHeader(gasLimit: 101)
|
||||
hdr1 = common.BlockHeader(gasLimit: 100)
|
||||
hdr2 = common.BlockHeader(gasLimit: 101)
|
||||
hash1 = hdr1.blockHash
|
||||
hash2 = hdr2.blockHash
|
||||
|
||||
|
@ -165,7 +165,7 @@ proc testEngineApiSupport() =
|
|||
test "test header queue":
|
||||
api.put(hash1, hdr1)
|
||||
api.put(hash2, hdr2)
|
||||
var eh1, eh2: EthBlockHeader
|
||||
var eh1, eh2: common.BlockHeader
|
||||
check api.get(hash1, eh1)
|
||||
check api.get(hash2, eh2)
|
||||
check eh1.gasLimit == hdr1.gasLimit
|
||||
|
|
Loading…
Reference in New Issue