Skip to content

Commit

Permalink
Timelord peak change (#15856)
Browse files Browse the repository at this point in the history
* skip if known heavier unfinished block

* change comment

* None check

* None check

* None check

* test known heavier block skip

* rename

* tests

* lint

* fix test

* ccomment out lower weight case

* fix rc_hash check

* refactor check

* add init file

* fix setup_timelord call

* use class for tests

* add config to tests

* more lint

* more lint
  • Loading branch information
almogdepaz authored Sep 1, 2023
1 parent 09f5873 commit 67b3cec
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 7 deletions.
9 changes: 7 additions & 2 deletions chia/simulator/setup_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,8 +462,13 @@ async def setup_full_system_inner(
)
vdf1_port = uint16(find_available_listen_port("vdf1"))
vdf2_port = uint16(find_available_listen_port("vdf2"))
timelord_iter = setup_timelord(full_node_0_port, False, consensus_constants, b_tools, vdf_port=vdf1_port)
timelord_bluebox_iter = setup_timelord(uint16(1000), True, consensus_constants, b_tools_1, vdf_port=vdf2_port)

timelord_iter = setup_timelord(
full_node_0_port, False, consensus_constants, b_tools.config, b_tools.root_path, vdf_port=vdf1_port
)
timelord_bluebox_iter = setup_timelord(
uint16(1000), True, consensus_constants, b_tools_1.config, b_tools_1.root_path, vdf_port=vdf2_port
)
harvester_service = await harvester_iter.__anext__()
harvester = harvester_service._node

Expand Down
6 changes: 3 additions & 3 deletions chia/simulator/setup_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,10 +468,10 @@ async def setup_timelord(
full_node_port: int,
sanitizer: bool,
consensus_constants: ConsensusConstants,
b_tools: BlockTools,
config: Dict[str, Any],
root_path: Path,
vdf_port: uint16 = uint16(0),
) -> AsyncGenerator[Service[Timelord, TimelordAPI], None]:
config = b_tools.config
service_config = config["timelord"]
service_config["full_node_peer"]["port"] = full_node_port
service_config["bluebox_mode"] = sanitizer
Expand All @@ -481,7 +481,7 @@ async def setup_timelord(
service_config["rpc_port"] = uint16(0)

service = create_timelord_service(
b_tools.root_path,
root_path,
config,
consensus_constants,
connect_to_daemon=False,
Expand Down
14 changes: 14 additions & 0 deletions chia/timelord/timelord_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ async def new_peak_timelord(self, new_peak: timelord_protocol.NewPeakTimelord) -
if self.timelord.bluebox_mode:
return None
self.timelord.max_allowed_inactivity_time = 60

# if there is a heavier unfinished block from a diff chain, skip
for unf_block in self.timelord.unfinished_blocks:
if unf_block.reward_chain_block.total_iters > new_peak.reward_chain_block.total_iters:
found = False
for rc, total_iters in new_peak.previous_reward_challenges:
if rc == unf_block.rc_prev:
found = True
break

if not found:
log.info("there is a heavier unfinished block that does not belong to this chain- skip peak")
return None

if new_peak.reward_chain_block.weight > self.timelord.last_state.get_weight():
log.info("Not skipping peak, don't have. Maybe we are not the fastest timelord")
log.info(
Expand Down
1 change: 1 addition & 0 deletions mypy-exclusions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ tests.wallet.test_wallet_interested_store
tests.wallet.test_wallet_key_val_store
tests.wallet.test_wallet_user_store
tests.weight_proof.test_weight_proof
tests.timelord.test_new_peak
tools.analyze-chain
tools.run_block
tools.test_full_sync
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -916,13 +916,13 @@ async def introducer_service(bt):

@pytest_asyncio.fixture(scope="function")
async def timelord(bt):
async for service in setup_timelord(uint16(0), False, bt.constants, bt):
async for service in setup_timelord(uint16(0), False, bt.constants, bt.config, bt.root_path):
yield service._api, service._node.server


@pytest_asyncio.fixture(scope="function")
async def timelord_service(bt):
async for _ in setup_timelord(uint16(0), False, bt.constants, bt):
async for _ in setup_timelord(uint16(0), False, bt.constants, bt.config, bt.root_path):
yield _


Expand Down
Empty file added tests/timelord/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions tests/timelord/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from __future__ import annotations

parallel = True
checkout_blocks_and_plots = True
212 changes: 212 additions & 0 deletions tests/timelord/test_new_peak.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
from __future__ import annotations

from typing import List, Optional, Tuple

import pytest

from chia.consensus.block_record import BlockRecord
from chia.consensus.blockchain_interface import BlockchainInterface
from chia.consensus.constants import ConsensusConstants
from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_difficulty
from chia.consensus.make_sub_epoch_summary import next_sub_epoch_summary
from chia.protocols import timelord_protocol
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.blockchain_format.sub_epoch_summary import SubEpochSummary
from chia.util.ints import uint128
from tests.blockchain.blockchain_test_utils import _validate_and_add_block
from tests.util.blockchain import create_blockchain


class TestNewPeak:
@pytest.mark.asyncio
async def test_timelord_new_peak_basic(self, bt, timelord, default_1000_blocks) -> None:
b1, db_wrapper1, db_path1 = await create_blockchain(bt.constants, 2)
b2, db_wrapper2, db_path2 = await create_blockchain(bt.constants, 2)

timelord_api, timelord_server = timelord
for block in default_1000_blocks:
await _validate_and_add_block(b1, block)
await _validate_and_add_block(b2, block)

peak = timelord_peak_from_block(default_1000_blocks[-1], b1, bt.constants)
assert peak is not None
assert timelord_api.timelord.new_peak is None
await timelord_api.new_peak_timelord(peak)
assert timelord_api.timelord.new_peak is not None
assert timelord_api.timelord.new_peak.reward_chain_block.height == peak.reward_chain_block.height
blocks = bt.get_consecutive_blocks(1, default_1000_blocks)
await _validate_and_add_block(b1, blocks[-1])
await _validate_and_add_block(b2, blocks[-1])

await timelord_api.new_peak_timelord(timelord_peak_from_block(blocks[-1], b1, bt.constants))
assert timelord_api.timelord.new_peak.reward_chain_block.height == blocks[-1].height

blocks_1 = bt.get_consecutive_blocks(2, blocks)
await _validate_and_add_block(b1, blocks_1[-2])
await _validate_and_add_block(b1, blocks_1[-1])
await timelord_api.new_peak_timelord(timelord_peak_from_block(blocks_1[-2], b1, bt.constants))
await timelord_api.new_peak_timelord(timelord_peak_from_block(blocks_1[-1], b1, bt.constants))
assert timelord_api.timelord.new_peak.reward_chain_block.height == blocks_1[-1].height

# # new unknown peak, weight less then curr peak
# blocks_2 = bt.get_consecutive_blocks(1, blocks)
# await _validate_and_add_block(b2, blocks_2[-1])
# await timelord_api.new_peak_timelord(timelord_peak_from_block(blocks_2[-1], b2, bt.constants))
# assert timelord_api.timelord.new_peak.reward_chain_block.height == blocks_1[-1].height

await db_wrapper1.close()
await db_wrapper2.close()
b1.shut_down()
b2.shut_down()
db_path1.unlink()
db_path2.unlink()

return None

@pytest.mark.asyncio
async def test_timelord_new_peak_heavier_unfinished(self, bt, timelord, default_1000_blocks) -> None:
b1, db_wrapper1, db_path1 = await create_blockchain(bt.constants, 2)
b2, db_wrapper2, db_path2 = await create_blockchain(bt.constants, 2)

timelord_api, timelord_server = timelord
for block in default_1000_blocks:
await _validate_and_add_block(b1, block)
await _validate_and_add_block(b2, block)

peak = timelord_peak_from_block(default_1000_blocks[-1], b1, bt.constants)
assert peak is not None
assert timelord_api.timelord.new_peak is None
await timelord_api.new_peak_timelord(peak)
assert timelord_api.timelord.new_peak is not None
assert timelord_api.timelord.new_peak.reward_chain_block.height == peak.reward_chain_block.height

# make two new blocks on tip
blocks_1 = bt.get_consecutive_blocks(2, default_1000_blocks)
await _validate_and_add_block(b1, blocks_1[-2])
await _validate_and_add_block(b1, blocks_1[-1])
blocks_2 = bt.get_consecutive_blocks(1, default_1000_blocks)
await _validate_and_add_block(b2, blocks_2[-1])
block_record = b1.block_record(blocks_1[-1].header_hash)
block = blocks_1[-1]

ses: Optional[SubEpochSummary] = next_sub_epoch_summary(
bt.constants,
b1,
block_record.required_iters,
block,
True,
)

sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty(
bt.constants,
len(block.finished_sub_slots) > 0,
b1.block_record(blocks_1[-1].prev_header_hash),
b1,
)

if block.reward_chain_block.signage_point_index == 0:
# find first in slot and find slot challenge
blk = b1.block_record(blocks_1[-1].header_hash)
while blk.first_in_sub_slot is False:
blk = b1.block_record(blocks_1[-1].prev_header_hash)
full_blk = await b1.get_full_block(blk.header_hash)
sub_slot = None
for s in full_blk.finished_sub_slots:
if s is not None and s.challenge_chain.get_hash() == block.reward_chain_block.pos_ss_cc_challenge_hash:
sub_slot = s
if sub_slot is None:
assert block.reward_chain_block.pos_ss_cc_challenge_hash == bt.constants.GENESIS_CHALLENGE
rc_prev = bt.constants.GENESIS_CHALLENGE
else:
rc_prev = sub_slot.reward_chain.get_hash()
else:
assert block.reward_chain_block.reward_chain_sp_vdf is not None
rc_prev = block.reward_chain_block.reward_chain_sp_vdf.challenge

timelord_unf_block = timelord_protocol.NewUnfinishedBlockTimelord(
block.reward_chain_block.get_unfinished(),
difficulty,
sub_slot_iters,
block.foliage,
ses,
rc_prev,
)

timelord_api.new_unfinished_block_timelord(timelord_unf_block)

await timelord_api.new_peak_timelord(timelord_peak_from_block(blocks_2[-1], b2, bt.constants))
assert timelord_api.timelord.last_state.get_height() == peak.reward_chain_block.height

await db_wrapper1.close()
await db_wrapper2.close()
b1.shut_down()
b2.shut_down()
db_path1.unlink()
db_path2.unlink()

return None


def get_recent_reward_challenges(
blockchain: BlockchainInterface, constants: ConsensusConstants
) -> List[Tuple[bytes32, uint128]]:
peak = blockchain.get_peak()
if peak is None:
return []
recent_rc: List[Tuple[bytes32, uint128]] = []
curr: Optional[BlockRecord] = peak
while curr is not None and len(recent_rc) < 2 * constants.MAX_SUB_SLOT_BLOCKS:
if curr != peak:
recent_rc.append((curr.reward_infusion_new_challenge, curr.total_iters))
if curr.first_in_sub_slot:
assert curr.finished_reward_slot_hashes is not None
sub_slot_total_iters = curr.ip_sub_slot_total_iters(constants)
# Start from the most recent
for rc in reversed(curr.finished_reward_slot_hashes):
if sub_slot_total_iters < curr.sub_slot_iters:
break
recent_rc.append((rc, sub_slot_total_iters))
sub_slot_total_iters = uint128(sub_slot_total_iters - curr.sub_slot_iters)
curr = blockchain.try_block_record(curr.prev_hash)
return list(reversed(recent_rc))


def timelord_peak_from_block(block, blockchain: BlockchainInterface, constants: ConsensusConstants):
peak = blockchain.block_record(block.header_hash)
_, difficulty = get_next_sub_slot_iters_and_difficulty(constants, False, peak, blockchain)
ses: Optional[SubEpochSummary] = next_sub_epoch_summary(
constants,
blockchain,
peak.required_iters,
block,
True,
)

recent_rc = get_recent_reward_challenges(blockchain, constants)
curr = peak
while not curr.is_challenge_block(constants) and not curr.first_in_sub_slot:
curr = blockchain.block_record(curr.prev_hash)

if curr.is_challenge_block(constants):
last_csb_or_eos = curr.total_iters
else:
last_csb_or_eos = curr.ip_sub_slot_total_iters(constants)

curr = peak
passed_ses_height_but_not_yet_included = True
while (curr.height % constants.SUB_EPOCH_BLOCKS) != 0:
if curr.sub_epoch_summary_included:
passed_ses_height_but_not_yet_included = False
curr = blockchain.block_record(curr.prev_hash)
if curr.sub_epoch_summary_included or curr.height == 0:
passed_ses_height_but_not_yet_included = False
return timelord_protocol.NewPeakTimelord(
block.reward_chain_block,
difficulty,
peak.deficit,
peak.sub_slot_iters,
ses,
recent_rc,
last_csb_or_eos,
passed_ses_height_but_not_yet_included,
)

0 comments on commit 67b3cec

Please sign in to comment.