Added average_block_time to get_blockchain_state FullNode RPC API (#15777)

* Added `average_block_time` to `get_blockchain_state` FullNode RPC API

* Updated the code according to the review

* Fixed lint error

* Fixed lint error 2

* Fixed lint error 3

* Applied review suggestion

* Fixed lint error

* Update

* Added tests

* Fixed test
This commit is contained in:
Izumi Hoshino 2023-07-18 06:34:04 +09:00 committed by GitHub
parent e772e4ff69
commit 3b116c738b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 129 additions and 1 deletions

View File

@ -4,7 +4,7 @@ from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from chia.consensus.block_record import BlockRecord
from chia.consensus.blockchain import BlockchainMutexPriority
from chia.consensus.blockchain import Blockchain, BlockchainMutexPriority
from chia.consensus.cost_calculator import NPCResult
from chia.consensus.pos_quality import UI_ACTUAL_SPACE_CONSTANT_FACTOR
from chia.full_node.fee_estimator_interface import FeeEstimatorInterface
@ -32,6 +32,46 @@ def coin_record_dict_backwards_compat(coin_record: Dict[str, Any]) -> Dict[str,
return coin_record
async def get_nearest_transaction_block(blockchain: Blockchain, block: BlockRecord) -> BlockRecord:
if block.is_transaction_block:
return block
prev_hash = blockchain.height_to_hash(block.prev_transaction_block_height)
# Genesis block is a transaction block, so theoretically `prev_hash` of all blocks
# other than genesis block cannot be `None`.
assert prev_hash
tb = await blockchain.get_block_record_from_db(prev_hash)
assert tb
return tb
async def get_average_block_time(
blockchain: Blockchain,
base_block: BlockRecord,
height_distance: int,
) -> Optional[uint32]:
newer_block = await get_nearest_transaction_block(blockchain, base_block)
if newer_block.height < 1:
return None
prev_height = uint32(max(newer_block.height - 1, newer_block.height - height_distance))
prev_hash = blockchain.height_to_hash(prev_height)
assert prev_hash
prev_block = await blockchain.get_block_record_from_db(prev_hash)
assert prev_block
older_block = await get_nearest_transaction_block(blockchain, prev_block)
assert newer_block.timestamp is not None and older_block.timestamp is not None
average_block_time = uint32(
(newer_block.timestamp - older_block.timestamp) / (newer_block.height - older_block.height)
)
return average_block_time
class FullNodeRpcApi:
def __init__(self, service: FullNode) -> None:
self.service = service
@ -129,6 +169,7 @@ class FullNodeRpcApi:
"difficulty": 0,
"sub_slot_iters": 0,
"space": 0,
"average_block_time": None,
"mempool_size": 0,
"mempool_cost": 0,
"mempool_min_fees": {
@ -167,6 +208,7 @@ class FullNodeRpcApi:
else:
sync_progress_height = uint32(0)
average_block_time: Optional[uint32] = None
if peak is not None and peak.height > 1:
newer_block_hex = peak.header_hash.hex()
# Average over the last day
@ -176,6 +218,7 @@ class FullNodeRpcApi:
space = await self.get_network_space(
{"newer_block_header_hash": newer_block_hex, "older_block_header_hash": older_block_hex}
)
average_block_time = await get_average_block_time(self.service.blockchain, peak, 4608)
else:
space = {"space": uint128(0)}
@ -213,6 +256,7 @@ class FullNodeRpcApi:
"difficulty": difficulty,
"sub_slot_iters": sub_slot_iters,
"space": space["space"],
"average_block_time": average_block_time,
"mempool_size": mempool_size,
"mempool_cost": mempool_cost,
"mempool_fees": mempool_fees,

View File

@ -7,9 +7,11 @@ import pytest
from blspy import AugSchemeMPL
from clvm.casts import int_to_bytes
from chia.consensus.block_record import BlockRecord
from chia.consensus.pot_iterations import is_overflow_block
from chia.full_node.signage_point import SignagePoint
from chia.protocols import full_node_protocol
from chia.rpc.full_node_rpc_api import get_average_block_time, get_nearest_transaction_block
from chia.rpc.full_node_rpc_client import FullNodeRpcClient
from chia.server.outbound_message import NodeType
from chia.simulator.block_tools import get_signage_point
@ -427,3 +429,85 @@ class TestRpc:
# Checks that the RPC manages to stop the node
client.close()
await client.await_closed()
@pytest.mark.asyncio
async def test_get_blockchain_state(self, one_wallet_and_one_simulator_services, self_hostname):
num_blocks = 5
nodes, _, bt = one_wallet_and_one_simulator_services
(full_node_service_1,) = nodes
full_node_api_1 = full_node_service_1._api
try:
client = await FullNodeRpcClient.create(
self_hostname,
full_node_service_1.rpc_server.listen_port,
full_node_service_1.root_path,
full_node_service_1.config,
)
await validate_get_routes(client, full_node_service_1.rpc_server.rpc_api)
state = await client.get_blockchain_state()
assert state["peak"] is None
assert not state["sync"]["sync_mode"]
assert state["difficulty"] > 0
assert state["sub_slot_iters"] > 0
assert state["space"] == 0
assert state["average_block_time"] is None
blocks: List[FullBlock] = bt.get_consecutive_blocks(num_blocks)
blocks = bt.get_consecutive_blocks(num_blocks, block_list_input=blocks, guarantee_transaction_block=True)
for block in blocks:
unf = UnfinishedBlock(
block.finished_sub_slots,
block.reward_chain_block.get_unfinished(),
block.challenge_chain_sp_proof,
block.reward_chain_sp_proof,
block.foliage,
block.foliage_transaction_block,
block.transactions_info,
block.transactions_generator,
[],
)
await full_node_api_1.full_node.add_unfinished_block(unf, None)
await full_node_api_1.full_node.add_block(block, None)
state = await client.get_blockchain_state()
assert state["space"] > 0
assert state["average_block_time"] > 0
block_records: List[BlockRecord] = [
await full_node_api_1.full_node.blockchain.get_block_record_from_db(rec.header_hash) for rec in blocks
]
first_non_transaction_block_index = -1
for i, b in enumerate(block_records):
if not b.is_transaction_block:
first_non_transaction_block_index = i
break
# Genesis block(height=0) must be a transaction block
# so first_non_transaction_block_index != 0
assert first_non_transaction_block_index > 0
transaction_blocks: List[BlockRecord] = [b for b in block_records if b.is_transaction_block]
non_transaction_block: List[BlockRecord] = [b for b in block_records if not b.is_transaction_block]
assert len(transaction_blocks) > 0
assert len(non_transaction_block) > 0
assert transaction_blocks[0] == await get_nearest_transaction_block(
full_node_api_1.full_node.blockchain, transaction_blocks[0]
)
nearest_transaction_block = block_records[first_non_transaction_block_index - 1]
expected_nearest_transaction_block = await get_nearest_transaction_block(
full_node_api_1.full_node.blockchain, block_records[first_non_transaction_block_index]
)
assert expected_nearest_transaction_block == nearest_transaction_block
# When supplying genesis block, there are no older blocks so `None` should be returned
assert await get_average_block_time(full_node_api_1.full_node.blockchain, block_records[0], 4608) is None
assert (
await get_average_block_time(full_node_api_1.full_node.blockchain, block_records[-1], 4608) is not None
)
finally:
# Checks that the RPC manages to stop the node
client.close()
await client.await_closed()