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