Skip to content

keep track of peak peer #16305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions chia/full_node/full_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -1007,22 +1007,29 @@ async def request_validate_wp(
if response is None or not isinstance(response, full_node_protocol.RespondProofOfWeight):
await weight_proof_peer.close(600)
raise RuntimeError(f"Weight proof did not arrive in time from peer: {weight_proof_peer.peer_info.host}")
if response.wp.recent_chain_data[-1].reward_chain_block.height != peak_height:
await weight_proof_peer.close(600)
raise RuntimeError(f"Weight proof had the wrong height: {weight_proof_peer.peer_info.host}")
if response.wp.recent_chain_data[-1].reward_chain_block.weight != peak_weight:
await weight_proof_peer.close(600)
raise RuntimeError(f"Weight proof had the wrong weight: {weight_proof_peer.peer_info.host}")
if self.in_bad_peak_cache(response.wp):
raise ValueError("Weight proof failed bad peak cache validation")
# dont sync to wp if local peak is heavier,
# dont ban peer, we asked for this peak
if response.wp.recent_chain_data[-1].reward_chain_block.height != peak_height:
peak_first_peer = self.get_peer(self.sync_store.peak_to_first_peer[peak_header_hash])
await peak_first_peer.close(600)
raise RuntimeError(
f"Weight proof had the wrong height, ban peer that first sent us the peak: "
f"{peak_first_peer.peer_info.host}"
)
if response.wp.recent_chain_data[-1].reward_chain_block.weight != peak_weight:
peak_first_peer = self.get_peer(self.sync_store.peak_to_first_peer[peak_header_hash])
await peak_first_peer.close(600)
raise RuntimeError(
f"Weight proof had the wrong weight, ban peer that first sent us the peak: "
f"{peak_first_peer.peer_info.host}"
)
current_peak = self.blockchain.get_peak()
if current_peak is not None:
if response.wp.recent_chain_data[-1].reward_chain_block.weight <= current_peak.weight:
raise RuntimeError(
f"current peak is heavier than Weight proof peek: {weight_proof_peer.peer_info.host}"
)
raise RuntimeError(f"current peak is heavier than Weight proof peek: {weight_proof_peer.peer_info.host}")
try:
validated, fork_point, summaries = await self.weight_proof_handler.validate_weight_proof(response.wp)
except Exception as e:
Expand Down Expand Up @@ -1138,6 +1145,9 @@ def get_peers_with_peak(self, peak_hash: bytes32) -> List[WSChiaConnection]:
return []
return [c for c in self.server.all_connections.values() if c.peer_node_id in peer_ids]

def get_peer(self, node_id: bytes32) -> WSChiaConnection:
return self.server.all_connections[node_id]

async def _wallets_sync_task_handler(self) -> None:
while not self._shut_down:
try:
Expand Down
7 changes: 6 additions & 1 deletion chia/full_node/sync_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class SyncStore:
long_sync: bool = False
# Header hash : peer node id
peak_to_peer: OrderedDict[bytes32, Set[bytes32]] = field(default_factory=orderedDict)
peak_to_first_peer: OrderedDict[bytes32, bytes32] = field(default_factory=orderedDict)
# peer node id : Peak
peer_to_peak: Dict[bytes32, Peak] = field(default_factory=dict)
# Peak we are syncing towards
Expand Down Expand Up @@ -67,12 +68,16 @@ def peer_has_block(
self.peak_to_peer[header_hash].add(peer_id)
else:
self.peak_to_peer[header_hash] = {peer_id}
self.peak_to_first_peer[header_hash] = peer_id

if len(self.peak_to_peer) > 256: # nice power of two
item = self.peak_to_peer.popitem(last=False) # Remove the oldest entry
# sync target hash is used throughout the sync process and should not be deleted.
if self.target_peak is not None and item[0] == self.target_peak.header_hash:
self.peak_to_peer[item[0]] = item[1] # Put it back in if it was the sync target
self.peak_to_peer.popitem(last=False) # Remove the oldest entry again
item = self.peak_to_peer.popitem(last=False) # Remove the oldest entry again

del self.peak_to_first_peer[item[0]]
if new_peak:
self.peer_to_peak[peer_id] = Peak(header_hash, height, weight)

Expand Down
33 changes: 33 additions & 0 deletions tests/core/full_node/full_sync/test_full_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,39 @@ async def test_bad_peak_cache_invalidation(
full_node_1.full_node.add_to_bad_peak_cache(block.header_hash, block.height)
assert len(full_node_1.full_node.bad_peak_cache) == 1

@pytest.mark.asyncio
async def test_handle_bad_peak(self, three_nodes, default_1000_blocks, self_hostname):
full_node_1, full_node_2, full_node_3 = three_nodes
server_1 = full_node_1.full_node.server
server_2 = full_node_2.full_node.server
server_3 = full_node_3.full_node.server

for block in default_1000_blocks:
await full_node_2.full_node.add_block(block)

peak = await full_node_2.full_node.blockchain.get_full_peak()
await server_2.start_client(PeerInfo(self_hostname, uint16(server_1._port)), full_node_2.full_node.on_connect)
request = full_node_protocol.NewPeak(
peak.header_hash,
peak.height,
peak.weight + 100,
peak.height,
peak.reward_chain_block.get_unfinished().get_hash(),
)
con = server_2.all_connections[server_1.node_id]

# invoke new peak with fake weight
await con.call_api(FullNodeAPI.new_peak, request, timeout=6)
# let sync
await server_3.start_client(PeerInfo(self_hostname, uint16(server_1._port)), full_node_3.full_node.on_connect)
assert len(full_node_1.full_node.server.all_connections.values()) == 2

await time_out_assert(60, full_node_1.full_node.sync_store.get_long_sync, True)
await time_out_assert(250, full_node_1.full_node.sync_store.get_long_sync, False)
# check ban correct peer
assert server_3.node_id in full_node_1.full_node.server.all_connections
assert server_2.node_id not in full_node_1.full_node.server.all_connections


def has_peers_with_peak(node: FullNode, header_hash: bytes32) -> bool:
return len(node.sync_store.get_peers_that_have_peak([header_hash])) > 0