json-rpc: able to query finalized block and safe block header
engine-api: - store safe block hash and finalized block hash engine-api test: - fix test case related to safe block hash and finalized block hash
This commit is contained in:
parent
cad74db423
commit
b80eca0718
|
@ -7,7 +7,7 @@ import
|
|||
web3/engine_api_types,
|
||||
json_rpc/rpcclient,
|
||||
../../../nimbus/merge/mergeutils,
|
||||
../../../nimbus/debug,
|
||||
../../../nimbus/[debug, constants],
|
||||
./engine_client
|
||||
|
||||
# Consensus Layer Client Mock used to sync the Execution Clients once the TTD has been reached
|
||||
|
@ -34,6 +34,10 @@ type
|
|||
client : RpcClient
|
||||
ttd : DifficultyInt
|
||||
|
||||
slotsToSafe : int
|
||||
slotsToFinalized : int
|
||||
headHashHistory : seq[BlockHash]
|
||||
|
||||
BlockProcessCallbacks* = object
|
||||
onPayloadProducerSelected* : proc(): bool {.gcsafe.}
|
||||
onGetPayloadID* : proc(): bool {.gcsafe.}
|
||||
|
@ -47,6 +51,8 @@ type
|
|||
proc init*(cl: CLMocker, client: RpcClient, ttd: DifficultyInt) =
|
||||
cl.client = client
|
||||
cl.ttd = ttd
|
||||
cl.slotsToSafe = 1
|
||||
cl.slotsToFinalized = 2
|
||||
|
||||
proc newClMocker*(client: RpcClient, ttd: DifficultyInt): CLMocker =
|
||||
new result
|
||||
|
@ -63,8 +69,13 @@ proc waitForTTD*(cl: CLMocker): Future[bool] {.async.} =
|
|||
|
||||
let headerHash = BlockHash(common.blockHash(cl.latestHeader).data)
|
||||
cl.latestForkchoice.headBlockHash = headerHash
|
||||
|
||||
if cl.slotsToSafe == 0:
|
||||
cl.latestForkchoice.safeBlockHash = headerHash
|
||||
|
||||
if cl.slotsToFinalized == 0:
|
||||
cl.latestForkchoice.finalizedBlockHash = headerHash
|
||||
|
||||
cl.latestHeadNumber = cl.latestHeader.blockNumber.truncate(uint64)
|
||||
|
||||
let res = cl.client.forkchoiceUpdatedV1(cl.latestForkchoice)
|
||||
|
@ -256,8 +267,19 @@ proc produceSingleBlock*(cl: CLMocker, cb: BlockProcessCallbacks): bool {.gcsafe
|
|||
return false
|
||||
|
||||
# Broadcast forkchoice updated with new HeadBlock to all clients
|
||||
let blockHash = cl.latestPayloadBuilt.blockHash
|
||||
cl.latestForkchoice.headBlockHash = blockHash
|
||||
let previousForkchoice = cl.latestForkchoice
|
||||
cl.headHashHistory.add cl.latestPayloadBuilt.blockHash
|
||||
|
||||
cl.latestForkchoice = ForkchoiceStateV1()
|
||||
cl.latestForkchoice.headBlockHash = cl.latestPayloadBuilt.blockHash
|
||||
|
||||
let hhLen = cl.headHashHistory.len
|
||||
if hhLen > cl.slotsToSafe:
|
||||
cl.latestForkchoice.safeBlockHash = cl.headHashHistory[hhLen - cl.slotsToSafe - 1]
|
||||
|
||||
if hhLen > cl.slotsToFinalized:
|
||||
cl.latestForkchoice.finalizedBlockHash = cl.headHashHistory[hhLen - cl.slotsToFinalized - 1]
|
||||
|
||||
if not cl.broadcastLatestForkchoice():
|
||||
return false
|
||||
|
||||
|
@ -266,19 +288,16 @@ proc produceSingleBlock*(cl: CLMocker, cb: BlockProcessCallbacks): bool {.gcsafe
|
|||
return false
|
||||
|
||||
# Broadcast forkchoice updated with new SafeBlock to all clients
|
||||
cl.latestForkchoice.safeBlockHash = blockHash
|
||||
if not cl.broadcastLatestForkchoice():
|
||||
return false
|
||||
|
||||
if cb.onSafeBlockChange != nil:
|
||||
if cb.onSafeBlockChange != nil and cl.latestForkchoice.safeBlockHash != previousForkchoice.safeBlockHash:
|
||||
if not cb.onSafeBlockChange():
|
||||
return false
|
||||
|
||||
# Broadcast forkchoice updated with new FinalizedBlock to all clients
|
||||
cl.latestForkchoice.finalizedBlockHash = blockHash
|
||||
if not cl.broadcastLatestForkchoice():
|
||||
if cb.onFinalizedBlockChange != nil and cl.latestForkchoice.finalizedBlockHash != previousForkchoice.finalizedBlockHash:
|
||||
if not cb.onFinalizedBlockChange():
|
||||
return false
|
||||
|
||||
# Broadcast forkchoice updated with new FinalizedBlock to all clients
|
||||
# Save the number of the first PoS block
|
||||
if cl.firstPoSBlockNumber.isNone:
|
||||
let number = cl.latestHeader.blockNumber.truncate(uint64) + 1
|
||||
|
@ -300,12 +319,37 @@ proc produceSingleBlock*(cl: CLMocker, cb: BlockProcessCallbacks): bool {.gcsafe
|
|||
hash=newHash.toHex
|
||||
return false
|
||||
|
||||
cl.latestHeader = newHeader
|
||||
|
||||
if cb.onFinalizedBlockChange != nil:
|
||||
if not cb.onFinalizedBlockChange():
|
||||
# Check that the new finalized header has the correct properties
|
||||
# ommersHash == 0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347
|
||||
if newHeader.ommersHash != EMPTY_UNCLE_HASH:
|
||||
error "CLMocker: Client produced a new header with incorrect ommersHash", ommersHash = newHeader.ommersHash
|
||||
return false
|
||||
|
||||
# difficulty == 0
|
||||
if newHeader.difficulty != 0.u256:
|
||||
error "CLMocker: Client produced a new header with incorrect difficulty", difficulty = newHeader.difficulty
|
||||
return false
|
||||
|
||||
# mixHash == prevRandao
|
||||
if newHeader.mixDigest != cl.prevRandaoHistory[cl.latestHeadNumber]:
|
||||
error "CLMocker: Client produced a new header with incorrect mixHash",
|
||||
get = newHeader.mixDigest.data.toHex,
|
||||
expect = cl.prevRandaoHistory[cl.latestHeadNumber].data.toHex
|
||||
return false
|
||||
|
||||
# nonce == 0x0000000000000000
|
||||
if newHeader.nonce != default(BlockNonce):
|
||||
error "CLMocker: Client produced a new header with incorrect nonce",
|
||||
nonce = newHeader.nonce.toHex
|
||||
return false
|
||||
|
||||
if newHeader.extraData.len > 32:
|
||||
error "CLMocker: Client produced a new header with incorrect extraData (len > 32)",
|
||||
len = newHeader.extraData.len
|
||||
return false
|
||||
|
||||
cl.latestHeader = newHeader
|
||||
|
||||
return true
|
||||
|
||||
# Loop produce PoS blocks by using the Engine API
|
||||
|
|
|
@ -158,6 +158,16 @@ proc latestBlock*(client: RpcClient, output: var common.EthBlock): Result[void,
|
|||
except ValueError as e:
|
||||
return err(e.msg)
|
||||
|
||||
proc namedHeader*(client: RpcClient, name: string, output: var common.BlockHeader): Result[void, string] =
|
||||
try:
|
||||
let res = waitFor client.eth_getBlockByNumber(name, false)
|
||||
if res.isNone:
|
||||
return err("failed to get named blockHeader")
|
||||
output = toBlockHeader(res.get())
|
||||
return ok()
|
||||
except ValueError as e:
|
||||
return err(e.msg)
|
||||
|
||||
proc sendTransaction*(client: RpcClient, tx: common.Transaction): Result[void, string] =
|
||||
try:
|
||||
let encodedTx = rlp.encode(tx)
|
||||
|
|
|
@ -929,101 +929,67 @@ template blockStatusHeadBlockGen(procname: untyped, transitionBlock: bool) =
|
|||
blockStatusHeadBlockGen(blockStatusHeadBlock1, false)
|
||||
blockStatusHeadBlockGen(blockStatusHeadBlock2, true)
|
||||
|
||||
template blockStatusSafeBlockGen(procname: untyped, transitionBlock: bool) =
|
||||
proc procName(t: TestEnv): TestStatus =
|
||||
proc blockStatusSafeBlock(t: TestEnv): TestStatus =
|
||||
result = TestStatus.OK
|
||||
|
||||
# Wait until TTD is reached by this client
|
||||
let ok = waitFor t.clMock.waitForTTD()
|
||||
testCond ok
|
||||
|
||||
# Produce blocks before starting the test, only if we are not testing the transition block
|
||||
when not transitionBlock:
|
||||
let produce5BlockRes = t.clMock.produceBlocks(5, BlockProcessCallbacks())
|
||||
testCond produce5BlockRes
|
||||
|
||||
let clMock = t.clMock
|
||||
let client = t.rpcClient
|
||||
let shadow = Shadow()
|
||||
|
||||
var produceSingleBlockRes = clMock.produceSingleBlock(BlockProcessCallbacks(
|
||||
onPayloadProducerSelected: proc(): bool =
|
||||
var address: EthAddress
|
||||
testCond t.sendTx(address, 1.u256)
|
||||
shadow.hash = rlpHash(t.tx)
|
||||
return true
|
||||
,
|
||||
# Run test after a forkchoice with new HeadBlockHash has been broadcasted
|
||||
# On PoW mode, `safe` tag shall return error.
|
||||
var header: EthBlockHeader
|
||||
var rr = client.namedHeader("safe", header)
|
||||
testCond rr.isErr
|
||||
|
||||
# Wait until this client catches up with latest PoS Block
|
||||
let ok = waitFor t.clMock.waitForTTD()
|
||||
testCond ok
|
||||
|
||||
# First ForkchoiceUpdated sent was equal to 0x00..00, `safe` should return error now
|
||||
rr = client.namedHeader("safe", header)
|
||||
testCond rr.isErr
|
||||
|
||||
let pbres = clMock.produceBlocks(3, BlockProcessCallbacks(
|
||||
# Run test after a forkchoice with new SafeBlockHash has been broadcasted
|
||||
onSafeBlockChange: proc(): bool =
|
||||
var lastHeader: EthBlockHeader
|
||||
var hRes = client.latestHeader(lastHeader)
|
||||
if hRes.isErr:
|
||||
error "unable to get latest header", msg=hRes.error
|
||||
return false
|
||||
|
||||
let lastHash = BlockHash lastHeader.blockHash.data
|
||||
if lastHash != clMock.latestForkchoice.headBlockHash:
|
||||
error "latest block header doesn't match SafeBlock hash", hash=lastHash.toHex
|
||||
return false
|
||||
|
||||
let rr = client.txReceipt(shadow.hash)
|
||||
if rr.isErr:
|
||||
error "unable to get transaction receipt"
|
||||
return false
|
||||
return true
|
||||
var header: EthBlockHeader
|
||||
let rr = client.namedHeader("safe", header)
|
||||
testCond rr.isOk
|
||||
let safeBlockHash = hash256(clMock.latestForkchoice.safeBlockHash)
|
||||
header.blockHash == safeBlockHash
|
||||
))
|
||||
testCond produceSingleBlockRes
|
||||
|
||||
blockStatusSafeBlockGen(blockStatusSafeBlock1, false)
|
||||
blockStatusSafeBlockGen(blockStatusSafeBlock2, true)
|
||||
testCond pbres
|
||||
|
||||
template blockStatusFinalizedBlockGen(procname: untyped, transitionBlock: bool) =
|
||||
proc procName(t: TestEnv): TestStatus =
|
||||
proc blockStatusFinalizedBlock(t: TestEnv): TestStatus =
|
||||
result = TestStatus.OK
|
||||
|
||||
# Wait until TTD is reached by this client
|
||||
let ok = waitFor t.clMock.waitForTTD()
|
||||
testCond ok
|
||||
|
||||
# Produce blocks before starting the test, only if we are not testing the transition block
|
||||
when not transitionBlock:
|
||||
let produce5BlockRes = t.clMock.produceBlocks(5, BlockProcessCallbacks())
|
||||
testCond produce5BlockRes
|
||||
|
||||
let clMock = t.clMock
|
||||
let client = t.rpcClient
|
||||
let shadow = Shadow()
|
||||
|
||||
var produceSingleBlockRes = clMock.produceSingleBlock(BlockProcessCallbacks(
|
||||
onPayloadProducerSelected: proc(): bool =
|
||||
var address: EthAddress
|
||||
testCond t.sendTx(address, 1.u256)
|
||||
shadow.hash = rlpHash(t.tx)
|
||||
return true
|
||||
,
|
||||
# Run test after a forkchoice with new HeadBlockHash has been broadcasted
|
||||
# On PoW mode, `finalized` tag shall return error.
|
||||
var header: EthBlockHeader
|
||||
var rr = client.namedHeader("finalized", header)
|
||||
testCond rr.isErr
|
||||
|
||||
# Wait until this client catches up with latest PoS Block
|
||||
let ok = waitFor t.clMock.waitForTTD()
|
||||
testCond ok
|
||||
|
||||
# First ForkchoiceUpdated sent was equal to 0x00..00, `finalized` should return error now
|
||||
rr = client.namedHeader("finalized", header)
|
||||
testCond rr.isErr
|
||||
|
||||
let pbres = clMock.produceBlocks(3, BlockProcessCallbacks(
|
||||
# Run test after a forkchoice with new FinalizedBlockHash has been broadcasted
|
||||
onFinalizedBlockChange: proc(): bool =
|
||||
var lastHeader: EthBlockHeader
|
||||
var hRes = client.latestHeader(lastHeader)
|
||||
if hRes.isErr:
|
||||
error "unable to get latest header", msg=hRes.error
|
||||
return false
|
||||
|
||||
let lastHash = BlockHash lastHeader.blockHash.data
|
||||
if lastHash != clMock.latestForkchoice.headBlockHash:
|
||||
error "latest block header doesn't match FinalizedBlock hash", hash=lastHash.toHex
|
||||
return false
|
||||
|
||||
let rr = client.txReceipt(shadow.hash)
|
||||
if rr.isErr:
|
||||
error "unable to get transaction receipt"
|
||||
return false
|
||||
return true
|
||||
var header: EthBlockHeader
|
||||
let rr = client.namedHeader("finalized", header)
|
||||
testCond rr.isOk
|
||||
let finalizedBlockHash = hash256(clMock.latestForkchoice.finalizedBlockHash)
|
||||
header.blockHash == finalizedBlockHash
|
||||
))
|
||||
testCond produceSingleBlockRes
|
||||
|
||||
blockStatusFinalizedBlockGen(blockStatusFinalizedBlock1, false)
|
||||
blockStatusFinalizedBlockGen(blockStatusFinalizedBlock2, true)
|
||||
testCond pbres
|
||||
|
||||
proc blockStatusReorg(t: TestEnv): TestStatus =
|
||||
result = TestStatus.OK
|
||||
|
@ -1900,21 +1866,13 @@ const engineTestList* = [
|
|||
ttd: 5,
|
||||
),
|
||||
TestSpec(
|
||||
name: "Latest Block after New SafeBlock",
|
||||
run: blockStatusSafeBlock1,
|
||||
),
|
||||
TestSpec(
|
||||
name: "Latest Block after New SafeBlock (Transition Block)",
|
||||
run: blockStatusSafeBlock2,
|
||||
name: "safe Block after New SafeBlockHash",
|
||||
run: blockStatusSafeBlock,
|
||||
ttd: 5,
|
||||
),
|
||||
TestSpec(
|
||||
name: "Latest Block after New FinalizedBlock",
|
||||
run: blockStatusFinalizedBlock1,
|
||||
),
|
||||
TestSpec(
|
||||
name: "Latest Block after New FinalizedBlock (Transition Block)",
|
||||
run: blockStatusFinalizedBlock2,
|
||||
name: "finalized Block after New FinalizedBlockHash",
|
||||
run: blockStatusFinalizedBlock,
|
||||
ttd: 5,
|
||||
),
|
||||
TestSpec(
|
||||
|
|
|
@ -435,3 +435,21 @@ proc persistUncles*(self: BaseChainDB, uncles: openArray[BlockHeader]): Hash256
|
|||
let enc = rlp.encode(uncles)
|
||||
result = keccakHash(enc)
|
||||
self.db.put(genericHashKey(result).toOpenArray, enc)
|
||||
|
||||
proc safeHeaderHash*(self: BaseChainDB): Hash256 =
|
||||
discard self.getHash(safeHashKey(), result)
|
||||
|
||||
proc safeHeaderHash*(self: BaseChainDB, headerHash: Hash256) =
|
||||
self.db.put(safeHashKey().toOpenArray, rlp.encode(headerHash))
|
||||
|
||||
proc finalizedHeaderHash*(self: BaseChainDB): Hash256 =
|
||||
discard self.getHash(finalizedHashKey(), result)
|
||||
|
||||
proc finalizedHeaderHash*(self: BaseChainDB, headerHash: Hash256) =
|
||||
self.db.put(finalizedHashKey().toOpenArray, rlp.encode(headerHash))
|
||||
|
||||
proc safeHeader*(self: BaseChainDB): BlockHeader =
|
||||
self.getBlockHeader(self.safeHeaderHash)
|
||||
|
||||
proc finalizedHeader*(self: BaseChainDB): BlockHeader =
|
||||
self.getBlockHeader(self.finalizedHeaderHash)
|
||||
|
|
|
@ -13,6 +13,8 @@ type
|
|||
cliqueSnapshot
|
||||
transitionStatus
|
||||
terminalHash
|
||||
safeHash
|
||||
finalizedHash
|
||||
|
||||
DbKey* = object
|
||||
# The first byte stores the key type. The rest are key-specific values
|
||||
|
@ -69,6 +71,14 @@ proc terminalHashKey*(): DbKey =
|
|||
result.data[0] = byte ord(terminalHash)
|
||||
result.dataEndPos = uint8 1
|
||||
|
||||
proc safeHashKey*(): DbKey {.inline.} =
|
||||
result.data[0] = byte ord(safeHash)
|
||||
result.dataEndPos = uint8 1
|
||||
|
||||
proc finalizedHashKey*(): DbKey {.inline.} =
|
||||
result.data[0] = byte ord(finalizedHash)
|
||||
result.dataEndPos = uint8 1
|
||||
|
||||
template toOpenArray*(k: DbKey): openArray[byte] =
|
||||
k.data.toOpenArray(0, int(k.dataEndPos))
|
||||
|
||||
|
|
|
@ -277,6 +277,7 @@ proc setupEngineAPI*(
|
|||
finalHash=finalHash.data.toHex,
|
||||
finalizedBlockHash=finalizedBlockHash.data.toHex
|
||||
raise (ref InvalidRequest)(code: engineApiInvalidParams, msg: "finalilized block not canonical")
|
||||
db.finalizedHeaderHash(finalizedBlockHash)
|
||||
|
||||
let safeBlockHash = update.safeBlockHash.asEthHash
|
||||
if safeBlockHash != Hash256():
|
||||
|
@ -295,6 +296,7 @@ proc setupEngineAPI*(
|
|||
safeHash=safeHash.data.toHex,
|
||||
safeBlockHash=safeBlockHash.data.toHex
|
||||
raise (ref InvalidRequest)(code: engineApiInvalidParams, msg: "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
|
||||
|
|
|
@ -35,6 +35,8 @@ proc headerFromTag*(chain: BaseChainDB, blockTag: string): BlockHeader =
|
|||
case tag
|
||||
of "latest": result = chain.getCanonicalHead()
|
||||
of "earliest": result = chain.getBlockHeader(GENESIS_BLOCK_NUMBER)
|
||||
of "safe": result = chain.safeHeader()
|
||||
of "finalized": result = chain.finalizedHeader()
|
||||
of "pending":
|
||||
#TODO: Implement get pending block
|
||||
raise newException(ValueError, "Pending tag not yet implemented")
|
||||
|
|
Loading…
Reference in New Issue