From 5d09eb9df6ad525b89b5d4e48eb43e1f1105c438 Mon Sep 17 00:00:00 2001 From: isaac1227 Date: Wed, 13 Nov 2024 18:18:38 +0100 Subject: [PATCH 01/43] prueba --- nebula/core/engine.py | 132 ++- nebula/core/network/communications.py | 170 +++- nebula/core/network/connection.py | 34 + nebula/core/network/messages.py | 29 +- nebula/core/pb/nebula.proto | 13 + nebula/core/pb/nebula/core/pb/nebula_pb2.py | 60 ++ nebula/core/pb/nebula_pb2.py | 66 +- nebula/core/reputation/Reputation.py | 916 ++++++++++++++++++++ nebula/scenarios.py | 14 + 9 files changed, 1391 insertions(+), 43 deletions(-) create mode 100644 nebula/core/pb/nebula/core/pb/nebula_pb2.py create mode 100644 nebula/core/reputation/Reputation.py diff --git a/nebula/core/engine.py b/nebula/core/engine.py index 51ea21f63..561f1ebb8 100755 --- a/nebula/core/engine.py +++ b/nebula/core/engine.py @@ -12,6 +12,7 @@ from nebula.core.network.communications import CommunicationsManager from nebula.core.pb import nebula_pb2 from nebula.core.utils.locker import Locker +from nebula.core.reputation.Reputation import Reputation logging.getLogger("requests").setLevel(logging.WARNING) logging.getLogger("urllib3").setLevel(logging.WARNING) @@ -168,6 +169,13 @@ def __init__( ) # ... add more callbacks here + # Reputation + self.reputation_instance = Reputation(self) + self.reputation = {} + self.reputation_with_feedback = {} + self.rejected_nodes = set() + self.change_weight_nodes = set() + @property def cm(self): return self._cm @@ -217,6 +225,9 @@ def get_federation_setup_lock(self): def get_round_lock(self): return self.round_lock + + def get_reputation(self): + return self.reputation @event_handler(nebula_pb2.DiscoveryMessage, nebula_pb2.DiscoveryMessage.Action.DISCOVER) async def _discovery_discover_callback(self, source, message): @@ -465,6 +476,105 @@ async def _waiting_model_updates(self): else: logging.error("Aggregation finished with no parameters") + async def calculate_reputation(self): + logging.info(f"rejected nodes at round {self.round}: {self.rejected_nodes}") + if self.rejected_nodes is not None: + self.rejected_nodes.clear() + logging.info(f"rejected nodes after clear at round {self.round}: {self.rejected_nodes}") + + logging.info(f"change weight nodes at round {self.round}: {self.change_weight_nodes}") + if self.change_weight_nodes is not None: + self.change_weight_nodes.clear() + logging.info(f"change weight nodes after clear at round {self.round}: {self.change_weight_nodes}") + + current_round = self.get_round() + neighbors = set(await self.cm.get_addrs_current_connections(only_direct=True)) + + for nei in neighbors: + avg_reputation = self.reputation_instance.calculate_reputation( + self.config.participant["scenario_args"]["name"], + self.log_dir, + self.idx, + self.addr, + nei, + current_round= current_round + ) + + if nei not in self.reputation: + self.reputation[nei] = {"reputation": avg_reputation, "round": self.round, "last_feedback_round": -1} + else: + self.reputation[nei]["reputation"] = avg_reputation + self.reputation[nei]["round"] = self.round + + if self.reputation[nei]["reputation"] is not None: + logging.info(f"Reputation of node {nei}: {self.reputation[nei]['reputation']}") + if self.reputation[nei]["reputation"] <= 0.6: + self.rejected_nodes.add(nei) + logging.info(f"Rejected nodes: {self.rejected_nodes}") + elif 0.6 < self.reputation[nei]["reputation"] < 0.8: + logging.info(f"Change weight node: {nei}") + self.change_weight_nodes.add(nei) + + await self.include_feedback_in_reputation() + + if self.reputation is not None: + reputation_dict_with_values = { + f"Reputation/{self.addr}": { + node_id: float(data["reputation"]) for node_id, data in self.reputation.items() if data["reputation"] is not None + } + } + + logging.info(f"Reputation dict: {reputation_dict_with_values}") + self.trainer._logger.log_data(reputation_dict_with_values, step=self.round) + + for nei, data in self.reputation.items(): + if data["reputation"] is not None: + message_data = self.cm.mm.generate_reputation_message( + node_id=nei, + score=data["reputation"], + round=data["round"], + ) + self.cm.store_send_timestamp(nei, current_round, "reputation") + await self.cm.send_message_to_neighbors(message_data, [nei]) + + + async def include_feedback_in_reputation(self): + if self._cm.reputation_with_all_feedback is not None: + current_round = self.get_round() + for(current_node, node_ip, round_num), scores in self._cm.reputation_with_all_feedback.items(): + + if node_ip in self.reputation and "last_feedback_round" in self.reputation[node_ip]: + if self.reputation[node_ip]["last_feedback_round"] >= round_num: + continue + + + logging.info(f"current_node: {current_node} | node_ip: {node_ip} | round_num: {round_num} | scores: {scores}") + if scores: + avg_feedback = sum(scores) / len(scores) + logging.info(f"Receive feedback to node {node_ip} with average score {avg_feedback}") + + logging.info(f"self.reputation: {self.reputation}") + if node_ip in self.reputation: + current_reputation = self.reputation[node_ip]["reputation"] + logging.info(f"Current reputation for node {node_ip}: {current_reputation}") + else: + logging.info(f"No node {node_ip} in reputation history.") + + if current_reputation: + combined_reputation = (current_reputation + avg_feedback) / 2 + logging.info(f"Combined reputation for node {node_ip} in round {round_num}: {combined_reputation}") + else: + combined_reputation = current_reputation + logging.info(f"No reputation calculate for node {node_ip}.") + + self.reputation[node_ip] = { + "reputation": combined_reputation, + "round": current_round, + "last_feedback_round": round_num + } + + logging.info(f"Updated self.reputation for {node_ip}: {self.reputation[node_ip]}") + async def _learning_cycle(self): while self.round is not None and self.round < self.total_rounds: print_msg_box( @@ -482,6 +592,8 @@ async def _learning_cycle(self): await self.aggregator.update_federation_nodes(self.federation_nodes) await self._extended_learning_cycle() + await self.calculate_reputation() + await self.get_round_lock().acquire_async() print_msg_box( msg=f"Round {self.round} of {self.total_rounds} finished.", @@ -607,15 +719,31 @@ def __init__( self.fit_time = 0.0 self.extra_time = 0.0 - self.round_start_attack = 3 - self.round_stop_attack = 6 + self.round_start_attack = 6 + self.round_stop_attack = 9 self.aggregator_bening = self._aggregator + async def flood_attack(self, repetitions=10, interval=0.05): + neighbors = set(await self.cm.get_addrs_current_connections(only_direct=True)) + for nei in neighbors: + for i in range(repetitions): + message_data = self.cm.mm.generate_flood_attack_message( + attacker_id=self.addr, + frequency=int(i), + duration=int(interval*1000), + target_node=nei, + ) + await self.cm.send_message_to_neighbors(message_data, neighbors={nei}) + logging.info(f"Flood attack message sent to {nei} - Attempt {i + 1}/{repetitions}.") + await asyncio.sleep(interval) + self.cm.store_send_timestamp(nei, self.round, "flood_attack") + async def _extended_learning_cycle(self): if self.attack != None: if self.round in range(self.round_start_attack, self.round_stop_attack): logging.info("Changing aggregation function maliciously...") + await self.flood_attack("flood_attack") self._aggregator = create_malicious_aggregator(self._aggregator, self.attack) elif self.round == self.round_stop_attack: logging.info("Changing aggregation function benignly...") diff --git a/nebula/core/network/communications.py b/nebula/core/network/communications.py index b207d6a6e..c1300ecbf 100755 --- a/nebula/core/network/communications.py +++ b/nebula/core/network/communications.py @@ -27,6 +27,10 @@ pearson_correlation_metric, ) from nebula.core.utils.locker import Locker +from nebula.core.reputation.Reputation import ( + Reputation, + save_data, +) if TYPE_CHECKING: from nebula.core.engine import Engine @@ -78,6 +82,12 @@ def __init__(self, engine: "Engine"): max_concurrent_tasks = 5 self.semaphore_send_model = asyncio.Semaphore(max_concurrent_tasks) + # Reputation + self.reputation_instance = Reputation(self.engine) + self.reputation_with_all_feedback = {} + self.message_timestamps = {} + self.fraction_of_params_changed = {} + @property def engine(self): return self._engine @@ -152,6 +162,12 @@ async def handle_incoming_message(self, data, addr_from): if self.config.participant["device_args"]["proxy"] or message_wrapper.model_message.round == -1: await self.forwarder.forward(data, addr_from=addr_from) await self.handle_model_message(source, message_wrapper.model_message) + elif message_wrapper.HasField("reputation_message"): + if await self.include_received_message_hash(hashlib.md5(data).hexdigest()): + self.forwarder.forward(data, addr_from=addr_from) + await self.handle_reputation_message(source, message_wrapper.reputation_message) + elif message_wrapper.HasField("flood_attack_message"): + await self.handle_flooding_attack_message(source, message_wrapper.flood_attack_message) elif message_wrapper.HasField("connection_message"): await self.handle_connection_message(source, message_wrapper.connection_message) else: @@ -186,6 +202,7 @@ async def handle_federation_message(self, source, message): ) try: await self.engine.event_manager.trigger_event(source, message) + self.store_receive_timestamp(source, "federation", message.round) except Exception as e: logging.exception( f"📝 handle_federation_message | Error while processing: {message.action} {message.arguments} | {e}" @@ -256,18 +273,21 @@ async def handle_model_message(self, source, message): decoded_model, similarity=True, ) - with open( - f"{self.log_dir}/participant_{self.idx}_similarity.csv", - "a+", - ) as f: - if os.stat(f"{self.log_dir}/participant_{self.idx}_similarity.csv").st_size == 0: - f.write( - "timestamp,source_ip,nodes,round,current_round,cosine,euclidean,minkowski,manhattan,pearson_correlation,jaccard\n" - ) + with open(f"{self.engine.log_dir}/participant_{self.idx}_similarity.csv", "a+") as f: + if os.stat(f"{self.engine.log_dir}/participant_{self.idx}_similarity.csv").st_size == 0: + f.write("timestamp,source_ip,nodes,round,current_round,cosine,euclidean,minkowski,manhattan,pearson_correlation,jaccard\n") f.write( f"{datetime.now()}, {source}, {message.round}, {current_round}, {cosine_value}, {euclidean_value}, {minkowski_value}, {manhattan_value}, {pearson_correlation_value}, {jaccard_value}\n" ) + # Manage communication latency + self.store_receive_timestamp(source, "model", message.round) + self.calculate_latency(source, "model") + + # Manage parameters of models + parameters_local = self.engine.trainer.get_model_parameters() + self.fraction_of_parameters_changed(source, parameters_local, decoded_model, current_round) + await self.engine.aggregator.include_model_in_buffer( decoded_model, message.weight, @@ -329,6 +349,97 @@ async def handle_connection_message(self, source, message): except Exception as e: logging.exception(f"🔗 handle_connection_message | Error while processing: {message.action} | {e}") + async def handle_reputation_message(self, source, message): + try: + logging.info(f"handle_reputation_message | Reputation message received from {source} | Node: {message.node_id} | Score: {message.score} | Round: {message.round}") + + self.store_receive_timestamp(source, "reputation", message.round) + #self.calculate_latency(source, "reputation") + + current_node = self.addr + + # Manage reputation + if current_node != source: + key = (current_node, source, message.round) + + if key not in self.reputation_with_all_feedback: + self.reputation_with_all_feedback[key] = [] + + self.reputation_with_all_feedback[key].append(message.score) + + except Exception as e: + logging.error(f"Error handling reputation message: {e}") + + async def handle_flooding_attack_message(self, source, message): + try: + logging.info(f"🔥 handle_flooding_attack_message | Received flooding attack message from {source} | Attacker: {message.attacker_id} | Frequency: {message.frequency} | Duration: {message.duration} | Target node: {message.target_node}") + current_round = self.engine.get_round() + self.store_receive_timestamp(source, "flooding_attack", current_round) + except Exception as e: + logging.error(f"🔥 handle_flooding_attack_message | Error while processing: {e}") + + def fraction_of_parameters_changed(self, source, parameters_local, parameters_received, current_round): + # logging.info(f"🤖 fraction_of_parameters_changed | Managing parameters of models") + # logging.info(f"🤖 fraction_of_parameters_changed | Parameters local: {parameters_local}") + # logging.info(f"🤖 fraction_of_parameters_changed | Parameters received: {parameters_received}") + differences = [] + total_params = 0 + changed_params = 0 + changes_record = {} + prev_threshold = None + + if source in self.fraction_of_params_changed and current_round - 1 in self.fraction_of_params_changed[source]: + prev_threshold = self.fraction_of_params_changed[source][current_round - 1][-1]["threshold"] + + for key in parameters_local.keys(): + #logging.info(f"🤖 fraction_of_parameters_changed | Key: {key}") + if key in parameters_received: + diff = torch.abs(parameters_local[key] - parameters_received[key]) + differences.extend(diff.flatten().tolist()) + total_params += diff.numel() + #logging.info(f"🤖 fraction_of_parameters_changed | Total params: {total_params}") + + if differences: + mean_threshold = torch.mean(torch.tensor(differences)).item() + current_threshold = (prev_threshold + mean_threshold) / 2 if prev_threshold is not None else mean_threshold + else: + current_threshold = 0 + + + for key in parameters_local.keys(): + if key in parameters_received: + diff = torch.abs(parameters_local[key] - parameters_received[key]) + num_changed = torch.sum(diff > current_threshold).item() + changed_params += num_changed + if num_changed > 0: + changes_record[key] = num_changed + + fraction_changed = changed_params / total_params if total_params > 0 else 0.0 + + if source not in self.fraction_of_params_changed: + self.fraction_of_params_changed[source] = {} + if current_round not in self.fraction_of_params_changed[source]: + self.fraction_of_params_changed[source][current_round] = [] + + self.fraction_of_params_changed[source][current_round].append({ + "fraction_changed": fraction_changed, + "total_params": total_params, + "changed_params": changed_params, + "threshold": current_threshold, + "changes_record": changes_record + }) + + save_data(self.config.participant['scenario_args']['name'], + 'fraction_of_params_changed', + source, + self.addr, + current_round, + fraction_changed=fraction_changed, + total_params=total_params, + changed_params=changed_params, + threshold=current_threshold, + changes_record=changes_record) + def get_connections_lock(self): return self.connections_lock @@ -650,6 +761,45 @@ async def send_message(self, dest_addr, message): logging.exception(f"❗️ Cannot send message {message} to {dest_addr}. Error: {e!s}") await self.disconnect(dest_addr, mutual_disconnection=False) + def store_send_timestamp(self, dest_addr, round_number, type_message): + send_timestamp = datetime.now().strftime("%H:%M:%S") + self.message_timestamps[(self.addr, dest_addr, type_message)] = { + "send": send_timestamp, + "receive": None, + "latency": None, + "round": round_number, + "type": type_message + } + + def store_receive_timestamp(self, source, type_message, round=None): + current_time = time.time() + if current_time: + save_data(self.config.participant['scenario_args']['name'], 'time_message', source, self.addr, round=round, time=current_time, type_message=type_message, current_round=self.get_round()) + + receive_timestamp = datetime.now().strftime("%H:%M:%S") + if (self.addr, source, type_message) in self.message_timestamps: + self.message_timestamps[(self.addr, source, type_message)]["receive"] = receive_timestamp + + def calculate_latency(self, source, type_message): + if (self.addr, source, type_message) in self.message_timestamps: + send_time = self.message_timestamps[(self.addr, source, type_message)]["send"] + receive_time = self.message_timestamps[(self.addr, source, type_message)]["receive"] + round_number = self.message_timestamps[(self.addr, source, type_message)]["round"] + current_round = self.get_round() + + if send_time and receive_time and type_message == "model": + send_time = datetime.strptime(send_time, "%H:%M:%S") + receive_time = datetime.strptime(receive_time, "%H:%M:%S") + + latency = (receive_time - send_time).total_seconds() + logging.info(f"🕒 Latency from {source} with type message {type_message} in round {round_number}: {latency}") + + self.message_timestamps[(self.addr, source, type_message)]["latency"] = latency + save_data(self.config.participant['scenario_args']['name'], 'communication', source, self.addr, round_number, time=latency, type_message=type_message, current_round=current_round) + + return latency + return None + async def send_model(self, dest_addr, round, serialized_model, weight=1): async with self.semaphore_send_model: try: @@ -657,6 +807,10 @@ async def send_model(self, dest_addr, round, serialized_model, weight=1): if conn is None: logging.info(f"❗️ Connection with {dest_addr} not found") return + + if round != -1: + self.store_send_timestamp(dest_addr, round, "model") + logging.info( f"Sending model to {dest_addr} with round {round}: weight={weight} | size={sys.getsizeof(serialized_model) / (1024** 2) if serialized_model is not None else 0} MB" ) diff --git a/nebula/core/network/connection.py b/nebula/core/network/connection.py index 21d75519a..13910e708 100755 --- a/nebula/core/network/connection.py +++ b/nebula/core/network/connection.py @@ -8,6 +8,7 @@ import zlib from dataclasses import dataclass from typing import TYPE_CHECKING, Any +from nebula.core.reputation.Reputation import save_data import lz4.frame from geopy import distance @@ -61,6 +62,7 @@ def __init__( self.process_task = None self.pending_messages_queue = asyncio.Queue(maxsize=100) self.message_buffers: dict[bytes, dict[int, MessageChunk]] = {} + self._chunk_data = self.cm.reputation_instance.chunk_data self.EOT_CHAR = b"\x00\x00\x00\x04" self.COMPRESSION_CHAR = b"\x00\x00\x00\x01" @@ -281,7 +283,39 @@ async def handle_incoming_message(self) -> None: self._store_chunk(message_id, chunk_index, chunk_data, is_last_chunk) # logging.debug(f"Received chunk {chunk_index} of message {message_id.hex()} | size: {len(chunk_data)} bytes") + message_id_decoded = message_id.hex() + if chunk_index == 0: + chunk_bytes = chunk_data.tobytes() + # logging.info(f"chunk data: {chunk_bytes}") + comparation_with = b"\x01\x00\x00\x00x\x9c\x00" + if chunk_bytes[:len(comparation_with)] == comparation_with: + if message_id_decoded not in self._chunk_data: + self._chunk_data[message_id_decoded] = {} + + self._chunk_data[message_id_decoded]['start_time'] = time.time() + # if message_id_decoded and message_id_decoded in self._chunk_data: + # logging.info(f"message_id_decoded in start_time: {self._chunk_data}") + if is_last_chunk: + if message_id_decoded in self._chunk_data and message_id_decoded and 'start_time' in self._chunk_data[message_id_decoded]: + self._chunk_data[message_id_decoded]['end_time'] = time.time() + # logging.info(f"message_id_decoded in end_time: {self._chunk_data}") + + if 'start_time' in self._chunk_data[message_id_decoded] and 'end_time' in self._chunk_data[message_id_decoded]: + latency = self._chunk_data[message_id_decoded]['end_time'] - self._chunk_data[message_id_decoded]['start_time'] + # logging.info(f"message_id_decoded in latency: {latency}") + source = self.addr + + if source != self.cm.get_addr(): + # logging.info(f"SAVE DATA CHUNK") + save_data(self.config.participant['scenario_args']['name'], + "chunk_latency", + source, + self.cm.get_addr(), + self.cm.get_round(), + message_id_decoded=message_id_decoded, + latency=latency) + await self._process_complete_message(message_id) except asyncio.CancelledError: logging.info("Message handling cancelled") diff --git a/nebula/core/network/messages.py b/nebula/core/network/messages.py index 5468abca2..4ed43ed3a 100755 --- a/nebula/core/network/messages.py +++ b/nebula/core/network/messages.py @@ -72,12 +72,37 @@ def generate_connection_message(self, action): data = message_wrapper.SerializeToString() return data - def generate_reputation_message(self, reputation): + # def generate_reputation_message(self, reputation): + # message = nebula_pb2.ReputationMessage( + # reputation=reputation, + # ) + # message_wrapper = nebula_pb2.Wrapper() + # message_wrapper.source = self.addr + # message_wrapper.reputation_message.CopyFrom(message) + # data = message_wrapper.SerializeToString() + # return data + + def generate_reputation_message(self, node_id, score, round): message = nebula_pb2.ReputationMessage( - reputation=reputation, + node_id=node_id, + score=score, + round=round, ) message_wrapper = nebula_pb2.Wrapper() message_wrapper.source = self.addr message_wrapper.reputation_message.CopyFrom(message) data = message_wrapper.SerializeToString() return data + + def generate_flood_attack_message(self, attacker_id, frequency, duration, target_node): + message = nebula_pb2.FloodAttackMessage( + attacker_id=attacker_id, + frequency=frequency, + duration=duration, + target_node=target_node, + ) + message_wrapper = nebula_pb2.Wrapper() + message_wrapper.source = self.addr + message_wrapper.flood_attack_message.CopyFrom(message) + data = message_wrapper.SerializeToString() + return data diff --git a/nebula/core/pb/nebula.proto b/nebula/core/pb/nebula.proto index 49f3164f1..39cbe9795 100755 --- a/nebula/core/pb/nebula.proto +++ b/nebula/core/pb/nebula.proto @@ -79,3 +79,16 @@ message ConnectionMessage { message ResponseMessage { string response = 1; // Outcome of the requested operation. } + +message ReputationMessage { + string node_id = 1; //Id to node thats send the reputation + float score = 2; //Score reputation + int32 round = 3; //Round to send the reputation +} + +message FloodAttackMessage { + string attacker_id = 1; // ID of the attacker node + int32 frequency = 2; // Frequency of message sending in the attack + int32 duration = 3; // Duration of the attack in seconds + string target_node = 4; // Optional: ID of the target node (if specified) +} diff --git a/nebula/core/pb/nebula/core/pb/nebula_pb2.py b/nebula/core/pb/nebula/core/pb/nebula_pb2.py new file mode 100644 index 000000000..767bf8bcf --- /dev/null +++ b/nebula/core/pb/nebula/core/pb/nebula_pb2.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: nebula/core/pb/nebula.proto +# Protobuf Python Version: 5.28.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 28, + 1, + '', + 'nebula/core/pb/nebula.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bnebula/core/pb/nebula.proto\x12\x06nebula\"\xe4\x02\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x42\t\n\x07message\"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02\"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02\"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t\"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04\"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05\"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03\"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05\"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action\"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01\"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t\"B\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\"c\n\x12\x46loodAttackMessage\x12\x13\n\x0b\x61ttacker_id\x18\x01 \x01(\t\x12\x11\n\tfrequency\x18\x02 \x01(\x05\x12\x10\n\x08\x64uration\x18\x03 \x01(\x05\x12\x13\n\x0btarget_node\x18\x04 \x01(\tb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'nebula.core.pb.nebula_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_WRAPPER']._serialized_start=40 + _globals['_WRAPPER']._serialized_end=396 + _globals['_DISCOVERYMESSAGE']._serialized_start=399 + _globals['_DISCOVERYMESSAGE']._serialized_end=557 + _globals['_DISCOVERYMESSAGE_ACTION']._serialized_start=505 + _globals['_DISCOVERYMESSAGE_ACTION']._serialized_end=557 + _globals['_CONTROLMESSAGE']._serialized_start=560 + _globals['_CONTROLMESSAGE']._serialized_end=714 + _globals['_CONTROLMESSAGE_ACTION']._serialized_start=638 + _globals['_CONTROLMESSAGE_ACTION']._serialized_end=714 + _globals['_FEDERATIONMESSAGE']._serialized_start=717 + _globals['_FEDERATIONMESSAGE']._serialized_end=922 + _globals['_FEDERATIONMESSAGE_ACTION']._serialized_start=822 + _globals['_FEDERATIONMESSAGE_ACTION']._serialized_end=922 + _globals['_MODELMESSAGE']._serialized_start=924 + _globals['_MODELMESSAGE']._serialized_end=989 + _globals['_CONNECTIONMESSAGE']._serialized_start=991 + _globals['_CONNECTIONMESSAGE']._serialized_end=1099 + _globals['_CONNECTIONMESSAGE_ACTION']._serialized_start=1062 + _globals['_CONNECTIONMESSAGE_ACTION']._serialized_end=1099 + _globals['_RESPONSEMESSAGE']._serialized_start=1101 + _globals['_RESPONSEMESSAGE']._serialized_end=1136 + _globals['_REPUTATIONMESSAGE']._serialized_start=1138 + _globals['_REPUTATIONMESSAGE']._serialized_end=1204 + _globals['_FLOODATTACKMESSAGE']._serialized_start=1206 + _globals['_FLOODATTACKMESSAGE']._serialized_end=1305 +# @@protoc_insertion_point(module_scope) diff --git a/nebula/core/pb/nebula_pb2.py b/nebula/core/pb/nebula_pb2.py index bf6290e6e..f4186cd33 100755 --- a/nebula/core/pb/nebula_pb2.py +++ b/nebula/core/pb/nebula_pb2.py @@ -1,47 +1,51 @@ +# -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: nebula.proto -# Protobuf Python Version: 4.25.3 +# Protobuf Python Version: 5.28.1 """Generated protocol buffer code.""" - from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder - # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( - b'\n\x0cnebula.proto\x12\x06nebula"\xe4\x02\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x42\t\n\x07message"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\tb\x06proto3' -) + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cnebula.proto\x12\x06nebula\"\xe4\x02\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x42\t\n\x07message\"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02\"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02\"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t\"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04\"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05\"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03\"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05\"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action\"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01\"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t\"B\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\"c\n\x12\x46loodAttackMessage\x12\x13\n\x0b\x61ttacker_id\x18\x01 \x01(\t\x12\x11\n\tfrequency\x18\x02 \x01(\x05\x12\x10\n\x08\x64uration\x18\x03 \x01(\x05\x12\x13\n\x0btarget_node\x18\x04 \x01(\tb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "nebula_pb2", _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _globals["_WRAPPER"]._serialized_start = 25 - _globals["_WRAPPER"]._serialized_end = 381 - _globals["_DISCOVERYMESSAGE"]._serialized_start = 384 - _globals["_DISCOVERYMESSAGE"]._serialized_end = 542 - _globals["_DISCOVERYMESSAGE_ACTION"]._serialized_start = 490 - _globals["_DISCOVERYMESSAGE_ACTION"]._serialized_end = 542 - _globals["_CONTROLMESSAGE"]._serialized_start = 545 - _globals["_CONTROLMESSAGE"]._serialized_end = 699 - _globals["_CONTROLMESSAGE_ACTION"]._serialized_start = 623 - _globals["_CONTROLMESSAGE_ACTION"]._serialized_end = 699 - _globals["_FEDERATIONMESSAGE"]._serialized_start = 702 - _globals["_FEDERATIONMESSAGE"]._serialized_end = 907 - _globals["_FEDERATIONMESSAGE_ACTION"]._serialized_start = 807 - _globals["_FEDERATIONMESSAGE_ACTION"]._serialized_end = 907 - _globals["_MODELMESSAGE"]._serialized_start = 909 - _globals["_MODELMESSAGE"]._serialized_end = 974 - _globals["_CONNECTIONMESSAGE"]._serialized_start = 976 - _globals["_CONNECTIONMESSAGE"]._serialized_end = 1084 - _globals["_CONNECTIONMESSAGE_ACTION"]._serialized_start = 1047 - _globals["_CONNECTIONMESSAGE_ACTION"]._serialized_end = 1084 - _globals["_RESPONSEMESSAGE"]._serialized_start = 1086 - _globals["_RESPONSEMESSAGE"]._serialized_end = 1121 +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'nebula_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_WRAPPER']._serialized_start=25 + _globals['_WRAPPER']._serialized_end=381 + _globals['_DISCOVERYMESSAGE']._serialized_start=384 + _globals['_DISCOVERYMESSAGE']._serialized_end=542 + _globals['_DISCOVERYMESSAGE_ACTION']._serialized_start=490 + _globals['_DISCOVERYMESSAGE_ACTION']._serialized_end=542 + _globals['_CONTROLMESSAGE']._serialized_start=545 + _globals['_CONTROLMESSAGE']._serialized_end=699 + _globals['_CONTROLMESSAGE_ACTION']._serialized_start=623 + _globals['_CONTROLMESSAGE_ACTION']._serialized_end=699 + _globals['_FEDERATIONMESSAGE']._serialized_start=702 + _globals['_FEDERATIONMESSAGE']._serialized_end=907 + _globals['_FEDERATIONMESSAGE_ACTION']._serialized_start=807 + _globals['_FEDERATIONMESSAGE_ACTION']._serialized_end=907 + _globals['_MODELMESSAGE']._serialized_start=909 + _globals['_MODELMESSAGE']._serialized_end=974 + _globals['_CONNECTIONMESSAGE']._serialized_start=976 + _globals['_CONNECTIONMESSAGE']._serialized_end=1084 + _globals['_CONNECTIONMESSAGE_ACTION']._serialized_start=1047 + _globals['_CONNECTIONMESSAGE_ACTION']._serialized_end=1084 + _globals['_RESPONSEMESSAGE']._serialized_start=1086 + _globals['_RESPONSEMESSAGE']._serialized_end=1121 + _globals['_REPUTATIONMESSAGE']._serialized_start=1123 + _globals['_REPUTATIONMESSAGE']._serialized_end=1189 + _globals['_FLOODATTACKMESSAGE']._serialized_start=1191 + _globals['_FLOODATTACKMESSAGE']._serialized_end=1290 # @@protoc_insertion_point(module_scope) diff --git a/nebula/core/reputation/Reputation.py b/nebula/core/reputation/Reputation.py new file mode 100644 index 000000000..b48ec24b0 --- /dev/null +++ b/nebula/core/reputation/Reputation.py @@ -0,0 +1,916 @@ +import os +import csv +import logging +import json +import time +import numpy as np +from datetime import datetime, timedelta +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from nebula.core.engine import Engine + +def save_data(scenario, type_data, source_ip, addr, round=None, time=None, data_contribution=None, type_message=None, current_round=None, fraction_changed=None, total_params=None, changed_params=None, threshold=None, changes_record=None, rate_of_change=None): + """ + Save communication data between nodes and aggregated models. + + Args: + source_ip (str): Source IP address. + addr (str): Destination IP address. + round (int): Round number. + time (float): Time taken to process the data. + """ + + source_ip = source_ip.split(":")[0] + addr = addr.split(":")[0] + + try: + combined_data = {} + + if type_data == 'communication': + combined_data["communication"] = { + "time": time, + "current_round": current_round, + "round": round, + "type_message": type_message, + } + elif type_data == 'time_message': + combined_data["time_message"] = { + "time": time, + "type_message": type_message, + "round": round, + "current_round": current_round, + } + elif type_data == 'fraction_of_params_changed': + combined_data["fraction_of_params_changed"] = { + "total_params": total_params, + "changed_params": changed_params, + "fraction_changed": fraction_changed, + "threshold": threshold, + "changes_record": changes_record, + "round": round, + } + + script_dir = os.path.dirname(os.path.abspath(__file__)) + file_name = f"{addr}_storing_{source_ip}_info.json" + full_file_path = os.path.join(script_dir, scenario, file_name) + os.makedirs(os.path.dirname(full_file_path), exist_ok=True) + + all_metrics = [] + if os.path.exists(full_file_path): + with open(full_file_path, 'r') as existing_file: + try: + all_metrics = json.load(existing_file) + except json.JSONDecodeError: + logging.error(f"JSON decode error in file: {full_file_path}") + all_metrics = [] + + all_metrics.append(combined_data) + + with open(full_file_path, 'w') as json_file: + json.dump(all_metrics, json_file, indent=4) + + except Exception as e: + logging.error(f"Error saving data: {e}") + +class Reputation: + """ + Class to define the reputation of a participant. + """ + reputation_history = {} + communication_history = {} + frequency_history = {} + neighbor_reputation_history = {} + fraction_changed_history = {} + communication_data = [] + messages_frequency = [] + previous_threshold_freq = {} + mean_time_communication = None + communication_score = 0.0 + + def __init__(self, engine: "Engine"): + self._engine = engine + + @property + def engine(self): + return self._engine + + def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_round=None): + """ + Calculate the reputation of each participant based on the data stored. + + Args: + scenario (str): Scenario name. + """ + logging.info(f"id_node: {id_node}, addr: {addr}, nei: {nei}") + addr = addr.split(":")[0].strip() + nei = nei.split(":")[0].strip() + + # array_communication = [] + # array_messages_frequency = [] + communication_time_normalized = 0 + messages_frequency_normalized = 0 + messages_frequency_count = 0 + avg_communication_time_normalized = 0 + avg_messages_frequency_normalized = 0 + fraction_score = 0 + fraction_score_asign = 0 + + try: + script_dir = os.path.dirname(os.path.abspath(__file__)) + file_name = f"{addr}_storing_{nei}_info.json" # Read the file to calculate the reputation + full_file_path = os.path.join(script_dir, scenario, file_name) + os.makedirs(os.path.dirname(full_file_path), exist_ok=True) + + if os.path.exists(full_file_path) and os.path.getsize(full_file_path) > 0: + with open(full_file_path, 'r') as json_file: + all_metrics = json.load(json_file) + for metric in all_metrics: + if "communication" in metric: + type_message = metric["communication"]["type_message"] + round = metric["communication"]["round"] + current_round_comm = metric["communication"]["current_round"] + time = metric["communication"]["time"] + if current_round == current_round_comm: + communication_time_normalized = Reputation.manage_metric_communication(type_message, round, current_round_comm, time, addr , nei) + if "time_message" in metric: + round = metric["time_message"]["round"] + current_round_mess = metric["time_message"]["current_round"] + time = metric["time_message"]["time"] + type_message = metric["time_message"]["type_message"] + if current_round == current_round_mess: + Reputation.messages_frequency.append({"time_message": time, "type_message": type_message, "round": round, "current_round": current_round, "key": (addr, nei)}) + if "fraction_of_params_changed" in metric: + round = metric["fraction_of_params_changed"]["round"] + total_params = metric["fraction_of_params_changed"]["total_params"] + changed_params = metric["fraction_of_params_changed"]["changed_params"] + fraction_changed = metric["fraction_of_params_changed"]["fraction_changed"] + threshold = metric["fraction_of_params_changed"]["threshold"] + changes_record = metric["fraction_of_params_changed"]["changes_record"] + if round == current_round: + Reputation.analyze_anomalies(addr, nei, round, current_round, fraction_changed, threshold, changes_record, changed_params, total_params) + #logging.info(f"Reputation.fraction_changed_history: {Reputation.fraction_changed_history}") + + similarity_file = os.path.join(log_dir, f"participant_{id_node}_similarity.csv") + similarity_reputation = Reputation.read_similarity_file(similarity_file, nei) + #logging.info(f"Similarity reputation: {similarity_reputation}") + + if communication_time_normalized is not None: + avg_communication_time_normalized = Reputation.save_communication_history(addr, nei, communication_time_normalized, current_round) + if avg_communication_time_normalized is None and current_round >= 5: + avg_communication_time_normalized = Reputation.communication_history[(addr, nei)][current_round - 1]["avg_communication"] + logging.info(f"Avg communication is None and current_round = {current_round}, avg_communication_time_normalized: {avg_communication_time_normalized}") + + if Reputation.messages_frequency is not None: + messages_frequency_normalized, messages_frequency_count = Reputation.manage_metric_frequency(Reputation.messages_frequency, addr, nei, current_round) + avg_messages_frequency_normalized = Reputation.save_frequency_history(addr, nei, messages_frequency_normalized, current_round) + if avg_messages_frequency_normalized is None and current_round >= 4: + avg_messages_frequency_normalized = Reputation.frequency_history[(addr, nei)][current_round - 1]["avg_frequency"] + logging.info(f"Avg messages frequency is None and curret_round = {current_round}, avg_messages_frequency_normalized: {avg_messages_frequency_normalized}") + + if Reputation.fraction_changed_history is not None: + key = (addr, nei, current_round) + if key not in Reputation.fraction_changed_history: + key = (addr, nei, current_round - 1) if current_round > 0 else None + logging.info(f"Key prev: {key}") + fraction_score = Reputation.fraction_changed_history[key].get("fraction_score") + logging.info(f"Fraction score: {fraction_score} | key: {key}") + fraction_score_asign = fraction_score if fraction_score is not None else 0 + logging.info(f"Fraction score asign: {fraction_score_asign}") + + # Weights for each metric + if current_round is not None: + if current_round >= 4: + weight_to_similarity = 0.5 + weight_to_communication = 0.0 + weight_to_fraction = 0.0 + weight_to_message_frequency = 0.5 + elif current_round >= 5: + weight_to_similarity = 0.3 + weight_to_communication = 0.3 + weight_to_fraction = 0.2 + weight_to_message_frequency = 0.2 + else: + weight_to_similarity = 1.0 + weight_to_communication = 0.0 + weight_to_fraction = 0.0 + weight_to_message_frequency = 0.0 + + # Reputation calculation + reputation = ( weight_to_communication * avg_communication_time_normalized + + weight_to_message_frequency * avg_messages_frequency_normalized + + weight_to_similarity * similarity_reputation + + weight_to_fraction * fraction_score_asign) + + # Create graphics to metrics + self.create_graphics_to_metrics(avg_communication_time_normalized, messages_frequency_count, avg_messages_frequency_normalized, similarity_reputation, fraction_score_asign, addr, nei, current_round, self.engine.total_rounds) + + # Save history reputation + average_reputation = Reputation.save_reputation_history_in_memory(addr, nei, reputation, current_round) + + logging.info(f"Average reputation to node {nei}: {average_reputation}") + return average_reputation + except Exception as e: + logging.error(f"Error calculating reputation: {e}, type: {type(e).__name__}") + + def create_graphics_to_metrics(self, com_time, mess_fre_count, mess_fre_norm, similarity, fraction, addr, nei, current_round, total_rounds): + """ + Create graphics to metrics. + """ + + if current_round is not None and current_round < total_rounds: + communication_time_dict = { + f"Reputation_communication_time/{addr}": { + nei: com_time + } + } + + messages_frequency_count_dict = { + f"Reputation_messages_frequency_count/{addr}": { + nei: mess_fre_count + } + } + + messages_frequency_norm_dict = { + f"Reputation_messages_frequency_normalized/{addr}": { + nei: mess_fre_norm + } + } + + similarity_dict = { + f"Reputation_similarity/{addr}": { + nei: similarity + } + } + + fraction_dict = { + f"Reputation_fraction/{addr}": { + nei: fraction + } + } + + if communication_time_dict is not None: + self.engine.trainer._logger.log_data(communication_time_dict, step=current_round) + + if messages_frequency_count_dict is not None: + self.engine.trainer._logger.log_data(messages_frequency_count_dict, step=current_round) + + if messages_frequency_norm_dict is not None: + self.engine.trainer._logger.log_data(messages_frequency_norm_dict, step=current_round) + + if similarity_dict is not None: + self.engine.trainer._logger.log_data(similarity_dict, step=current_round) + + if fraction_dict is not None: + self.engine.trainer._logger.log_data(fraction_dict, step=current_round) + + @staticmethod + def analyze_anomalies(addr, nei, round_num, current_round, fraction_changed, threshold, changes_record, changed_params, total_params): + """ + Analyze anomalies in the fraction of parameters changed. + """ + try: + key = (addr, nei, round_num) + + if key not in Reputation.fraction_changed_history: + prev_key = (addr, nei, round_num - 1) + if round_num > 0 and prev_key in Reputation.fraction_changed_history: + previous_data = Reputation.fraction_changed_history[prev_key] + fraction_changed = fraction_changed if fraction_changed is not None else previous_data["fraction_changed"] + threshold = threshold if threshold is not None else previous_data["threshold"] + else: + fraction_changed = fraction_changed if fraction_changed is not None else 0 + threshold = threshold if threshold is not None else 0 + + Reputation.fraction_changed_history[key] = { + "fraction_changed": fraction_changed, + "threshold": threshold, + "fraction_score": None, + "fraction_anomaly": False, + "threshold_anomaly": False, + "mean_fraction": None, + "std_dev_fraction": None, + "mean_threshold": None, + "std_dev_threshold": None, + } + + # Calcular y almacenar estadísticas solo hasta la ronda 4 + if round_num < 5: + past_fractions = [] + past_thresholds = [] + + for r in range(round_num): + past_key = (addr, nei, r) + if past_key in Reputation.fraction_changed_history: + past_fractions.append(Reputation.fraction_changed_history[past_key]["fraction_changed"]) + past_thresholds.append(Reputation.fraction_changed_history[past_key]["threshold"]) + + if past_fractions: + mean_fraction = np.mean(past_fractions) + std_dev_fraction = np.std(past_fractions) + Reputation.fraction_changed_history[key]["mean_fraction"] = mean_fraction + Reputation.fraction_changed_history[key]["std_dev_fraction"] = std_dev_fraction + + if past_thresholds: + mean_threshold = np.mean(past_thresholds) + std_dev_threshold = np.std(past_thresholds) + Reputation.fraction_changed_history[key]["mean_threshold"] = mean_threshold + Reputation.fraction_changed_history[key]["std_dev_threshold"] = std_dev_threshold + + else: + fraction_value = 0 + threshold_value = 0 + prev_key = (addr, nei, round_num - 1) + if prev_key not in Reputation.fraction_changed_history: + prev_key = (addr, nei, round_num - 2) + + mean_fraction_prev = Reputation.fraction_changed_history[prev_key]["mean_fraction"] + std_dev_fraction_prev = Reputation.fraction_changed_history[prev_key]["std_dev_fraction"] + mean_threshold_prev = Reputation.fraction_changed_history[prev_key]["mean_threshold"] + std_dev_threshold_prev = Reputation.fraction_changed_history[prev_key]["std_dev_threshold"] + # logging.info(f"Round: {round_num}, Mean fraction: {mean_fraction_prev}, Std dev fraction: {std_dev_fraction_prev}, Mean threshold: {mean_threshold_prev}, Std dev threshold: {std_dev_threshold_prev}") + + current_fraction = Reputation.fraction_changed_history[key]["fraction_changed"] + current_threshold = Reputation.fraction_changed_history[key]["threshold"] + # logging.info(f"Round: {round_num}, Current fraction: {current_fraction}, Current threshold: {current_threshold}") + + #low_mean_fraction_prev = mean_fraction_prev - std_dev_fraction_prev + upper_mean_fraction_prev = mean_fraction_prev + std_dev_fraction_prev + #low_mean_threshold_prev = mean_threshold_prev - std_dev_threshold_prev + upper_mean_threshold_prev = mean_threshold_prev + std_dev_threshold_prev + + #fraction_anomaly = not (low_mean_fraction_prev <= current_fraction <= upper_mean_fraction_prev) + #threshold_anomaly = not (low_mean_threshold_prev <= current_threshold <= upper_mean_threshold_prev) + fraction_anomaly = current_fraction > upper_mean_fraction_prev + threshold_anomaly = current_threshold > upper_mean_threshold_prev + # logging.info(f"Round: {round_num}, Fraction anomaly: {fraction_anomaly}, Threshold anomaly: {threshold_anomaly}") + + Reputation.fraction_changed_history[key]["fraction_anomaly"] = fraction_anomaly + Reputation.fraction_changed_history[key]["threshold_anomaly"] = threshold_anomaly + + # Calculate the fraction score + k_fraction = 1 / std_dev_fraction_prev if std_dev_fraction_prev != 0 else 1 + # logging.info(f"Round: {round_num}, K fraction: {k_fraction}") + k_threshold = 1 / std_dev_threshold_prev if std_dev_threshold_prev != 0 else 1 + # logging.info(f"Round: {round_num}, K threshold: {k_threshold}") + fraction_value = 1 / (1 + np.exp(-k_fraction * (current_fraction - mean_fraction_prev))) if current_fraction is not None and mean_fraction_prev is not None else 0 + # logging.info(f"Round: {round_num}, Fraction: {fraction_value}") + threshold_value = 1 / (1 + np.exp(-k_threshold * (current_threshold - mean_threshold_prev))) if current_threshold is not None and mean_threshold_prev is not None else 0 + # logging.info(f"Round: {round_num}, Threshold: {threshold_value}") + + if threshold_anomaly: + fraction_weight = 0.8 + threshold_weight = 0.2 + elif fraction_anomaly: + fraction_weight = 0.4 + threshold_weight = 0.6 + else: + fraction_weight = 0.5 + threshold_weight = 0.5 + + fraction_score = 1 - (fraction_weight * fraction_value + threshold_weight * threshold_value) + Reputation.fraction_changed_history[key]["fraction_score"] = fraction_score + + # Upload the values to the history + Reputation.fraction_changed_history[key]["mean_fraction"] = (current_fraction + mean_fraction_prev) / 2 + Reputation.fraction_changed_history[key]["std_dev_fraction"] = np.sqrt(((current_fraction - mean_fraction_prev) ** 2 + std_dev_fraction_prev ** 2) / 2) + Reputation.fraction_changed_history[key]["mean_threshold"] = (current_threshold + mean_threshold_prev) / 2 + Reputation.fraction_changed_history[key]["std_dev_threshold"] = np.sqrt(((0.1 * (current_threshold - mean_threshold_prev) ** 2) + std_dev_threshold_prev ** 2) / 2) + + # Score not negative + Reputation.fraction_changed_history[key]["fraction_score"] = max(fraction_score, 0) + except Exception as e: + logging.error(f"Error analyzing anomalies: {e}") + + @staticmethod + def save_frequency_history(addr, nei, messages_frequency_normalized, current_round): + """ + Save the frequency history of a participant (addr) regarding its neighbor (nei) in memory. + + Args: + addr (str): The identifier of the node whose frequency history is being saved. + nei (str): The neighboring node involved. + messages_frequency_normalized (float): The frequency value to be saved. + current_round (int): The current round number. + + Returns: + float: The cumulative frequency including the current round. + """ + + try: + key = (addr, nei) + + if key not in Reputation.frequency_history: + Reputation.frequency_history[key] = {} + + Reputation.frequency_history[key][current_round] = { + "frequency": messages_frequency_normalized + } + + # logging.info(f"Frequency: {messages_frequency_normalized}") + # logging.info(f"Frequency history: {Reputation.frequency_history}") + + rounds = Reputation.frequency_history[key] + if messages_frequency_normalized != 0 and current_round > 3: + previous_avg = Reputation.frequency_history[key].get(current_round - 1, {}).get("avg_frequency", None) + # logging.info(f"Previous avg frequency: {previous_avg}") + if previous_avg is not None: + avg_frequency = (messages_frequency_normalized + previous_avg) / 2 + else: + avg_frequency = messages_frequency_normalized + + Reputation.frequency_history[key][current_round]["avg_frequency"] = avg_frequency + else: + avg_frequency = 0 + + # logging.info(f"Avg frequency: {avg_frequency}") + return avg_frequency + except Exception as e: + logging.error(f"Error saving frequency history: {e}") + + @staticmethod + def manage_metric_frequency(messages_frequency, addr, nei, current_round): + try: + # start_time = time.time() + # interval = 60 + current_addr_nei = (addr, nei) + threshold = 0 + previous_threshold = Reputation.previous_threshold_freq.get(current_addr_nei, 0) + relevant_messages = [msg for msg in messages_frequency if msg["key"] == current_addr_nei and msg["current_round"] == current_round] + messages_count = len(relevant_messages) if relevant_messages else 0 + # logging.info(f"Round {current_round}. Relevant messages: {relevant_messages}, Messages count: {messages_count}") + + if current_round >= 0 and current_round <= 2: + previous_counts = [ + len([m for m in messages_frequency if m["key"] == current_addr_nei and m["current_round"] == r]) + for r in range(3) + ] + threshold = np.mean(previous_counts) if previous_counts else 0 + Reputation.previous_threshold_freq[current_addr_nei] = threshold + # logging.info(f"Round {current_round}. Previous counts: {previous_counts}, Threshold: {threshold}") + normalized_messages = 0.0 + elif current_round >= 3: + threshold = previous_threshold + # logging.info(f"Round {current_round}. Prev threshold: {threshold}") + + if messages_count > threshold: + excess = messages_count - threshold + normalized_messages = 1 - (excess / messages_count) if messages_count > 0 else 0 + # logging.info(f"Round {current_round}. Excess: {excess}, Normalized messages: {normalized_messages}") + else: + normalized_messages = 1 - ((threshold - messages_count) / (threshold + messages_count)) if threshold > 0 else 0 + # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") + + if previous_threshold > 0: + threshold = (messages_count + previous_threshold) / 2 + Reputation.previous_threshold_freq[current_addr_nei] = threshold + # logging.info(f"Round {current_round}. New threshold: {threshold}") + else: + normalized_messages = 0.0 + + return normalized_messages, messages_count + except Exception as e: + logging.error(f"Error managing frequency metric: {e}") + return 0.0, 0 + + @staticmethod + def manage_metric_communication(type_message, round, current_round, time, addr, nei): + """ + Manage the communication metric. + + Args: + type_message (str): Type of message. + round (int): Round number. + current_round (int): Current round number. + time (float): Time taken to process the data. + addr (str): Source IP address. + nei (str): Destination IP address. + + Returns: + float: Normalized communication value. + """ + try: + Reputation.communication_data.append({ + "time": time, + "type_message": type_message, + "round": round, + "current_round": current_round, + "key": (addr, nei) + }) + + #logging.info(f"Communication data: {Reputation.communication_data}") + + current_addr_nei = (addr, nei) + filtered_communications = [comm for comm in Reputation.communication_data if comm["key"] == current_addr_nei] + #logging.info(f"Round {current_round}. Filtered by key: {filtered_communications}") + + if current_round == 5: + model_times = [comm["time"] for comm in filtered_communications if comm["type_message"] == "model" and comm["current_round"] < 5] + #logging.info(f"Round {current_round}. Model times: {model_times}") + + if model_times: + Reputation.mean_time_communication = np.mean(model_times) + #logging.info(f"Round {current_round}. Mean time communication: {Reputation.mean_time_communication}") + + difference = abs(time - Reputation.mean_time_communication) + #logging.info(f"Round {current_round}. Time: {time} difference: {difference}") + + penalty_range = difference / Reputation.mean_time_communication + #logging.info(f"Round {current_round}. Penalty range: {penalty_range}") + + if time < Reputation.mean_time_communication: + reduction_factor = 1 - (difference / Reputation.mean_time_communication) + penalty_range *= reduction_factor + #logging.info(f"Round {current_round}. Normal penalty reduction: {penalty_range}") + + if round != current_round: + round_difference = abs(current_round - round) + penalty_range += round_difference * 0.1 + #logging.info(f"Round {round} != current round {current_round}. Additional penalty for round difference.") + + Reputation.communication_score = max(0, 1 - penalty_range) + #logging.info(f"Round {current_round}. Communication score: {Reputation.communication_score}") + else: + Reputation.communication_score = 0.0 + Reputation.mean_time_communication = 0.0 + #logging.info(f"Round {current_round}. No data to calculate communication score") + + return Reputation.communication_score + + elif current_round > 5 and Reputation.mean_time_communication is not None: + previous_mean = Reputation.mean_time_communication + Reputation.mean_time_communication = (previous_mean + time) / 2 + #logging.info(f"Round {current_round}. Update mean time communication: {Reputation.mean_time_communication}") + + difference = abs(time - Reputation.mean_time_communication) + #logging.info(f"Round {current_round}. Time: {time} difference: {difference}") + + # max_time = max(comm["time"] for comm in Reputation.communication_data if comm["type_message"] == "model" and comm["key"] == (addr, nei)) + # logging.info(f"Round {current_round}. Max time for score calculation: {max_time}") + + penalty_range = difference / Reputation.mean_time_communication + #logging.info(f"Round {current_round}. Penalty range: {penalty_range}") + + if time < Reputation.mean_time_communication: + reduction_factor = 1 - (difference / Reputation.mean_time_communication) + penalty_range *= reduction_factor + #logging.info(f"Round {current_round}. Normal penalty reduction: {penalty_range}") + + if round != current_round: + round_difference = abs(current_round - round) + penalty_range += round_difference * 0.1 + #logging.info(f"Round {round} != current round {current_round}. Additional penalty for round difference.") + + penalty_range = min(penalty_range, 1) + #logging.info(f"Round {current_round}. Penalty range: {penalty_range}") + + Reputation.communication_score = max(0, 1 - penalty_range) + #logging.info(f"Round {current_round}. Communication score: {Reputation.communication_score}") + else: + Reputation.communication_score = 0.0 + + return Reputation.communication_score + + except Exception as e: + logging.error(f"Error managing communication metric: {e}") + + @staticmethod + def save_communication_history(addr, nei, communication, current_round): + """ + Save the communication history of a participant (addr) regarding its neighbor (nei) in memory. + + Args: + addr (str): The identifier of the node whose communication history is being saved. + nei (str): The neighboring node involved. + communication (float): The communication value to be saved. + current_round (int): The current round number. + + Returns: + float: The cumulative communication including the current round. + """ + try: + key = (addr, nei) + + if key not in Reputation.communication_history: + Reputation.communication_history[key] = {} + + Reputation.communication_history[key][current_round] = { + "communication": communication + } + + # logging.info(f"Communication: {communication}") + # logging.info(f"Communication history: {Reputation.communication_history}") + + rounds = Reputation.communication_history[key] + #recent_rounds = sorted(rounds.keys(), reverse=True) #[:2] + if communication != 0 and current_round > 4: + previous_avg = Reputation.communication_history[key].get(current_round - 1, {}).get("avg_communication", None) + # logging.info(f"Previous avg communication: {previous_avg}") + + if previous_avg is not None: + avg_communication = (communication + previous_avg) / 2 + else: + avg_communication = communication + + Reputation.communication_history[key][current_round]["avg_communication"] = avg_communication + else: + avg_communication = 0 + + # logging.info(f"Avg communication: {avg_communication}") + return avg_communication + except Exception as e: + logging.error(f"Error saving communication history: {e}") + + @staticmethod + def save_reputation_history_in_memory(addr, nei, reputation, current_round): + """ + Save the reputation history of a participant (addr) regarding its neighbor (nei) in memory + and calculate the average reputation. + + Args: + addr (str): The identifier of the node whose reputation is being saved. + nei (str): The neighboring node involved. + reputation (float): The reputation value to be saved. + current_round (int): The current round number. + + Returns: + float: The cumulative reputation including the current round. + """ + try: + key = (addr, nei) + + if key not in Reputation.reputation_history: + Reputation.reputation_history[key] = {} + + Reputation.reputation_history[key][current_round] = reputation + + total_reputation = 0 + total_weights = 0 + rounds = sorted(Reputation.reputation_history[key].keys(), reverse=True)[:3] + + for i, round in enumerate(rounds, start=1): + rep = Reputation.reputation_history[key][round] + decay_factor = Reputation.calculate_decay_rate(rep) ** (i * 2) # Aument the decay factor * 2 + total_reputation += rep * decay_factor + total_weights += decay_factor + # logging.info(f"Round: {round}, Reputation: {rep}, Decay: {decay_factor}, Total reputation: {total_reputation}") + + if total_weights > 0: + avg_reputation = total_reputation / total_weights + else: + avg_reputation = 0 + + return avg_reputation + except Exception as e: + logging.error(f"Error saving reputation history: {e}") + + @staticmethod + def calculate_decay_rate(reputation): + """ + Calculate the decay rate for a reputation value. + + Args: + reputation (float): Reputation value. + + Returns: + float: Decay rate. + """ + + if reputation > 0.8: + return 0.9 # Muy bajo decaimiento + elif reputation > 0.6: + return 0.7 # Bajo decaimiento + elif reputation > 0.4: + return 0.5 # Moderado decaimiento + else: + return 0.2 # Alto decaimiento + + @staticmethod + def read_similarity_file(file_path, nei): + """ + Read a similarity file and extract relevant data for each IP. + + Args: + file_path (str): Path to the similarity file. + + Returns: + dict: A dictionary containing relevant data for each IP extracted from the file. + Each IP will have a dictionary containing cosine, euclidean, minkowski, + manhattan, pearson_correlation, and jaccard values. + """ + nei = nei.split(":")[0].strip() + similarity = 0.0 + with open(file_path, 'r') as file: + reader = csv.DictReader(file) + for row in reader: + source_ip = row['source_ip'].split(":")[0].strip() + if source_ip == nei: + try: + # Design weights for each similarity metric + weight_cosine = 0.4 + weight_euclidean = 0.2 + weight_manhattan = 0.2 + weight_pearson = 0.2 + + # Retrieve and normalize metrics if necessary + cosine = float(row['cosine']) + euclidean = float(row['euclidean']) + manhattan = float(row['manhattan']) + pearson_correlation = float(row['pearson_correlation']) + + # Calculate similarity + similarity = ( weight_cosine * cosine + + weight_euclidean * euclidean + + weight_manhattan * manhattan + + weight_pearson * pearson_correlation) + except Exception as e: + logging.error(f"Error reading similarity file: {e}") + return similarity + + @staticmethod + def callback_normalized_value(array, metric=None, key=None): + """ + Calculate the reputation of a node based on the normalized values. + + Args: + array (list): List of values to normalize. + + Returns: + float: Reputation value (or None if array is empty). + """ + + if not array: + return None + + if len(array) == 1: + if array[0] >= 1: + return Reputation.normalize(array[0], 0.25, array[0]+1) + else: + return Reputation.normalize(array[0], 0.25, array[0]) + + min_value = min(array) + max_value = max(array) + + # See values from array + normalized_values = [Reputation.normalize(value, min_value, max_value) for value in array] + reputation_normalized = sum(normalized_values) / len(normalized_values) + + return reputation_normalized + + @staticmethod + def normalize(value, min_value, max_value): + """ + Normalize value within a given range. + + Args: + value (float): Reputation value. + min_reputation (float): Minimum reputation value. + max_reputation (float): Maximum reputation value. + + Returns: + float: Normalized reputation value within the range [1, 10]. + """ + + if max_value == min_value: + return 0.25 + + normalized_value = (value - min_value) / (max_value - min_value) + + return max(0.0, min(1.0, normalized_value)) + + # @staticmethod + # def manage_metric_fraction_of_params_changed(round, total_params, changed_params, changes_record, nei): + # if nei not in Reputation.fraction_parameters_changed: + # logging.info(f"Creating new entry for neighbor: {nei}") + # Reputation.fraction_parameters_changed[nei] = {} + + # if round not in Reputation.fraction_parameters_changed[nei]: + # logging.info(f"Creating new entry for round: {round}") + # Reputation.fraction_parameters_changed[nei][round] = {} + + # fraction_changed = changed_params / total_params if total_params > 0 else 0.0 + # logging.info(f"Calculating fraction of parameters changed for neighbor: {nei}, round: {round}, fraction_changed: {fraction_changed}") + + # normalized_fraction = None + # threshold = None + + # if round == 3: + # previous_fractions = [ + # Reputation.fraction_parameters_changed[nei][i]["fraction_changed"] + # for i in range(3) if i in Reputation.fraction_parameters_changed[nei] + # ] + # logging.info(f"Round 3: Previous fractions: {previous_fractions}") + + # if previous_fractions: + # threshold = sum(previous_fractions) / len(previous_fractions) + # logging.info(f"Round 3: Calculated threshold: {threshold}, fraction_changed: {fraction_changed}, normalized_fraction: {normalized_fraction}") + + # elif round > 3: + # previous_threshold = Reputation.fraction_parameters_changed[nei][round - 1]["threshold"] + + # if previous_threshold is not None: + # logging.info(f"Round: {round}, Previous threshold: {previous_threshold}") + # threshold = (previous_threshold + fraction_changed) / 2 + # logging.info(f"Round: {round}, Calculated threshold: {threshold}") + # else: + # previous_fractions = [ + # Reputation.fraction_parameters_changed[nei][i]["fraction_changed"] + # for i in range(3) if i in Reputation.fraction_parameters_changed[nei] + # ] + # if previous_fractions: + # threshold = sum(previous_fractions) / len(previous_fractions) + # logging.info(f"Round: {round}, Calculated threshold: {threshold} based on previous fractions") + + + # Reputation.fraction_parameters_changed[nei][round] = { + # "total_params": total_params, + # "change_params": changed_params, + # "changes_record": changes_record, + # "threshold": threshold, + # "fraction_changed": fraction_changed, + # } + + # logging.info(f"Reputation.fraction_parameters_changed: {Reputation.fraction_parameters_changed}") + + # return threshold + + # METHODS TO BE USED IN THE FUTURE + + # @staticmethod + # def get_existing_reputation(current, nei, round): + + # Get the existing reputation for a node with respect to a specific neighbor for a specific round. + + # Args: + # addr (str): The identifier of the node whose reputation is being retrieved. + # nei (str): The identifier of the neighbor. + # round (int): Round number. + + # Returns: + # float: Existing reputation score, or None if not found. + + # key = (current, nei, round) + + # # Comprobar si la clave existe en el historial de reputación + # if key in Reputation.reputation_history: + # logging.info(f"History key: {key}, reputation: {Reputation.reputation_history[key]}") + # history_list = Reputation.reputation_history[key] + + # # Si la estructura es una lista simple, devolvemos el último valor + # return history_list[-1] if history_list else None + + # return None + + # def combine_reputation_with_neighbour(self, current_node, source, node_ip, score, round): + + # Combine the reputation of a node with its neighbour. + + # Args: + # current_node (str): The current node's IP address. + # source (str): Source IP address (the node sending the reputation). + # node_ip (str): The node being evaluated. + # score (float): Reputation score from neighbour. + # round (int): Round number. + + # Returns: + # float: Combined reputation score or None if the calculation has already been done for this round. + + + # current_node = current_node.split(":")[0].strip() + # source = source.split(":")[0].strip() + # node_ip = node_ip.split(":")[0].strip() + # logging.info(f"Combining reputation - Current node: {current_node}, Source: {source}, Neighbour: {node_ip}, Score: {score}, Round: {round}") + + # if current_node == node_ip: + # logging.info(f"Node {current_node} ignoring score about itself ({node_ip}).") + # return None + + # # Definir la clave con current_node, node_ip y round + # key = (current_node, node_ip, round) + + # # Proceder con el cálculo de reputación si no existe previamente + # if key not in Reputation.neighbor_reputation_history: + # Reputation.neighbor_reputation_history[key] = [] + + # # Guardar la nueva puntuación enviada por el vecino + # Reputation.neighbor_reputation_history[key].append(score) + + # # Calcular la reputación promedio del vecino + # total_reputation = sum(Reputation.neighbor_reputation_history[key]) + # average_reputation = total_reputation / len(Reputation.neighbor_reputation_history[key]) + + # # Obtener la reputación existente del historial + # existing_reputation = Reputation.get_existing_reputation(current_node, node_ip, round) + # if existing_reputation is None: + # logging.warning(f"No existing reputation found for node {current_node} with neighbor {node_ip} in round {round}.") + # return None + + # # Definir los pesos para combinar reputaciones + # weight_existing = 0.5 + # weight_new = 0.5 + + # # Combinar la reputación existente con la nueva reputación del vecino + # combined_reputation = (weight_existing * existing_reputation) + (weight_new * average_reputation) + # logging.info(f"Combined reputation for node {current_node} with neighbor {source}: {combined_reputation}") + + # return combined_reputation \ No newline at end of file diff --git a/nebula/scenarios.py b/nebula/scenarios.py index b979c9d8d..cc4fbaccc 100644 --- a/nebula/scenarios.py +++ b/nebula/scenarios.py @@ -1009,6 +1009,20 @@ def remove_files_by_scenario(cls, scenario_name): logging.exception("Unknown error while removing files") logging.exception(e) raise e + + try: + nebula_reputation = os.path.join(os.environ["NEBULA_CORE"], "reputation", scenario_name) + if os.path.exists(nebula_reputation): + shutil.rmtree(nebula_reputation) + logging.info(f"Reputation folder {nebula_reputation} removed successfully") + else: + logging.info(f"Reputation folder {nebula_reputation} not found") + except FileNotFoundError: + logging.warning("Files not found in reputation folder, nothing to remove") + except Exception as e: + logging.error("Unknown error while removing files from reputation folder") + logging.error(e) + raise e def scenario_finished(self, timeout_seconds): client = docker.from_env() From c30e1dfc34335e1a5da841aac347c02ee4526161 Mon Sep 17 00:00:00 2001 From: isaac1227 Date: Fri, 22 Nov 2024 17:05:00 +0100 Subject: [PATCH 02/43] solved when the scenario stops because some nodes go faster than others --- nebula/addons/attacks/attacks.py | 34 + nebula/core/aggregation/aggregator.py | 65 +- nebula/core/engine.py | 35 +- nebula/core/network/communications.py | 154 ++-- nebula/core/network/connection.py | 4 +- nebula/core/pb/nebula.proto | 3 + nebula/core/pb/nebula/core/pb/nebula_pb2.py | 52 +- nebula/core/pb/nebula_pb2.py | 52 +- nebula/core/reputation/Reputation.py | 661 +++++++++++++----- .../frontend/config/participant.json.example | 4 +- 10 files changed, 765 insertions(+), 299 deletions(-) diff --git a/nebula/addons/attacks/attacks.py b/nebula/addons/attacks/attacks.py index a7a87b14b..b5db0481d 100755 --- a/nebula/addons/attacks/attacks.py +++ b/nebula/addons/attacks/attacks.py @@ -4,6 +4,8 @@ import numpy as np import torch +import time +import asyncio from torchmetrics.functional import pairwise_cosine_similarity # To take into account: @@ -26,6 +28,8 @@ def create_attack(attack_name): return SwappingWeightsAttack() elif attack_name == "DelayerAttack": return DelayerAttack() + elif attack_name == "FloodAttack": + return FloodAttack() else: return None @@ -142,6 +146,36 @@ def __init__(self): def attack(self, received_weights): logging.info("[DelayerAttack] Performing delayer attack") + + logging.info("Delaying time from 15 seconds") + time.sleep(15) + logging.info("Delaying time finished") + if self.weights is None: self.weights = deepcopy(received_weights) return self.weights + +class FloodAttack(Attack): + """ + Function to perform flood attack on the received weights. It floods the + weights with a high value. + """ + + def __init__(self): + super().__init__() + + async def attack(self, repetitions=10, interval=0.05): + logging.info("[FloodAttack] Performing flood attack") + neighbors = set(await self.cm.get_addrs_current_connections(only_direct=True)) + for nei in neighbors: + for i in range(repetitions): + message_data = self.cm.mm.generate_flood_attack_message( + attacker_id=self.addr, + frequency=int(i), + duration=int(interval*1000), + target_node=nei, + ) + await self.cm.send_message_to_neighbors(message_data, neighbors={nei}) + logging.info(f"Flood attack message sent to {nei} - Attempt {i + 1}/{repetitions}.") + await asyncio.sleep(interval) + self.cm.store_send_timestamp(nei, self.round, "flood_attack") diff --git a/nebula/core/aggregation/aggregator.py b/nebula/core/aggregation/aggregator.py index c42482d41..64074b06e 100755 --- a/nebula/core/aggregation/aggregator.py +++ b/nebula/core/aggregation/aggregator.py @@ -119,25 +119,27 @@ async def _handle_global_update(self, model, source): await self._aggregation_done_lock.release_async() async def _add_pending_model(self, model, weight, source): + valid_federation_nodes = self._federation_nodes - self.engine.rejected_nodes if len(self._federation_nodes) <= len(self.get_nodes_pending_models_to_aggregate()): logging.info("🔄 _add_pending_model | Ignoring model...") await self._add_model_lock.release_async() return None + logging.info(f"🔄 _add_pending_model | source={source} | federation_nodes={self._federation_nodes} | pending_models_to_aggregate={self.get_nodes_pending_models_to_aggregate()} | rejected_nodes={self.engine.rejected_nodes}") + if source not in self._federation_nodes: logging.info(f"🔄 _add_pending_model | Can't add a model from ({source}), which is not in the federation.") await self._add_model_lock.release_async() return None - elif source not in self.get_nodes_pending_models_to_aggregate(): - logging.info( - "🔄 _add_pending_model | Node is not in the aggregation buffer --> Include model in the aggregation buffer." - ) - self._pending_models_to_aggregate.update({source: (model, weight)}) + if source in self.engine.rejected_nodes: + logging.info("🔄 _add_pending_model | Ignoring model from rejected node...") + else: + self._pending_models_to_aggregate.update({source: (model, weight)}) + logging.info("🔄 _add_pending_model | Node is not in the aggregation buffer --> Include model in the aggregation buffer.") - logging.info( - f"🔄 _add_pending_model | Model added in aggregation buffer ({len(self.get_nodes_pending_models_to_aggregate())!s}/{len(self._federation_nodes)!s}) | Pending nodes: {self._federation_nodes - self.get_nodes_pending_models_to_aggregate()}" - ) + logging.info(f"🔄 _add_pending_model | ({len(self.get_nodes_pending_models_to_aggregate())!s}/{len(valid_federation_nodes)})") + #logging.info(f"🔄 _add_pending_model | Model added in aggregation buffer ({len(self.get_nodes_pending_models_to_aggregate())!s}/{len(self._federation_nodes)!s}) | Pending nodes: {self._federation_nodes - self.get_nodes_pending_models_to_aggregate()}") # Check if _future_models_to_aggregate has models in the current round to include in the aggregation buffer if self.engine.get_round() in self._future_models_to_aggregate: @@ -148,10 +150,8 @@ async def _add_pending_model(self, model, weight, source): if future_model is None: continue future_model, future_weight, future_source = future_model - if ( - future_source in self._federation_nodes - and future_source not in self.get_nodes_pending_models_to_aggregate() - ): + #if (future_source in self._federation_nodes and future_source not in self.get_nodes_pending_models_to_aggregate()): + if (future_source in valid_federation_nodes and future_source not in self.get_nodes_pending_models_to_aggregate()): self._pending_models_to_aggregate.update({future_source: (future_model, future_weight)}) logging.info( f"🔄 _add_pending_model | Next model added in aggregation buffer ({len(self.get_nodes_pending_models_to_aggregate())!s}/{len(self._federation_nodes)!s}) | Pending nodes: {self._federation_nodes - self.get_nodes_pending_models_to_aggregate()}" @@ -162,9 +162,21 @@ async def _add_pending_model(self, model, weight, source): if future_round < self.engine.get_round(): del self._future_models_to_aggregate[future_round] - if len(self.get_nodes_pending_models_to_aggregate()) >= len(self._federation_nodes): + logging.info(f"🔄 _add_pending_model | pending_models_to_aggregate={self.get_nodes_pending_models_to_aggregate()}") + logging.info(f"🔄 _add_pending_model | federation_nodes={self._federation_nodes}") + logging.info(f"🔄 _add_pending_model | rejected_nodes={self.engine.rejected_nodes}") + logging.info(f"🔄 _add_pending_model | valid_federation_nodes={valid_federation_nodes}") + if len(self.get_nodes_pending_models_to_aggregate()) >= len(valid_federation_nodes): logging.info("🔄 _add_pending_model | All models were added in the aggregation buffer. Run aggregation...") - await self._aggregation_done_lock.release_async() + if self._aggregation_done_lock.locked(): + logging.info("🔄 _add_pending_model | Releasing aggregation_done_lock") + await self._aggregation_done_lock.release_async() + else: + logging.info("🔄 _add_pending_model | aggregation_done_lock is not locked") + else: + logging.info("🔄 _add_pending_model | Not all models were added in the aggregation buffer. Waiting for more models...") + logging.info(f"🔄 _add_pending_model | pending_models_to_aggregate={self.get_nodes_pending_models_to_aggregate()}") + await self._add_model_lock.release_async() return self.get_nodes_pending_models_to_aggregate() @@ -173,6 +185,7 @@ async def include_model_in_buffer(self, model, weight, source=None, round=None, logging.info( f"🔄 include_model_in_buffer | source={source} | round={round} | weight={weight} |--| __models={self._pending_models_to_aggregate.keys()} | federation_nodes={self._federation_nodes} | pending_models_to_aggregate={self.get_nodes_pending_models_to_aggregate()}" ) + if model is None: logging.info("🔄 include_model_in_buffer | Ignoring model bad formed...") await self._add_model_lock.release_async() @@ -190,10 +203,10 @@ async def include_model_in_buffer(self, model, weight, source=None, round=None, await self._add_pending_model(model, weight, source) - if len(self.get_nodes_pending_models_to_aggregate()) >= len(self._federation_nodes): - logging.info( - f"🔄 include_model_in_buffer | Broadcasting MODELS_INCLUDED for round {self.engine.get_round()}" - ) + valid_federation_nodes = self._federation_nodes - self.engine.rejected_nodes + #if len(self.get_nodes_pending_models_to_aggregate()) >= len(self._federation_nodes): + if len(self.get_nodes_pending_models_to_aggregate()) >= len(valid_federation_nodes): + logging.info(f"🔄 include_model_in_buffer | Broadcasting MODELS_INCLUDED for round {self.engine.get_round()}") message = self.cm.mm.generate_federation_message( nebula_pb2.FederationMessage.Action.FEDERATION_MODELS_INCLUDED, [self.engine.get_round()], @@ -207,7 +220,7 @@ async def get_aggregation(self): timeout = self.config.participant["aggregator_args"]["aggregation_timeout"] await self._aggregation_done_lock.acquire_async(timeout=timeout) except TimeoutError: - logging.exception("🔄 get_aggregation | Timeout reached for aggregation") + logging.error("🔄 get_aggregation | Timeout reached for aggregation") finally: await self._aggregation_done_lock.release_async() @@ -220,13 +233,23 @@ async def get_aggregation(self): return aggregated_model unique_nodes_involved = set(node for key in self._pending_models_to_aggregate for node in key.split()) + logging.info(f"🔄 get_aggregation | unique_nodes_involved={unique_nodes_involved}") + expected_nodes = self._federation_nodes - self.engine.rejected_nodes + logging.info(f"🔄 get_aggregation | expected_nodes={expected_nodes}") + missing_nodes = expected_nodes - unique_nodes_involved + logging.info(f"🔄 get_aggregation | missing_nodes={missing_nodes}") - if len(unique_nodes_involved) != len(self._federation_nodes): - missing_nodes = self._federation_nodes - unique_nodes_involved + if missing_nodes: logging.info(f"🔄 get_aggregation | Aggregation incomplete, missing models from: {missing_nodes}") else: logging.info("🔄 get_aggregation | All models accounted for, proceeding with aggregation.") + # if len(unique_nodes_involved) != len(self._federation_nodes): + # missing_nodes = self._federation_nodes - unique_nodes_involved + # logging.info(f"🔄 get_aggregation | Aggregation incomplete, missing models from: {missing_nodes}") + # else: + # logging.info("🔄 get_aggregation | All models accounted for, proceeding with aggregation.") + aggregated_result = self.run_aggregation(self._pending_models_to_aggregate) self._pending_models_to_aggregate.clear() return aggregated_result diff --git a/nebula/core/engine.py b/nebula/core/engine.py index 561f1ebb8..cd6f20aea 100755 --- a/nebula/core/engine.py +++ b/nebula/core/engine.py @@ -716,34 +716,35 @@ def __init__( noise_type, ) self.attack = create_attack(config.participant["adversarial_args"]["attacks"]) + logging.info(f"Attack: {self.attack}") self.fit_time = 0.0 self.extra_time = 0.0 - self.round_start_attack = 6 - self.round_stop_attack = 9 + self.round_start_attack = 10 + self.round_stop_attack = 17 self.aggregator_bening = self._aggregator - async def flood_attack(self, repetitions=10, interval=0.05): - neighbors = set(await self.cm.get_addrs_current_connections(only_direct=True)) - for nei in neighbors: - for i in range(repetitions): - message_data = self.cm.mm.generate_flood_attack_message( - attacker_id=self.addr, - frequency=int(i), - duration=int(interval*1000), - target_node=nei, - ) - await self.cm.send_message_to_neighbors(message_data, neighbors={nei}) - logging.info(f"Flood attack message sent to {nei} - Attempt {i + 1}/{repetitions}.") - await asyncio.sleep(interval) - self.cm.store_send_timestamp(nei, self.round, "flood_attack") + # async def flood_attack(self, repetitions=10, interval=0.05): + # neighbors = set(await self.cm.get_addrs_current_connections(only_direct=True)) + # for nei in neighbors: + # for i in range(repetitions): + # message_data = self.cm.mm.generate_flood_attack_message( + # attacker_id=self.addr, + # frequency=int(i), + # duration=int(interval*1000), + # target_node=nei, + # ) + # await self.cm.send_message_to_neighbors(message_data, neighbors={nei}) + # logging.info(f"Flood attack message sent to {nei} - Attempt {i + 1}/{repetitions}.") + # await asyncio.sleep(interval) + # self.cm.store_send_timestamp(nei, self.round, "flood_attack") async def _extended_learning_cycle(self): if self.attack != None: if self.round in range(self.round_start_attack, self.round_stop_attack): logging.info("Changing aggregation function maliciously...") - await self.flood_attack("flood_attack") + # await self.flood_attack(repetitions=10, interval=0.05) self._aggregator = create_malicious_aggregator(self._aggregator, self.attack) elif self.round == self.round_stop_attack: logging.info("Changing aggregation function benignly...") diff --git a/nebula/core/network/communications.py b/nebula/core/network/communications.py index c1300ecbf..636367634 100755 --- a/nebula/core/network/communications.py +++ b/nebula/core/network/communications.py @@ -6,6 +6,8 @@ import subprocess import sys import traceback +import time +import torch from datetime import datetime from typing import TYPE_CHECKING @@ -215,6 +217,61 @@ async def handle_model_message(self, source, message): current_round = self.get_round() await self.engine.get_round_lock().release_async() + if not self.engine.get_federation_ready_lock().locked() or self.engine.get_initialization_status(): + decoded_model = self.engine.trainer.deserialize_model(message.parameters) + if self.config.participant["adaptive_args"]["model_similarity"]: + logging.info("🤖 handle_model_message | Checking model similarity") + cosine_value = cosine_metric( + self.engine.trainer.get_model_parameters(), + decoded_model, + similarity=True, + ) + euclidean_value = euclidean_metric( + self.engine.trainer.get_model_parameters(), + decoded_model, + similarity=True, + ) + minkowski_value = minkowski_metric( + self.engine.trainer.get_model_parameters(), + decoded_model, + p=2, + similarity=True, + ) + manhattan_value = manhattan_metric( + self.engine.trainer.get_model_parameters(), + decoded_model, + similarity=True, + ) + pearson_correlation_value = pearson_correlation_metric( + self.engine.trainer.get_model_parameters(), + decoded_model, + similarity=True, + ) + jaccard_value = jaccard_metric( + self.engine.trainer.get_model_parameters(), + decoded_model, + similarity=True, + ) + file = f"{self.engine.log_dir}/participant_{self.engine.idx}_similarity.csv" + directory = os.path.dirname(file) + os.makedirs(directory, exist_ok=True) + if not os.path.isfile(file): + with open(file, "w") as f: + f.write("timestamp,source_ip,round,current_round,cosine,euclidean,minkowski,manhattan,pearson_correlation,jaccard\n") + with open(file, "a") as f: + f.write(f"{datetime.now()}, {source}, {message.round}, {current_round}, {cosine_value}, {euclidean_value}, {minkowski_value}, {manhattan_value}, {pearson_correlation_value}, {jaccard_value}\n") + + if cosine_value < 0.6: + self.engine.rejected_nodes.add(source) + + # Manage communication latency + self.store_receive_timestamp(source, "model", message.round) + self.calculate_latency(source, "model") + + # Manage parameters of models + parameters_local = self.engine.trainer.get_model_parameters() + self.fraction_of_parameters_changed(source, parameters_local, decoded_model, current_round) + if message.round != current_round and message.round != -1: logging.info( f"❗️ handle_model_message | Received a model from a different round | Model round: {message.round} | Current round: {current_round}" @@ -240,53 +297,57 @@ async def handle_model_message(self, source, message): # non-starting nodes receive the initialized model from the starting node if not self.engine.get_federation_ready_lock().locked() or self.engine.get_initialization_status(): decoded_model = self.engine.trainer.deserialize_model(message.parameters) - if self.config.participant["adaptive_args"]["model_similarity"]: - logging.info("🤖 handle_model_message | Checking model similarity") - cosine_value = cosine_metric( - self.trainer.get_model_parameters(), - decoded_model, - similarity=True, - ) - euclidean_value = euclidean_metric( - self.trainer.get_model_parameters(), - decoded_model, - similarity=True, - ) - minkowski_value = minkowski_metric( - self.trainer.get_model_parameters(), - decoded_model, - p=2, - similarity=True, - ) - manhattan_value = manhattan_metric( - self.trainer.get_model_parameters(), - decoded_model, - similarity=True, - ) - pearson_correlation_value = pearson_correlation_metric( - self.trainer.get_model_parameters(), - decoded_model, - similarity=True, - ) - jaccard_value = jaccard_metric( - self.trainer.get_model_parameters(), - decoded_model, - similarity=True, - ) - with open(f"{self.engine.log_dir}/participant_{self.idx}_similarity.csv", "a+") as f: - if os.stat(f"{self.engine.log_dir}/participant_{self.idx}_similarity.csv").st_size == 0: - f.write("timestamp,source_ip,nodes,round,current_round,cosine,euclidean,minkowski,manhattan,pearson_correlation,jaccard\n") - f.write( - f"{datetime.now()}, {source}, {message.round}, {current_round}, {cosine_value}, {euclidean_value}, {minkowski_value}, {manhattan_value}, {pearson_correlation_value}, {jaccard_value}\n" - ) - - # Manage communication latency - self.store_receive_timestamp(source, "model", message.round) - self.calculate_latency(source, "model") + # if self.config.participant["adaptive_args"]["model_similarity"]: + # logging.info("🤖 handle_model_message | Checking model similarity") + # cosine_value = cosine_metric( + # self.engine.trainer.get_model_parameters(), + # decoded_model, + # similarity=True, + # ) + # euclidean_value = euclidean_metric( + # self.engine.trainer.get_model_parameters(), + # decoded_model, + # similarity=True, + # ) + # minkowski_value = minkowski_metric( + # self.engine.trainer.get_model_parameters(), + # decoded_model, + # p=2, + # similarity=True, + # ) + # manhattan_value = manhattan_metric( + # self.engine.trainer.get_model_parameters(), + # decoded_model, + # similarity=True, + # ) + # pearson_correlation_value = pearson_correlation_metric( + # self.engine.trainer.get_model_parameters(), + # decoded_model, + # similarity=True, + # ) + # jaccard_value = jaccard_metric( + # self.engine.trainer.get_model_parameters(), + # decoded_model, + # similarity=True, + # ) + # file = f"{self.engine.log_dir}/participant_{self.engine.idx}_similarity.csv" + # logging.info(f"self.engine.log_dir: {self.engine.log_dir}") + # directory = os.path.dirname(file) + # os.makedirs(directory, exist_ok=True) + # if not os.path.isfile(file): + # with open(file, "w") as f: + # f.write("timestamp,source_ip,round,current_round,cosine,euclidean,minkowski,manhattan,pearson_correlation,jaccard\n") + # with open(file, "a") as f: + # f.write(f"{datetime.now()}, {source}, {message.round}, {current_round}, {cosine_value}, {euclidean_value}, {minkowski_value}, {manhattan_value}, {pearson_correlation_value}, {jaccard_value}\n") + + + # # Manage communication latency + # self.store_receive_timestamp(source, "model", message.round) + # self.calculate_latency(source, "model") - # Manage parameters of models - parameters_local = self.engine.trainer.get_model_parameters() - self.fraction_of_parameters_changed(source, parameters_local, decoded_model, current_round) + # # Manage parameters of models + # parameters_local = self.engine.trainer.get_model_parameters() + # self.fraction_of_parameters_changed(source, parameters_local, decoded_model, current_round) await self.engine.aggregator.include_model_in_buffer( decoded_model, @@ -294,7 +355,6 @@ async def handle_model_message(self, source, message): source=source, round=message.round, ) - else: if message.round != -1: # Be sure that the model message is from the initialization round (round = -1) diff --git a/nebula/core/network/connection.py b/nebula/core/network/connection.py index 13910e708..2a51ae284 100755 --- a/nebula/core/network/connection.py +++ b/nebula/core/network/connection.py @@ -86,8 +86,8 @@ def __str__(self): def __repr__(self): return self.__str__() - def __del__(self): - self.stop() + async def __del__(self): + await self.stop() def get_addr(self): return self.addr diff --git a/nebula/core/pb/nebula.proto b/nebula/core/pb/nebula.proto index 39cbe9795..4c17b87f7 100755 --- a/nebula/core/pb/nebula.proto +++ b/nebula/core/pb/nebula.proto @@ -15,6 +15,7 @@ package nebula; message Wrapper { string source = 1; // Unique identifier of the source node. + string time = 9; oneof message { DiscoveryMessage discovery_message = 2; ControlMessage control_message = 3; @@ -22,6 +23,8 @@ message Wrapper { ModelMessage model_message = 5; ConnectionMessage connection_message = 6; ResponseMessage response_message = 7; + ReputationMessage reputation_message = 8; + FloodAttackMessage flood_attack_message = 10; } } diff --git a/nebula/core/pb/nebula/core/pb/nebula_pb2.py b/nebula/core/pb/nebula/core/pb/nebula_pb2.py index 767bf8bcf..6ff3b7eb1 100644 --- a/nebula/core/pb/nebula/core/pb/nebula_pb2.py +++ b/nebula/core/pb/nebula/core/pb/nebula_pb2.py @@ -24,7 +24,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bnebula/core/pb/nebula.proto\x12\x06nebula\"\xe4\x02\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x42\t\n\x07message\"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02\"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02\"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t\"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04\"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05\"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03\"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05\"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action\"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01\"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t\"B\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\"c\n\x12\x46loodAttackMessage\x12\x13\n\x0b\x61ttacker_id\x18\x01 \x01(\t\x12\x11\n\tfrequency\x18\x02 \x01(\x05\x12\x10\n\x08\x64uration\x18\x03 \x01(\x05\x12\x13\n\x0btarget_node\x18\x04 \x01(\tb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bnebula/core/pb/nebula.proto\x12\x06nebula\"\xe7\x03\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\t \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x12\x37\n\x12reputation_message\x18\x08 \x01(\x0b\x32\x19.nebula.ReputationMessageH\x00\x12:\n\x14\x66lood_attack_message\x18\n \x01(\x0b\x32\x1a.nebula.FloodAttackMessageH\x00\x42\t\n\x07message\"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02\"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02\"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t\"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04\"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05\"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03\"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05\"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action\"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01\"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t\"B\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\"c\n\x12\x46loodAttackMessage\x12\x13\n\x0b\x61ttacker_id\x18\x01 \x01(\t\x12\x11\n\tfrequency\x18\x02 \x01(\x05\x12\x10\n\x08\x64uration\x18\x03 \x01(\x05\x12\x13\n\x0btarget_node\x18\x04 \x01(\tb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -32,29 +32,29 @@ if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals['_WRAPPER']._serialized_start=40 - _globals['_WRAPPER']._serialized_end=396 - _globals['_DISCOVERYMESSAGE']._serialized_start=399 - _globals['_DISCOVERYMESSAGE']._serialized_end=557 - _globals['_DISCOVERYMESSAGE_ACTION']._serialized_start=505 - _globals['_DISCOVERYMESSAGE_ACTION']._serialized_end=557 - _globals['_CONTROLMESSAGE']._serialized_start=560 - _globals['_CONTROLMESSAGE']._serialized_end=714 - _globals['_CONTROLMESSAGE_ACTION']._serialized_start=638 - _globals['_CONTROLMESSAGE_ACTION']._serialized_end=714 - _globals['_FEDERATIONMESSAGE']._serialized_start=717 - _globals['_FEDERATIONMESSAGE']._serialized_end=922 - _globals['_FEDERATIONMESSAGE_ACTION']._serialized_start=822 - _globals['_FEDERATIONMESSAGE_ACTION']._serialized_end=922 - _globals['_MODELMESSAGE']._serialized_start=924 - _globals['_MODELMESSAGE']._serialized_end=989 - _globals['_CONNECTIONMESSAGE']._serialized_start=991 - _globals['_CONNECTIONMESSAGE']._serialized_end=1099 - _globals['_CONNECTIONMESSAGE_ACTION']._serialized_start=1062 - _globals['_CONNECTIONMESSAGE_ACTION']._serialized_end=1099 - _globals['_RESPONSEMESSAGE']._serialized_start=1101 - _globals['_RESPONSEMESSAGE']._serialized_end=1136 - _globals['_REPUTATIONMESSAGE']._serialized_start=1138 - _globals['_REPUTATIONMESSAGE']._serialized_end=1204 - _globals['_FLOODATTACKMESSAGE']._serialized_start=1206 - _globals['_FLOODATTACKMESSAGE']._serialized_end=1305 + _globals['_WRAPPER']._serialized_end=527 + _globals['_DISCOVERYMESSAGE']._serialized_start=530 + _globals['_DISCOVERYMESSAGE']._serialized_end=688 + _globals['_DISCOVERYMESSAGE_ACTION']._serialized_start=636 + _globals['_DISCOVERYMESSAGE_ACTION']._serialized_end=688 + _globals['_CONTROLMESSAGE']._serialized_start=691 + _globals['_CONTROLMESSAGE']._serialized_end=845 + _globals['_CONTROLMESSAGE_ACTION']._serialized_start=769 + _globals['_CONTROLMESSAGE_ACTION']._serialized_end=845 + _globals['_FEDERATIONMESSAGE']._serialized_start=848 + _globals['_FEDERATIONMESSAGE']._serialized_end=1053 + _globals['_FEDERATIONMESSAGE_ACTION']._serialized_start=953 + _globals['_FEDERATIONMESSAGE_ACTION']._serialized_end=1053 + _globals['_MODELMESSAGE']._serialized_start=1055 + _globals['_MODELMESSAGE']._serialized_end=1120 + _globals['_CONNECTIONMESSAGE']._serialized_start=1122 + _globals['_CONNECTIONMESSAGE']._serialized_end=1230 + _globals['_CONNECTIONMESSAGE_ACTION']._serialized_start=1193 + _globals['_CONNECTIONMESSAGE_ACTION']._serialized_end=1230 + _globals['_RESPONSEMESSAGE']._serialized_start=1232 + _globals['_RESPONSEMESSAGE']._serialized_end=1267 + _globals['_REPUTATIONMESSAGE']._serialized_start=1269 + _globals['_REPUTATIONMESSAGE']._serialized_end=1335 + _globals['_FLOODATTACKMESSAGE']._serialized_start=1337 + _globals['_FLOODATTACKMESSAGE']._serialized_end=1436 # @@protoc_insertion_point(module_scope) diff --git a/nebula/core/pb/nebula_pb2.py b/nebula/core/pb/nebula_pb2.py index f4186cd33..11e9df1b0 100755 --- a/nebula/core/pb/nebula_pb2.py +++ b/nebula/core/pb/nebula_pb2.py @@ -15,7 +15,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cnebula.proto\x12\x06nebula\"\xe4\x02\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x42\t\n\x07message\"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02\"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02\"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t\"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04\"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05\"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03\"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05\"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action\"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01\"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t\"B\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\"c\n\x12\x46loodAttackMessage\x12\x13\n\x0b\x61ttacker_id\x18\x01 \x01(\t\x12\x11\n\tfrequency\x18\x02 \x01(\x05\x12\x10\n\x08\x64uration\x18\x03 \x01(\x05\x12\x13\n\x0btarget_node\x18\x04 \x01(\tb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cnebula.proto\x12\x06nebula\"\xe7\x03\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\t \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x12\x37\n\x12reputation_message\x18\x08 \x01(\x0b\x32\x19.nebula.ReputationMessageH\x00\x12:\n\x14\x66lood_attack_message\x18\n \x01(\x0b\x32\x1a.nebula.FloodAttackMessageH\x00\x42\t\n\x07message\"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02\"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02\"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t\"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04\"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05\"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03\"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05\"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action\"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01\"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t\"B\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\"c\n\x12\x46loodAttackMessage\x12\x13\n\x0b\x61ttacker_id\x18\x01 \x01(\t\x12\x11\n\tfrequency\x18\x02 \x01(\x05\x12\x10\n\x08\x64uration\x18\x03 \x01(\x05\x12\x13\n\x0btarget_node\x18\x04 \x01(\tb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -23,29 +23,29 @@ if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals['_WRAPPER']._serialized_start=25 - _globals['_WRAPPER']._serialized_end=381 - _globals['_DISCOVERYMESSAGE']._serialized_start=384 - _globals['_DISCOVERYMESSAGE']._serialized_end=542 - _globals['_DISCOVERYMESSAGE_ACTION']._serialized_start=490 - _globals['_DISCOVERYMESSAGE_ACTION']._serialized_end=542 - _globals['_CONTROLMESSAGE']._serialized_start=545 - _globals['_CONTROLMESSAGE']._serialized_end=699 - _globals['_CONTROLMESSAGE_ACTION']._serialized_start=623 - _globals['_CONTROLMESSAGE_ACTION']._serialized_end=699 - _globals['_FEDERATIONMESSAGE']._serialized_start=702 - _globals['_FEDERATIONMESSAGE']._serialized_end=907 - _globals['_FEDERATIONMESSAGE_ACTION']._serialized_start=807 - _globals['_FEDERATIONMESSAGE_ACTION']._serialized_end=907 - _globals['_MODELMESSAGE']._serialized_start=909 - _globals['_MODELMESSAGE']._serialized_end=974 - _globals['_CONNECTIONMESSAGE']._serialized_start=976 - _globals['_CONNECTIONMESSAGE']._serialized_end=1084 - _globals['_CONNECTIONMESSAGE_ACTION']._serialized_start=1047 - _globals['_CONNECTIONMESSAGE_ACTION']._serialized_end=1084 - _globals['_RESPONSEMESSAGE']._serialized_start=1086 - _globals['_RESPONSEMESSAGE']._serialized_end=1121 - _globals['_REPUTATIONMESSAGE']._serialized_start=1123 - _globals['_REPUTATIONMESSAGE']._serialized_end=1189 - _globals['_FLOODATTACKMESSAGE']._serialized_start=1191 - _globals['_FLOODATTACKMESSAGE']._serialized_end=1290 + _globals['_WRAPPER']._serialized_end=512 + _globals['_DISCOVERYMESSAGE']._serialized_start=515 + _globals['_DISCOVERYMESSAGE']._serialized_end=673 + _globals['_DISCOVERYMESSAGE_ACTION']._serialized_start=621 + _globals['_DISCOVERYMESSAGE_ACTION']._serialized_end=673 + _globals['_CONTROLMESSAGE']._serialized_start=676 + _globals['_CONTROLMESSAGE']._serialized_end=830 + _globals['_CONTROLMESSAGE_ACTION']._serialized_start=754 + _globals['_CONTROLMESSAGE_ACTION']._serialized_end=830 + _globals['_FEDERATIONMESSAGE']._serialized_start=833 + _globals['_FEDERATIONMESSAGE']._serialized_end=1038 + _globals['_FEDERATIONMESSAGE_ACTION']._serialized_start=938 + _globals['_FEDERATIONMESSAGE_ACTION']._serialized_end=1038 + _globals['_MODELMESSAGE']._serialized_start=1040 + _globals['_MODELMESSAGE']._serialized_end=1105 + _globals['_CONNECTIONMESSAGE']._serialized_start=1107 + _globals['_CONNECTIONMESSAGE']._serialized_end=1215 + _globals['_CONNECTIONMESSAGE_ACTION']._serialized_start=1178 + _globals['_CONNECTIONMESSAGE_ACTION']._serialized_end=1215 + _globals['_RESPONSEMESSAGE']._serialized_start=1217 + _globals['_RESPONSEMESSAGE']._serialized_end=1252 + _globals['_REPUTATIONMESSAGE']._serialized_start=1254 + _globals['_REPUTATIONMESSAGE']._serialized_end=1320 + _globals['_FLOODATTACKMESSAGE']._serialized_start=1322 + _globals['_FLOODATTACKMESSAGE']._serialized_end=1421 # @@protoc_insertion_point(module_scope) diff --git a/nebula/core/reputation/Reputation.py b/nebula/core/reputation/Reputation.py index b48ec24b0..095a4043f 100644 --- a/nebula/core/reputation/Reputation.py +++ b/nebula/core/reputation/Reputation.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from nebula.core.engine import Engine -def save_data(scenario, type_data, source_ip, addr, round=None, time=None, data_contribution=None, type_message=None, current_round=None, fraction_changed=None, total_params=None, changed_params=None, threshold=None, changes_record=None, rate_of_change=None): +def save_data(scenario, type_data, source_ip, addr, round=None, time=None, type_message=None, current_round=None, fraction_changed=None, total_params=None, changed_params=None, threshold=None, changes_record=None, message_id_decoded=None, latency=None): """ Save communication data between nodes and aggregated models. @@ -50,6 +50,12 @@ def save_data(scenario, type_data, source_ip, addr, round=None, time=None, data_ "changes_record": changes_record, "round": round, } + elif type_data == 'chunk_latency': + combined_data["chunk_latency"] = { + "message_id_decoded": message_id_decoded, + "latency": latency, + "round": round, + } script_dir = os.path.dirname(os.path.abspath(__file__)) file_name = f"{addr}_storing_{source_ip}_info.json" @@ -82,14 +88,18 @@ class Reputation: frequency_history = {} neighbor_reputation_history = {} fraction_changed_history = {} - communication_data = [] + communication_data = {} messages_frequency = [] previous_threshold_freq = {} - mean_time_communication = None - communication_score = 0.0 + previous_std_dev_freq = {} + messages_chunk_latency = [] + chunk_history = {} + previous_percentile_25_freq = {} + previous_percentile_75_freq = {} def __init__(self, engine: "Engine"): self._engine = engine + self.chunk_data = {} @property def engine(self): @@ -106,8 +116,6 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro addr = addr.split(":")[0].strip() nei = nei.split(":")[0].strip() - # array_communication = [] - # array_messages_frequency = [] communication_time_normalized = 0 messages_frequency_normalized = 0 messages_frequency_count = 0 @@ -115,6 +123,8 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro avg_messages_frequency_normalized = 0 fraction_score = 0 fraction_score_asign = 0 + messages_chunk_normalized = 0 + avg_chunk_latency = 0 try: script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -150,20 +160,29 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro if round == current_round: Reputation.analyze_anomalies(addr, nei, round, current_round, fraction_changed, threshold, changes_record, changed_params, total_params) #logging.info(f"Reputation.fraction_changed_history: {Reputation.fraction_changed_history}") + if "chunk_latency" in metric: + round = metric["chunk_latency"]["round"] + message_id_decoded = metric["chunk_latency"]["message_id_decoded"] + latency = metric["chunk_latency"]["latency"] + if round == current_round: + Reputation.messages_chunk_latency.append({"message_id_decoded": message_id_decoded, "latency": latency, "round":round, "key": (addr, nei)}) similarity_file = os.path.join(log_dir, f"participant_{id_node}_similarity.csv") similarity_reputation = Reputation.read_similarity_file(similarity_file, nei) - #logging.info(f"Similarity reputation: {similarity_reputation}") - if communication_time_normalized is not None: + if communication_time_normalized >= 0: + logging.info(f"Communication time normalized: {communication_time_normalized}") avg_communication_time_normalized = Reputation.save_communication_history(addr, nei, communication_time_normalized, current_round) + logging.info(f"Avg communication time normalized: {avg_communication_time_normalized}") if avg_communication_time_normalized is None and current_round >= 5: avg_communication_time_normalized = Reputation.communication_history[(addr, nei)][current_round - 1]["avg_communication"] logging.info(f"Avg communication is None and current_round = {current_round}, avg_communication_time_normalized: {avg_communication_time_normalized}") if Reputation.messages_frequency is not None: messages_frequency_normalized, messages_frequency_count = Reputation.manage_metric_frequency(Reputation.messages_frequency, addr, nei, current_round) + logging.info(f"Messages frequency normalized: {messages_frequency_normalized}") avg_messages_frequency_normalized = Reputation.save_frequency_history(addr, nei, messages_frequency_normalized, current_round) + logging.info(f"Avg messages frequency normalized: {avg_messages_frequency_normalized}") if avg_messages_frequency_normalized is None and current_round >= 4: avg_messages_frequency_normalized = Reputation.frequency_history[(addr, nei)][current_round - 1]["avg_frequency"] logging.info(f"Avg messages frequency is None and curret_round = {current_round}, avg_messages_frequency_normalized: {avg_messages_frequency_normalized}") @@ -172,38 +191,69 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro key = (addr, nei, current_round) if key not in Reputation.fraction_changed_history: key = (addr, nei, current_round - 1) if current_round > 0 else None - logging.info(f"Key prev: {key}") fraction_score = Reputation.fraction_changed_history[key].get("fraction_score") - logging.info(f"Fraction score: {fraction_score} | key: {key}") fraction_score_asign = fraction_score if fraction_score is not None else 0 - logging.info(f"Fraction score asign: {fraction_score_asign}") - + logging.info(f"Fraction score: {fraction_score_asign}") + + if Reputation.messages_chunk_latency is not None: + messages_chunk_normalized = Reputation.manage_metric_chunk_latency(Reputation.messages_chunk_latency, addr, nei, current_round) + logging.info(f"Score chunk: {messages_chunk_normalized}") + avg_chunk_latency = Reputation.save_chunk_history(addr, nei, messages_chunk_normalized, current_round) + logging.info(f"Return avg chunk latency: {avg_chunk_latency}") + if avg_chunk_latency is None and current_round > 4: + avg_chunk_latency = Reputation.chunk_history[(addr, nei, current_round - 1)]["avg_chunk_latency"] + logging.info(f"Avg chunk latency is None and current_round = {current_round}, avg_chunk_latency: {avg_chunk_latency}") + # Weights for each metric if current_round is not None: - if current_round >= 4: + if current_round == 4: # Solo para la ronda 4 weight_to_similarity = 0.5 weight_to_communication = 0.0 weight_to_fraction = 0.0 weight_to_message_frequency = 0.5 - elif current_round >= 5: - weight_to_similarity = 0.3 + weight_to_chunk = 0.0 + elif current_round >= 5: # Para la ronda 5 y posteriores + # Weight to scenarios with delay + weight_to_similarity = 0.1 weight_to_communication = 0.3 - weight_to_fraction = 0.2 - weight_to_message_frequency = 0.2 - else: + weight_to_fraction = 0.1 + weight_to_message_frequency = 0.3 + weight_to_chunk = 0.2 + # Weight to scenarios with noise injection + # weight_to_similarity = 0.3 + # weight_to_communication = 0.1 + # weight_to_fraction = 0.3 + # weight_to_message_frequency = 0.1 + # weight_to_chunk = 0.2 + # Weight to scenarios with flood + # weight_to_similarity = 0.1 + # weight_to_communication = 0.3 + # weight_to_fraction = 0.1 + # weight_to_message_frequency = 0.3 + # weight_to_chunk = 0.2 + elif current_round < 4: # Para las rondas de la 0 a la 3 weight_to_similarity = 1.0 weight_to_communication = 0.0 weight_to_fraction = 0.0 weight_to_message_frequency = 0.0 + weight_to_chunk = 0.0 + # logging.info(f"Current round: {current_round}") + # logging.info(f"Communication time normalized: {communication_time_normalized} and weight: {weight_to_communication}") + # logging.info(f"Messages frequency normalized: {avg_messages_frequency_normalized} and weight: {weight_to_message_frequency}") + # logging.info(f"Similarity reputation: {similarity_reputation} and weight: {weight_to_similarity}") + # logging.info(f"Fraction score: {fraction_score_asign} and weight: {weight_to_fraction}") + # logging.info(f"Chunk latency: {avg_chunk_latency} and weight: {weight_to_chunk}") + # Reputation calculation reputation = ( weight_to_communication * avg_communication_time_normalized + weight_to_message_frequency * avg_messages_frequency_normalized + weight_to_similarity * similarity_reputation - + weight_to_fraction * fraction_score_asign) + + weight_to_fraction * fraction_score_asign + + weight_to_chunk * avg_chunk_latency ) # Create graphics to metrics - self.create_graphics_to_metrics(avg_communication_time_normalized, messages_frequency_count, avg_messages_frequency_normalized, similarity_reputation, fraction_score_asign, addr, nei, current_round, self.engine.total_rounds) + self.create_graphics_to_metrics(avg_communication_time_normalized, messages_frequency_count, avg_messages_frequency_normalized, similarity_reputation, fraction_score_asign, avg_chunk_latency, addr, nei, current_round, self.engine.total_rounds) # Save history reputation average_reputation = Reputation.save_reputation_history_in_memory(addr, nei, reputation, current_round) @@ -213,7 +263,7 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro except Exception as e: logging.error(f"Error calculating reputation: {e}, type: {type(e).__name__}") - def create_graphics_to_metrics(self, com_time, mess_fre_count, mess_fre_norm, similarity, fraction, addr, nei, current_round, total_rounds): + def create_graphics_to_metrics(self, com_time, mess_fre_count, mess_fre_norm, similarity, fraction, chunk_latency, addr, nei, current_round, total_rounds): """ Create graphics to metrics. """ @@ -248,6 +298,12 @@ def create_graphics_to_metrics(self, com_time, mess_fre_count, mess_fre_norm, si nei: fraction } } + + chunk_latency_dict = { + f"Reputation_chunk_latency/{addr}": { + nei: chunk_latency + } + } if communication_time_dict is not None: self.engine.trainer._logger.log_data(communication_time_dict, step=current_round) @@ -264,6 +320,9 @@ def create_graphics_to_metrics(self, com_time, mess_fre_count, mess_fre_norm, si if fraction_dict is not None: self.engine.trainer._logger.log_data(fraction_dict, step=current_round) + if chunk_latency_dict is not None: + self.engine.trainer._logger.log_data(chunk_latency_dict, step=current_round) + @staticmethod def analyze_anomalies(addr, nei, round_num, current_round, fraction_changed, threshold, changes_record, changed_params, total_params): """ @@ -358,15 +417,18 @@ def analyze_anomalies(addr, nei, round_num, current_round, fraction_changed, thr threshold_value = 1 / (1 + np.exp(-k_threshold * (current_threshold - mean_threshold_prev))) if current_threshold is not None and mean_threshold_prev is not None else 0 # logging.info(f"Round: {round_num}, Threshold: {threshold_value}") - if threshold_anomaly: - fraction_weight = 0.8 - threshold_weight = 0.2 - elif fraction_anomaly: - fraction_weight = 0.4 - threshold_weight = 0.6 - else: - fraction_weight = 0.5 - threshold_weight = 0.5 + # if threshold_anomaly: + # fraction_weight = 0.8 + # threshold_weight = 0.2 + # elif fraction_anomaly: + # fraction_weight = 0.4 + # threshold_weight = 0.6 + # else: + # fraction_weight = 0.5 + # threshold_weight = 0.5 + + fraction_weight = 0.5 + threshold_weight = 0.5 fraction_score = 1 - (fraction_weight * fraction_value + threshold_weight * threshold_value) Reputation.fraction_changed_history[key]["fraction_score"] = fraction_score @@ -410,7 +472,7 @@ def save_frequency_history(addr, nei, messages_frequency_normalized, current_rou # logging.info(f"Frequency: {messages_frequency_normalized}") # logging.info(f"Frequency history: {Reputation.frequency_history}") - rounds = Reputation.frequency_history[key] + #rounds = Reputation.frequency_history[key] if messages_frequency_normalized != 0 and current_round > 3: previous_avg = Reputation.frequency_history[key].get(current_round - 1, {}).get("avg_frequency", None) # logging.info(f"Previous avg frequency: {previous_avg}") @@ -428,51 +490,160 @@ def save_frequency_history(addr, nei, messages_frequency_normalized, current_rou except Exception as e: logging.error(f"Error saving frequency history: {e}") + @staticmethod + def manage_metric_chunk_latency(messages_chunk_latency, addr, nei, current_round): + """ + Manage the chunk latency metric with persistent storage of mean latency. + + Args: + messages_chunk_latency (list): List of messages chunk latency. + addr (str): Source IP address. + nei (str): Destination IP address. + current_round (int): Current round number. + + Returns: + float: Normalized chunk latency value. + """ + try: + current_key = (addr, nei) + mean_chunk_latency = 0.0 + score = 0.0 + + # logging.info(f"Round {current_round}. Messages chunk latency: {messages_chunk_latency}") + + if current_round == 4: + latencies = [msg["latency"] for msg in messages_chunk_latency if msg["key"] == current_key and msg["round"] < 4] + current_latency = [msg["latency"] for msg in messages_chunk_latency if msg["key"] == current_key and msg["round"] == current_round] + # logging.info(f"Round {current_round}. Latencies for rounds 0-3: {latencies}") + + mean_chunk_latency = np.mean(latencies) * 1.10 if latencies else 0 + # logging.info(f"Round {current_round}. Mean chunk latency for rounds 0-3: {mean_chunk_latency}") + + difference = abs (current_latency - mean_chunk_latency) + # logging.info(f"Round {current_round}. Difference: {difference}") + + penalty_range = difference / mean_chunk_latency + # logging.info(f"Round {current_round}. Penalty range: {penalty_range}") + + if current_latency < mean_chunk_latency: + reduction_factor = 1 - (difference / (mean_chunk_latency or 1)) + # logging.info(f"Round {current_round}. Reduction factor: {reduction_factor}") + penalty_range *= reduction_factor + + score = np.exp(-penalty_range) + # logging.info(f"Round {current_round}. Chunk score: {score}") + + for msg in messages_chunk_latency: + if msg["key"] == current_key and msg["round"] == current_round: + msg["mean_chunk_latency"] = mean_chunk_latency + msg["chunk_score"] = score + + # logging.info(f"Round {current_round}. Mean chunk latency for round 4: {mean_chunk_latency}") + + elif current_round > 4: + current_latency = [msg["latency"] for msg in messages_chunk_latency if msg["key"] == current_key and msg["round"] == current_round] + current_latency = current_latency[-1] if current_latency else -1 + # logging.info(f"Round {current_round}. Current latency: {current_latency}") + + previous_mean = [msg["mean_chunk_latency"] for msg in messages_chunk_latency if msg["key"] == current_key and msg["round"] == current_round - 1] + previous_mean = previous_mean[-1] if previous_mean else -1 + # logging.info(f"Round {current_round}. Current mean chunk latency: {previous_mean}") + + if current_latency != -1 and previous_mean != -1: + difference = abs (current_latency - previous_mean) + # logging.info(f"Round {current_round}. Difference: {difference}") + + penalty_range = difference / previous_mean + # logging.info(f"Round {current_round}. Penalty range: {penalty_range}") + + if current_latency < previous_mean: + reduction_factor = 1 - (difference / (previous_mean or 1)) + # logging.info(f"Round {current_round}. Reduction factor: {reduction_factor}") + penalty_range *= reduction_factor + + score = np.exp(-penalty_range) + # logging.info(f"Round {current_round}. Chunk score: {score}") + + mean_chunk_latency = ((previous_mean + current_latency) / 2 ) * 1.10 + # logging.info(f"Round {current_round}. Update mean chunk latency: {mean_chunk_latency}") + else: + return -1 + + if mean_chunk_latency is not None and score is not None: + for msg in messages_chunk_latency: + if msg["key"] == current_key and msg["round"] == current_round: + msg["mean_chunk_latency"] = mean_chunk_latency + msg["chunk_score"] = score + + return score + + except Exception as e: + logging.error(f"Error managing chunk latency metric: {e}") + return -1 + @staticmethod def manage_metric_frequency(messages_frequency, addr, nei, current_round): + """ + Manage the frequency metric using percentiles for normalization. + + Args: + messages_frequency (list): List of messages frequency. + addr (str): Source IP address. + nei (str): Destination IP address. + current_round (int): Current round number. + + Returns: + float: Normalized frequency value. + int: Messages count. + """ try: - # start_time = time.time() - # interval = 60 - current_addr_nei = (addr, nei) - threshold = 0 - previous_threshold = Reputation.previous_threshold_freq.get(current_addr_nei, 0) + current_addr_nei = (addr, nei) relevant_messages = [msg for msg in messages_frequency if msg["key"] == current_addr_nei and msg["current_round"] == current_round] messages_count = len(relevant_messages) if relevant_messages else 0 - # logging.info(f"Round {current_round}. Relevant messages: {relevant_messages}, Messages count: {messages_count}") - if current_round >= 0 and current_round <= 2: + # Calculate percentiles for rounds 0-4 only + if current_round >= 0 and current_round <= 3: previous_counts = [ len([m for m in messages_frequency if m["key"] == current_addr_nei and m["current_round"] == r]) - for r in range(3) + for r in range(4) ] - threshold = np.mean(previous_counts) if previous_counts else 0 - Reputation.previous_threshold_freq[current_addr_nei] = threshold - # logging.info(f"Round {current_round}. Previous counts: {previous_counts}, Threshold: {threshold}") + # Calculate the 25th and 75th percentiles as lower and upper bounds + Reputation.previous_percentile_25_freq[current_addr_nei] = np.percentile(previous_counts, 25) * 1.10 if previous_counts else 0 + # logging.info(f"Round {current_round}. Reputation.previous_percentile_25_freq: {Reputation.previous_percentile_25_freq}") + Reputation.previous_percentile_75_freq[current_addr_nei] = np.percentile(previous_counts, 75) * 1.10 if previous_counts else 0 + # logging.info(f"Round {current_round}. Reputation.previous_percentile_75_freq: {Reputation.previous_percentile_75_freq}") normalized_messages = 0.0 - elif current_round >= 3: - threshold = previous_threshold - # logging.info(f"Round {current_round}. Prev threshold: {threshold}") - - if messages_count > threshold: - excess = messages_count - threshold - normalized_messages = 1 - (excess / messages_count) if messages_count > 0 else 0 - # logging.info(f"Round {current_round}. Excess: {excess}, Normalized messages: {normalized_messages}") + elif current_round > 3: + percentile_25 = Reputation.previous_percentile_25_freq.get(current_addr_nei, 0) + # logging.info(f"Round {current_round}. Percentile 25: {percentile_25}") + percentile_75 = Reputation.previous_percentile_75_freq.get(current_addr_nei, 0) + # logging.info(f"Round {current_round}. Percentile 75: {percentile_75}") + + if messages_count <= percentile_25: + # logging.info(f"Round {current_round}. Messages count <= percentile 25") + normalized_messages = 1 - (percentile_25 - messages_count) / (percentile_25 + percentile_75) + # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") + elif messages_count >= percentile_75: + # logging.info(f"Round {current_round}. Messages count >= percentile 75") + normalized_messages = 1 / (1 + np.exp((messages_count - percentile_75) / (percentile_75 - percentile_25 + 1e-6))) + # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") else: - normalized_messages = 1 - ((threshold - messages_count) / (threshold + messages_count)) if threshold > 0 else 0 + # logging.info(f"Round {current_round}. Percentile 25 < Messages count < Percentile 75") + relative_position = (messages_count - percentile_25) / (percentile_75 - percentile_25) + # logging.info(f"Round {current_round}. Relative position: {relative_position}") + normalized_messages = 1 - relative_position / 2 # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") - if previous_threshold > 0: - threshold = (messages_count + previous_threshold) / 2 - Reputation.previous_threshold_freq[current_addr_nei] = threshold - # logging.info(f"Round {current_round}. New threshold: {threshold}") + Reputation.previous_percentile_25_freq[current_addr_nei] = np.percentile([percentile_25, messages_count], 25) * 1.10 + Reputation.previous_percentile_75_freq[current_addr_nei] = np.percentile([percentile_75, messages_count], 75) * 1.10 else: normalized_messages = 0.0 - + return normalized_messages, messages_count except Exception as e: - logging.error(f"Error managing frequency metric: {e}") + logging.error(f"Error managing metric frequency: {e}") return 0.0, 0 - + @staticmethod def manage_metric_communication(type_message, round, current_round, time, addr, nei): """ @@ -490,89 +661,181 @@ def manage_metric_communication(type_message, round, current_round, time, addr, float: Normalized communication value. """ try: - Reputation.communication_data.append({ - "time": time, - "type_message": type_message, - "round": round, - "current_round": current_round, - "key": (addr, nei) - }) - - #logging.info(f"Communication data: {Reputation.communication_data}") - - current_addr_nei = (addr, nei) - filtered_communications = [comm for comm in Reputation.communication_data if comm["key"] == current_addr_nei] - #logging.info(f"Round {current_round}. Filtered by key: {filtered_communications}") + current_addr_nei = (addr, nei, current_round) + score = 0.0 + + if current_addr_nei not in Reputation.communication_data: + Reputation.communication_data[current_addr_nei] = { + "time": time, + "type_message": type_message, + "round": round, + "current_round": current_round, + "mean_time": None + } + else: + Reputation.communication_data[current_addr_nei].update({ + "time": time, + "type_message": type_message, + "round": round, + "current_round": current_round + }) + logging.info(f"Round {current_round}. Communication data: {Reputation.communication_data}") + + filtered_communications = [ + comm for (a, n, r), comm in Reputation.communication_data.items() + if a == addr and n == nei and 0 <= r <= current_round + ] + # logging.info(f"Round {current_round}. Filtered by key: {filtered_communications}") if current_round == 5: - model_times = [comm["time"] for comm in filtered_communications if comm["type_message"] == "model" and comm["current_round"] < 5] - #logging.info(f"Round {current_round}. Model times: {model_times}") + model_times = [ + comm["time"] for comm in filtered_communications + if comm["type_message"] == "model" and comm["current_round"] <= 5 + and comm["time"] > 0 + ] + # logging.info(f"Round {current_round}. Model times: {model_times}") if model_times: - Reputation.mean_time_communication = np.mean(model_times) - #logging.info(f"Round {current_round}. Mean time communication: {Reputation.mean_time_communication}") + mean_time = np.mean(model_times) + logging.info(f"Round {current_round}. Mean time communication: {mean_time}") - difference = abs(time - Reputation.mean_time_communication) - #logging.info(f"Round {current_round}. Time: {time} difference: {difference}") + difference = abs(time - mean_time) + logging.info(f"Round {current_round}. Time: {time} difference: {difference}") - penalty_range = difference / Reputation.mean_time_communication - #logging.info(f"Round {current_round}. Penalty range: {penalty_range}") + penalty_range = difference / mean_time if mean_time > 0 else 1 + # logging.info(f"Round {current_round}. Penalty range: {penalty_range}") - if time < Reputation.mean_time_communication: - reduction_factor = 1 - (difference / Reputation.mean_time_communication) + if time < mean_time: + reduction_factor = 1 / (1 + np.exp(difference / mean_time)) + logging.info(f"Round {current_round}. Reduction factor: {reduction_factor}") penalty_range *= reduction_factor - #logging.info(f"Round {current_round}. Normal penalty reduction: {penalty_range}") + logging.info(f"Round {current_round}. Normal penalty reduction: {penalty_range}") if round != current_round: round_difference = abs(current_round - round) penalty_range += round_difference * 0.1 - #logging.info(f"Round {round} != current round {current_round}. Additional penalty for round difference.") + # logging.info(f"Round {round} != current round {current_round}. Additional penalty for round difference.") - Reputation.communication_score = max(0, 1 - penalty_range) - #logging.info(f"Round {current_round}. Communication score: {Reputation.communication_score}") + score = np.exp(-penalty_range) + logging.info(f"Round {current_round}. Communication score: {score}") + + Reputation.communication_data[current_addr_nei]["mean_time"] = mean_time + logging.info(f"Round {current_round}. Update mean time communication: {Reputation.communication_data[current_addr_nei]['mean_time']}") else: - Reputation.communication_score = 0.0 - Reputation.mean_time_communication = 0.0 - #logging.info(f"Round {current_round}. No data to calculate communication score") + score = 0.0 + mean_time = 0.0 + # logging.info(f"Round {current_round}. No data to calculate communication score") - return Reputation.communication_score + return score - elif current_round > 5 and Reputation.mean_time_communication is not None: - previous_mean = Reputation.mean_time_communication - Reputation.mean_time_communication = (previous_mean + time) / 2 - #logging.info(f"Round {current_round}. Update mean time communication: {Reputation.mean_time_communication}") + elif current_round > 5: + # logging.info(f"Round {current_round}. Current round > 5 and time: {time}") + previous_round = (addr,nei, current_round - 1) + # logging.info(f"Round {current_round}. Previous round: {previous_round}") - difference = abs(time - Reputation.mean_time_communication) - #logging.info(f"Round {current_round}. Time: {time} difference: {difference}") + if previous_round in Reputation.communication_data: + previous_mean = Reputation.communication_data[previous_round]["mean_time"] + # logging.info(f"Round {current_round}. time: {time} | previous_mean: {previous_mean}") + else: + previous_round = (addr,nei, current_round - 2) + if previous_round in Reputation.communication_data: + previous_mean = Reputation.communication_data[previous_round]["mean_time"] + # logging.info(f"Round {current_round}. time: {time} | previous_mean: {previous_mean}") + else: + previous_mean = None + + if previous_mean is not None: + if time == 0.0: + time = 1.0 + difference = abs(time - previous_mean) + logging.info(f"Round {current_round}. Time: {time} difference: {difference}") + + if previous_mean > 0: + penalty_range = difference / previous_mean + logging.info(f"Round {current_round}. Penalty range: {penalty_range}") + + if time < difference: + reduction_factor = 1 / (1 + np.exp(difference / previous_mean)) + logging.info(f"Round {current_round}. Reduction factor: {reduction_factor}") + penalty_range *= reduction_factor + logging.info(f"Round {current_round}. Normal penalty reduction: {penalty_range}") + else: + penalty_range = 1 - # max_time = max(comm["time"] for comm in Reputation.communication_data if comm["type_message"] == "model" and comm["key"] == (addr, nei)) - # logging.info(f"Round {current_round}. Max time for score calculation: {max_time}") + if round != current_round: + round_difference = abs(current_round - round) + penalty_range += round_difference * 0.1 + # logging.info(f"Round {round} != current round {current_round}. Additional penalty for round difference.") - penalty_range = difference / Reputation.mean_time_communication - #logging.info(f"Round {current_round}. Penalty range: {penalty_range}") + score = np.exp(-penalty_range) + logging.info(f"Round {current_round}. Communication score: {score}") - if time < Reputation.mean_time_communication: - reduction_factor = 1 - (difference / Reputation.mean_time_communication) - penalty_range *= reduction_factor - #logging.info(f"Round {current_round}. Normal penalty reduction: {penalty_range}") + mean_time = (previous_mean + time) / 2 + logging.info(f"Round {current_round}. Update mean time communication: {mean_time}") + + Reputation.communication_data[current_addr_nei]["mean_time"] = mean_time + else: + score = -1 + # logging.info(f"Round {current_round}. No previous mean time to calculate communication score in key {previous_round}") + return score + else: + score = 0.0 + + return score + + except Exception as e: + logging.error(f"Error managing communication metric: {e}") + return -1 + + @staticmethod + def save_chunk_history(addr, nei, chunk, current_round): + """ + Save the chunk history of a participant (addr) regarding its neighbor (nei) in memory. - if round != current_round: - round_difference = abs(current_round - round) - penalty_range += round_difference * 0.1 - #logging.info(f"Round {round} != current round {current_round}. Additional penalty for round difference.") + Args: + addr (str): The identifier of the node whose chunk history is being saved. + nei (str): The neighboring node involved. + chunk (float): The chunk value to be saved. + current_round (int): The current round number. - penalty_range = min(penalty_range, 1) - #logging.info(f"Round {current_round}. Penalty range: {penalty_range}") + Returns: + float: The cumulative chunk including the current round. + """ + try: + key = (addr, nei, current_round) + + if key not in Reputation.chunk_history: + Reputation.chunk_history[key] = {} + + Reputation.chunk_history[key] = { + "chunk_latency": chunk + } - Reputation.communication_score = max(0, 1 - penalty_range) - #logging.info(f"Round {current_round}. Communication score: {Reputation.communication_score}") + # logging.info(f"chunk history: {Reputation.chunk_history} | chunk: {chunk} | current_round: {current_round}") + + if chunk >= 0 and current_round > 4: + # logging.info(f" if chunk >= 0 and current_round > 4") + previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 1), {}).get("avg_chunk_latency", 0) + # logging.info(f"Previous avg chunk latency: {previous_avg}") + + if previous_avg != 0: + avg_chunk_latency = (chunk + previous_avg) / 2 + else: + avg_chunk_latency = chunk + elif chunk == -1 and current_round > 4: + # logging.info(f" elif chunk == -1 and current_round > 4") + previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 1), {}).get("avg_chunk_latency", 0) + # logging.info(f"Previous avg chunk latency: {previous_avg}") + + avg_chunk_latency = previous_avg - (previous_avg * 0.1) else: - Reputation.communication_score = 0.0 + avg_chunk_latency = 0 - return Reputation.communication_score + Reputation.chunk_history[key]["avg_chunk_latency"] = avg_chunk_latency + return avg_chunk_latency except Exception as e: - logging.error(f"Error managing communication metric: {e}") + logging.error(f"Error saving chunk history: {e}") @staticmethod def save_communication_history(addr, nei, communication, current_round): @@ -601,9 +864,10 @@ def save_communication_history(addr, nei, communication, current_round): # logging.info(f"Communication: {communication}") # logging.info(f"Communication history: {Reputation.communication_history}") - rounds = Reputation.communication_history[key] + #rounds = Reputation.communication_history[key] #recent_rounds = sorted(rounds.keys(), reverse=True) #[:2] - if communication != 0 and current_round > 4: + avg_communication = 0 + if communication >= 0 and current_round > 4: previous_avg = Reputation.communication_history[key].get(current_round - 1, {}).get("avg_communication", None) # logging.info(f"Previous avg communication: {previous_avg}") @@ -613,8 +877,10 @@ def save_communication_history(addr, nei, communication, current_round): avg_communication = communication Reputation.communication_history[key][current_round]["avg_communication"] = avg_communication - else: - avg_communication = 0 + elif communication == -1 and current_round > 4: + previous_avg = Reputation.communication_history[key].get(current_round - 1, {}).get("avg_communication", None) + # logging.info(f"Previous avg communication: {previous_avg}") + avg_communication = previous_avg - (previous_avg * 0.1) # logging.info(f"Avg communication: {avg_communication}") return avg_communication @@ -726,57 +992,136 @@ def read_similarity_file(file_path, nei): except Exception as e: logging.error(f"Error reading similarity file: {e}") return similarity + + # @staticmethod + # def manage_metric_frequency(messages_frequency, addr, nei, current_round): + # """ + # Manage the frequency metric. - @staticmethod - def callback_normalized_value(array, metric=None, key=None): - """ - Calculate the reputation of a node based on the normalized values. - - Args: - array (list): List of values to normalize. + # Args: + # messages_frequency (list): List of messages frequency. + # addr (str): Source IP address. + # nei (str): Destination IP address. + # current_round (int): Current round number. - Returns: - float: Reputation value (or None if array is empty). - """ + # Returns: + # float: Normalized frequency value. + # int: Messages count. + # """ + # try: + # current_addr_nei = (addr, nei) + # threshold = 0 + # previous_threshold = Reputation.previous_threshold_freq.get(current_addr_nei, 0) + # previous_std_dev = Reputation.previous_std_dev_freq.get(current_addr_nei, 0) + # relevant_messages = [msg for msg in messages_frequency if msg["key"] == current_addr_nei and msg["current_round"] == current_round] + # messages_count = len(relevant_messages) if relevant_messages else 0 + # # logging.info(f"Round {current_round}. Relevant messages: {relevant_messages}, Messages count: {messages_count}") + + # if current_round >= 0 and current_round <= 3: + # previous_counts = [ + # len([m for m in messages_frequency if m["key"] == current_addr_nei and m["current_round"] == r]) + # for r in range(4) + # ] - if not array: - return None - - if len(array) == 1: - if array[0] >= 1: - return Reputation.normalize(array[0], 0.25, array[0]+1) - else: - return Reputation.normalize(array[0], 0.25, array[0]) + # threshold = np.mean(previous_counts) if previous_counts else 0 + # std_dev = np.std(previous_counts) if previous_counts else 0 + # Reputation.previous_threshold_freq[current_addr_nei] = threshold + # Reputation.previous_std_dev_freq[current_addr_nei] = std_dev + # logging.info(f"Round {current_round}. Previous counts: {previous_counts}, Threshold: {threshold}, Std dev: {std_dev}") + + # normalized_messages = 0.0 + # elif current_round > 3: + # threshold = previous_threshold + # std_dev = previous_std_dev + # logging.info(f"Round {current_round}. Prev threshold: {threshold} | Prev std dev: {std_dev}") + + # lower_bound = abs(threshold - (std_dev * 1.1)) + # logging.info(f"Round {current_round}. Lower bound: {lower_bound}") + # upper_bound = threshold + (std_dev * 1.1) + # logging.info(f"Round {current_round}. Upper bound: {upper_bound}") + + # logging.info(f"Round {current_round}. Messages count: {messages_count}") + # if messages_count <= lower_bound: + # logging.info(f"Round {current_round}. Messages count <= lower bound") + # normalized_messages = 1 - (lower_bound - messages_count) / (abs(lower_bound) + std_dev + 1e-6) + # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") + # elif messages_count >= upper_bound: + # logging.info(f"Round {current_round}. Messages count >= upper bound") + # normalized_messages = 1 / (1 + np.exp((messages_count - upper_bound) / (std_dev + 1e-6))) + # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") + # else: + # logging.info(f"Round {current_round}. Lower bound < Messages count < Upper bound") + # relative_position = (messages_count - lower_bound) / (upper_bound - lower_bound) + # logging.info(f"Round {current_round}. Relative position: {relative_position}") + # normalized_messages = 1 - relative_position / 2 + # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") + + # if previous_threshold > 0 and previous_std_dev > 0: + # threshold = (messages_count + previous_threshold) / 2 + # std_dev = np.sqrt(((messages_count - previous_threshold) ** 2 + previous_std_dev ** 2) / 2) + + # Reputation.previous_threshold_freq[current_addr_nei] = threshold + # Reputation.previous_std_dev_freq[current_addr_nei] = std_dev + # logging.info(f"Round {current_round}. New threshold: {threshold} | New std dev: {std_dev}") + # else: + # normalized_messages = 0.0 + + # return normalized_messages, messages_count - min_value = min(array) - max_value = max(array) + # except Exception as e: + # logging.error(f"Error managing frequency metric: {e}") + # return 0.0, 0 + + # @staticmethod + # def normalize(value, min_value, max_value): + # """ + # Normalize value within a given range. + + # Args: + # value (float): Reputation value. + # min_reputation (float): Minimum reputation value. + # max_reputation (float): Maximum reputation value. + + # Returns: + # float: Normalized reputation value within the range [1, 10]. + # """ + + # if max_value == min_value: + # return 0.25 - # See values from array - normalized_values = [Reputation.normalize(value, min_value, max_value) for value in array] - reputation_normalized = sum(normalized_values) / len(normalized_values) + # normalized_value = (value - min_value) / (max_value - min_value) - return reputation_normalized + # return max(0.0, min(1.0, normalized_value)) - @staticmethod - def normalize(value, min_value, max_value): - """ - Normalize value within a given range. - - Args: - value (float): Reputation value. - min_reputation (float): Minimum reputation value. - max_reputation (float): Maximum reputation value. + # @staticmethod + # def callback_normalized_value(array, metric=None, key=None): + # """ + # Calculate the reputation of a node based on the normalized values. - Returns: - float: Normalized reputation value within the range [1, 10]. - """ + # Args: + # array (list): List of values to normalize. + + # Returns: + # float: Reputation value (or None if array is empty). + # """ - if max_value == min_value: - return 0.25 + # if not array: + # return None + + # if len(array) == 1: + # if array[0] >= 1: + # return Reputation.normalize(array[0], 0.25, array[0]+1) + # else: + # return Reputation.normalize(array[0], 0.25, array[0]) + + # min_value = min(array) + # max_value = max(array) - normalized_value = (value - min_value) / (max_value - min_value) + # # See values from array + # normalized_values = [Reputation.normalize(value, min_value, max_value) for value in array] + # reputation_normalized = sum(normalized_values) / len(normalized_values) - return max(0.0, min(1.0, normalized_value)) + # return reputation_normalized # @staticmethod # def manage_metric_fraction_of_params_changed(round, total_params, changed_params, changes_record, nei): diff --git a/nebula/frontend/config/participant.json.example b/nebula/frontend/config/participant.json.example index c68266120..9d2694b9d 100755 --- a/nebula/frontend/config/participant.json.example +++ b/nebula/frontend/config/participant.json.example @@ -51,7 +51,7 @@ "reordering": "0%" }, "adaptive_args": { - "model_similarity": false + "model_similarity": true }, "mobility_args": { "latitude": "", @@ -87,7 +87,7 @@ }, "aggregator_args": { "algorithm": "FedAvg", - "aggregation_timeout": 300 + "aggregation_timeout": 20 }, "defense_args": { "with_reputation": false, From 767954baaad4b4b6cfc67f334f2318fd8fbd08a1 Mon Sep 17 00:00:00 2001 From: isaac Date: Tue, 3 Dec 2024 16:19:25 +0100 Subject: [PATCH 03/43] add in frontend new attack at select --- nebula/frontend/templates/deployment.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nebula/frontend/templates/deployment.html b/nebula/frontend/templates/deployment.html index f642919a9..d8f15fcdc 100755 --- a/nebula/frontend/templates/deployment.html +++ b/nebula/frontend/templates/deployment.html @@ -204,7 +204,7 @@
Scenario description
Deployment
Deployment type
- + @@ -212,7 +212,7 @@
Deployment type
+ value="docker" checked> @@ -545,6 +545,7 @@
Attack Type
+ From 6df3b4df5db57e221146edec51a68a5b742eee92 Mon Sep 17 00:00:00 2001 From: Isaac Marroqui Date: Thu, 5 Dec 2024 11:09:53 +0100 Subject: [PATCH 08/43] changes in .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 09634cee8..0a8b61de9 100755 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,12 @@ __pycache__/ # C extensions *.so +# Data escenarios reputation +nebula/core/reputation/nebula_DFL_* + +# File uv.lock +uv.lock + # Distribution / packaging .Python docs/build/ From 600c5b5185af4e21cf2d260b229a1de830d03605 Mon Sep 17 00:00:00 2001 From: Isaac Marroqui Date: Fri, 13 Dec 2024 17:38:41 +0100 Subject: [PATCH 09/43] changes of the week --- nebula/addons/attacks/attacks.py | 2 +- nebula/core/aggregation/aggregator.py | 43 ++- nebula/core/network/connection.py | 59 +-- nebula/core/reputation/Reputation.py | 510 ++++++++++++++------------ 4 files changed, 344 insertions(+), 270 deletions(-) diff --git a/nebula/addons/attacks/attacks.py b/nebula/addons/attacks/attacks.py index c444796e4..ab2d8db28 100755 --- a/nebula/addons/attacks/attacks.py +++ b/nebula/addons/attacks/attacks.py @@ -186,7 +186,7 @@ class FloodingAttack(Attack): def __init__(self): super().__init__() - async def attack(self, cm: CommunicationsManager, addr, num_round, repetitions=20, interval=0.05): + async def attack(self, cm: CommunicationsManager, addr, num_round, repetitions=5, interval=0.1): logging.info("[FloodingAttack] Performing flood attack") neighbors = set(await cm.get_addrs_current_connections(only_direct=True)) logging.info(f"Neighbors: {neighbors}") diff --git a/nebula/core/aggregation/aggregator.py b/nebula/core/aggregation/aggregator.py index 64074b06e..117d419c1 100755 --- a/nebula/core/aggregation/aggregator.py +++ b/nebula/core/aggregation/aggregator.py @@ -125,8 +125,10 @@ async def _add_pending_model(self, model, weight, source): await self._add_model_lock.release_async() return None - logging.info(f"🔄 _add_pending_model | source={source} | federation_nodes={self._federation_nodes} | pending_models_to_aggregate={self.get_nodes_pending_models_to_aggregate()} | rejected_nodes={self.engine.rejected_nodes}") - + logging.info( + f"🔄 _add_pending_model | source={source} | federation_nodes={self._federation_nodes} | pending_models_to_aggregate={self.get_nodes_pending_models_to_aggregate()} | rejected_nodes={self.engine.rejected_nodes}" + ) + if source not in self._federation_nodes: logging.info(f"🔄 _add_pending_model | Can't add a model from ({source}), which is not in the federation.") await self._add_model_lock.release_async() @@ -136,10 +138,14 @@ async def _add_pending_model(self, model, weight, source): logging.info("🔄 _add_pending_model | Ignoring model from rejected node...") else: self._pending_models_to_aggregate.update({source: (model, weight)}) - logging.info("🔄 _add_pending_model | Node is not in the aggregation buffer --> Include model in the aggregation buffer.") + logging.info( + "🔄 _add_pending_model | Node is not in the aggregation buffer --> Include model in the aggregation buffer." + ) - logging.info(f"🔄 _add_pending_model | ({len(self.get_nodes_pending_models_to_aggregate())!s}/{len(valid_federation_nodes)})") - #logging.info(f"🔄 _add_pending_model | Model added in aggregation buffer ({len(self.get_nodes_pending_models_to_aggregate())!s}/{len(self._federation_nodes)!s}) | Pending nodes: {self._federation_nodes - self.get_nodes_pending_models_to_aggregate()}") + logging.info( + f"🔄 _add_pending_model | ({len(self.get_nodes_pending_models_to_aggregate())!s}/{len(valid_federation_nodes)})" + ) + # logging.info(f"🔄 _add_pending_model | Model added in aggregation buffer ({len(self.get_nodes_pending_models_to_aggregate())!s}/{len(self._federation_nodes)!s}) | Pending nodes: {self._federation_nodes - self.get_nodes_pending_models_to_aggregate()}") # Check if _future_models_to_aggregate has models in the current round to include in the aggregation buffer if self.engine.get_round() in self._future_models_to_aggregate: @@ -150,8 +156,11 @@ async def _add_pending_model(self, model, weight, source): if future_model is None: continue future_model, future_weight, future_source = future_model - #if (future_source in self._federation_nodes and future_source not in self.get_nodes_pending_models_to_aggregate()): - if (future_source in valid_federation_nodes and future_source not in self.get_nodes_pending_models_to_aggregate()): + # if (future_source in self._federation_nodes and future_source not in self.get_nodes_pending_models_to_aggregate()): + if ( + future_source in valid_federation_nodes + and future_source not in self.get_nodes_pending_models_to_aggregate() + ): self._pending_models_to_aggregate.update({future_source: (future_model, future_weight)}) logging.info( f"🔄 _add_pending_model | Next model added in aggregation buffer ({len(self.get_nodes_pending_models_to_aggregate())!s}/{len(self._federation_nodes)!s}) | Pending nodes: {self._federation_nodes - self.get_nodes_pending_models_to_aggregate()}" @@ -162,7 +171,9 @@ async def _add_pending_model(self, model, weight, source): if future_round < self.engine.get_round(): del self._future_models_to_aggregate[future_round] - logging.info(f"🔄 _add_pending_model | pending_models_to_aggregate={self.get_nodes_pending_models_to_aggregate()}") + logging.info( + f"🔄 _add_pending_model | pending_models_to_aggregate={self.get_nodes_pending_models_to_aggregate()}" + ) logging.info(f"🔄 _add_pending_model | federation_nodes={self._federation_nodes}") logging.info(f"🔄 _add_pending_model | rejected_nodes={self.engine.rejected_nodes}") logging.info(f"🔄 _add_pending_model | valid_federation_nodes={valid_federation_nodes}") @@ -174,8 +185,12 @@ async def _add_pending_model(self, model, weight, source): else: logging.info("🔄 _add_pending_model | aggregation_done_lock is not locked") else: - logging.info("🔄 _add_pending_model | Not all models were added in the aggregation buffer. Waiting for more models...") - logging.info(f"🔄 _add_pending_model | pending_models_to_aggregate={self.get_nodes_pending_models_to_aggregate()}") + logging.info( + "🔄 _add_pending_model | Not all models were added in the aggregation buffer. Waiting for more models..." + ) + logging.info( + f"🔄 _add_pending_model | pending_models_to_aggregate={self.get_nodes_pending_models_to_aggregate()}" + ) await self._add_model_lock.release_async() return self.get_nodes_pending_models_to_aggregate() @@ -204,9 +219,11 @@ async def include_model_in_buffer(self, model, weight, source=None, round=None, await self._add_pending_model(model, weight, source) valid_federation_nodes = self._federation_nodes - self.engine.rejected_nodes - #if len(self.get_nodes_pending_models_to_aggregate()) >= len(self._federation_nodes): + # if len(self.get_nodes_pending_models_to_aggregate()) >= len(self._federation_nodes): if len(self.get_nodes_pending_models_to_aggregate()) >= len(valid_federation_nodes): - logging.info(f"🔄 include_model_in_buffer | Broadcasting MODELS_INCLUDED for round {self.engine.get_round()}") + logging.info( + f"🔄 include_model_in_buffer | Broadcasting MODELS_INCLUDED for round {self.engine.get_round()}" + ) message = self.cm.mm.generate_federation_message( nebula_pb2.FederationMessage.Action.FEDERATION_MODELS_INCLUDED, [self.engine.get_round()], @@ -220,7 +237,7 @@ async def get_aggregation(self): timeout = self.config.participant["aggregator_args"]["aggregation_timeout"] await self._aggregation_done_lock.acquire_async(timeout=timeout) except TimeoutError: - logging.error("🔄 get_aggregation | Timeout reached for aggregation") + logging.exception("🔄 get_aggregation | Timeout reached for aggregation") finally: await self._aggregation_done_lock.release_async() diff --git a/nebula/core/network/connection.py b/nebula/core/network/connection.py index 2a51ae284..b04b991fc 100755 --- a/nebula/core/network/connection.py +++ b/nebula/core/network/connection.py @@ -8,11 +8,12 @@ import zlib from dataclasses import dataclass from typing import TYPE_CHECKING, Any -from nebula.core.reputation.Reputation import save_data import lz4.frame from geopy import distance +from nebula.core.reputation.Reputation import save_data + if TYPE_CHECKING: from nebula.core.network.communications import CommunicationsManager @@ -281,40 +282,56 @@ async def handle_incoming_message(self) -> None: chunk_data = await self._read_chunk(reusable_buffer) self._store_chunk(message_id, chunk_index, chunk_data, is_last_chunk) - # logging.debug(f"Received chunk {chunk_index} of message {message_id.hex()} | size: {len(chunk_data)} bytes") + logging.debug( + f"Received chunk {chunk_index} of message {message_id.hex()} | size: {len(chunk_data)} bytes" + ) message_id_decoded = message_id.hex() if chunk_index == 0: chunk_bytes = chunk_data.tobytes() # logging.info(f"chunk data: {chunk_bytes}") comparation_with = b"\x01\x00\x00\x00x\x9c\x00" - if chunk_bytes[:len(comparation_with)] == comparation_with: + if chunk_bytes[: len(comparation_with)] == comparation_with: if message_id_decoded not in self._chunk_data: self._chunk_data[message_id_decoded] = {} - self._chunk_data[message_id_decoded]['start_time'] = time.time() + self._chunk_data[message_id_decoded]["start_time"] = time.time() # if message_id_decoded and message_id_decoded in self._chunk_data: # logging.info(f"message_id_decoded in start_time: {self._chunk_data}") - + if is_last_chunk: - if message_id_decoded in self._chunk_data and message_id_decoded and 'start_time' in self._chunk_data[message_id_decoded]: - self._chunk_data[message_id_decoded]['end_time'] = time.time() + if ( + message_id_decoded in self._chunk_data + and message_id_decoded + and "start_time" in self._chunk_data[message_id_decoded] + ): + self._chunk_data[message_id_decoded]["end_time"] = time.time() # logging.info(f"message_id_decoded in end_time: {self._chunk_data}") - if 'start_time' in self._chunk_data[message_id_decoded] and 'end_time' in self._chunk_data[message_id_decoded]: - latency = self._chunk_data[message_id_decoded]['end_time'] - self._chunk_data[message_id_decoded]['start_time'] - # logging.info(f"message_id_decoded in latency: {latency}") - source = self.addr - - if source != self.cm.get_addr(): - # logging.info(f"SAVE DATA CHUNK") - save_data(self.config.participant['scenario_args']['name'], - "chunk_latency", - source, - self.cm.get_addr(), - self.cm.get_round(), - message_id_decoded=message_id_decoded, - latency=latency) + if "start_time" in self._chunk_data[message_id_decoded]: + if "end_time" in self._chunk_data[message_id_decoded]: + latency = ( + self._chunk_data[message_id_decoded]["end_time"] + - self._chunk_data[message_id_decoded]["start_time"] + ) + # logging.info(f"message_id_decoded in latency: {latency}") + source = self.addr + + if source != self.cm.get_addr(): + # logging.info(f"SAVE DATA CHUNK") + save_data( + self.config.participant["scenario_args"]["name"], + "chunk_latency", + source, + self.cm.get_addr(), + self.cm.get_round(), + message_id_decoded=message_id_decoded, + latency=latency, + ) + else: + logging.info(f"message_id_decoded not in end_time: {self._chunk_data}") + else: + logging.info(f"message_id_decoded not in start_time: {self._chunk_data}") await self._process_complete_message(message_id) except asyncio.CancelledError: diff --git a/nebula/core/reputation/Reputation.py b/nebula/core/reputation/Reputation.py index 128694a88..8da49c39c 100644 --- a/nebula/core/reputation/Reputation.py +++ b/nebula/core/reputation/Reputation.py @@ -110,10 +110,10 @@ class Reputation: messages_frequency: ClassVar[list] = [] previous_threshold_freq: ClassVar[dict] = {} previous_std_dev_freq: ClassVar[dict] = {} - messages_chunk_latency: ClassVar[list] = [] + messages_chunk_latency: ClassVar[dict] = {} chunk_history: ClassVar[dict] = {} - previous_percentile_5_freq: ClassVar[dict] = {} - previous_percentile_95_freq: ClassVar[dict] = {} + previous_percentile_25_freq: ClassVar[dict] = {} + previous_percentile_85_freq: ClassVar[dict] = {} def __init__(self, engine: "Engine"): self._engine = engine @@ -168,8 +168,12 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro current_round_time = metric["time_message"]["current_round"] time = metric["time_message"]["time"] type_message = metric["time_message"]["type_message"] - if current_round_time == current_round: - logging.info(f"Round: {round_time}, current_round_time: {current_round_time}, Time: {time}, Type message: {type_message}") + previous_round_time = current_round - 1 + logging.info( + f"Current round time: {current_round_time}, previous round time: {previous_round_time}" + ) + if round_time == previous_round_time: + logging.info(f"Time message: {time}, type message: {type_message}, round: {round_time}") Reputation.messages_frequency.append({ "time_message": time, "type_message": type_message, @@ -202,12 +206,13 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro message_id_decoded = metric["chunk_latency"]["message_id_decoded"] latency = metric["chunk_latency"]["latency"] if round_latency == current_round: - Reputation.messages_chunk_latency.append({ - "message_id_decoded": message_id_decoded, - "latency": latency, - "round": round_latency, - "key": (addr, nei), - }) + messages_chunk_normalized = Reputation.manage_chunk_latency( + round_latency, + addr, + nei, + latency, + ) + logging.info(f"messages_chunk_normalized: {messages_chunk_normalized}") similarity_file = os.path.join(log_dir, f"participant_{id_node}_similarity.csv") similarity_reputation = Reputation.read_similarity_file(similarity_file, nei) @@ -226,15 +231,27 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro f"Avg communication is None and current_round = {current_round}, avg_communication_time_normalized: {avg_communication_time_normalized}" ) + if messages_chunk_normalized >= 0: + avg_chunk_latency = Reputation.save_chunk_history( + addr, nei, messages_chunk_normalized, current_round + ) + if avg_chunk_latency is None and current_round >= 5: + avg_chunk_latency = Reputation.chunk_history[(addr, nei)][current_round - 1][ + "chunk_latency" + ] + logging.info( + f"Avg chunk latency is None and current_round = {current_round}, chunk_latency: {avg_chunk_latency}" + ) + if Reputation.messages_frequency is not None: messages_frequency_normalized, messages_frequency_count = Reputation.manage_metric_frequency( - Reputation.messages_frequency, addr, nei, current_round + Reputation.messages_frequency, addr, nei, current_round, scenario ) - logging.info(f"Messages frequency normalized: {messages_frequency_normalized}") + # logging.info(f"Messages frequency normalized: {messages_frequency_normalized}") avg_messages_frequency_normalized = Reputation.save_frequency_history( addr, nei, messages_frequency_normalized, current_round ) - logging.info(f"Avg messages frequency normalized: {avg_messages_frequency_normalized}") + # logging.info(f"Avg messages frequency normalized: {avg_messages_frequency_normalized}") if avg_messages_frequency_normalized is None and current_round >= 4: avg_messages_frequency_normalized = Reputation.frequency_history[(addr, nei)][ current_round - 1 @@ -244,29 +261,51 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro ) if Reputation.fraction_changed_history is not None: - key = (addr, nei, current_round) - if key not in Reputation.fraction_changed_history: - key = (addr, nei, current_round - 1) if current_round > 0 else None - fraction_score = Reputation.fraction_changed_history[key].get("fraction_score") - fraction_score_asign = fraction_score if fraction_score is not None else 0 - logging.info(f"Fraction score: {fraction_score_asign}") - - if Reputation.messages_chunk_latency is not None: - messages_chunk_normalized = Reputation.manage_chunk_latency( - Reputation.messages_chunk_latency, addr, nei, current_round - ) - # logging.info(f"Score chunk: {messages_chunk_normalized}") - avg_chunk_latency = Reputation.save_chunk_history( - addr, nei, messages_chunk_normalized, current_round - ) - # logging.info(f"Return avg chunk latency: {avg_chunk_latency}") - if avg_chunk_latency is None and current_round > 4: - avg_chunk_latency = Reputation.chunk_history[(addr, nei, current_round - 1)][ - "avg_chunk_latency" - ] - logging.info( - f"Avg chunk latency is None and current_round = {current_round}, avg_chunk_latency: {avg_chunk_latency}" + key_current_round = (addr, nei, current_round) if current_round else None + key_previous_round = (addr, nei, current_round - 1) if current_round - 1 > 0 else None + fraction_current_round = None + fraction_previous_round = None + converge_fraction = None + + # logging.info(f"Key_current_round: {key_current_round}, key_previous_round: {key_previous_round}") + # logging.info(f"Reputation.fraction_changed_history: {Reputation.fraction_changed_history}") + + if key_current_round in Reputation.fraction_changed_history: + fraction_score = Reputation.fraction_changed_history[key_current_round].get( + "fraction_score" ) + fraction_current_round = fraction_score if fraction_score is not None else None + # logging.info(f"Fraction score current round: {fraction_current_round}") + + if key_previous_round is not None and key_previous_round in Reputation.fraction_changed_history: + fraction_score = Reputation.fraction_changed_history[key_previous_round].get( + "fraction_score" + ) + fraction_previous_round = fraction_score if fraction_score is not None else None + # logging.info(f"Fraction score previous round: {fraction_previous_round}") + + if fraction_current_round is not None and fraction_previous_round is not None: + converge_fraction = (fraction_current_round + fraction_previous_round) / 2 + + fraction_score_asign = converge_fraction if converge_fraction is not None else 0 + # logging.info(f"Fraction score: {fraction_score_asign}") + + # if Reputation.messages_chunk_latency is not None: + # messages_chunk_normalized = Reputation.manage_chunk_latency( + # Reputation.messages_chunk_latency, addr, nei, current_round + # ) + # logging.info(f"Score chunk: {messages_chunk_normalized}") + # avg_chunk_latency = Reputation.save_chunk_history( + # addr, nei, messages_chunk_normalized, current_round + # ) + # logging.info(f"Return avg chunk latency: {avg_chunk_latency}") + # if avg_chunk_latency is None and current_round > 4: + # avg_chunk_latency = Reputation.chunk_history[(addr, nei, current_round - 1)][ + # "chunk_latency" + # ] + # logging.info( + # f"Avg chunk latency is None and current_round = {current_round}, chunk_latency: {avg_chunk_latency}" + # ) # Weights for each metric if current_round is not None: @@ -278,17 +317,17 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro weight_to_chunk = 0.0 elif current_round >= 5: # only for rounds 5 and later # Weight to scenarios with delay - weight_to_similarity = 0.1 - weight_to_communication = 0.3 - weight_to_fraction = 0.1 - weight_to_message_frequency = 0.3 - weight_to_chunk = 0.2 - # Weight to scenarios with noise injection - # weight_to_similarity = 0.3 - # weight_to_communication = 0.1 - # weight_to_fraction = 0.3 - # weight_to_message_frequency = 0.1 + # weight_to_similarity = 0.1 + # weight_to_communication = 0.3 + # weight_to_fraction = 0.1 + # weight_to_message_frequency = 0.3 # weight_to_chunk = 0.2 + # Weight to scenarios with noise injection + weight_to_similarity = 0.3 + weight_to_communication = 0.1 + weight_to_fraction = 0.3 + weight_to_message_frequency = 0.1 + weight_to_chunk = 0.2 # Weight to scenarios with flood # weight_to_similarity = 0.1 # weight_to_communication = 0.2 @@ -437,13 +476,17 @@ def analyze_anomalies( if past_fractions: mean_fraction = np.mean(past_fractions) + # logging.info(f"Round: {round_num}, Mean fraction: {mean_fraction}") std_dev_fraction = np.std(past_fractions) + # logging.info(f"Round: {round_num}, Std dev fraction: {std_dev_fraction}") Reputation.fraction_changed_history[key]["mean_fraction"] = mean_fraction Reputation.fraction_changed_history[key]["std_dev_fraction"] = std_dev_fraction if past_thresholds: mean_threshold = np.mean(past_thresholds) + # logging.info(f"Round: {round_num}, Mean threshold: {mean_threshold}") std_dev_threshold = np.std(past_thresholds) + # logging.info(f"Round: {round_num}, Std dev threshold: {std_dev_threshold}") Reputation.fraction_changed_history[key]["mean_threshold"] = mean_threshold Reputation.fraction_changed_history[key]["std_dev_threshold"] = std_dev_threshold @@ -465,9 +508,10 @@ def analyze_anomalies( # logging.info(f"Round: {round_num}, Current fraction: {current_fraction}, Current threshold: {current_threshold}") # low_mean_fraction_prev = mean_fraction_prev - std_dev_fraction_prev - upper_mean_fraction_prev = mean_fraction_prev + std_dev_fraction_prev + upper_mean_fraction_prev = (mean_fraction_prev * 1.2) + (std_dev_fraction_prev * 1.2) # low_mean_threshold_prev = mean_threshold_prev - std_dev_threshold_prev - upper_mean_threshold_prev = mean_threshold_prev + std_dev_threshold_prev + upper_mean_threshold_prev = (mean_threshold_prev * 1.2) + (std_dev_threshold_prev * 1.2) + # logging.info(f"Round: {round_num}, Upper mean fraction: {upper_mean_fraction_prev}, Upper mean threshold: {upper_mean_threshold_prev}") # fraction_anomaly = not (low_mean_fraction_prev <= current_fraction <= upper_mean_fraction_prev) # threshold_anomaly = not (low_mean_threshold_prev <= current_threshold <= upper_mean_threshold_prev) @@ -484,17 +528,18 @@ def analyze_anomalies( k_threshold = 1 / std_dev_threshold_prev if std_dev_threshold_prev != 0 else 1 # logging.info(f"Round: {round_num}, K threshold: {k_threshold}") fraction_value = ( - 1 / (1 + np.exp(-k_fraction * (current_fraction - mean_fraction_prev))) + 1 - (1 / (1 + np.exp(-k_fraction * (current_fraction - mean_fraction_prev)))) if current_fraction is not None and mean_fraction_prev is not None else 0 ) - # logging.info(f"Round: {round_num}, Fraction: {fraction_value}") + # logging.info(f"Round: {round_num}, Fraction_value: {fraction_value}") + threshold_value = ( - 1 / (1 + np.exp(-k_threshold * (current_threshold - mean_threshold_prev))) + 1 - (1 / (1 + np.exp(-k_threshold * (current_threshold - mean_threshold_prev)))) if current_threshold is not None and mean_threshold_prev is not None else 0 ) - # logging.info(f"Round: {round_num}, Threshold: {threshold_value}") + # logging.info(f"Round: {round_num}, Threshold_value: {threshold_value}") # if threshold_anomaly: # fraction_weight = 0.8 @@ -509,7 +554,8 @@ def analyze_anomalies( fraction_weight = 0.5 threshold_weight = 0.5 - fraction_score = 1 - (fraction_weight * fraction_value + threshold_weight * threshold_value) + fraction_score = fraction_weight * fraction_value + threshold_weight * threshold_value + # logging.info(f"Round: {round_num}, Fraction score: {fraction_score}") Reputation.fraction_changed_history[key]["fraction_score"] = fraction_score # Upload the values to the history @@ -523,6 +569,10 @@ def analyze_anomalies( Reputation.fraction_changed_history[key]["std_dev_threshold"] = np.sqrt( ((0.1 * (current_threshold - mean_threshold_prev) ** 2) + std_dev_threshold_prev**2) / 2 ) + # logging.info(f"Round: {round_num}, Mean fraction: {Reputation.fraction_changed_history[key]['mean_fraction']}") + # logging.info(f"Round: {round_num}, Std dev fraction: {Reputation.fraction_changed_history[key]['std_dev_fraction']}") + # logging.info(f"Round: {round_num}, Mean threshold: {Reputation.fraction_changed_history[key]['mean_threshold']}") + # logging.info(f"Round: {round_num}, Std dev threshold: {Reputation.fraction_changed_history[key]['std_dev_threshold']}") # Score not negative Reputation.fraction_changed_history[key]["fraction_score"] = max(fraction_score, 0) @@ -553,19 +603,19 @@ def save_frequency_history(addr, nei, messages_frequency_normalized, current_rou Reputation.frequency_history[key][current_round] = {"frequency": messages_frequency_normalized} - logging.info(f"Frequency: {messages_frequency_normalized}") + # logging.info(f"Frequency: {messages_frequency_normalized}") # logging.info(f"Frequency history: {Reputation.frequency_history}") # rounds = Reputation.frequency_history[key] if messages_frequency_normalized != 0 and current_round >= 4: previous_avg = Reputation.frequency_history[key].get(current_round - 1, {}).get("avg_frequency", None) - logging.info(f"Previous avg frequency: {previous_avg}") + # logging.info(f"Previous avg frequency: {previous_avg}") if previous_avg is not None: avg_frequency = (messages_frequency_normalized + previous_avg) / 2 - logging.info(f"Avg frequency in if: {avg_frequency}") + # logging.info(f"Avg frequency in if: {avg_frequency}") else: avg_frequency = messages_frequency_normalized - logging.info(f"Avg frequency in else: {avg_frequency}") + # logging.info(f"Avg frequency in else: {avg_frequency}") Reputation.frequency_history[key][current_round]["avg_frequency"] = avg_frequency else: @@ -578,7 +628,7 @@ def save_frequency_history(addr, nei, messages_frequency_normalized, current_rou return -1 @staticmethod - def manage_chunk_latency(messages_chunk_latency, addr, nei, current_round): + def manage_chunk_latency(round_num, addr, nei, latency): """ Manage the chunk latency metric with persistent storage of mean latency. @@ -592,91 +642,123 @@ def manage_chunk_latency(messages_chunk_latency, addr, nei, current_round): float: Normalized chunk latency value. """ try: - current_key = (addr, nei) + current_key = (addr, nei, round_num) mean_chunk_latency = 0.0 score = 0.0 previous_mean = -1 - # logging.info(f"Round {current_round}. Messages chunk latency: {messages_chunk_latency}") + if current_key not in Reputation.communication_data: + Reputation.messages_chunk_latency[current_key] = { + "latency": latency, + "round": round_num, + "mean_chunk_latency": None, + "chunk_score": None, + "std_dev_chunk": None, + } + else: + Reputation.messages_chunk_latency[current_key].update({ + "latency": latency, + "round": round_num, + "mean_chunk_latency": None, + "chunk_score": None, + "std_dev_chunk": None, + }) + logging.info(f"Round {round_num}. Messages chunk latency: {Reputation.messages_chunk_latency}") - if current_round == 4: + if round_num == 4: latencies = [ - msg["latency"] for msg in messages_chunk_latency if msg["key"] == current_key and msg["round"] < 4 - ] - current_latency = [ msg["latency"] - for msg in messages_chunk_latency - if msg["key"] == current_key and msg["round"] == current_round + for key, msg in Reputation.messages_chunk_latency.items() + if key[:2] == current_key[:2] and msg["round"] < 4 ] - logging.info(f"Round {current_round}. Latencies for rounds 0-3: {latencies}") + logging.info(f"Round {round_num}. Latencies for rounds 0-3: {latencies}") - mean_chunk_latency = np.mean(latencies) * 1.10 if latencies else 0 - logging.info(f"Round {current_round}. Mean chunk latency for rounds 0-3: {mean_chunk_latency}") + if latencies: + mean_chunk_latency = np.mean(latencies) + std_dev_chunk = np.std(latencies) + logging.info(f"Round {round_num}. Mean chunk latency for rounds 0-3: {mean_chunk_latency}") + logging.info(f"Round {round_num}. Std dev chunk latency for rounds 0-3: {std_dev_chunk}") - difference = abs(current_latency - mean_chunk_latency) - logging.info(f"Round {current_round}. Difference: {difference}") + current_latency = [ + msg["latency"] for key, msg in Reputation.messages_chunk_latency.items() if key == current_key + ] + current_latency = current_latency[-1] if current_latency else -1 + logging.info(f"Round {round_num}. Current latency: {current_latency}") - penalty_range = difference / mean_chunk_latency - logging.info(f"Round {current_round}. Penalty range: {penalty_range}") + if std_dev_chunk: + difference = abs(current_latency - mean_chunk_latency) + logging.info(f"Round {round_num}. Difference: {difference}") - if current_latency < mean_chunk_latency: - reduction_factor = 1 - (difference / (mean_chunk_latency or 1)) - logging.info(f"Round {current_round}. Reduction factor: {reduction_factor}") - penalty_range *= reduction_factor + penalty_range = 1 / (1 + np.exp(-difference / (std_dev_chunk or 1))) + logging.info(f"Round {round_num}. Penalty range: {penalty_range}") - score = np.exp(-penalty_range) - logging.info(f"Round {current_round}. Chunk score: {score}") + if current_latency < mean_chunk_latency: + reduction_factor = max(0, 1 - (difference / (mean_chunk_latency or 1))) + logging.info(f"Round {round_num}. Reduction factor: {reduction_factor}") + penalty_range *= reduction_factor - for msg in messages_chunk_latency: - if msg["key"] == current_key and msg["round"] == current_round: - msg["mean_chunk_latency"] = mean_chunk_latency - msg["chunk_score"] = score + score = np.exp(-penalty_range) + logging.info(f"Round {round_num}. Chunk score: {score}") - logging.info(f"Round {current_round}. Mean chunk latency for round 4: {mean_chunk_latency}") + Reputation.messages_chunk_latency[current_key]["mean_chunk_latency"] = mean_chunk_latency + Reputation.messages_chunk_latency[current_key]["chunk_score"] = score + Reputation.messages_chunk_latency[current_key]["std_dev_chunk"] = std_dev_chunk + elif round_num > 4: + last_key = (addr, nei, round_num - 1) - elif current_round > 4: current_latency = [ - msg["latency"] - for msg in messages_chunk_latency - if msg["key"] == current_key and msg["round"] == current_round + msg["latency"] for key, msg in Reputation.messages_chunk_latency.items() if key == current_key ] current_latency = current_latency[-1] if current_latency else -1 - logging.info(f"Round {current_round}. Current latency: {current_latency}") + logging.info(f"Round {round_num}. Current latency: {current_latency}") - for msg in messages_chunk_latency: - if msg["key"] == current_key and msg["round"] == current_round - 1: - previous_mean = msg["mean_chunk_latency"] - logging.info(f"Round {current_round}. Previous mean chunk latency: {previous_mean}") - break + previous_mean = [ + msg["mean_chunk_latency"] + for key, msg in Reputation.messages_chunk_latency.items() + if key == last_key + ] + previous_mean = previous_mean[-1] if previous_mean else -1 + logging.info(f"Round {round_num}. Previous mean chunk latency: {previous_mean}") - logging.info(f"Round {current_round}. Current mean chunk latency: {previous_mean}") + previous_std = [ + msg["std_dev_chunk"] for key, msg in Reputation.messages_chunk_latency.items() if key == last_key + ] + previous_std = previous_std[-1] if previous_std else -1 + logging.info(f"Round {round_num}. Previous std dev chunk latency: {previous_std}") - if current_latency != -1 and previous_mean != -1: + if current_latency != -1 and previous_mean != -1 and previous_std != -1: + # Calcular la diferencia y rango de penalización difference = abs(current_latency - previous_mean) - logging.info(f"Round {current_round}. Difference: {difference}") + logging.info(f"Round {round_num}. Difference: {difference}") - penalty_range = difference / previous_mean - logging.info(f"Round {current_round}. Penalty range: {penalty_range}") + penalty_range = 1 / (1 + np.exp(-difference / (previous_std or 1))) + logging.info(f"Round {round_num}. Penalty range: {penalty_range}") if current_latency < previous_mean: - reduction_factor = 1 - (difference / (previous_mean or 1)) - logging.info(f"Round {current_round}. Reduction factor: {reduction_factor}") + reduction_factor = max(0, 1 - (difference / (previous_mean or 1))) + logging.info(f"Round {round_num}. Reduction factor: {reduction_factor}") penalty_range *= reduction_factor + # Calcular el score basado en la penalización score = np.exp(-penalty_range) - logging.info(f"Round {current_round}. Chunk score: {score}") + logging.info(f"Round {round_num}. Chunk score: {score}") - mean_chunk_latency = ((previous_mean + current_latency) / 2) * 1.10 - logging.info(f"Round {current_round}. Update mean chunk latency: {mean_chunk_latency}") + # Actualizar la latencia media + mean_chunk_latency = (previous_mean + current_latency) / 2 + logging.info(f"Round {round_num}. Updated mean chunk latency: {mean_chunk_latency}") + + # Actualizar la métrica en el diccionario + Reputation.messages_chunk_latency[current_key]["mean_chunk_latency"] = mean_chunk_latency + Reputation.messages_chunk_latency[current_key]["chunk_score"] = score + Reputation.messages_chunk_latency[current_key]["std_dev_chunk"] = previous_std else: + logging.info(f"Round {round_num}. Missing data for calculation.") return -1 - - if mean_chunk_latency is not None and score is not None: - for msg in messages_chunk_latency: - if msg["key"] == current_key and msg["round"] == current_round: - msg["mean_chunk_latency"] = mean_chunk_latency - msg["chunk_score"] = score else: + if 0 <= round_num <= 3: + logging.info(f"Round {round_num}. Not enough data to calculate the metric.") + else: + logging.info(f"Round {round_num}. Missing data for calculation.") return -1 return score @@ -685,10 +767,8 @@ def manage_chunk_latency(messages_chunk_latency, addr, nei, current_round): logging.exception("Error managing chunk latency metric") return -1 - # @staticmethod - @staticmethod - def manage_metric_frequency(messages_frequency, addr, nei, current_round): + def manage_metric_frequency(messages_frequency, addr, nei, current_round, scenario): """ Manage the frequency metric using percentiles for normalization, considering the last 4 rounds dynamically. @@ -703,143 +783,95 @@ def manage_metric_frequency(messages_frequency, addr, nei, current_round): int: Messages count. """ try: + if current_round == 0: + return 0.0, 0 + + previous_round = current_round - 1 + logging.info(f"Round {current_round}. Previous round: {previous_round}") current_addr_nei = (addr, nei) relevant_messages = [ - msg for msg in messages_frequency if msg["key"] == current_addr_nei and msg["current_round"] == current_round + msg for msg in messages_frequency if msg["key"] == current_addr_nei and msg["round"] == previous_round ] messages_count = len(relevant_messages) if relevant_messages else 0 + logging.info(f"Round {current_round}. Messages count: {messages_count}") - # Dynamically select rounds based on the current round - if current_round >= 4: - rounds_to_consider = [current_round - 4, current_round - 3, current_round - 2, current_round - 1] - elif current_round == 3: + if previous_round >= 4: + rounds_to_consider = [previous_round - 4, previous_round - 3, previous_round - 2, previous_round - 1] + elif previous_round == 3: rounds_to_consider = [0, 1, 2] - else: - # For rounds 0, 1, and 2, we only consider the rounds available up to the current round - rounds_to_consider = list(range(current_round + 1)) - + elif previous_round == 2: + rounds_to_consider = [0, 1] + elif previous_round == 1: + rounds_to_consider = [0] logging.info(f"Round {current_round}. Rounds to consider: {rounds_to_consider}") previous_counts = [ - len([m for m in messages_frequency if m["key"] == current_addr_nei and m["current_round"] == r]) + len([m for m in messages_frequency if m["key"] == current_addr_nei and m["round"] == r]) for r in rounds_to_consider ] - logging.info(f"Round {current_round}. Previous counts: {previous_counts}") + logging.info(f"Round {current_round}. Total counts of rounds_to_consider: {previous_counts}") # Calculate the 25th and 75th percentiles based on the selected rounds - Reputation.previous_percentile_5_freq[current_addr_nei] = ( - np.percentile(previous_counts, 5) * 1.60 if previous_counts else 0 + Reputation.previous_percentile_25_freq[current_addr_nei] = ( + np.percentile(previous_counts, 25) if previous_counts else 0 ) - Reputation.previous_percentile_95_freq[current_addr_nei] = ( - np.percentile(previous_counts, 95) * 1.60 if previous_counts else 0 + Reputation.previous_percentile_85_freq[current_addr_nei] = ( + np.percentile(previous_counts, 85) * 1.15 if previous_counts else 0 ) normalized_messages = 0.0 # Apply the normalizations using the percentiles - if current_round >= 4: # Ensure there's enough data to calculate percentiles - percentile_5 = Reputation.previous_percentile_5_freq.get(current_addr_nei, 0) - logging.info(f"Round {current_round}. Percentile 5: {percentile_5}") - percentile_95 = Reputation.previous_percentile_95_freq.get(current_addr_nei, 0) - logging.info(f"Round {current_round}. percentile 95: {percentile_95}") - - logging.info(f"Round {current_round}. Messages count: {messages_count}") - if percentile_95 - percentile_5 == 0: - relative_position = (messages_count - percentile_5) / (percentile_95 + 1e-6) - logging.info(f"Round {current_round}. Relative position: {relative_position}") + if previous_round >= 4: # Ensure there's enough data to calculate percentiles + percentile_25 = Reputation.previous_percentile_25_freq.get(current_addr_nei, 0) + logging.info(f"Round {current_round}. percentile_25: {percentile_25}") + percentile_85 = Reputation.previous_percentile_85_freq.get(current_addr_nei, 0) + logging.info(f"Round {current_round}. percentile_85: {percentile_85}") + + # logging.info(f"Round {current_round}. Messages count: {messages_count}") + if percentile_85 - percentile_25 == 0: + relative_position = (messages_count - percentile_25) / (percentile_85) + # logging.info(f"Round {current_round}. Relative position: {relative_position}") else: - relative_position = (messages_count - percentile_5) / (percentile_95 - percentile_5) - logging.info(f"Round {current_round}. Relative position: {relative_position}") + relative_position = (messages_count - percentile_25) / (percentile_85 - percentile_25) + # logging.info(f"Round {current_round}. Relative position: {relative_position}") normalized_messages = 1 - 1 / (1 + np.exp(-(relative_position - 1))) - logging.info(f"Round {current_round}. Normalized messages sin max: {normalized_messages}") + # logging.info(f"Round {current_round}. Normalized messages sin max: {normalized_messages}") normalized_messages = max(0.01, normalized_messages) - logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") - - # Update the percentiles based on the last available rounds - Reputation.previous_percentile_5_freq[current_addr_nei] = ( - np.percentile([percentile_5] + previous_counts, 5) * 1.60 - ) - Reputation.previous_percentile_95_freq[current_addr_nei] = ( - np.percentile([percentile_95] + previous_counts, 95) * 1.60 - ) + # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") + + current_dir = os.path.dirname(os.path.realpath(__file__)) + csv_path = os.path.join(current_dir, f"{scenario}/metrics_{addr}_{nei}/messages_frequency.csv") + logging.info(f"Round {current_round}. CSV path: {csv_path}") + csv_dir = os.path.dirname(csv_path) + + if not os.path.exists(csv_dir): + os.makedirs(csv_dir) + + data = { + "addr": addr, + "nei": nei, + "round": current_round, + "messages_count": messages_count, + "normalized_messages": normalized_messages, + "percentile_25": Reputation.previous_percentile_25_freq[current_addr_nei], + "percentile_85": Reputation.previous_percentile_85_freq[current_addr_nei], + } + try: + with open(csv_path, mode="a", newline="") as file: + writer = csv.DictWriter(file, fieldnames=data.keys()) + if file.tell() == 0: + writer.writeheader() + writer.writerow(data) + except Exception: + logging.exception("Error saving messages frequency data to CSV") return normalized_messages, messages_count except Exception: logging.exception("Error managing metric frequency") return 0.0, 0 - # def manage_metric_frequency(messages_frequency, addr, nei, current_round): - # """ - # Manage the frequency metric using percentiles for normalization. - - # Args: - # messages_frequency (list): List of messages frequency. - # addr (str): Source IP address. - # nei (str): Destination IP address. - # current_round (int): Current round number. - - # Returns: - # float: Normalized frequency value. - # int: Messages count. - # """ - # try: - # current_addr_nei = (addr, nei) - # relevant_messages = [msg for msg in messages_frequency if msg["key"] == current_addr_nei and msg["current_round"] == current_round] - # messages_count = len(relevant_messages) if relevant_messages else 0 - - # # Calculate percentiles for rounds 0-4 only - # if current_round >= 0 and current_round <= 3: - # previous_counts = [ - # len([m for m in messages_frequency if m["key"] == current_addr_nei and m["current_round"] == r]) - # for r in range(4) - # ] - # # Calculate the 25th and 75th percentiles as lower and upper bounds - # Reputation.previous_percentile_25_freq[current_addr_nei] = np.percentile(previous_counts, 25) * 1.10 if previous_counts else 0 - # # logging.info(f"Round {current_round}. Reputation.previous_percentile_25_freq: {Reputation.previous_percentile_25_freq}") - # Reputation.previous_percentile_75_freq[current_addr_nei] = np.percentile(previous_counts, 75) * 1.10 if previous_counts else 0 - # # logging.info(f"Round {current_round}. Reputation.previous_percentile_75_freq: {Reputation.previous_percentile_75_freq}") - # normalized_messages = 0.0 - # elif current_round > 3: - # if current_addr_nei not in Reputation.previous_percentile_25_freq: - # prev_addr_nei = (addr, nei, current_round - 1) - # logging.info(f"Round {current_round}. Previous addr nei: {prev_addr_nei}") - # percentile_25 = Reputation.previous_percentile_25_freq.get(prev_addr_nei, 0) - # logging.info(f"Round {current_round}. Percentile 25: {percentile_25}") - # percentile_75 = Reputation.previous_percentile_75_freq.get(prev_addr_nei, 0) - # logging.info(f"Round {current_round}. Percentile 75: {percentile_75}") - # else: - # percentile_25 = Reputation.previous_percentile_25_freq.get(current_addr_nei, 0) - # logging.info(f"Round {current_round}. Percentile 25: {percentile_25}") - # percentile_75 = Reputation.previous_percentile_75_freq.get(current_addr_nei, 0) - # logging.info(f"Round {current_round}. Percentile 75: {percentile_75}") - - # logging.info(f"Round {current_round}. Messages count: {messages_count}") - # if messages_count <= percentile_25: - # logging.info(f"Round {current_round}. Messages count <= percentile 25") - # normalized_messages = 1 - (percentile_25 - messages_count) / (percentile_25 + percentile_75) - # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") - # elif messages_count >= percentile_75: - # logging.info(f"Round {current_round}. Messages count >= percentile 75") - # normalized_messages = 1 / (1 + np.exp((messages_count - percentile_75) / (percentile_75 - percentile_25 + 1e-6))) - # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") - # else: - # logging.info(f"Round {current_round}. Percentile 25 < Messages count < Percentile 75") - # relative_position = (messages_count - percentile_25) / (percentile_75 - percentile_25) - # logging.info(f"Round {current_round}. Relative position: {relative_position}") - # normalized_messages = 1 - relative_position / 2 - # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") - - # Reputation.previous_percentile_25_freq[current_addr_nei] = np.percentile([percentile_25, messages_count], 25) * 1.10 - # Reputation.previous_percentile_75_freq[current_addr_nei] = np.percentile([percentile_75, messages_count], 75) * 1.10 - # else: - # normalized_messages = 0.0 - - # return normalized_messages, messages_count - # except Exception as e: - # logging.error(f"Error managing metric frequency: {e}") - # return 0.0, 0 - @staticmethod def manage_communication(type_message, num_round, current_round, time, addr, nei): """ @@ -893,7 +925,7 @@ def manage_communication(type_message, num_round, current_round, time, addr, nei # logging.info(f"Round {current_round}. Model times: {model_times}") if model_times: - mean_time = np.mean(model_times) + mean_time = np.mean(model_times) * 1.20 # logging.info(f"Round {current_round}. Mean time communication: {mean_time}") difference = abs(time - mean_time) @@ -967,7 +999,7 @@ def manage_communication(type_message, num_round, current_round, time, addr, nei score = np.exp(-penalty_range) # logging.info(f"Round {current_round}. Communication score: {score}") - mean_time = (previous_mean + time) / 2 + mean_time = ((previous_mean + time) / 2) * 1.20 # logging.info(f"Round {current_round}. Update mean time communication: {mean_time}") Reputation.communication_data[current_addr_nei]["mean_time"] = mean_time @@ -1003,25 +1035,33 @@ def save_chunk_history(addr, nei, chunk, current_round): Reputation.chunk_history[key] = {"chunk_latency": chunk} - # logging.info(f"chunk history: {Reputation.chunk_history} | chunk: {chunk} | current_round: {current_round}") + logging.info(f"chunk history: {Reputation.chunk_history} | chunk: {chunk} | current_round: {current_round}") if chunk >= 0 and current_round > 4: - # logging.info(f" if chunk >= 0 and current_round > 4") - previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 1), {}).get( - "avg_chunk_latency", 0 - ) - # logging.info(f"Previous avg chunk latency: {previous_avg}") + logging.info(" if chunk >= 0 and current_round > 4") + previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 1), {}).get("chunk_latency", 0) + logging.info(f"Previous avg chunk latency: {previous_avg}") + + if isinstance(previous_avg, np.ndarray): + previous_avg = previous_avg.item() + logging.info(f"Previous avg chunk latency: {previous_avg}") avg_chunk_latency = (chunk + previous_avg) / 2 if previous_avg is not None else chunk return avg_chunk_latency elif chunk == -1 and current_round > 4: - # logging.info(f" elif chunk == -1 and current_round > 4") - previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 1), {}).get( - "avg_chunk_latency", 0 - ) - # logging.info(f"Previous avg chunk latency: {previous_avg}") + logging.info(" elif chunk == -1 and current_round > 4") + previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 1), {}).get("chunk_latency", 0) + if previous_avg is None: + previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 2), {}).get( + "chunk_latency", 0 + ) + logging.info(f"Previous avg chunk latency: {previous_avg}") + + if isinstance(previous_avg, np.ndarray): + previous_avg = previous_avg.item() + logging.info(f"Previous avg chunk latency: {previous_avg}") - avg_chunk_latency = previous_avg - (previous_avg * 0.1) + avg_chunk_latency = previous_avg - (previous_avg * 0.1) if previous_avg is not None else 0 else: avg_chunk_latency = 0 From 393bb0c2c816efab77d4057f8f467b116e228a22 Mon Sep 17 00:00:00 2001 From: Isaac Marroqui Date: Mon, 16 Dec 2024 18:29:35 +0100 Subject: [PATCH 10/43] changes to improve metrics fraction of changed parameters --- nebula/core/network/communications.py | 54 +-- nebula/core/reputation/Reputation.py | 534 +++++++------------------- 2 files changed, 134 insertions(+), 454 deletions(-) diff --git a/nebula/core/network/communications.py b/nebula/core/network/communications.py index d57b1f266..a8b95c00b 100755 --- a/nebula/core/network/communications.py +++ b/nebula/core/network/communications.py @@ -274,7 +274,7 @@ async def handle_model_message(self, source, message): # Manage parameters of models parameters_local = self.engine.trainer.get_model_parameters() - self.fraction_of_parameters_changed(source, parameters_local, decoded_model, current_round) + self.fraction_of_parameters_changed(source, parameters_local, decoded_model, message.round) if message.round != current_round and message.round != -1: logging.info( @@ -301,57 +301,7 @@ async def handle_model_message(self, source, message): # non-starting nodes receive the initialized model from the starting node if not self.engine.get_federation_ready_lock().locked() or self.engine.get_initialization_status(): decoded_model = self.engine.trainer.deserialize_model(message.parameters) - # if self.config.participant["adaptive_args"]["model_similarity"]: - # logging.info("🤖 handle_model_message | Checking model similarity") - # cosine_value = cosine_metric( - # self.engine.trainer.get_model_parameters(), - # decoded_model, - # similarity=True, - # ) - # euclidean_value = euclidean_metric( - # self.engine.trainer.get_model_parameters(), - # decoded_model, - # similarity=True, - # ) - # minkowski_value = minkowski_metric( - # self.engine.trainer.get_model_parameters(), - # decoded_model, - # p=2, - # similarity=True, - # ) - # manhattan_value = manhattan_metric( - # self.engine.trainer.get_model_parameters(), - # decoded_model, - # similarity=True, - # ) - # pearson_correlation_value = pearson_correlation_metric( - # self.engine.trainer.get_model_parameters(), - # decoded_model, - # similarity=True, - # ) - # jaccard_value = jaccard_metric( - # self.engine.trainer.get_model_parameters(), - # decoded_model, - # similarity=True, - # ) - # file = f"{self.engine.log_dir}/participant_{self.engine.idx}_similarity.csv" - # logging.info(f"self.engine.log_dir: {self.engine.log_dir}") - # directory = os.path.dirname(file) - # os.makedirs(directory, exist_ok=True) - # if not os.path.isfile(file): - # with open(file, "w") as f: - # f.write("timestamp,source_ip,round,current_round,cosine,euclidean,minkowski,manhattan,pearson_correlation,jaccard\n") - # with open(file, "a") as f: - # f.write(f"{datetime.now()}, {source}, {message.round}, {current_round}, {cosine_value}, {euclidean_value}, {minkowski_value}, {manhattan_value}, {pearson_correlation_value}, {jaccard_value}\n") - - # # Manage communication latency - # self.store_receive_timestamp(source, "model", message.round) - # self.calculate_latency(source, "model") - - # # Manage parameters of models - # parameters_local = self.engine.trainer.get_model_parameters() - # self.fraction_of_parameters_changed(source, parameters_local, decoded_model, current_round) - + await self.engine.aggregator.include_model_in_buffer( decoded_model, message.weight, diff --git a/nebula/core/reputation/Reputation.py b/nebula/core/reputation/Reputation.py index 8da49c39c..d1452d9c8 100644 --- a/nebula/core/reputation/Reputation.py +++ b/nebula/core/reputation/Reputation.py @@ -140,6 +140,7 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro avg_communication_time_normalized = 0 avg_messages_frequency_normalized = 0 fraction_score = 0 + fraction_score_normalized = 0 fraction_score_asign = 0 messages_chunk_normalized = 0 avg_chunk_latency = 0 @@ -169,11 +170,9 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro time = metric["time_message"]["time"] type_message = metric["time_message"]["type_message"] previous_round_time = current_round - 1 - logging.info( - f"Current round time: {current_round_time}, previous round time: {previous_round_time}" - ) + # logging.info(f"Current round time: {current_round_time}, previous round time: {previous_round_time}") if round_time == previous_round_time: - logging.info(f"Time message: {time}, type message: {type_message}, round: {round_time}") + # logging.info(f"Time message: {time}, type message: {type_message}, round: {round_time}") Reputation.messages_frequency.append({ "time_message": time, "type_message": type_message, @@ -189,7 +188,7 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro threshold = metric["fraction_of_params_changed"]["threshold"] changes_record = metric["fraction_of_params_changed"]["changes_record"] if round_fraction == current_round: - Reputation.analyze_anomalies( + fraction_score_normalized = Reputation.analyze_anomalies( addr, nei, round_fraction, @@ -199,6 +198,7 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro changes_record, changed_params, total_params, + scenario ) # logging.info(f"Reputation.fraction_changed_history: {Reputation.fraction_changed_history}") if "chunk_latency" in metric: @@ -212,7 +212,7 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro nei, latency, ) - logging.info(f"messages_chunk_normalized: {messages_chunk_normalized}") + # logging.info(f"messages_chunk_normalized: {messages_chunk_normalized}") similarity_file = os.path.join(log_dir, f"participant_{id_node}_similarity.csv") similarity_reputation = Reputation.read_similarity_file(similarity_file, nei) @@ -227,9 +227,7 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro avg_communication_time_normalized = Reputation.communication_history[(addr, nei)][ current_round - 1 ]["avg_communication"] - logging.info( - f"Avg communication is None and current_round = {current_round}, avg_communication_time_normalized: {avg_communication_time_normalized}" - ) + # logging.info(f"Avg communication is None and current_round = {current_round}, avg_communication_time_normalized: {avg_communication_time_normalized}") if messages_chunk_normalized >= 0: avg_chunk_latency = Reputation.save_chunk_history( @@ -239,9 +237,7 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro avg_chunk_latency = Reputation.chunk_history[(addr, nei)][current_round - 1][ "chunk_latency" ] - logging.info( - f"Avg chunk latency is None and current_round = {current_round}, chunk_latency: {avg_chunk_latency}" - ) + # logging.info(f"Avg chunk latency is None and current_round = {current_round}, chunk_latency: {avg_chunk_latency}") if Reputation.messages_frequency is not None: messages_frequency_normalized, messages_frequency_count = Reputation.manage_metric_frequency( @@ -260,52 +256,47 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro f"Avg messages frequency is None and curret_round = {current_round}, avg_messages_frequency_normalized: {avg_messages_frequency_normalized}" ) - if Reputation.fraction_changed_history is not None: - key_current_round = (addr, nei, current_round) if current_round else None - key_previous_round = (addr, nei, current_round - 1) if current_round - 1 > 0 else None - fraction_current_round = None - fraction_previous_round = None - converge_fraction = None - - # logging.info(f"Key_current_round: {key_current_round}, key_previous_round: {key_previous_round}") - # logging.info(f"Reputation.fraction_changed_history: {Reputation.fraction_changed_history}") - - if key_current_round in Reputation.fraction_changed_history: - fraction_score = Reputation.fraction_changed_history[key_current_round].get( - "fraction_score" - ) - fraction_current_round = fraction_score if fraction_score is not None else None - # logging.info(f"Fraction score current round: {fraction_current_round}") - - if key_previous_round is not None and key_previous_round in Reputation.fraction_changed_history: - fraction_score = Reputation.fraction_changed_history[key_previous_round].get( - "fraction_score" - ) - fraction_previous_round = fraction_score if fraction_score is not None else None - # logging.info(f"Fraction score previous round: {fraction_previous_round}") - - if fraction_current_round is not None and fraction_previous_round is not None: - converge_fraction = (fraction_current_round + fraction_previous_round) / 2 - - fraction_score_asign = converge_fraction if converge_fraction is not None else 0 - # logging.info(f"Fraction score: {fraction_score_asign}") - - # if Reputation.messages_chunk_latency is not None: - # messages_chunk_normalized = Reputation.manage_chunk_latency( - # Reputation.messages_chunk_latency, addr, nei, current_round - # ) - # logging.info(f"Score chunk: {messages_chunk_normalized}") - # avg_chunk_latency = Reputation.save_chunk_history( - # addr, nei, messages_chunk_normalized, current_round - # ) - # logging.info(f"Return avg chunk latency: {avg_chunk_latency}") - # if avg_chunk_latency is None and current_round > 4: - # avg_chunk_latency = Reputation.chunk_history[(addr, nei, current_round - 1)][ - # "chunk_latency" - # ] - # logging.info( - # f"Avg chunk latency is None and current_round = {current_round}, chunk_latency: {avg_chunk_latency}" - # ) + if current_round >= 5: + if fraction_score_normalized > 0: + key_previous_round = (addr, nei, current_round - 1) if current_round - 1 > 0 else None + fraction_previous_round = None + + if key_previous_round is not None and key_previous_round in Reputation.fraction_changed_history: + fraction_score = Reputation.fraction_changed_history[key_previous_round].get("fraction_score") + fraction_previous_round = fraction_score if fraction_score is not None else None + logging.info(f"Fraction score previous round: {fraction_previous_round}") + + if fraction_previous_round is not None: + fraction_score_asign = (fraction_score_normalized + fraction_previous_round) / 2 + logging.info(f"Fraction score normalized: {fraction_score_asign}") + Reputation.fraction_changed_history[(addr, nei, current_round)]["fraction_score"] = fraction_score_asign + else: + fraction_score_asign = fraction_score_normalized + logging.info(f"Fraction score normalized: {fraction_score_asign}") + Reputation.fraction_changed_history[(addr, nei, current_round)]["fraction_score"] = fraction_score_asign + else: + logging.info(f"No fraction score to assign") + fraction_previous_round = None + key_previous_round = (addr, nei, current_round - 1) if current_round - 1 > 0 else None + if key_previous_round is not None and key_previous_round in Reputation.fraction_changed_history: + fraction_score = Reputation.fraction_changed_history[key_previous_round].get("fraction_score") + fraction_previous_round = fraction_score if fraction_score is not None else None + logging.info(f"Fraction score previous round: {fraction_previous_round}") + + if fraction_previous_round is not None: + fraction_score_asign = fraction_previous_round - (fraction_previous_round * 0.1) + logging.info(f"Fraction score normalized: {fraction_score_asign}") + + if fraction_previous_round is None: + fraction_neighbors_scores = {} + for key, value in Reputation.fraction_changed_history.items(): + fraction_neighbors_scores[key] = value.get("fraction_score") + logging.info(f"Fraction neighbors {key} scores: {fraction_neighbors_scores}") + + fraction_score_asign = np.mean(list(fraction_neighbors_scores.values())) + logging.info(f"Fraction score normalized: {fraction_score_asign}") + else: + fraction_score_asign = 0 # Weights for each metric if current_round is not None: @@ -323,11 +314,11 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro # weight_to_message_frequency = 0.3 # weight_to_chunk = 0.2 # Weight to scenarios with noise injection - weight_to_similarity = 0.3 + weight_to_similarity = 0.4 weight_to_communication = 0.1 weight_to_fraction = 0.3 weight_to_message_frequency = 0.1 - weight_to_chunk = 0.2 + weight_to_chunk = 0.1 # Weight to scenarios with flood # weight_to_similarity = 0.1 # weight_to_communication = 0.2 @@ -341,12 +332,6 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro weight_to_message_frequency = 0.0 weight_to_chunk = 0.0 - # logging.info(f"Current round: {current_round}") - # logging.info(f"Communication time normalized: {communication_time_normalized} and weight: {weight_to_communication}") - # logging.info(f"Messages frequency normalized: {avg_messages_frequency_normalized} and weight: {weight_to_message_frequency}") - # logging.info(f"Similarity reputation: {similarity_reputation} and weight: {weight_to_similarity}") - # logging.info(f"Fraction score: {fraction_score_asign} and weight: {weight_to_fraction}") - # logging.info(f"Chunk latency: {avg_chunk_latency} and weight: {weight_to_chunk}") # Reputation calculation reputation = ( @@ -431,7 +416,7 @@ def create_graphics_to_metrics( @staticmethod def analyze_anomalies( - addr, nei, round_num, current_round, fraction_changed, threshold, changes_record, changed_params, total_params + addr, nei, round_num, current_round, fraction_changed, threshold, changes_record, changed_params, total_params, scenario ): """ Analyze anomalies in the fraction of parameters changed. @@ -476,20 +461,21 @@ def analyze_anomalies( if past_fractions: mean_fraction = np.mean(past_fractions) - # logging.info(f"Round: {round_num}, Mean fraction: {mean_fraction}") + logging.info(f"Round: {round_num}, Mean fraction: {mean_fraction}") std_dev_fraction = np.std(past_fractions) - # logging.info(f"Round: {round_num}, Std dev fraction: {std_dev_fraction}") + logging.info(f"Round: {round_num}, Std dev fraction: {std_dev_fraction}") Reputation.fraction_changed_history[key]["mean_fraction"] = mean_fraction Reputation.fraction_changed_history[key]["std_dev_fraction"] = std_dev_fraction if past_thresholds: mean_threshold = np.mean(past_thresholds) - # logging.info(f"Round: {round_num}, Mean threshold: {mean_threshold}") + logging.info(f"Round: {round_num}, Mean threshold: {mean_threshold}") std_dev_threshold = np.std(past_thresholds) - # logging.info(f"Round: {round_num}, Std dev threshold: {std_dev_threshold}") + logging.info(f"Round: {round_num}, Std dev threshold: {std_dev_threshold}") Reputation.fraction_changed_history[key]["mean_threshold"] = mean_threshold Reputation.fraction_changed_history[key]["std_dev_threshold"] = std_dev_threshold + return 0 else: fraction_value = 0 threshold_value = 0 @@ -501,45 +487,45 @@ def analyze_anomalies( std_dev_fraction_prev = Reputation.fraction_changed_history[prev_key]["std_dev_fraction"] mean_threshold_prev = Reputation.fraction_changed_history[prev_key]["mean_threshold"] std_dev_threshold_prev = Reputation.fraction_changed_history[prev_key]["std_dev_threshold"] - # logging.info(f"Round: {round_num}, Mean fraction: {mean_fraction_prev}, Std dev fraction: {std_dev_fraction_prev}, Mean threshold: {mean_threshold_prev}, Std dev threshold: {std_dev_threshold_prev}") + logging.info(f"Round: {round_num}, Mean fraction: {mean_fraction_prev}, Std dev fraction: {std_dev_fraction_prev}, Mean threshold: {mean_threshold_prev}, Std dev threshold: {std_dev_threshold_prev}") current_fraction = Reputation.fraction_changed_history[key]["fraction_changed"] current_threshold = Reputation.fraction_changed_history[key]["threshold"] - # logging.info(f"Round: {round_num}, Current fraction: {current_fraction}, Current threshold: {current_threshold}") + logging.info(f"Round: {round_num}, Current fraction: {current_fraction}, Current threshold: {current_threshold}") # low_mean_fraction_prev = mean_fraction_prev - std_dev_fraction_prev - upper_mean_fraction_prev = (mean_fraction_prev * 1.2) + (std_dev_fraction_prev * 1.2) + upper_mean_fraction_prev = (mean_fraction_prev) + (std_dev_fraction_prev) # low_mean_threshold_prev = mean_threshold_prev - std_dev_threshold_prev - upper_mean_threshold_prev = (mean_threshold_prev * 1.2) + (std_dev_threshold_prev * 1.2) - # logging.info(f"Round: {round_num}, Upper mean fraction: {upper_mean_fraction_prev}, Upper mean threshold: {upper_mean_threshold_prev}") + upper_mean_threshold_prev = (mean_threshold_prev) + (std_dev_threshold_prev) + logging.info(f"Round: {round_num}, Upper mean fraction: {upper_mean_fraction_prev}, Upper mean threshold: {upper_mean_threshold_prev}") # fraction_anomaly = not (low_mean_fraction_prev <= current_fraction <= upper_mean_fraction_prev) # threshold_anomaly = not (low_mean_threshold_prev <= current_threshold <= upper_mean_threshold_prev) fraction_anomaly = current_fraction > upper_mean_fraction_prev threshold_anomaly = current_threshold > upper_mean_threshold_prev - # logging.info(f"Round: {round_num}, Fraction anomaly: {fraction_anomaly}, Threshold anomaly: {threshold_anomaly}") + logging.info(f"Round: {round_num}, Fraction anomaly: {fraction_anomaly}, Threshold anomaly: {threshold_anomaly}") Reputation.fraction_changed_history[key]["fraction_anomaly"] = fraction_anomaly Reputation.fraction_changed_history[key]["threshold_anomaly"] = threshold_anomaly # Calculate the fraction score - k_fraction = 1 / std_dev_fraction_prev if std_dev_fraction_prev != 0 else 1 - # logging.info(f"Round: {round_num}, K fraction: {k_fraction}") - k_threshold = 1 / std_dev_threshold_prev if std_dev_threshold_prev != 0 else 1 - # logging.info(f"Round: {round_num}, K threshold: {k_threshold}") + k_fraction = std_dev_fraction_prev if std_dev_fraction_prev != 0 else 1 + logging.info(f"Round: {round_num}, K fraction: {k_fraction}") + k_threshold = std_dev_threshold_prev if std_dev_threshold_prev != 0 else 1 + logging.info(f"Round: {round_num}, K threshold: {k_threshold}") fraction_value = ( 1 - (1 / (1 + np.exp(-k_fraction * (current_fraction - mean_fraction_prev)))) if current_fraction is not None and mean_fraction_prev is not None else 0 ) - # logging.info(f"Round: {round_num}, Fraction_value: {fraction_value}") + logging.info(f"Round: {round_num}, Fraction_value: {fraction_value}") threshold_value = ( 1 - (1 / (1 + np.exp(-k_threshold * (current_threshold - mean_threshold_prev)))) if current_threshold is not None and mean_threshold_prev is not None else 0 ) - # logging.info(f"Round: {round_num}, Threshold_value: {threshold_value}") + logging.info(f"Round: {round_num}, Threshold_value: {threshold_value}") # if threshold_anomaly: # fraction_weight = 0.8 @@ -555,8 +541,8 @@ def analyze_anomalies( threshold_weight = 0.5 fraction_score = fraction_weight * fraction_value + threshold_weight * threshold_value - # logging.info(f"Round: {round_num}, Fraction score: {fraction_score}") - Reputation.fraction_changed_history[key]["fraction_score"] = fraction_score + logging.info(f"Round: {round_num}, Fraction score: {fraction_score}") + # Reputation.fraction_changed_history[key]["fraction_score"] = fraction_score # Upload the values to the history Reputation.fraction_changed_history[key]["mean_fraction"] = (current_fraction + mean_fraction_prev) / 2 @@ -569,13 +555,18 @@ def analyze_anomalies( Reputation.fraction_changed_history[key]["std_dev_threshold"] = np.sqrt( ((0.1 * (current_threshold - mean_threshold_prev) ** 2) + std_dev_threshold_prev**2) / 2 ) - # logging.info(f"Round: {round_num}, Mean fraction: {Reputation.fraction_changed_history[key]['mean_fraction']}") - # logging.info(f"Round: {round_num}, Std dev fraction: {Reputation.fraction_changed_history[key]['std_dev_fraction']}") - # logging.info(f"Round: {round_num}, Mean threshold: {Reputation.fraction_changed_history[key]['mean_threshold']}") - # logging.info(f"Round: {round_num}, Std dev threshold: {Reputation.fraction_changed_history[key]['std_dev_threshold']}") - # Score not negative - Reputation.fraction_changed_history[key]["fraction_score"] = max(fraction_score, 0) + data = { + "addr": addr, + "nei": nei, + "round": current_round, + "fraction_changed": current_fraction, + "threshold": current_threshold, + "fraction_score": fraction_score, + } + Reputation.metrics(scenario, data, addr, nei, "fraction_changed") + + return max(fraction_score, 0) except Exception: logging.exception("Error analyzing anomalies") @@ -663,7 +654,7 @@ def manage_chunk_latency(round_num, addr, nei, latency): "chunk_score": None, "std_dev_chunk": None, }) - logging.info(f"Round {round_num}. Messages chunk latency: {Reputation.messages_chunk_latency}") + # logging.info(f"Round {round_num}. Messages chunk latency: {Reputation.messages_chunk_latency}") if round_num == 4: latencies = [ @@ -671,34 +662,34 @@ def manage_chunk_latency(round_num, addr, nei, latency): for key, msg in Reputation.messages_chunk_latency.items() if key[:2] == current_key[:2] and msg["round"] < 4 ] - logging.info(f"Round {round_num}. Latencies for rounds 0-3: {latencies}") + # logging.info(f"Round {round_num}. Latencies for rounds 0-3: {latencies}") if latencies: mean_chunk_latency = np.mean(latencies) std_dev_chunk = np.std(latencies) - logging.info(f"Round {round_num}. Mean chunk latency for rounds 0-3: {mean_chunk_latency}") - logging.info(f"Round {round_num}. Std dev chunk latency for rounds 0-3: {std_dev_chunk}") + # logging.info(f"Round {round_num}. Mean chunk latency for rounds 0-3: {mean_chunk_latency}") + # logging.info(f"Round {round_num}. Std dev chunk latency for rounds 0-3: {std_dev_chunk}") current_latency = [ msg["latency"] for key, msg in Reputation.messages_chunk_latency.items() if key == current_key ] current_latency = current_latency[-1] if current_latency else -1 - logging.info(f"Round {round_num}. Current latency: {current_latency}") + # logging.info(f"Round {round_num}. Current latency: {current_latency}") if std_dev_chunk: difference = abs(current_latency - mean_chunk_latency) - logging.info(f"Round {round_num}. Difference: {difference}") + # logging.info(f"Round {round_num}. Difference: {difference}") penalty_range = 1 / (1 + np.exp(-difference / (std_dev_chunk or 1))) - logging.info(f"Round {round_num}. Penalty range: {penalty_range}") + # logging.info(f"Round {round_num}. Penalty range: {penalty_range}") if current_latency < mean_chunk_latency: reduction_factor = max(0, 1 - (difference / (mean_chunk_latency or 1))) - logging.info(f"Round {round_num}. Reduction factor: {reduction_factor}") + # logging.info(f"Round {round_num}. Reduction factor: {reduction_factor}") penalty_range *= reduction_factor score = np.exp(-penalty_range) - logging.info(f"Round {round_num}. Chunk score: {score}") + # logging.info(f"Round {round_num}. Chunk score: {score}") Reputation.messages_chunk_latency[current_key]["mean_chunk_latency"] = mean_chunk_latency Reputation.messages_chunk_latency[current_key]["chunk_score"] = score @@ -710,7 +701,7 @@ def manage_chunk_latency(round_num, addr, nei, latency): msg["latency"] for key, msg in Reputation.messages_chunk_latency.items() if key == current_key ] current_latency = current_latency[-1] if current_latency else -1 - logging.info(f"Round {round_num}. Current latency: {current_latency}") + # logging.info(f"Round {round_num}. Current latency: {current_latency}") previous_mean = [ msg["mean_chunk_latency"] @@ -718,34 +709,34 @@ def manage_chunk_latency(round_num, addr, nei, latency): if key == last_key ] previous_mean = previous_mean[-1] if previous_mean else -1 - logging.info(f"Round {round_num}. Previous mean chunk latency: {previous_mean}") + # logging.info(f"Round {round_num}. Previous mean chunk latency: {previous_mean}") previous_std = [ msg["std_dev_chunk"] for key, msg in Reputation.messages_chunk_latency.items() if key == last_key ] previous_std = previous_std[-1] if previous_std else -1 - logging.info(f"Round {round_num}. Previous std dev chunk latency: {previous_std}") + # logging.info(f"Round {round_num}. Previous std dev chunk latency: {previous_std}") if current_latency != -1 and previous_mean != -1 and previous_std != -1: # Calcular la diferencia y rango de penalización difference = abs(current_latency - previous_mean) - logging.info(f"Round {round_num}. Difference: {difference}") + # logging.info(f"Round {round_num}. Difference: {difference}") penalty_range = 1 / (1 + np.exp(-difference / (previous_std or 1))) - logging.info(f"Round {round_num}. Penalty range: {penalty_range}") + # logging.info(f"Round {round_num}. Penalty range: {penalty_range}") if current_latency < previous_mean: reduction_factor = max(0, 1 - (difference / (previous_mean or 1))) - logging.info(f"Round {round_num}. Reduction factor: {reduction_factor}") + # logging.info(f"Round {round_num}. Reduction factor: {reduction_factor}") penalty_range *= reduction_factor # Calcular el score basado en la penalización score = np.exp(-penalty_range) - logging.info(f"Round {round_num}. Chunk score: {score}") + # logging.info(f"Round {round_num}. Chunk score: {score}") # Actualizar la latencia media mean_chunk_latency = (previous_mean + current_latency) / 2 - logging.info(f"Round {round_num}. Updated mean chunk latency: {mean_chunk_latency}") + # logging.info(f"Round {round_num}. Updated mean chunk latency: {mean_chunk_latency}") # Actualizar la métrica en el diccionario Reputation.messages_chunk_latency[current_key]["mean_chunk_latency"] = mean_chunk_latency @@ -793,7 +784,7 @@ def manage_metric_frequency(messages_frequency, addr, nei, current_round, scenar msg for msg in messages_frequency if msg["key"] == current_addr_nei and msg["round"] == previous_round ] messages_count = len(relevant_messages) if relevant_messages else 0 - logging.info(f"Round {current_round}. Messages count: {messages_count}") + # logging.info(f"Round {current_round}. Messages count: {messages_count}") if previous_round >= 4: rounds_to_consider = [previous_round - 4, previous_round - 3, previous_round - 2, previous_round - 1] @@ -801,15 +792,15 @@ def manage_metric_frequency(messages_frequency, addr, nei, current_round, scenar rounds_to_consider = [0, 1, 2] elif previous_round == 2: rounds_to_consider = [0, 1] - elif previous_round == 1: - rounds_to_consider = [0] - logging.info(f"Round {current_round}. Rounds to consider: {rounds_to_consider}") + else: + rounds_to_consider = list(range(current_round + 1)) + # logging.info(f"Round {current_round}. Rounds to consider: {rounds_to_consider}") previous_counts = [ len([m for m in messages_frequency if m["key"] == current_addr_nei and m["round"] == r]) for r in rounds_to_consider ] - logging.info(f"Round {current_round}. Total counts of rounds_to_consider: {previous_counts}") + # logging.info(f"Round {current_round}. Total counts of rounds_to_consider: {previous_counts}") # Calculate the 25th and 75th percentiles based on the selected rounds Reputation.previous_percentile_25_freq[current_addr_nei] = ( @@ -824,9 +815,9 @@ def manage_metric_frequency(messages_frequency, addr, nei, current_round, scenar # Apply the normalizations using the percentiles if previous_round >= 4: # Ensure there's enough data to calculate percentiles percentile_25 = Reputation.previous_percentile_25_freq.get(current_addr_nei, 0) - logging.info(f"Round {current_round}. percentile_25: {percentile_25}") + # logging.info(f"Round {current_round}. percentile_25: {percentile_25}") percentile_85 = Reputation.previous_percentile_85_freq.get(current_addr_nei, 0) - logging.info(f"Round {current_round}. percentile_85: {percentile_85}") + # logging.info(f"Round {current_round}. percentile_85: {percentile_85}") # logging.info(f"Round {current_round}. Messages count: {messages_count}") if percentile_85 - percentile_25 == 0: @@ -841,14 +832,6 @@ def manage_metric_frequency(messages_frequency, addr, nei, current_round, scenar normalized_messages = max(0.01, normalized_messages) # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") - current_dir = os.path.dirname(os.path.realpath(__file__)) - csv_path = os.path.join(current_dir, f"{scenario}/metrics_{addr}_{nei}/messages_frequency.csv") - logging.info(f"Round {current_round}. CSV path: {csv_path}") - csv_dir = os.path.dirname(csv_path) - - if not os.path.exists(csv_dir): - os.makedirs(csv_dir) - data = { "addr": addr, "nei": nei, @@ -858,14 +841,7 @@ def manage_metric_frequency(messages_frequency, addr, nei, current_round, scenar "percentile_25": Reputation.previous_percentile_25_freq[current_addr_nei], "percentile_85": Reputation.previous_percentile_85_freq[current_addr_nei], } - try: - with open(csv_path, mode="a", newline="") as file: - writer = csv.DictWriter(file, fieldnames=data.keys()) - if file.tell() == 0: - writer.writeheader() - writer.writerow(data) - except Exception: - logging.exception("Error saving messages frequency data to CSV") + Reputation.metrics(scenario, data, addr, nei, "frequency") return normalized_messages, messages_count except Exception: @@ -1035,31 +1011,31 @@ def save_chunk_history(addr, nei, chunk, current_round): Reputation.chunk_history[key] = {"chunk_latency": chunk} - logging.info(f"chunk history: {Reputation.chunk_history} | chunk: {chunk} | current_round: {current_round}") + # logging.info(f"chunk history: {Reputation.chunk_history} | chunk: {chunk} | current_round: {current_round}") if chunk >= 0 and current_round > 4: - logging.info(" if chunk >= 0 and current_round > 4") + # logging.info(" if chunk >= 0 and current_round > 4") previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 1), {}).get("chunk_latency", 0) - logging.info(f"Previous avg chunk latency: {previous_avg}") + # logging.info(f"Previous avg chunk latency: {previous_avg}") if isinstance(previous_avg, np.ndarray): previous_avg = previous_avg.item() - logging.info(f"Previous avg chunk latency: {previous_avg}") + # logging.info(f"Previous avg chunk latency: {previous_avg}") avg_chunk_latency = (chunk + previous_avg) / 2 if previous_avg is not None else chunk return avg_chunk_latency elif chunk == -1 and current_round > 4: - logging.info(" elif chunk == -1 and current_round > 4") + # logging.info(" elif chunk == -1 and current_round > 4") previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 1), {}).get("chunk_latency", 0) if previous_avg is None: previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 2), {}).get( "chunk_latency", 0 ) - logging.info(f"Previous avg chunk latency: {previous_avg}") + # logging.info(f"Previous avg chunk latency: {previous_avg}") if isinstance(previous_avg, np.ndarray): previous_avg = previous_avg.item() - logging.info(f"Previous avg chunk latency: {previous_avg}") + # logging.info(f"Previous avg chunk latency: {previous_avg}") avg_chunk_latency = previous_avg - (previous_avg * 0.1) if previous_avg is not None else 0 else: @@ -1228,267 +1204,21 @@ def read_similarity_file(file_path, nei): logging.exception("Error reading similarity file") return similarity - # @staticmethod - # def manage_metric_frequency(messages_frequency, addr, nei, current_round): - # """ - # Manage the frequency metric. - - # Args: - # messages_frequency (list): List of messages frequency. - # addr (str): Source IP address. - # nei (str): Destination IP address. - # current_round (int): Current round number. - - # Returns: - # float: Normalized frequency value. - # int: Messages count. - # """ - # try: - # current_addr_nei = (addr, nei) - # threshold = 0 - # previous_threshold = Reputation.previous_threshold_freq.get(current_addr_nei, 0) - # previous_std_dev = Reputation.previous_std_dev_freq.get(current_addr_nei, 0) - # relevant_messages = [msg for msg in messages_frequency if msg["key"] == current_addr_nei and msg["current_round"] == current_round] - # messages_count = len(relevant_messages) if relevant_messages else 0 - # # logging.info(f"Round {current_round}. Relevant messages: {relevant_messages}, Messages count: {messages_count}") - - # if current_round >= 0 and current_round <= 3: - # previous_counts = [ - # len([m for m in messages_frequency if m["key"] == current_addr_nei and m["current_round"] == r]) - # for r in range(4) - # ] - - # threshold = np.mean(previous_counts) if previous_counts else 0 - # std_dev = np.std(previous_counts) if previous_counts else 0 - # Reputation.previous_threshold_freq[current_addr_nei] = threshold - # Reputation.previous_std_dev_freq[current_addr_nei] = std_dev - # logging.info(f"Round {current_round}. Previous counts: {previous_counts}, Threshold: {threshold}, Std dev: {std_dev}") - - # normalized_messages = 0.0 - # elif current_round > 3: - # threshold = previous_threshold - # std_dev = previous_std_dev - # logging.info(f"Round {current_round}. Prev threshold: {threshold} | Prev std dev: {std_dev}") - - # lower_bound = abs(threshold - (std_dev * 1.1)) - # logging.info(f"Round {current_round}. Lower bound: {lower_bound}") - # upper_bound = threshold + (std_dev * 1.1) - # logging.info(f"Round {current_round}. Upper bound: {upper_bound}") - - # logging.info(f"Round {current_round}. Messages count: {messages_count}") - # if messages_count <= lower_bound: - # logging.info(f"Round {current_round}. Messages count <= lower bound") - # normalized_messages = 1 - (lower_bound - messages_count) / (abs(lower_bound) + std_dev + 1e-6) - # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") - # elif messages_count >= upper_bound: - # logging.info(f"Round {current_round}. Messages count >= upper bound") - # normalized_messages = 1 / (1 + np.exp((messages_count - upper_bound) / (std_dev + 1e-6))) - # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") - # else: - # logging.info(f"Round {current_round}. Lower bound < Messages count < Upper bound") - # relative_position = (messages_count - lower_bound) / (upper_bound - lower_bound) - # logging.info(f"Round {current_round}. Relative position: {relative_position}") - # normalized_messages = 1 - relative_position / 2 - # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") - - # if previous_threshold > 0 and previous_std_dev > 0: - # threshold = (messages_count + previous_threshold) / 2 - # std_dev = np.sqrt(((messages_count - previous_threshold) ** 2 + previous_std_dev ** 2) / 2) - - # Reputation.previous_threshold_freq[current_addr_nei] = threshold - # Reputation.previous_std_dev_freq[current_addr_nei] = std_dev - # logging.info(f"Round {current_round}. New threshold: {threshold} | New std dev: {std_dev}") - # else: - # normalized_messages = 0.0 - - # return normalized_messages, messages_count - - # except Exception as e: - # logging.error(f"Error managing frequency metric: {e}") - # return 0.0, 0 - - # @staticmethod - # def normalize(value, min_value, max_value): - # """ - # Normalize value within a given range. - - # Args: - # value (float): Reputation value. - # min_reputation (float): Minimum reputation value. - # max_reputation (float): Maximum reputation value. - - # Returns: - # float: Normalized reputation value within the range [1, 10]. - # """ - - # if max_value == min_value: - # return 0.25 - - # normalized_value = (value - min_value) / (max_value - min_value) - - # return max(0.0, min(1.0, normalized_value)) - - # @staticmethod - # def callback_normalized_value(array, metric=None, key=None): - # """ - # Calculate the reputation of a node based on the normalized values. - - # Args: - # array (list): List of values to normalize. - - # Returns: - # float: Reputation value (or None if array is empty). - # """ - - # if not array: - # return None - - # if len(array) == 1: - # if array[0] >= 1: - # return Reputation.normalize(array[0], 0.25, array[0]+1) - # else: - # return Reputation.normalize(array[0], 0.25, array[0]) - - # min_value = min(array) - # max_value = max(array) - - # # See values from array - # normalized_values = [Reputation.normalize(value, min_value, max_value) for value in array] - # reputation_normalized = sum(normalized_values) / len(normalized_values) - - # return reputation_normalized - - # @staticmethod - # def manage_metric_fraction_of_params_changed(round, total_params, changed_params, changes_record, nei): - # if nei not in Reputation.fraction_parameters_changed: - # logging.info(f"Creating new entry for neighbor: {nei}") - # Reputation.fraction_parameters_changed[nei] = {} - - # if round not in Reputation.fraction_parameters_changed[nei]: - # logging.info(f"Creating new entry for round: {round}") - # Reputation.fraction_parameters_changed[nei][round] = {} - - # fraction_changed = changed_params / total_params if total_params > 0 else 0.0 - # logging.info(f"Calculating fraction of parameters changed for neighbor: {nei}, round: {round}, fraction_changed: {fraction_changed}") - - # normalized_fraction = None - # threshold = None - - # if round == 3: - # previous_fractions = [ - # Reputation.fraction_parameters_changed[nei][i]["fraction_changed"] - # for i in range(3) if i in Reputation.fraction_parameters_changed[nei] - # ] - # logging.info(f"Round 3: Previous fractions: {previous_fractions}") - - # if previous_fractions: - # threshold = sum(previous_fractions) / len(previous_fractions) - # logging.info(f"Round 3: Calculated threshold: {threshold}, fraction_changed: {fraction_changed}, normalized_fraction: {normalized_fraction}") - - # elif round > 3: - # previous_threshold = Reputation.fraction_parameters_changed[nei][round - 1]["threshold"] - - # if previous_threshold is not None: - # logging.info(f"Round: {round}, Previous threshold: {previous_threshold}") - # threshold = (previous_threshold + fraction_changed) / 2 - # logging.info(f"Round: {round}, Calculated threshold: {threshold}") - # else: - # previous_fractions = [ - # Reputation.fraction_parameters_changed[nei][i]["fraction_changed"] - # for i in range(3) if i in Reputation.fraction_parameters_changed[nei] - # ] - # if previous_fractions: - # threshold = sum(previous_fractions) / len(previous_fractions) - # logging.info(f"Round: {round}, Calculated threshold: {threshold} based on previous fractions") - - # Reputation.fraction_parameters_changed[nei][round] = { - # "total_params": total_params, - # "change_params": changed_params, - # "changes_record": changes_record, - # "threshold": threshold, - # "fraction_changed": fraction_changed, - # } - - # logging.info(f"Reputation.fraction_parameters_changed: {Reputation.fraction_parameters_changed}") - - # return threshold - - # METHODS TO BE USED IN THE FUTURE - - # @staticmethod - # def get_existing_reputation(current, nei, round): - - # Get the existing reputation for a node with respect to a specific neighbor for a specific round. - - # Args: - # addr (str): The identifier of the node whose reputation is being retrieved. - # nei (str): The identifier of the neighbor. - # round (int): Round number. - - # Returns: - # float: Existing reputation score, or None if not found. - - # key = (current, nei, round) - - # # Comprobar si la clave existe en el historial de reputación - # if key in Reputation.reputation_history: - # logging.info(f"History key: {key}, reputation: {Reputation.reputation_history[key]}") - # history_list = Reputation.reputation_history[key] - - # # Si la estructura es una lista simple, devolvemos el último valor - # return history_list[-1] if history_list else None - - # return None - - # def combine_reputation_with_neighbour(self, current_node, source, node_ip, score, round): - - # Combine the reputation of a node with its neighbour. - - # Args: - # current_node (str): The current node's IP address. - # source (str): Source IP address (the node sending the reputation). - # node_ip (str): The node being evaluated. - # score (float): Reputation score from neighbour. - # round (int): Round number. - - # Returns: - # float: Combined reputation score or None if the calculation has already been done for this round. - - # current_node = current_node.split(":")[0].strip() - # source = source.split(":")[0].strip() - # node_ip = node_ip.split(":")[0].strip() - # logging.info(f"Combining reputation - Current node: {current_node}, Source: {source}, Neighbour: {node_ip}, Score: {score}, Round: {round}") - - # if current_node == node_ip: - # logging.info(f"Node {current_node} ignoring score about itself ({node_ip}).") - # return None - - # # Definir la clave con current_node, node_ip y round - # key = (current_node, node_ip, round) - - # # Proceder con el cálculo de reputación si no existe previamente - # if key not in Reputation.neighbor_reputation_history: - # Reputation.neighbor_reputation_history[key] = [] - - # # Guardar la nueva puntuación enviada por el vecino - # Reputation.neighbor_reputation_history[key].append(score) - - # # Calcular la reputación promedio del vecino - # total_reputation = sum(Reputation.neighbor_reputation_history[key]) - # average_reputation = total_reputation / len(Reputation.neighbor_reputation_history[key]) - - # # Obtener la reputación existente del historial - # existing_reputation = Reputation.get_existing_reputation(current_node, node_ip, round) - # if existing_reputation is None: - # logging.warning(f"No existing reputation found for node {current_node} with neighbor {node_ip} in round {round}.") - # return None - - # # Definir los pesos para combinar reputaciones - # weight_existing = 0.5 - # weight_new = 0.5 - - # # Combinar la reputación existente con la nueva reputación del vecino - # combined_reputation = (weight_existing * existing_reputation) + (weight_new * average_reputation) - # logging.info(f"Combined reputation for node {current_node} with neighbor {source}: {combined_reputation}") - - # return combined_reputation + @staticmethod + def metrics(scenario, data, addr, nei, type): + current_dir = os.path.dirname(os.path.realpath(__file__)) + csv_path = os.path.join(current_dir, f"{scenario}/metrics/{addr}_{nei}_messages_{type}.csv") + # logging.info(f"Round {current_round}. CSV path: {csv_path}") + csv_dir = os.path.dirname(csv_path) + + if not os.path.exists(csv_dir): + os.makedirs(csv_dir) + + try: + with open(csv_path, mode="a", newline="") as file: + writer = csv.DictWriter(file, fieldnames=data.keys()) + if file.tell() == 0: + writer.writeheader() + writer.writerow(data) + except Exception: + logging.exception("Error saving messages frequency data to CSV") From 24534e59940076fef5ddb00d2bdd7d8398775d21 Mon Sep 17 00:00:00 2001 From: Isaac Marroqui Date: Tue, 7 Jan 2025 11:41:30 +0100 Subject: [PATCH 11/43] changes metrics --- nebula/addons/attacks/attacks.py | 10 +- nebula/core/engine.py | 122 +- nebula/core/network/communications.py | 142 +- nebula/core/network/connection.py | 84 +- nebula/core/reputation/Reputation.py | 1183 ++++++++--------- .../frontend/config/participant.json.example | 2 +- 6 files changed, 765 insertions(+), 778 deletions(-) diff --git a/nebula/addons/attacks/attacks.py b/nebula/addons/attacks/attacks.py index ab2d8db28..69113a5a0 100755 --- a/nebula/addons/attacks/attacks.py +++ b/nebula/addons/attacks/attacks.py @@ -172,8 +172,8 @@ def __init__(self): async def attack(self): logging.info("[DelayerAttack] Performing delayer attack") - logging.info("Delaying time from 15 seconds") - time.sleep(15) + logging.info("Delaying time from 30 seconds") + time.sleep(30) logging.info("Delaying time finished") @@ -186,11 +186,13 @@ class FloodingAttack(Attack): def __init__(self): super().__init__() - async def attack(self, cm: CommunicationsManager, addr, num_round, repetitions=5, interval=0.1): + async def attack(self, cm: CommunicationsManager, addr, num_round, repetitions=2, interval=0.05): logging.info("[FloodingAttack] Performing flood attack") neighbors = set(await cm.get_addrs_current_connections(only_direct=True)) logging.info(f"Neighbors: {neighbors}") + logging.info(f"Round: {num_round}") + logging.info(f"Interval: {interval}") logging.info(f"Repetitions: {repetitions}") scaled_repetitions = len(neighbors) * repetitions logging.info(f"Total repetitions: {scaled_repetitions}") @@ -205,4 +207,4 @@ async def attack(self, cm: CommunicationsManager, addr, num_round, repetitions=5 await cm.send_message_to_neighbors(message_data, neighbors={nei}) logging.info(f"Flood attack message sent to {nei} - Attempt {i + 1}/{repetitions}.") await asyncio.sleep(interval) - cm.store_send_timestamp(nei, num_round, "flood_attack") + # cm.store_send_timestamp(nei, num_round, "flood_attack") diff --git a/nebula/core/engine.py b/nebula/core/engine.py index 8d426da3b..a994ba82c 100755 --- a/nebula/core/engine.py +++ b/nebula/core/engine.py @@ -25,7 +25,6 @@ from nebula.config.config import Config from nebula.core.training.lightning import Lightning -from nebula.core.utils.helper import cosine_metric def handle_exception(exc_type, exc_value, exc_traceback): @@ -505,7 +504,7 @@ async def calculate_reputation(self): if self.reputation[nei]["reputation"] is not None: logging.info(f"Reputation of node {nei}: {self.reputation[nei]['reputation']}") if self.reputation[nei]["reputation"] <= 0.6: - self.rejected_nodes.add(nei) + # self.rejected_nodes.add(nei) logging.info(f"Rejected nodes: {self.rejected_nodes}") elif 0.6 < self.reputation[nei]["reputation"] < 0.8: logging.info(f"Change weight node: {nei}") @@ -540,16 +539,36 @@ async def calculate_reputation(self): round=data["round"], ) + metrics_data = { + "addr": self.addr.split(":")[0].strip(), + "nei": nei.split(":")[0].strip(), + "round": self.round, + "reputation_with_feedback": data["reputation"], + } + + self.reputation_instance.metrics( + self.experiment_name, + metrics_data, + self.addr.split(":")[0].strip(), + nei.split(":")[0].strip(), + "reputation", + update_field="reputation_with_feedback", + ) + for neighbor in neighbors_to_send: logging.info( f"Sending reputation to node {nei} from node {neighbor} with reputation {data['reputation']}" ) - self.cm.store_send_timestamp(nei, current_round, "reputation") + # self.cm.store_send_timestamp(nei, current_round, "reputation") await self.cm.send_message_to_neighbors(message_data, [neighbor]) else: logging.info(f"Reputation already sent to node {nei}") async def include_feedback_in_reputation(self): + data = None + weight_current_reputation = 0.9 + weight_feedback = 0.1 + if self._cm.reputation_with_all_feedback is not None: current_round = self.get_round() for (current_node, node_ip, round_num), scores in self._cm.reputation_with_all_feedback.items(): @@ -560,6 +579,7 @@ async def include_feedback_in_reputation(self): logging.info( f"current_node: {current_node} | node_ip: {node_ip} | round_num: {round_num} | scores: {scores}" ) + if scores: avg_feedback = sum(scores) / len(scores) logging.info(f"Receive feedback to node {node_ip} with average score {avg_feedback}") @@ -573,7 +593,9 @@ async def include_feedback_in_reputation(self): return False if current_reputation: - combined_reputation = (current_reputation + avg_feedback) / 2 + combined_reputation = (current_reputation * weight_current_reputation) + ( + avg_feedback * weight_feedback + ) logging.info( f"Combined reputation for node {node_ip} in round {round_num}: {combined_reputation}" ) @@ -591,7 +613,7 @@ async def include_feedback_in_reputation(self): return True else: - return False + return False, None async def _learning_cycle(self): while self.round is not None and self.round < self.total_rounds: @@ -665,50 +687,50 @@ async def _extended_learning_cycle(self): """ pass - def reputation_calculation(self, aggregated_models_weights): - cossim_threshold = 0.5 - loss_threshold = 0.5 - - current_models = {} - for subnodes in aggregated_models_weights.keys(): - sublist = subnodes.split() - submodel = aggregated_models_weights[subnodes][0] - for node in sublist: - current_models[node] = submodel - - malicious_nodes = [] - reputation_score = {} - local_model = self.trainer.get_model_parameters() - untrusted_nodes = list(current_models.keys()) - logging.info(f"reputation_calculation untrusted_nodes at round {self.round}: {untrusted_nodes}") - - for untrusted_node in untrusted_nodes: - logging.info(f"reputation_calculation untrusted_node at round {self.round}: {untrusted_node}") - logging.info(f"reputation_calculation self.get_name() at round {self.round}: {self.get_name()}") - if untrusted_node != self.get_name(): - untrusted_model = current_models[untrusted_node] - cossim = cosine_metric(local_model, untrusted_model, similarity=True) - logging.info(f"reputation_calculation cossim at round {self.round}: {untrusted_node}: {cossim}") - self.trainer._logger.log_data({f"Reputation/cossim_{untrusted_node}": cossim}, step=self.round) - - avg_loss = self.trainer.validate_neighbour_model(untrusted_model) - logging.info(f"reputation_calculation avg_loss at round {self.round} {untrusted_node}: {avg_loss}") - self.trainer._logger.log_data({f"Reputation/avg_loss_{untrusted_node}": avg_loss}, step=self.round) - reputation_score[untrusted_node] = (cossim, avg_loss) - - if cossim < cossim_threshold or avg_loss > loss_threshold: - malicious_nodes.append(untrusted_node) - else: - self._secure_neighbors.append(untrusted_node) - - return malicious_nodes, reputation_score - - async def send_reputation(self, malicious_nodes): - logging.info(f"Sending REPUTATION to the rest of the topology: {malicious_nodes}") - message = self.cm.mm.generate_federation_message( - nebula_pb2.FederationMessage.Action.REPUTATION, malicious_nodes - ) - await self.cm.send_message_to_neighbors(message) + # def reputation_calculation(self, aggregated_models_weights): + # cossim_threshold = 0.5 + # loss_threshold = 0.5 + + # current_models = {} + # for subnodes in aggregated_models_weights.keys(): + # sublist = subnodes.split() + # submodel = aggregated_models_weights[subnodes][0] + # for node in sublist: + # current_models[node] = submodel + + # malicious_nodes = [] + # reputation_score = {} + # local_model = self.trainer.get_model_parameters() + # untrusted_nodes = list(current_models.keys()) + # logging.info(f"reputation_calculation untrusted_nodes at round {self.round}: {untrusted_nodes}") + + # for untrusted_node in untrusted_nodes: + # logging.info(f"reputation_calculation untrusted_node at round {self.round}: {untrusted_node}") + # logging.info(f"reputation_calculation self.get_name() at round {self.round}: {self.get_name()}") + # if untrusted_node != self.get_name(): + # untrusted_model = current_models[untrusted_node] + # cossim = cosine_metric(local_model, untrusted_model, similarity=True) + # logging.info(f"reputation_calculation cossim at round {self.round}: {untrusted_node}: {cossim}") + # self.trainer._logger.log_data({f"Reputation/cossim_{untrusted_node}": cossim}, step=self.round) + + # avg_loss = self.trainer.validate_neighbour_model(untrusted_model) + # logging.info(f"reputation_calculation avg_loss at round {self.round} {untrusted_node}: {avg_loss}") + # self.trainer._logger.log_data({f"Reputation/avg_loss_{untrusted_node}": avg_loss}, step=self.round) + # reputation_score[untrusted_node] = (cossim, avg_loss) + + # if cossim < cossim_threshold or avg_loss > loss_threshold: + # malicious_nodes.append(untrusted_node) + # else: + # self._secure_neighbors.append(untrusted_node) + + # return malicious_nodes, reputation_score + + # async def send_reputation(self, malicious_nodes): + # logging.info(f"Sending REPUTATION to the rest of the topology: {malicious_nodes}") + # message = self.cm.mm.generate_federation_message( + # nebula_pb2.FederationMessage.Action.REPUTATION, malicious_nodes + # ) + # await self.cm.send_message_to_neighbors(message) class MaliciousNode(Engine): @@ -747,7 +769,7 @@ async def _extended_learning_cycle(self): if type(self.attack).__name__ == "FloodingAttack": logging.info("Running Flooding Attack") if self.round in range(self.round_start_attack, self.round_stop_attack): - await self.attack.attack(self.cm, self.addr, self.round, repetitions=10, interval=0.05) + await self.attack.attack(self.cm, self.addr, self.round, repetitions=2, interval=0.05) if type(self.attack).__name__ == "DelayerAttack": logging.info("Running Delayer Attack") diff --git a/nebula/core/network/communications.py b/nebula/core/network/communications.py index a8b95c00b..15adf91ad 100755 --- a/nebula/core/network/communications.py +++ b/nebula/core/network/communications.py @@ -86,6 +86,7 @@ def __init__(self, engine: "Engine"): # Reputation self.reputation_instance = Reputation(self.engine) + self._model_arrival_latency_data = self.reputation_instance.model_arrival_latency_data self.reputation_with_all_feedback = {} self.message_timestamps = {} self.fraction_of_params_changed = {} @@ -204,7 +205,7 @@ async def handle_federation_message(self, source, message): ) try: await self.engine.event_manager.trigger_event(source, message) - self.store_receive_timestamp(source, "federation") + # self.store_receive_timestamp(source, "federation") except Exception as e: logging.exception( f"📝 handle_federation_message | Error while processing: {message.action} {message.arguments} | {e}" @@ -266,16 +267,55 @@ async def handle_model_message(self, source, message): ) if cosine_value < 0.6: + logging.info("🤖 handle_model_message | Model similarity is less than 0.6") self.engine.rejected_nodes.add(source) - # Manage communication latency - self.store_receive_timestamp(source, "model", message.round) - self.calculate_latency(source, "model") - # Manage parameters of models parameters_local = self.engine.trainer.get_model_parameters() self.fraction_of_parameters_changed(source, parameters_local, decoded_model, message.round) + # Manage model_arrival_latency latency + start_time = time.time() + round_id = message.round + if round_id not in self._model_arrival_latency_data: + self._model_arrival_latency_data[round_id] = {} + + if "time_0" not in self._model_arrival_latency_data[round_id]: + self._model_arrival_latency_data[round_id]["time_0"] = {"time": start_time, "source": source} + logging.info(f"start_time: {start_time} of source {self.addr} for round {round_id}") + + relative_time = start_time - self._model_arrival_latency_data[round_id]["time_0"]["time"] + + if source not in self._model_arrival_latency_data[round_id]: + self._model_arrival_latency_data[round_id][source] = { + "start_time": start_time, + "relative_time": relative_time, + } + logging.info(f"self.model_arrival_latency_data: {self._model_arrival_latency_data}") + logging.info(f"Node {source} | Time taken relative to time_0: {relative_time:.3f} seconds") + + if message.round == current_round: + save_data( + self.config.participant["scenario_args"]["name"], + "model_arrival_latency", + source, + self.get_addr(), + num_round=message.round, + latency=relative_time, + ) + # else: + # logging.info(f"🤖 handle_model_message | Received a model from a different round | Model round: {message.round} | Current round: {current_round}") + # if message.round > current_round: + # logging.info(f"🤖 handle_model_message | message.round > current_round") + # save_data( + # self.config.participant["scenario_args"]["name"], + # "model_arrival_latency", + # source, + # self.get_addr(), + # num_round=message.round, + # latency=relative_time, + # ) + if message.round != current_round and message.round != -1: logging.info( f"❗️ handle_model_message | Received a model from a different round | Model round: {message.round} | Current round: {current_round}" @@ -301,7 +341,7 @@ async def handle_model_message(self, source, message): # non-starting nodes receive the initialized model from the starting node if not self.engine.get_federation_ready_lock().locked() or self.engine.get_initialization_status(): decoded_model = self.engine.trainer.deserialize_model(message.parameters) - + await self.engine.aggregator.include_model_in_buffer( decoded_model, message.weight, @@ -783,15 +823,15 @@ async def send_message(self, dest_addr, message): logging.exception(f"❗️ Cannot send message {message} to {dest_addr}. Error: {e!s}") await self.disconnect(dest_addr, mutual_disconnection=False) - def store_send_timestamp(self, dest_addr, round_number, type_message): - send_timestamp = datetime.now().strftime("%H:%M:%S") - self.message_timestamps[(self.addr, dest_addr, type_message)] = { - "send": send_timestamp, - "receive": None, - "latency": None, - "round": round_number, - "type": type_message, - } + # def store_send_timestamp(self, dest_addr, round_number, type_message): + # send_timestamp = datetime.now().strftime("%H:%M:%S") + # self.message_timestamps[(self.addr, dest_addr, type_message)] = { + # "send": send_timestamp, + # "receive": None, + # "latency": None, + # "round": round_number, + # "type": type_message, + # } def store_receive_timestamp(self, source, type_message, round=None): current_time = time.time() @@ -810,40 +850,40 @@ def store_receive_timestamp(self, source, type_message, round=None): current_round=current_round, ) - receive_timestamp = datetime.now().strftime("%H:%M:%S") - if (self.addr, source, type_message) in self.message_timestamps: - self.message_timestamps[(self.addr, source, type_message)]["receive"] = receive_timestamp - - def calculate_latency(self, source, type_message): - if (self.addr, source, type_message) in self.message_timestamps: - send_time = self.message_timestamps[(self.addr, source, type_message)]["send"] - receive_time = self.message_timestamps[(self.addr, source, type_message)]["receive"] - round_number = self.message_timestamps[(self.addr, source, type_message)]["round"] - current_round = self.get_round() - - if send_time and receive_time and type_message == "model": - send_time = datetime.strptime(send_time, "%H:%M:%S") - receive_time = datetime.strptime(receive_time, "%H:%M:%S") - - latency = (receive_time - send_time).total_seconds() - logging.info( - f"🕒 Latency from {source} with type message {type_message} in round {round_number}: {latency}" - ) - - self.message_timestamps[(self.addr, source, type_message)]["latency"] = latency - save_data( - self.config.participant["scenario_args"]["name"], - "communication", - source, - self.addr, - num_round=round_number, - time=latency, - type_message=type_message, - current_round=current_round, - ) - - return latency - return None + # receive_timestamp = datetime.now().strftime("%H:%M:%S") + # if (self.addr, source, type_message) in self.message_timestamps: + # self.message_timestamps[(self.addr, source, type_message)]["receive"] = receive_timestamp + + # def calculate_latency(self, source, type_message): + # if (self.addr, source, type_message) in self.message_timestamps: + # send_time = self.message_timestamps[(self.addr, source, type_message)]["send"] + # receive_time = self.message_timestamps[(self.addr, source, type_message)]["receive"] + # round_number = self.message_timestamps[(self.addr, source, type_message)]["round"] + # current_round = self.get_round() + + # if send_time and receive_time and type_message == "model": + # send_time = datetime.strptime(send_time, "%H:%M:%S") + # receive_time = datetime.strptime(receive_time, "%H:%M:%S") + + # latency = (receive_time - send_time).total_seconds() + # logging.info( + # f"🕒 Latency from {source} with type message {type_message} in round {round_number}: {latency}" + # ) + + # self.message_timestamps[(self.addr, source, type_message)]["latency"] = latency + # save_data( + # self.config.participant["scenario_args"]["name"], + # "communication", + # source, + # self.addr, + # num_round=round_number, + # time=latency, + # type_message=type_message, + # current_round=current_round, + # ) + + # return latency + # return None async def send_model(self, dest_addr, round, serialized_model, weight=1): async with self.semaphore_send_model: @@ -853,8 +893,8 @@ async def send_model(self, dest_addr, round, serialized_model, weight=1): logging.info(f"❗️ Connection with {dest_addr} not found") return - if round != -1: - self.store_send_timestamp(dest_addr, round, "model") + # if round != -1: + # self.store_send_timestamp(dest_addr, round, "model") logging.info( f"Sending model to {dest_addr} with round {round}: weight={weight} | size={sys.getsizeof(serialized_model) / (1024** 2) if serialized_model is not None else 0} MB" diff --git a/nebula/core/network/connection.py b/nebula/core/network/connection.py index 82ffbd2ab..33318c13b 100755 --- a/nebula/core/network/connection.py +++ b/nebula/core/network/connection.py @@ -12,8 +12,6 @@ import lz4.frame from geopy import distance -from nebula.core.reputation.Reputation import save_data - if TYPE_CHECKING: from nebula.core.network.communications import CommunicationsManager @@ -63,7 +61,6 @@ def __init__( self.process_task = None self.pending_messages_queue = asyncio.Queue(maxsize=100) self.message_buffers: dict[bytes, dict[int, MessageChunk]] = {} - self._chunk_data = self.cm.reputation_instance.chunk_data self.EOT_CHAR = b"\x00\x00\x00\x04" self.COMPRESSION_CHAR = b"\x00\x00\x00\x01" @@ -286,53 +283,44 @@ async def handle_incoming_message(self) -> None: f"Received chunk {chunk_index} of message {message_id.hex()} | size: {len(chunk_data)} bytes" ) - message_id_decoded = message_id.hex() - if chunk_index == 0: - chunk_bytes = chunk_data.tobytes() - # logging.info(f"chunk data: {chunk_bytes}") - comparation_with = b"\x01\x00\x00\x00x\x9c\x00" - if chunk_bytes[: len(comparation_with)] == comparation_with: - if message_id_decoded not in self._chunk_data: - self._chunk_data[message_id_decoded] = {} - - self._chunk_data[message_id_decoded]["start_time"] = time.time() - # if message_id_decoded and message_id_decoded in self._chunk_data: - # logging.info(f"message_id_decoded in start_time: {self._chunk_data}") + # if chunk_index == 0: + # if round_id is not None: + # if round_id >= 0: + # start_time = time.time() + # chunk_bytes = chunk_data.tobytes() + # # logging.info(f"chunk data: {chunk_bytes}") + # comparation_with = b"\x01\x00\x00\x00x\x9c\x00" + # if chunk_bytes[: len(comparation_with)] == comparation_with: + # if round_id not in self._chunk_data: + # self._chunk_data[round_id] = {} + + # if "time_0" not in self._chunk_data[round_id]: + # self._chunk_data[round_id]["time_0"] = {"time": start_time, "source": source} + # logging.info(f"start_time: {start_time} of source {self.addr} for round {round_id}") + + # relative_time = start_time - self._chunk_data[round_id]["time_0"]["time"] + + # if source not in self._chunk_data[round_id]: + # self._chunk_data[round_id][source] = { + # "start_time": start_time, + # "relative_time": relative_time, + # } + # logging.info(f"self.chunk_data: {self._chunk_data}") + # logging.info(f"Node {source} | Time taken relative to time_0: {relative_time:.3f} seconds") + + # save_data( + # self.config.participant["scenario_args"]["name"], + # "chunk_latency", + # source, + # self.cm.get_addr(), + # num_round=round_id, + # message_id_decoded=message_id_decoded, + # latency=relative_time, + # ) + # else: + # logging.info(f"Node {source} save data at round {round_id} already exists") if is_last_chunk: - if ( - message_id_decoded in self._chunk_data - and message_id_decoded - and "start_time" in self._chunk_data[message_id_decoded] - ): - self._chunk_data[message_id_decoded]["end_time"] = time.time() - # logging.info(f"message_id_decoded in end_time: {self._chunk_data}") - - if "start_time" in self._chunk_data[message_id_decoded]: - if "end_time" in self._chunk_data[message_id_decoded]: - latency = ( - self._chunk_data[message_id_decoded]["end_time"] - - self._chunk_data[message_id_decoded]["start_time"] - ) - # logging.info(f"message_id_decoded in latency: {latency}") - source = self.addr - - if source != self.cm.get_addr(): - # logging.info(f"SAVE DATA CHUNK") - save_data( - self.config.participant["scenario_args"]["name"], - "chunk_latency", - source, - self.cm.get_addr(), - self.cm.get_round(), - message_id_decoded=message_id_decoded, - latency=latency, - ) - else: - logging.info(f"message_id_decoded not in end_time: {self._chunk_data}") - else: - logging.info(f"message_id_decoded not in start_time: {self._chunk_data}") - await self._process_complete_message(message_id) except asyncio.CancelledError: logging.info("Message handling cancelled") diff --git a/nebula/core/reputation/Reputation.py b/nebula/core/reputation/Reputation.py index d1452d9c8..e82e8cee4 100644 --- a/nebula/core/reputation/Reputation.py +++ b/nebula/core/reputation/Reputation.py @@ -28,7 +28,7 @@ def save_data( latency=None, ): """ - Save communication data between nodes and aggregated models. + Save data between nodes and aggregated models. Args: source_ip (str): Source IP address. @@ -43,14 +43,7 @@ def save_data( try: combined_data = {} - if type_data == "communication": - combined_data["communication"] = { - "time": time, - "current_round": current_round, - "round": num_round, - "type_message": type_message, - } - elif type_data == "time_message": + if type_data == "time_message": combined_data["time_message"] = { "time": time, "type_message": type_message, @@ -66,9 +59,8 @@ def save_data( "changes_record": changes_record, "round": num_round, } - elif type_data == "chunk_latency": - combined_data["chunk_latency"] = { - "message_id_decoded": message_id_decoded, + elif type_data == "model_arrival_latency": + combined_data["model_arrival_latency"] = { "latency": latency, "round": num_round, } @@ -102,22 +94,20 @@ class Reputation: """ reputation_history: ClassVar[dict] = {} - communication_history: ClassVar[dict] = {} - frequency_history: ClassVar[dict] = {} + time_message_history: ClassVar[dict] = {} neighbor_reputation_history: ClassVar[dict] = {} fraction_changed_history: ClassVar[dict] = {} - communication_data: ClassVar[dict] = {} - messages_frequency: ClassVar[list] = [] - previous_threshold_freq: ClassVar[dict] = {} - previous_std_dev_freq: ClassVar[dict] = {} - messages_chunk_latency: ClassVar[dict] = {} - chunk_history: ClassVar[dict] = {} - previous_percentile_25_freq: ClassVar[dict] = {} - previous_percentile_85_freq: ClassVar[dict] = {} + messages_time_message: ClassVar[list] = [] + previous_threshold_time_message: ClassVar[dict] = {} + previous_std_dev_time_message: ClassVar[dict] = {} + messages_model_arrival_latency: ClassVar[dict] = {} + model_arrival_latency_history: ClassVar[dict] = {} + previous_percentile_25_time_message: ClassVar[dict] = {} + previous_percentile_85_time_message: ClassVar[dict] = {} def __init__(self, engine: "Engine"): self._engine = engine - self.chunk_data = {} + self.model_arrival_latency_data = {} @property def engine(self): @@ -134,20 +124,19 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro addr = addr.split(":")[0].strip() nei = nei.split(":")[0].strip() - communication_time_normalized = 0 - messages_frequency_normalized = 0 - messages_frequency_count = 0 - avg_communication_time_normalized = 0 - avg_messages_frequency_normalized = 0 + messages_time_message_normalized = 0 + messages_time_message_count = 0 + avg_messages_time_message_normalized = 0 fraction_score = 0 fraction_score_normalized = 0 fraction_score_asign = 0 - messages_chunk_normalized = 0 - avg_chunk_latency = 0 + messages_model_arrival_latency_normalized = 0 + avg_model_arrival_latency = 0 + fraction_neighbors_scores = None try: script_dir = os.path.dirname(os.path.abspath(__file__)) - file_name = f"{addr}_storing_{nei}_info.json" # Read the file to calculate the reputation + file_name = f"{addr}_storing_{nei}_info.json" full_file_path = os.path.join(script_dir, scenario, file_name) os.makedirs(os.path.dirname(full_file_path), exist_ok=True) @@ -155,15 +144,6 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro with open(full_file_path) as json_file: all_metrics = json.load(json_file) for metric in all_metrics: - if "communication" in metric: - type_message = metric["communication"]["type_message"] - round_comm = metric["communication"]["round"] - current_round_comm = metric["communication"]["current_round"] - time = metric["communication"]["time"] - if current_round == current_round_comm: - communication_time_normalized = Reputation.manage_communication( - type_message, round_comm, current_round_comm, time, addr, nei - ) if "time_message" in metric: round_time = metric["time_message"]["round"] current_round_time = metric["time_message"]["current_round"] @@ -173,7 +153,7 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro # logging.info(f"Current round time: {current_round_time}, previous round time: {previous_round_time}") if round_time == previous_round_time: # logging.info(f"Time message: {time}, type message: {type_message}, round: {round_time}") - Reputation.messages_frequency.append({ + Reputation.messages_time_message.append({ "time_message": time, "type_message": type_message, "round": round_time, @@ -198,103 +178,110 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro changes_record, changed_params, total_params, - scenario + scenario, ) - # logging.info(f"Reputation.fraction_changed_history: {Reputation.fraction_changed_history}") - if "chunk_latency" in metric: - round_latency = metric["chunk_latency"]["round"] - message_id_decoded = metric["chunk_latency"]["message_id_decoded"] - latency = metric["chunk_latency"]["latency"] + # logging.info(f"Fraction score normalized: {fraction_score_normalized}") + if "model_arrival_latency" in metric: + round_latency = metric["model_arrival_latency"]["round"] + latency = metric["model_arrival_latency"]["latency"] if round_latency == current_round: - messages_chunk_normalized = Reputation.manage_chunk_latency( - round_latency, - addr, - nei, - latency, + messages_model_arrival_latency_normalized = Reputation.manage_model_arrival_latency( + round_latency, addr, nei, latency, scenario, self.model_arrival_latency_data + ) + logging.info( + f"messages_model_arrival_latency_normalized: {messages_model_arrival_latency_normalized}" ) - # logging.info(f"messages_chunk_normalized: {messages_chunk_normalized}") similarity_file = os.path.join(log_dir, f"participant_{id_node}_similarity.csv") similarity_reputation = Reputation.read_similarity_file(similarity_file, nei) - if communication_time_normalized >= 0: - # logging.info(f"Communication time normalized: {communication_time_normalized}") - avg_communication_time_normalized = Reputation.save_communication_history( - addr, nei, communication_time_normalized, current_round + if messages_model_arrival_latency_normalized >= 0: + avg_model_arrival_latency = Reputation.save_model_arrival_latency_history( + addr, nei, messages_model_arrival_latency_normalized, current_round ) - # logging.info(f"Avg communication time normalized: {avg_communication_time_normalized}") - if avg_communication_time_normalized is None and current_round >= 5: - avg_communication_time_normalized = Reputation.communication_history[(addr, nei)][ + if avg_model_arrival_latency is None and current_round > 4: + avg_model_arrival_latency = Reputation.model_arrival_latency_history[(addr, nei)][ current_round - 1 - ]["avg_communication"] - # logging.info(f"Avg communication is None and current_round = {current_round}, avg_communication_time_normalized: {avg_communication_time_normalized}") + ]["score"] + # logging.info(f"Avg model_arrival_latency latency is None and current_round = {current_round}, model_arrival_latency: {avg_model_arrival_latency}") - if messages_chunk_normalized >= 0: - avg_chunk_latency = Reputation.save_chunk_history( - addr, nei, messages_chunk_normalized, current_round - ) - if avg_chunk_latency is None and current_round >= 5: - avg_chunk_latency = Reputation.chunk_history[(addr, nei)][current_round - 1][ - "chunk_latency" - ] - # logging.info(f"Avg chunk latency is None and current_round = {current_round}, chunk_latency: {avg_chunk_latency}") - - if Reputation.messages_frequency is not None: - messages_frequency_normalized, messages_frequency_count = Reputation.manage_metric_frequency( - Reputation.messages_frequency, addr, nei, current_round, scenario + if Reputation.messages_time_message is not None: + messages_time_message_normalized, messages_time_message_count = ( + Reputation.manage_metric_time_message( + Reputation.messages_time_message, addr, nei, current_round, scenario + ) ) - # logging.info(f"Messages frequency normalized: {messages_frequency_normalized}") - avg_messages_frequency_normalized = Reputation.save_frequency_history( - addr, nei, messages_frequency_normalized, current_round + # logging.info(f"Messages time_message normalized: {messages_time_message_normalized}") + avg_messages_time_message_normalized = Reputation.save_time_message_history( + addr, nei, messages_time_message_normalized, current_round ) - # logging.info(f"Avg messages frequency normalized: {avg_messages_frequency_normalized}") - if avg_messages_frequency_normalized is None and current_round >= 4: - avg_messages_frequency_normalized = Reputation.frequency_history[(addr, nei)][ + # logging.info(f"Avg messages time_message normalized: {avg_messages_time_message_normalized}") + if avg_messages_time_message_normalized is None and current_round >= 4: + avg_messages_time_message_normalized = Reputation.time_message_history[(addr, nei)][ current_round - 1 - ]["avg_frequency"] - logging.info( - f"Avg messages frequency is None and curret_round = {current_round}, avg_messages_frequency_normalized: {avg_messages_frequency_normalized}" - ) + ]["avg_time_message"] + # (f"Avg messages time_message is None and curret_round = {current_round}, avg_messages_time_message_normalized: {avg_messages_time_message_normalized}") if current_round >= 5: if fraction_score_normalized > 0: key_previous_round = (addr, nei, current_round - 1) if current_round - 1 > 0 else None fraction_previous_round = None - if key_previous_round is not None and key_previous_round in Reputation.fraction_changed_history: - fraction_score = Reputation.fraction_changed_history[key_previous_round].get("fraction_score") + if ( + key_previous_round is not None + and key_previous_round in Reputation.fraction_changed_history + ): + fraction_score = Reputation.fraction_changed_history[key_previous_round].get( + "fraction_score" + ) fraction_previous_round = fraction_score if fraction_score is not None else None - logging.info(f"Fraction score previous round: {fraction_previous_round}") + # logging.info(f"Fraction score previous round: {fraction_previous_round}") if fraction_previous_round is not None: fraction_score_asign = (fraction_score_normalized + fraction_previous_round) / 2 - logging.info(f"Fraction score normalized: {fraction_score_asign}") - Reputation.fraction_changed_history[(addr, nei, current_round)]["fraction_score"] = fraction_score_asign + # logging.info(f"Fraction score normalized: {fraction_score_asign}") + Reputation.fraction_changed_history[(addr, nei, current_round)]["fraction_score"] = ( + fraction_score_asign + ) else: fraction_score_asign = fraction_score_normalized - logging.info(f"Fraction score normalized: {fraction_score_asign}") - Reputation.fraction_changed_history[(addr, nei, current_round)]["fraction_score"] = fraction_score_asign + # logging.info(f"Fraction score normalized: {fraction_score_asign}") + Reputation.fraction_changed_history[(addr, nei, current_round)]["fraction_score"] = ( + fraction_score_asign + ) else: - logging.info(f"No fraction score to assign") + # logging.info(f"No fraction score to assign") fraction_previous_round = None key_previous_round = (addr, nei, current_round - 1) if current_round - 1 > 0 else None - if key_previous_round is not None and key_previous_round in Reputation.fraction_changed_history: - fraction_score = Reputation.fraction_changed_history[key_previous_round].get("fraction_score") + if ( + key_previous_round is not None + and key_previous_round in Reputation.fraction_changed_history + ): + fraction_score = Reputation.fraction_changed_history[key_previous_round].get( + "fraction_score" + ) fraction_previous_round = fraction_score if fraction_score is not None else None - logging.info(f"Fraction score previous round: {fraction_previous_round}") + # logging.info(f"Fraction score previous round: {fraction_previous_round}") if fraction_previous_round is not None: fraction_score_asign = fraction_previous_round - (fraction_previous_round * 0.1) - logging.info(f"Fraction score normalized: {fraction_score_asign}") + # logging.info(f"Fraction score normalized: {fraction_score_asign}") - if fraction_previous_round is None: + if fraction_neighbors_scores is None: fraction_neighbors_scores = {} + # logging.info(f"fraction_neighbors_scores: {fraction_neighbors_scores}") + + if fraction_previous_round is None: for key, value in Reputation.fraction_changed_history.items(): - fraction_neighbors_scores[key] = value.get("fraction_score") - logging.info(f"Fraction neighbors {key} scores: {fraction_neighbors_scores}") + score = value.get("fraction_score") + # logging.info(f"Score: {score}") + if score is not None: + fraction_neighbors_scores[key] = score + if fraction_neighbors_scores: fraction_score_asign = np.mean(list(fraction_neighbors_scores.values())) - logging.info(f"Fraction score normalized: {fraction_score_asign}") + else: + fraction_score_asign = 0 # O un valor predeterminado adecuado else: fraction_score_asign = 0 @@ -302,58 +289,52 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro if current_round is not None: if current_round >= 4: # only for rounds 4 and later weight_to_similarity = 0.5 - weight_to_communication = 0.0 weight_to_fraction = 0.0 - weight_to_message_frequency = 0.5 - weight_to_chunk = 0.0 + weight_to_message_time_message = 0.5 + weight_to_model_arrival_latency = 0.0 elif current_round >= 5: # only for rounds 5 and later # Weight to scenarios with delay - # weight_to_similarity = 0.1 - # weight_to_communication = 0.3 - # weight_to_fraction = 0.1 - # weight_to_message_frequency = 0.3 - # weight_to_chunk = 0.2 + weight_to_similarity = 0.1 + weight_to_fraction = 0.1 + weight_to_message_time_message = 0.3 + weight_to_model_arrival_latency = 0.5 # Weight to scenarios with noise injection - weight_to_similarity = 0.4 - weight_to_communication = 0.1 - weight_to_fraction = 0.3 - weight_to_message_frequency = 0.1 - weight_to_chunk = 0.1 + # weight_to_similarity = 0.4 + # weight_to_fraction = 0.4 + # weight_to_message_time_message = 0.1 + # weight_to_model_arrival_latency = 0.1 # Weight to scenarios with flood # weight_to_similarity = 0.1 - # weight_to_communication = 0.2 # weight_to_fraction = 0.1 - # weight_to_message_frequency = 0.4 - # weight_to_chunk = 0.2 + # weight_to_message_time_message = 0.5 + # weight_to_model_arrival_latency = 0.3 elif current_round <= 3: # only for rounds 0, 1, 2, 3 weight_to_similarity = 1.0 - weight_to_communication = 0.0 weight_to_fraction = 0.0 - weight_to_message_frequency = 0.0 - weight_to_chunk = 0.0 - + weight_to_message_time_message = 0.0 + weight_to_model_arrival_latency = 0.0 # Reputation calculation reputation = ( - weight_to_communication * avg_communication_time_normalized - + weight_to_message_frequency * avg_messages_frequency_normalized + +weight_to_message_time_message * avg_messages_time_message_normalized + weight_to_similarity * similarity_reputation + weight_to_fraction * fraction_score_asign - + weight_to_chunk * avg_chunk_latency + + weight_to_model_arrival_latency * avg_model_arrival_latency ) # Create graphics to metrics self.create_graphics_to_metrics( - avg_communication_time_normalized, - messages_frequency_count, - avg_messages_frequency_normalized, + messages_time_message_count, + avg_messages_time_message_normalized, similarity_reputation, fraction_score_asign, - avg_chunk_latency, + avg_model_arrival_latency, addr, nei, current_round, self.engine.total_rounds, + scenario, + reputation, ) # Save history reputation @@ -368,42 +349,40 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro def create_graphics_to_metrics( self, - com_time, - mess_fre_count, - mess_fre_norm, + time_message_count, + time_message_norm, similarity, fraction, - chunk_latency, + model_arrival_latency, addr, nei, current_round, total_rounds, + scenario, + reputation, ): """ Create graphics to metrics. """ if current_round is not None and current_round < total_rounds: - communication_time_dict = {f"Communication_time_reputation/{addr}": {nei: com_time}} - - messages_frequency_count_dict = {f"Count_messages_frequency_reputation/{addr}": {nei: mess_fre_count}} - - messages_frequency_norm_dict = {f"Frequency_reputation/{addr}": {nei: mess_fre_norm}} + model_arrival_latency_dict = {f"R-Model_arrival_latency_reputation/{addr}": {nei: model_arrival_latency}} - similarity_dict = {f"Similarity_reputation/{addr}": {nei: similarity}} + messages_time_message_count_dict = { + f"R-Count_messages_time_message_reputation/{addr}": {nei: time_message_count} + } - fraction_dict = {f"Fraction_reputation/{addr}": {nei: fraction}} + messages_time_message_norm_dict = {f"R-time_message_reputation/{addr}": {nei: time_message_norm}} - chunk_latency_dict = {f"Chunk_latency_reputation/{addr}": {nei: chunk_latency}} + similarity_dict = {f"R-Similarity_reputation/{addr}": {nei: similarity}} - if communication_time_dict is not None: - self.engine.trainer._logger.log_data(communication_time_dict, step=current_round) + fraction_dict = {f"R-Fraction_reputation/{addr}": {nei: fraction}} - if messages_frequency_count_dict is not None: - self.engine.trainer._logger.log_data(messages_frequency_count_dict, step=current_round) + if messages_time_message_count_dict is not None: + self.engine.trainer._logger.log_data(messages_time_message_count_dict, step=current_round) - if messages_frequency_norm_dict is not None: - self.engine.trainer._logger.log_data(messages_frequency_norm_dict, step=current_round) + if messages_time_message_norm_dict is not None: + self.engine.trainer._logger.log_data(messages_time_message_norm_dict, step=current_round) if similarity_dict is not None: self.engine.trainer._logger.log_data(similarity_dict, step=current_round) @@ -411,12 +390,34 @@ def create_graphics_to_metrics( if fraction_dict is not None: self.engine.trainer._logger.log_data(fraction_dict, step=current_round) - if chunk_latency_dict is not None: - self.engine.trainer._logger.log_data(chunk_latency_dict, step=current_round) + if model_arrival_latency_dict is not None: + self.engine.trainer._logger.log_data(model_arrival_latency_dict, step=current_round) + + data = { + "addr": addr, + "nei": nei, + "round": current_round, + "time_message_count": time_message_count, + "time_message_norm": time_message_norm, + "similarity": similarity, + "fraction": fraction, + "model_arrival_latency": model_arrival_latency, + "reputation_without_feedback": reputation, + } + Reputation.metrics(scenario, data, addr, nei, "reputation") @staticmethod def analyze_anomalies( - addr, nei, round_num, current_round, fraction_changed, threshold, changes_record, changed_params, total_params, scenario + addr, + nei, + round_num, + current_round, + fraction_changed, + threshold, + changes_record, + changed_params, + total_params, + scenario, ): """ Analyze anomalies in the fraction of parameters changed. @@ -461,17 +462,17 @@ def analyze_anomalies( if past_fractions: mean_fraction = np.mean(past_fractions) - logging.info(f"Round: {round_num}, Mean fraction: {mean_fraction}") + # logging.info(f"Round: {round_num}, Mean fraction: {mean_fraction}") std_dev_fraction = np.std(past_fractions) - logging.info(f"Round: {round_num}, Std dev fraction: {std_dev_fraction}") + # logging.info(f"Round: {round_num}, Std dev fraction: {std_dev_fraction}") Reputation.fraction_changed_history[key]["mean_fraction"] = mean_fraction Reputation.fraction_changed_history[key]["std_dev_fraction"] = std_dev_fraction if past_thresholds: mean_threshold = np.mean(past_thresholds) - logging.info(f"Round: {round_num}, Mean threshold: {mean_threshold}") + # logging.info(f"Round: {round_num}, Mean threshold: {mean_threshold}") std_dev_threshold = np.std(past_thresholds) - logging.info(f"Round: {round_num}, Std dev threshold: {std_dev_threshold}") + # logging.info(f"Round: {round_num}, Std dev threshold: {std_dev_threshold}") Reputation.fraction_changed_history[key]["mean_threshold"] = mean_threshold Reputation.fraction_changed_history[key]["std_dev_threshold"] = std_dev_threshold @@ -481,296 +482,360 @@ def analyze_anomalies( threshold_value = 0 prev_key = (addr, nei, round_num - 1) if prev_key not in Reputation.fraction_changed_history: - prev_key = (addr, nei, round_num - 2) - - mean_fraction_prev = Reputation.fraction_changed_history[prev_key]["mean_fraction"] - std_dev_fraction_prev = Reputation.fraction_changed_history[prev_key]["std_dev_fraction"] - mean_threshold_prev = Reputation.fraction_changed_history[prev_key]["mean_threshold"] - std_dev_threshold_prev = Reputation.fraction_changed_history[prev_key]["std_dev_threshold"] - logging.info(f"Round: {round_num}, Mean fraction: {mean_fraction_prev}, Std dev fraction: {std_dev_fraction_prev}, Mean threshold: {mean_threshold_prev}, Std dev threshold: {std_dev_threshold_prev}") - - current_fraction = Reputation.fraction_changed_history[key]["fraction_changed"] - current_threshold = Reputation.fraction_changed_history[key]["threshold"] - logging.info(f"Round: {round_num}, Current fraction: {current_fraction}, Current threshold: {current_threshold}") - - # low_mean_fraction_prev = mean_fraction_prev - std_dev_fraction_prev - upper_mean_fraction_prev = (mean_fraction_prev) + (std_dev_fraction_prev) - # low_mean_threshold_prev = mean_threshold_prev - std_dev_threshold_prev - upper_mean_threshold_prev = (mean_threshold_prev) + (std_dev_threshold_prev) - logging.info(f"Round: {round_num}, Upper mean fraction: {upper_mean_fraction_prev}, Upper mean threshold: {upper_mean_threshold_prev}") - - # fraction_anomaly = not (low_mean_fraction_prev <= current_fraction <= upper_mean_fraction_prev) - # threshold_anomaly = not (low_mean_threshold_prev <= current_threshold <= upper_mean_threshold_prev) - fraction_anomaly = current_fraction > upper_mean_fraction_prev - threshold_anomaly = current_threshold > upper_mean_threshold_prev - logging.info(f"Round: {round_num}, Fraction anomaly: {fraction_anomaly}, Threshold anomaly: {threshold_anomaly}") - - Reputation.fraction_changed_history[key]["fraction_anomaly"] = fraction_anomaly - Reputation.fraction_changed_history[key]["threshold_anomaly"] = threshold_anomaly - - # Calculate the fraction score - k_fraction = std_dev_fraction_prev if std_dev_fraction_prev != 0 else 1 - logging.info(f"Round: {round_num}, K fraction: {k_fraction}") - k_threshold = std_dev_threshold_prev if std_dev_threshold_prev != 0 else 1 - logging.info(f"Round: {round_num}, K threshold: {k_threshold}") - fraction_value = ( - 1 - (1 / (1 + np.exp(-k_fraction * (current_fraction - mean_fraction_prev)))) - if current_fraction is not None and mean_fraction_prev is not None - else 0 - ) - logging.info(f"Round: {round_num}, Fraction_value: {fraction_value}") - - threshold_value = ( - 1 - (1 / (1 + np.exp(-k_threshold * (current_threshold - mean_threshold_prev)))) - if current_threshold is not None and mean_threshold_prev is not None - else 0 - ) - logging.info(f"Round: {round_num}, Threshold_value: {threshold_value}") - - # if threshold_anomaly: - # fraction_weight = 0.8 - # threshold_weight = 0.2 - # elif fraction_anomaly: - # fraction_weight = 0.4 - # threshold_weight = 0.6 - # else: - # fraction_weight = 0.5 - # threshold_weight = 0.5 - - fraction_weight = 0.5 - threshold_weight = 0.5 - - fraction_score = fraction_weight * fraction_value + threshold_weight * threshold_value - logging.info(f"Round: {round_num}, Fraction score: {fraction_score}") - # Reputation.fraction_changed_history[key]["fraction_score"] = fraction_score - - # Upload the values to the history - Reputation.fraction_changed_history[key]["mean_fraction"] = (current_fraction + mean_fraction_prev) / 2 - Reputation.fraction_changed_history[key]["std_dev_fraction"] = np.sqrt( - ((current_fraction - mean_fraction_prev) ** 2 + std_dev_fraction_prev**2) / 2 - ) - Reputation.fraction_changed_history[key]["mean_threshold"] = ( - current_threshold + mean_threshold_prev - ) / 2 - Reputation.fraction_changed_history[key]["std_dev_threshold"] = np.sqrt( - ((0.1 * (current_threshold - mean_threshold_prev) ** 2) + std_dev_threshold_prev**2) / 2 - ) + for i in range(0, round_num + 1): + potential_prev_key = (addr, nei, round_num - i) + if potential_prev_key in Reputation.fraction_changed_history: + prev_key = potential_prev_key + break + + if prev_key: + mean_fraction_prev = Reputation.fraction_changed_history[prev_key]["mean_fraction"] + std_dev_fraction_prev = Reputation.fraction_changed_history[prev_key]["std_dev_fraction"] + mean_threshold_prev = Reputation.fraction_changed_history[prev_key]["mean_threshold"] + std_dev_threshold_prev = Reputation.fraction_changed_history[prev_key]["std_dev_threshold"] + # logging.info(f"Round: {round_num}, Mean fraction: {mean_fraction_prev}, Std dev fraction: {std_dev_fraction_prev}, Mean threshold: {mean_threshold_prev}, Std dev threshold: {std_dev_threshold_prev}") + + current_fraction = Reputation.fraction_changed_history[key]["fraction_changed"] + current_threshold = Reputation.fraction_changed_history[key]["threshold"] + # logging.info(f"Round: {round_num}, Current fraction: {current_fraction}, Current threshold: {current_threshold}") + + # low_mean_fraction_prev = mean_fraction_prev - std_dev_fraction_prev + upper_mean_fraction_prev = (mean_fraction_prev) + (std_dev_fraction_prev) + # low_mean_threshold_prev = mean_threshold_prev - std_dev_threshold_prev + upper_mean_threshold_prev = (mean_threshold_prev) + (std_dev_threshold_prev) + # logging.info(f"Round: {round_num}, Upper mean fraction: {upper_mean_fraction_prev}, Upper mean threshold: {upper_mean_threshold_prev}") + + # fraction_anomaly = not (low_mean_fraction_prev <= current_fraction <= upper_mean_fraction_prev) + # threshold_anomaly = not (low_mean_threshold_prev <= current_threshold <= upper_mean_threshold_prev) + fraction_anomaly = current_fraction > upper_mean_fraction_prev + threshold_anomaly = current_threshold > upper_mean_threshold_prev + # logging.info(f"Round: {round_num}, Fraction anomaly: {fraction_anomaly}, Threshold anomaly: {threshold_anomaly}") + + Reputation.fraction_changed_history[key]["fraction_anomaly"] = fraction_anomaly + Reputation.fraction_changed_history[key]["threshold_anomaly"] = threshold_anomaly + + # Calculate the fraction score + k_fraction = std_dev_fraction_prev if std_dev_fraction_prev != 0 else 1 + # logging.info(f"Round: {round_num}, K fraction: {k_fraction}") + k_threshold = std_dev_threshold_prev if std_dev_threshold_prev != 0 else 1 + # logging.info(f"Round: {round_num}, K threshold: {k_threshold}") + fraction_value = ( + 1 - (1 / (1 + np.exp(-k_fraction * (current_fraction - mean_fraction_prev)))) + if current_fraction is not None and mean_fraction_prev is not None + else 0 + ) + # logging.info(f"Round: {round_num}, Fraction_value: {fraction_value}") - data = { - "addr": addr, - "nei": nei, - "round": current_round, - "fraction_changed": current_fraction, - "threshold": current_threshold, - "fraction_score": fraction_score, - } - Reputation.metrics(scenario, data, addr, nei, "fraction_changed") + threshold_value = ( + 1 - (1 / (1 + np.exp(-k_threshold * (current_threshold - mean_threshold_prev)))) + if current_threshold is not None and mean_threshold_prev is not None + else 0 + ) + # logging.info(f"Round: {round_num}, Threshold_value: {threshold_value}") + + # if threshold_anomaly: + # fraction_weight = 0.8 + # threshold_weight = 0.2 + # elif fraction_anomaly: + # fraction_weight = 0.4 + # threshold_weight = 0.6 + # else: + # fraction_weight = 0.5 + # threshold_weight = 0.5 + + fraction_weight = 0.5 + threshold_weight = 0.5 + + fraction_score = fraction_weight * fraction_value + threshold_weight * threshold_value + # logging.info(f"Round: {round_num}, Fraction score: {fraction_score}") + # Reputation.fraction_changed_history[key]["fraction_score"] = fraction_score + + # Upload the values to the history + Reputation.fraction_changed_history[key]["mean_fraction"] = ( + current_fraction + mean_fraction_prev + ) / 2 + Reputation.fraction_changed_history[key]["std_dev_fraction"] = np.sqrt( + ((current_fraction - mean_fraction_prev) ** 2 + std_dev_fraction_prev**2) / 2 + ) + Reputation.fraction_changed_history[key]["mean_threshold"] = ( + current_threshold + mean_threshold_prev + ) / 2 + Reputation.fraction_changed_history[key]["std_dev_threshold"] = np.sqrt( + ((0.1 * (current_threshold - mean_threshold_prev) ** 2) + std_dev_threshold_prev**2) / 2 + ) - return max(fraction_score, 0) + data = { + "addr": addr, + "nei": nei, + "round": current_round, + "fraction_changed": current_fraction, + "threshold": current_threshold, + "fraction_score": fraction_score, + } + Reputation.metrics(scenario, data, addr, nei, "fraction_changed") + + return max(fraction_score, 0) + else: + return -1 except Exception: logging.exception("Error analyzing anomalies") + return -1 @staticmethod - def save_frequency_history(addr, nei, messages_frequency_normalized, current_round): + def save_time_message_history(addr, nei, messages_time_message_normalized, current_round): """ - Save the frequency history of a participant (addr) regarding its neighbor (nei) in memory. + Save the time_message history of a participant (addr) regarding its neighbor (nei) in memory. Args: - addr (str): The identifier of the node whose frequency history is being saved. + addr (str): The identifier of the node whose time_message history is being saved. nei (str): The neighboring node involved. - messages_frequency_normalized (float): The frequency value to be saved. + messages_time_message_normalized (float): The time_message value to be saved. current_round (int): The current round number. Returns: - float: The cumulative frequency including the current round. + float: The cumulative time_message including the current round. """ try: key = (addr, nei) - avg_frequency = 0 + avg_time_message = 0 - if key not in Reputation.frequency_history: - Reputation.frequency_history[key] = {} + if key not in Reputation.time_message_history: + Reputation.time_message_history[key] = {} - Reputation.frequency_history[key][current_round] = {"frequency": messages_frequency_normalized} + Reputation.time_message_history[key][current_round] = {"time_message": messages_time_message_normalized} - # logging.info(f"Frequency: {messages_frequency_normalized}") - # logging.info(f"Frequency history: {Reputation.frequency_history}") + # logging.info(f"time_message: {messages_time_message_normalized}") + # logging.info(f"time_message history: {Reputation.time_message_history}") - # rounds = Reputation.frequency_history[key] - if messages_frequency_normalized != 0 and current_round >= 4: - previous_avg = Reputation.frequency_history[key].get(current_round - 1, {}).get("avg_frequency", None) - # logging.info(f"Previous avg frequency: {previous_avg}") + # rounds = Reputation.time_message_history[key] + if messages_time_message_normalized != 0 and current_round >= 4: + previous_avg = ( + Reputation.time_message_history[key].get(current_round - 1, {}).get("avg_time_message", None) + ) + # logging.info(f"Previous avg time_message: {previous_avg}") if previous_avg is not None: - avg_frequency = (messages_frequency_normalized + previous_avg) / 2 - # logging.info(f"Avg frequency in if: {avg_frequency}") + avg_time_message = (messages_time_message_normalized + previous_avg) / 2 + # logging.info(f"Avg time_message in if: {avg_time_message}") else: - avg_frequency = messages_frequency_normalized - # logging.info(f"Avg frequency in else: {avg_frequency}") + avg_time_message = messages_time_message_normalized + # logging.info(f"Avg time_message in else: {avg_time_message}") - Reputation.frequency_history[key][current_round]["avg_frequency"] = avg_frequency + Reputation.time_message_history[key][current_round]["avg_time_message"] = avg_time_message else: - avg_frequency = 0 + avg_time_message = 0 - # logging.info(f"Avg frequency: {avg_frequency}") - return avg_frequency + # logging.info(f"Avg time_message: {avg_time_message}") + return avg_time_message except Exception: - logging.exception("Error saving frequency history") + logging.exception("Error saving time_message history") return -1 + except Exception as e: + logging.exception(f"Error managing model_arrival_latency latency: {e}") + return 0.0 + @staticmethod - def manage_chunk_latency(round_num, addr, nei, latency): + def manage_model_arrival_latency(round_num, addr, nei, latency, scenario, model_arrival_latency_data): """ - Manage the chunk latency metric with persistent storage of mean latency. + Manage the model_arrival_latency latency metric with persistent storage of mean latency. Args: - messages_chunk_latency (list): List of messages chunk latency. + round_num (int): The round number. addr (str): Source IP address. nei (str): Destination IP address. - current_round (int): Current round number. + latency (float): Latency value for the current model_arrival_latency. + scenario (str): The scenario name for logging and metric storage. + model_arrival_latency_data (dict): model_arrival_latency-related data. Returns: - float: Normalized chunk latency value. + float: Normalized model_arrival_latency latency value between 0 and 1. """ try: - current_key = (addr, nei, round_num) - mean_chunk_latency = 0.0 - score = 0.0 - previous_mean = -1 - - if current_key not in Reputation.communication_data: - Reputation.messages_chunk_latency[current_key] = { - "latency": latency, - "round": round_num, - "mean_chunk_latency": None, - "chunk_score": None, - "std_dev_chunk": None, - } - else: - Reputation.messages_chunk_latency[current_key].update({ - "latency": latency, - "round": round_num, - "mean_chunk_latency": None, - "chunk_score": None, - "std_dev_chunk": None, - }) - # logging.info(f"Round {round_num}. Messages chunk latency: {Reputation.messages_chunk_latency}") - - if round_num == 4: - latencies = [ - msg["latency"] - for key, msg in Reputation.messages_chunk_latency.items() - if key[:2] == current_key[:2] and msg["round"] < 4 - ] - # logging.info(f"Round {round_num}. Latencies for rounds 0-3: {latencies}") + current_key = nei - if latencies: - mean_chunk_latency = np.mean(latencies) - std_dev_chunk = np.std(latencies) - # logging.info(f"Round {round_num}. Mean chunk latency for rounds 0-3: {mean_chunk_latency}") - # logging.info(f"Round {round_num}. Std dev chunk latency for rounds 0-3: {std_dev_chunk}") + if round_num not in Reputation.model_arrival_latency_history: + Reputation.model_arrival_latency_history[round_num] = {} - current_latency = [ - msg["latency"] for key, msg in Reputation.messages_chunk_latency.items() if key == current_key - ] - current_latency = current_latency[-1] if current_latency else -1 - # logging.info(f"Round {round_num}. Current latency: {current_latency}") + Reputation.model_arrival_latency_history[round_num][current_key] = { + "latency": latency, + "score": 0.0, + } + + # logging.info(f"Reputation.model_arrival_latency_history: {Reputation.model_arrival_latency_history}") + + if round_num >= 5: + if round_num > 5: + if ( + round_num - 1 in Reputation.model_arrival_latency_history + and current_key in Reputation.model_arrival_latency_history[round_num - 1] + ): + prev_mean_latency = Reputation.model_arrival_latency_history[round_num - 1][current_key].get( + "mean_latency", None + ) + prev_stdev_latency = Reputation.model_arrival_latency_history[round_num - 1][current_key].get( + "stdev_latency", None + ) + percentil_25 = Reputation.model_arrival_latency_history[round_num - 1][current_key].get( + "percentil_25", None + ) + percentil_75 = Reputation.model_arrival_latency_history[round_num - 1][current_key].get( + "percentil_75", None + ) - if std_dev_chunk: - difference = abs(current_latency - mean_chunk_latency) - # logging.info(f"Round {round_num}. Difference: {difference}") + # logging.info(f"Round {round_num} | Node {nei} | Round {round_num - 1} | Previous Mean Latency: {prev_mean_latency} | Previous Stdev Latency: {prev_stdev_latency}") + # logging.info(f"Round {round_num} | Node {nei} | Round {round_num - 1} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}") + + # Si no se encuentran datos en round_num - 1, recorrer las rondas previas desde round_num - 2 hasta 5 + if prev_mean_latency is None or prev_stdev_latency is None: + for r in range(round_num - 2, 5 - 1, -1): + if ( + r in Reputation.model_arrival_latency_history + and current_key in Reputation.model_arrival_latency_history[r] + ): + prev_mean_latency = Reputation.model_arrival_latency_history[r][current_key].get( + "mean_latency", None + ) + prev_stdev_latency = Reputation.model_arrival_latency_history[r][current_key].get( + "stdev_latency", None + ) + percentil_25 = Reputation.model_arrival_latency_history[r][current_key].get( + "percentil_25", None + ) + percentil_75 = Reputation.model_arrival_latency_history[r][current_key].get( + "percentil_75", None + ) + + # logging.info(f"Round {round_num} | Node {nei} | Round {r} | Previous Mean Latency: {prev_mean_latency} | Previous Stdev Latency: {prev_stdev_latency}") + # logging.info(f"Round {round_num} | Node {nei} | Round {r} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}") + + # Si se encuentran los datos, salir del bucle + if prev_mean_latency is not None and prev_stdev_latency is not None: + break + else: + logging.info( + f"Round {round_num} | Node {nei} | Round {r} | No previous data found." + ) + + # Si no se encontraron datos en ninguna ronda anterior, inicializar valores predeterminados + if prev_mean_latency is None or prev_stdev_latency is None: + prev_mean_latency = 0 + prev_stdev_latency = 1 + percentil_25 = 0 + percentil_75 = 0 + # logging.info(f"Round {round_num} | Node {nei} | No previous latency data found, initializing defaults.") + else: + all_latencies = [] + for r in range(round_num - 1): + if r in Reputation.model_arrival_latency_history: + for key, data in Reputation.model_arrival_latency_history[r].items(): + if "latency" in data and data["latency"] != 0: + all_latencies.append(data["latency"]) + + logging.info(f"Round {round_num} | Node {nei} | All latencies: {all_latencies}") + + prev_mean_latency = np.mean(all_latencies) if all_latencies else 0 + prev_stdev_latency = np.std(all_latencies) if all_latencies else 1 + logging.info( + f"Round {round_num} | Node {nei} | Initial Mean Latency: {prev_mean_latency} | Initial Stdev Latency: {prev_stdev_latency}" + ) - penalty_range = 1 / (1 + np.exp(-difference / (std_dev_chunk or 1))) - # logging.info(f"Round {round_num}. Penalty range: {penalty_range}") + percentil_25 = np.percentile(all_latencies, 25) if all_latencies else 0 + percentil_75 = np.percentile(all_latencies, 75) if all_latencies else 0 + logging.info( + f"Round {round_num} | Node {nei} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}" + ) - if current_latency < mean_chunk_latency: - reduction_factor = max(0, 1 - (difference / (mean_chunk_latency or 1))) - # logging.info(f"Round {round_num}. Reduction factor: {reduction_factor}") - penalty_range *= reduction_factor + k = 0.5 + prev_mean_latency = prev_mean_latency + k * (percentil_75 - percentil_25) + logging.info(f"Round {round_num} | Node {nei} | Mean latency with k: {prev_mean_latency}") + logging.info(f"Round {round_num} | Node {nei} | Latency: {latency}") - score = np.exp(-penalty_range) - # logging.info(f"Round {round_num}. Chunk score: {score}") + if latency == 0.0: + latency = 0.5 - Reputation.messages_chunk_latency[current_key]["mean_chunk_latency"] = mean_chunk_latency - Reputation.messages_chunk_latency[current_key]["chunk_score"] = score - Reputation.messages_chunk_latency[current_key]["std_dev_chunk"] = std_dev_chunk - elif round_num > 4: - last_key = (addr, nei, round_num - 1) + difference = latency - prev_mean_latency + logging.info(f"Round {round_num} | Node {nei} | Difference: {difference}") - current_latency = [ - msg["latency"] for key, msg in Reputation.messages_chunk_latency.items() if key == current_key - ] - current_latency = current_latency[-1] if current_latency else -1 - # logging.info(f"Round {round_num}. Current latency: {current_latency}") + if latency <= prev_mean_latency: + score = 1 + else: + if abs(difference) <= prev_mean_latency: + score = 1 + else: + score = 1 / (1 + np.exp(abs(difference) / prev_mean_latency)) + logging.info(f"Round {round_num} | Node {nei} | Score: {score}") + Reputation.model_arrival_latency_history[round_num][current_key]["score"] = score - previous_mean = [ - msg["mean_chunk_latency"] - for key, msg in Reputation.messages_chunk_latency.items() - if key == last_key - ] - previous_mean = previous_mean[-1] if previous_mean else -1 - # logging.info(f"Round {round_num}. Previous mean chunk latency: {previous_mean}") + n = round_num if round_num else 1 + # logging.info(f"Round {round_num} | Node {nei} | N: {n}") + + update_mean = (prev_mean_latency * n + latency) / (n + 1) + update_stdev = np.sqrt( + ((prev_stdev_latency**2) + (latency - prev_mean_latency) * (latency - update_mean)) / (n + 1) + ) + logging.info( + f"Round {round_num} | Node {nei} | Mean Latency: {update_mean} | Stdev Latency: {update_stdev}" + ) - previous_std = [ - msg["std_dev_chunk"] for key, msg in Reputation.messages_chunk_latency.items() if key == last_key + accumulated_latencies = [ + data["latency"] + for r in range(round_num + 1) + if r in Reputation.model_arrival_latency_history + for key, data in Reputation.model_arrival_latency_history[r].items() + if "latency" in data and data["latency"] != 0 ] - previous_std = previous_std[-1] if previous_std else -1 - # logging.info(f"Round {round_num}. Previous std dev chunk latency: {previous_std}") - - if current_latency != -1 and previous_mean != -1 and previous_std != -1: - # Calcular la diferencia y rango de penalización - difference = abs(current_latency - previous_mean) - # logging.info(f"Round {round_num}. Difference: {difference}") - - penalty_range = 1 / (1 + np.exp(-difference / (previous_std or 1))) - # logging.info(f"Round {round_num}. Penalty range: {penalty_range}") - - if current_latency < previous_mean: - reduction_factor = max(0, 1 - (difference / (previous_mean or 1))) - # logging.info(f"Round {round_num}. Reduction factor: {reduction_factor}") - penalty_range *= reduction_factor - - # Calcular el score basado en la penalización - score = np.exp(-penalty_range) - # logging.info(f"Round {round_num}. Chunk score: {score}") - - # Actualizar la latencia media - mean_chunk_latency = (previous_mean + current_latency) / 2 - # logging.info(f"Round {round_num}. Updated mean chunk latency: {mean_chunk_latency}") - - # Actualizar la métrica en el diccionario - Reputation.messages_chunk_latency[current_key]["mean_chunk_latency"] = mean_chunk_latency - Reputation.messages_chunk_latency[current_key]["chunk_score"] = score - Reputation.messages_chunk_latency[current_key]["std_dev_chunk"] = previous_std - else: - logging.info(f"Round {round_num}. Missing data for calculation.") - return -1 + logging.info(f"Round {round_num} | Node {nei} | Accumulated latencies: {accumulated_latencies}") + + update_percentil_25 = np.percentile(accumulated_latencies, 25) if accumulated_latencies else 0 + update_percentil_75 = np.percentile(accumulated_latencies, 75) if accumulated_latencies else 0 + logging.info( + f"Round {round_num} | Node {nei} | Percentil 25: {update_percentil_25} | Percentil 75: {update_percentil_75}" + ) + + Reputation.model_arrival_latency_history[round_num][current_key]["mean_latency"] = update_mean + Reputation.model_arrival_latency_history[round_num][current_key]["stdev_latency"] = update_stdev + Reputation.model_arrival_latency_history[round_num][current_key]["percentil_25"] = update_percentil_25 + Reputation.model_arrival_latency_history[round_num][current_key]["percentil_75"] = update_percentil_75 + else: - if 0 <= round_num <= 3: - logging.info(f"Round {round_num}. Not enough data to calculate the metric.") - else: - logging.info(f"Round {round_num}. Missing data for calculation.") - return -1 + # For rounds < 5, no scoring or updates + score = 0.0 + + # Store the metrics + data = { + "addr": addr, + "nei": nei, + "round": round_num, + "latency": latency, + "mean_latency": prev_mean_latency if round_num >= 5 else None, + "stdev_latency": prev_stdev_latency if round_num >= 5 else None, + "percentil_25": percentil_25 if round_num >= 5 else None, + "percentil_75": percentil_75 if round_num >= 5 else None, + "difference": difference if round_num >= 5 else None, + "score": score, + } + Reputation.metrics(scenario, data, addr, nei, "model_arrival_latency") return score - except Exception: - logging.exception("Error managing chunk latency metric") - return -1 + except Exception as e: + logging.exception(f"Error managing model_arrival_latency latency: {e}") + return 0.0 @staticmethod - def manage_metric_frequency(messages_frequency, addr, nei, current_round, scenario): + def manage_metric_time_message(messages_time_message, addr, nei, current_round, scenario): """ - Manage the frequency metric using percentiles for normalization, considering the last 4 rounds dynamically. + Manage the time_message metric using percentiles for normalization, considering the last 4 rounds dynamically. Args: - messages_frequency (list): List of messages frequency. + messages_time_message (list): List of messages time_message. addr (str): Source IP address. nei (str): Destination IP address. current_round (int): Current round number. Returns: - float: Normalized frequency value. + float: Normalized time_message value. int: Messages count. """ try: @@ -778,10 +843,13 @@ def manage_metric_frequency(messages_frequency, addr, nei, current_round, scenar return 0.0, 0 previous_round = current_round - 1 - logging.info(f"Round {current_round}. Previous round: {previous_round}") + # logging.info(f"Round {current_round}. Previous round: {previous_round}") + current_addr_nei = (addr, nei) relevant_messages = [ - msg for msg in messages_frequency if msg["key"] == current_addr_nei and msg["round"] == previous_round + msg + for msg in messages_time_message + if msg["key"] == current_addr_nei and msg["round"] == previous_round ] messages_count = len(relevant_messages) if relevant_messages else 0 # logging.info(f"Round {current_round}. Messages count: {messages_count}") @@ -797,16 +865,16 @@ def manage_metric_frequency(messages_frequency, addr, nei, current_round, scenar # logging.info(f"Round {current_round}. Rounds to consider: {rounds_to_consider}") previous_counts = [ - len([m for m in messages_frequency if m["key"] == current_addr_nei and m["round"] == r]) + len([m for m in messages_time_message if m["key"] == current_addr_nei and m["round"] == r]) for r in rounds_to_consider ] # logging.info(f"Round {current_round}. Total counts of rounds_to_consider: {previous_counts}") # Calculate the 25th and 75th percentiles based on the selected rounds - Reputation.previous_percentile_25_freq[current_addr_nei] = ( + Reputation.previous_percentile_25_time_message[current_addr_nei] = ( np.percentile(previous_counts, 25) if previous_counts else 0 ) - Reputation.previous_percentile_85_freq[current_addr_nei] = ( + Reputation.previous_percentile_85_time_message[current_addr_nei] = ( np.percentile(previous_counts, 85) * 1.15 if previous_counts else 0 ) @@ -814,9 +882,9 @@ def manage_metric_frequency(messages_frequency, addr, nei, current_round, scenar # Apply the normalizations using the percentiles if previous_round >= 4: # Ensure there's enough data to calculate percentiles - percentile_25 = Reputation.previous_percentile_25_freq.get(current_addr_nei, 0) + percentile_25 = Reputation.previous_percentile_25_time_message.get(current_addr_nei, 0) # logging.info(f"Round {current_round}. percentile_25: {percentile_25}") - percentile_85 = Reputation.previous_percentile_85_freq.get(current_addr_nei, 0) + percentile_85 = Reputation.previous_percentile_85_time_message.get(current_addr_nei, 0) # logging.info(f"Round {current_round}. percentile_85: {percentile_85}") # logging.info(f"Round {current_round}. Messages count: {messages_count}") @@ -838,261 +906,87 @@ def manage_metric_frequency(messages_frequency, addr, nei, current_round, scenar "round": current_round, "messages_count": messages_count, "normalized_messages": normalized_messages, - "percentile_25": Reputation.previous_percentile_25_freq[current_addr_nei], - "percentile_85": Reputation.previous_percentile_85_freq[current_addr_nei], + "percentile_25": Reputation.previous_percentile_25_time_message[current_addr_nei], + "percentile_85": Reputation.previous_percentile_85_time_message[current_addr_nei], } - Reputation.metrics(scenario, data, addr, nei, "frequency") + Reputation.metrics(scenario, data, addr, nei, "time_message") return normalized_messages, messages_count except Exception: - logging.exception("Error managing metric frequency") + logging.exception("Error managing metric time_message") return 0.0, 0 @staticmethod - def manage_communication(type_message, num_round, current_round, time, addr, nei): + def save_model_arrival_latency_history(addr, nei, model_arrival_latency, round_num): """ - Manage the communication metric. + Save the model_arrival_latency history of a participant (addr) regarding its neighbor (nei) in memory. Args: - type_message (str): Type of message. - num_round (int): Round number. - current_round (int): Current round number. - time (float): Time taken to process the data. - addr (str): Source IP address. - nei (str): Destination IP address. - - Returns: - float: Normalized communication value. - """ - try: - current_addr_nei = (addr, nei, current_round) - score = 0.0 - - if current_addr_nei not in Reputation.communication_data: - Reputation.communication_data[current_addr_nei] = { - "time": time, - "type_message": type_message, - "round": num_round, - "current_round": current_round, - "mean_time": None, - } - else: - Reputation.communication_data[current_addr_nei].update({ - "time": time, - "type_message": type_message, - "round": num_round, - "current_round": current_round, - }) - # logging.info(f"Round {current_round}. Communication data: {Reputation.communication_data}") - - filtered_communications = [ - comm - for (a, n, r), comm in Reputation.communication_data.items() - if a == addr and n == nei and 0 <= r <= current_round - ] - # logging.info(f"Round {current_round}. Filtered by key: {filtered_communications}") - - if current_round == 5: - model_times = [ - comm["time"] - for comm in filtered_communications - if comm["type_message"] == "model" and comm["current_round"] <= 5 and comm["time"] > 0 - ] - # logging.info(f"Round {current_round}. Model times: {model_times}") - - if model_times: - mean_time = np.mean(model_times) * 1.20 - # logging.info(f"Round {current_round}. Mean time communication: {mean_time}") - - difference = abs(time - mean_time) - # logging.info(f"Round {current_round}. Time: {time} difference: {difference}") - - penalty_range = difference / mean_time if mean_time > 0 else 1 - # logging.info(f"Round {current_round}. Penalty range: {penalty_range}") - - if time < mean_time: - reduction_factor = 1 / (1 + np.exp(difference / mean_time)) - # logging.info(f"Round {current_round}. Reduction factor: {reduction_factor}") - penalty_range *= reduction_factor - # logging.info(f"Round {current_round}. Normal penalty reduction: {penalty_range}") - - if num_round != current_round: - round_difference = abs(current_round - num_round) - penalty_range += round_difference * 0.1 - # logging.info(f"Round {round} != current round {current_round}. Additional penalty for round difference.") - - score = np.exp(-penalty_range) - # logging.info(f"Round {current_round}. Communication score: {score}") - - Reputation.communication_data[current_addr_nei]["mean_time"] = mean_time - # logging.info(f"Round {current_round}. Update mean time communication: {Reputation.communication_data[current_addr_nei]['mean_time']}") - else: - score = 0.0 - mean_time = 0.0 - # logging.info(f"Round {current_round}. No data to calculate communication score") - - return score - - elif current_round > 5: - # logging.info(f"Round {current_round}. Current round > 5 and time: {time}") - previous_round = (addr, nei, current_round - 1) - # logging.info(f"Round {current_round}. Previous round: {previous_round}") - - if previous_round in Reputation.communication_data: - previous_mean = Reputation.communication_data[previous_round]["mean_time"] - # logging.info(f"Round {current_round}. time: {time} | previous_mean: {previous_mean}") - else: - previous_round = (addr, nei, current_round - 2) - if previous_round in Reputation.communication_data: - previous_mean = Reputation.communication_data[previous_round]["mean_time"] - # logging.info(f"Round {current_round}. time: {time} | previous_mean: {previous_mean}") - else: - previous_mean = None - - if previous_mean is not None: - if time == 0.0: - time = 1.0 - difference = abs(time - previous_mean) - # logging.info(f"Round {current_round}. Time: {time} difference: {difference}") - - if previous_mean > 0: - penalty_range = difference / previous_mean - # logging.info(f"Round {current_round}. Penalty range: {penalty_range}") - - if time < difference: - reduction_factor = 1 / (1 + np.exp(difference / previous_mean)) - # logging.info(f"Round {current_round}. Reduction factor: {reduction_factor}") - penalty_range *= reduction_factor - # logging.info(f"Round {current_round}. Normal penalty reduction: {penalty_range}") - else: - penalty_range = 1 - - if num_round != current_round: - round_difference = abs(current_round - num_round) - penalty_range += round_difference * 0.1 - # logging.info(f"Round {round} != current round {current_round}. Additional penalty for round difference.") - - score = np.exp(-penalty_range) - # logging.info(f"Round {current_round}. Communication score: {score}") - - mean_time = ((previous_mean + time) / 2) * 1.20 - # logging.info(f"Round {current_round}. Update mean time communication: {mean_time}") - - Reputation.communication_data[current_addr_nei]["mean_time"] = mean_time - return score - else: - return -1 - # logging.info(f"Round {current_round}. No previous mean time to calculate communication score in key {previous_round}") - else: - return 0 - except Exception: - logging.exception("Error managing communication metric") - return -1 - - @staticmethod - def save_chunk_history(addr, nei, chunk, current_round): - """ - Save the chunk history of a participant (addr) regarding its neighbor (nei) in memory. - - Args: - addr (str): The identifier of the node whose chunk history is being saved. + addr (str): The identifier of the node whose model_arrival_latency history is being saved. nei (str): The neighboring node involved. - chunk (float): The chunk value to be saved. + model_arrival_latency (float): The model_arrival_latency value to be saved. current_round (int): The current round number. Returns: - float: The cumulative chunk including the current round. + float: The cumulative model_arrival_latency including the current round. """ try: - key = (addr, nei, current_round) - - if key not in Reputation.chunk_history: - Reputation.chunk_history[key] = {} - - Reputation.chunk_history[key] = {"chunk_latency": chunk} - - # logging.info(f"chunk history: {Reputation.chunk_history} | chunk: {chunk} | current_round: {current_round}") - - if chunk >= 0 and current_round > 4: - # logging.info(" if chunk >= 0 and current_round > 4") - previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 1), {}).get("chunk_latency", 0) - # logging.info(f"Previous avg chunk latency: {previous_avg}") - - if isinstance(previous_avg, np.ndarray): - previous_avg = previous_avg.item() - # logging.info(f"Previous avg chunk latency: {previous_avg}") - - avg_chunk_latency = (chunk + previous_avg) / 2 if previous_avg is not None else chunk - return avg_chunk_latency - elif chunk == -1 and current_round > 4: - # logging.info(" elif chunk == -1 and current_round > 4") - previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 1), {}).get("chunk_latency", 0) - if previous_avg is None: - previous_avg = Reputation.chunk_history.get((addr, nei, current_round - 2), {}).get( - "chunk_latency", 0 - ) - # logging.info(f"Previous avg chunk latency: {previous_avg}") - - if isinstance(previous_avg, np.ndarray): - previous_avg = previous_avg.item() - # logging.info(f"Previous avg chunk latency: {previous_avg}") - - avg_chunk_latency = previous_avg - (previous_avg * 0.1) if previous_avg is not None else 0 - else: - avg_chunk_latency = 0 - - Reputation.chunk_history[key]["avg_chunk_latency"] = avg_chunk_latency - return avg_chunk_latency - except Exception: - logging.exception("Error saving chunk history") - - @staticmethod - def save_communication_history(addr, nei, communication, current_round): - """ - Save the communication history of a participant (addr) regarding its neighbor (nei) in memory. + # Define the current key for the neighbor node (nei) + current_key = nei - Args: - addr (str): The identifier of the node whose communication history is being saved. - nei (str): The neighboring node involved. - communication (float): The communication value to be saved. - current_round (int): The current round number. + # Initialize the history for the current round if it doesn't exist + if round_num not in Reputation.model_arrival_latency_history: + Reputation.model_arrival_latency_history[round_num] = {} - Returns: - float: The cumulative communication including the current round. - """ - try: - key = (addr, nei) - - if key not in Reputation.communication_history: - Reputation.communication_history[key] = {} + # Store the latency value for the current round and neighbor + if current_key not in Reputation.model_arrival_latency_history[round_num]: + Reputation.model_arrival_latency_history[round_num][current_key] = {} - Reputation.communication_history[key][current_round] = {"communication": communication} + Reputation.model_arrival_latency_history[round_num][current_key].update({ + "score": model_arrival_latency, + }) - # logging.info(f"Communication: {communication}") - # logging.info(f"Communication history: {Reputation.communication_history}") + # logging.info(f"model_arrival_latency history: {Reputation.model_arrival_latency_history} | model_arrival_latency: {model_arrival_latency} | current_round: {current_round}") - # rounds = Reputation.communication_history[key] - # recent_rounds = sorted(rounds.keys(), reverse=True) #[:2] - avg_communication = 0 - if communication >= 0 and current_round > 4: + if model_arrival_latency > 0 and round_num > 5: + # logging.info(" if model_arrival_latency >= 0 and round_num > 5") previous_avg = ( - Reputation.communication_history[key].get(current_round - 1, {}).get("avg_communication", None) + Reputation.model_arrival_latency_history.get(round_num - 1, {}) + .get(current_key, {}) + .get("avg_model_arrival_latency", None) ) - # logging.info(f"Previous avg communication: {previous_avg}") + # logging.info(f"Previous avg model_arrival_latency latency: {previous_avg}") - avg_communication = (communication + previous_avg) / 2 if previous_avg is not None else communication - Reputation.communication_history[key][current_round]["avg_communication"] = avg_communication - elif communication == -1 and current_round > 4: + if previous_avg is not None: + avg_model_arrival_latency = ( + model_arrival_latency * 0.8 + previous_avg * 0.2 + if previous_avg is not None + else model_arrival_latency + ) + # logging.info(f"Avg model_arrival_latency latency IF: {avg_model_arrival_latency}") + else: + avg_model_arrival_latency = model_arrival_latency - (model_arrival_latency * 0.1) + # logging.info(f"Avg model_arrival_latency latency ELSE: {avg_model_arrival_latency}") + elif model_arrival_latency == 0 and round_num > 5: + # logging.info(" elif model_arrival_latency == 0 and round_num > 5") previous_avg = ( - Reputation.communication_history[key].get(current_round - 1, {}).get("avg_communication", None) + Reputation.model_arrival_latency_history.get(round_num - 1, {}) + .get(current_key, {}) + .get("avg_model_arrival_latency", None) ) - # logging.info(f"Previous avg communication: {previous_avg}") - avg_communication = previous_avg - (previous_avg * 0.1) + # logging.info(f"Previous avg model_arrival_latency latency: {previous_avg}") + avg_model_arrival_latency = previous_avg - (previous_avg * 0.05) + # logging.info(f"Avg model_arrival_latency latency ELIF: {avg_model_arrival_latency}") + else: + avg_model_arrival_latency = model_arrival_latency + + Reputation.model_arrival_latency_history[round_num][current_key]["avg_model_arrival_latency"] = ( + avg_model_arrival_latency + ) - return avg_communication + return avg_model_arrival_latency except Exception: - logging.exception("Error saving communication history") - return -1 + logging.exception("Error saving model_arrival_latency history") @staticmethod def save_reputation_history_in_memory(addr, nei, reputation, current_round): @@ -1205,20 +1099,61 @@ def read_similarity_file(file_path, nei): return similarity @staticmethod - def metrics(scenario, data, addr, nei, type): + def metrics(scenario, data, addr, nei, type, update_field=None): current_dir = os.path.dirname(os.path.realpath(__file__)) - csv_path = os.path.join(current_dir, f"{scenario}/metrics/{addr}_{nei}_messages_{type}.csv") + csv_path = os.path.join(current_dir, f"{scenario}/metrics/{type}/{addr}_{nei}_{type}.csv") # logging.info(f"Round {current_round}. CSV path: {csv_path}") csv_dir = os.path.dirname(csv_path) if not os.path.exists(csv_dir): os.makedirs(csv_dir) - - try: - with open(csv_path, mode="a", newline="") as file: - writer = csv.DictWriter(file, fieldnames=data.keys()) - if file.tell() == 0: - writer.writeheader() - writer.writerow(data) - except Exception: - logging.exception("Error saving messages frequency data to CSV") + + if type != "reputation": + try: + with open(csv_path, mode="a", newline="") as file: + writer = csv.DictWriter(file, fieldnames=data.keys()) + if file.tell() == 0: + writer.writeheader() + writer.writerow(data) + except Exception: + logging.exception("Error saving messages time_message data to CSV") + else: + # logging.info(f"Reputation data received for round {data['round']}: {data}") + rows = [] + updated = False + + fieldnames = [ + "addr", + "nei", + "round", + "time_message_count", + "time_message_norm", + "similarity", + "fraction", + "model_arrival_latency", + "reputation_without_feedback", + "reputation_with_feedback", + ] + + if os.path.exists(csv_path): + with open(csv_path, newline="") as file: + rows = list(csv.DictReader(file)) + # logging.info(f"Existing rows in CSV: {rows}") + + if update_field: + for row in rows: + if int(row["round"]) == int(data["round"]): + # logging.info(f"Updating row for round {data['round']}: {row}") + row.update(data) + updated = True + break + + if not updated: + rows.append(data) + # logging.info(f"Appended new data for round {data['round']}: {data}") + + with open(csv_path, mode="w", newline="") as file: + writer = csv.DictWriter(file, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(rows) + # logging.info(f"Final rows written to CSV: {rows}") diff --git a/nebula/frontend/config/participant.json.example b/nebula/frontend/config/participant.json.example index bea50a0d6..db79127e6 100755 --- a/nebula/frontend/config/participant.json.example +++ b/nebula/frontend/config/participant.json.example @@ -88,7 +88,7 @@ }, "aggregator_args": { "algorithm": "FedAvg", - "aggregation_timeout": 20 + "aggregation_timeout": 300 }, "defense_args": { "with_reputation": false, From 59ffc1757e82a126cf77c7d37746813ae86ef914 Mon Sep 17 00:00:00 2001 From: isaac Date: Tue, 7 Jan 2025 17:18:47 +0100 Subject: [PATCH 12/43] changes in history_reputation --- nebula/core/reputation/Reputation.py | 42 +++++++++++------------ nebula/frontend/templates/deployment.html | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/nebula/core/reputation/Reputation.py b/nebula/core/reputation/Reputation.py index e82e8cee4..d31b4e54d 100644 --- a/nebula/core/reputation/Reputation.py +++ b/nebula/core/reputation/Reputation.py @@ -287,36 +287,36 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro # Weights for each metric if current_round is not None: - if current_round >= 4: # only for rounds 4 and later - weight_to_similarity = 0.5 - weight_to_fraction = 0.0 - weight_to_message_time_message = 0.5 - weight_to_model_arrival_latency = 0.0 - elif current_round >= 5: # only for rounds 5 and later + if current_round >= 5: # only for rounds 5 and later # Weight to scenarios with delay - weight_to_similarity = 0.1 - weight_to_fraction = 0.1 - weight_to_message_time_message = 0.3 - weight_to_model_arrival_latency = 0.5 + # weight_to_similarity = 0.1 + # weight_to_fraction = 0.1 + # weight_to_message_time_message = 0.1 + # weight_to_model_arrival_latency = 0.7 # Weight to scenarios with noise injection # weight_to_similarity = 0.4 # weight_to_fraction = 0.4 # weight_to_message_time_message = 0.1 # weight_to_model_arrival_latency = 0.1 # Weight to scenarios with flood - # weight_to_similarity = 0.1 - # weight_to_fraction = 0.1 - # weight_to_message_time_message = 0.5 - # weight_to_model_arrival_latency = 0.3 - elif current_round <= 3: # only for rounds 0, 1, 2, 3 + weight_to_similarity = 0.1 + weight_to_fraction = 0.1 + weight_to_message_time_message = 0.6 + weight_to_model_arrival_latency = 0.2 + elif current_round <= 4: # only for rounds 0, 1, 2, 3 weight_to_similarity = 1.0 weight_to_fraction = 0.0 weight_to_message_time_message = 0.0 weight_to_model_arrival_latency = 0.0 + logging.info(f"Weight to similarity: {weight_to_similarity}") + logging.info(f"Weight to fraction: {weight_to_fraction}") + logging.info(f"Weight to message_time_message: {weight_to_message_time_message}") + logging.info(f"Weight to model_arrival_latency: {weight_to_model_arrival_latency}") + # Reputation calculation reputation = ( - +weight_to_message_time_message * avg_messages_time_message_normalized + weight_to_message_time_message * avg_messages_time_message_normalized + weight_to_similarity * similarity_reputation + weight_to_fraction * fraction_score_asign + weight_to_model_arrival_latency * avg_model_arrival_latency @@ -1018,10 +1018,10 @@ def save_reputation_history_in_memory(addr, nei, reputation, current_round): for i, n_round in enumerate(rounds, start=1): rep = Reputation.reputation_history[key][n_round] - decay_factor = Reputation.calculate_decay_rate(rep) ** (i * 2) # Aument the decay factor * 2 + decay_factor = Reputation.calculate_decay_rate(rep) ** i total_reputation += rep * decay_factor total_weights += decay_factor - # logging.info(f"Round: {round}, Reputation: {rep}, Decay: {decay_factor}, Total reputation: {total_reputation}") + logging.info(f"Round: {n_round}, Reputation: {rep}, Decay: {decay_factor}, Total reputation: {total_reputation}") avg_reputation = total_reputation / total_weights if total_weights > 0: @@ -1048,11 +1048,11 @@ def calculate_decay_rate(reputation): if reputation > 0.8: return 0.9 # Muy bajo decaimiento elif reputation > 0.6: - return 0.7 # Bajo decaimiento + return 0.6 # Bajo decaimiento elif reputation > 0.4: - return 0.5 # Moderado decaimiento + return 0.3 # Alto decaimiento else: - return 0.2 # Alto decaimiento + return 0.1 # Muy alto decaimiento @staticmethod def read_similarity_file(file_path, nei): diff --git a/nebula/frontend/templates/deployment.html b/nebula/frontend/templates/deployment.html index 6fbfb92c1..b442b6a29 100755 --- a/nebula/frontend/templates/deployment.html +++ b/nebula/frontend/templates/deployment.html @@ -245,7 +245,7 @@
Federation approach
Number of rounds
-
From 0166f53af161cd75a182fe10698cdb3616ec0610 Mon Sep 17 00:00:00 2001 From: isaac Date: Fri, 10 Jan 2025 15:23:48 +0100 Subject: [PATCH 13/43] changes at metric model_latency_arrival --- nebula/core/aggregation/aggregator.py | 1 + nebula/core/engine.py | 2 +- nebula/core/network/communications.py | 53 +++-- nebula/core/network/connection.py | 67 ++++-- nebula/core/reputation/Reputation.py | 207 ++++++++++++------ .../frontend/config/participant.json.example | 2 +- 6 files changed, 217 insertions(+), 115 deletions(-) diff --git a/nebula/core/aggregation/aggregator.py b/nebula/core/aggregation/aggregator.py index bc92a7f36..20990a1d3 100755 --- a/nebula/core/aggregation/aggregator.py +++ b/nebula/core/aggregation/aggregator.py @@ -120,6 +120,7 @@ async def _handle_global_update(self, model, source): await self._aggregation_done_lock.release_async() async def _add_pending_model(self, model, weight, source): + logging.info(f"🔄 _add_pending_model | rejected_nodes = {self.engine.rejected_nodes}") valid_federation_nodes = self._federation_nodes - self.engine.rejected_nodes if len(self._federation_nodes) <= len(self.get_nodes_pending_models_to_aggregate()): logging.info("🔄 _add_pending_model | Ignoring model...") diff --git a/nebula/core/engine.py b/nebula/core/engine.py index a994ba82c..097c0b26e 100755 --- a/nebula/core/engine.py +++ b/nebula/core/engine.py @@ -504,7 +504,7 @@ async def calculate_reputation(self): if self.reputation[nei]["reputation"] is not None: logging.info(f"Reputation of node {nei}: {self.reputation[nei]['reputation']}") if self.reputation[nei]["reputation"] <= 0.6: - # self.rejected_nodes.add(nei) + self.rejected_nodes.add(nei) logging.info(f"Rejected nodes: {self.rejected_nodes}") elif 0.6 < self.reputation[nei]["reputation"] < 0.8: logging.info(f"Change weight node: {nei}") diff --git a/nebula/core/network/communications.py b/nebula/core/network/communications.py index 15adf91ad..6b5d7805a 100755 --- a/nebula/core/network/communications.py +++ b/nebula/core/network/communications.py @@ -285,36 +285,47 @@ async def handle_model_message(self, source, message): logging.info(f"start_time: {start_time} of source {self.addr} for round {round_id}") relative_time = start_time - self._model_arrival_latency_data[round_id]["time_0"]["time"] + # federation_nodes = await self.get_addrs_current_connections(only_direct=True, myself=True) + # if len(self._model_arrival_latency_data[round_id]) >= len(federation_nodes) / 2: + # relative_times = [ + # data["relative_time"] + # for key, data in self._model_arrival_latency_data[round_id].items() + # if "relative_time" in data + # ] + # mean_time = np.mean(relative_times) + # std_time = np.std(relative_times) + # threshold = mean_time + 2 * std_time + + # logging.info(f"mean_time: {mean_time} | std_time: {std_time} | threshold: {threshold}") + # if relative_time > threshold: + # self.engine.rejected_nodes.add(source) + # logging.info(f"🤖 handle_model_message | Latency of source = {source} is higher than the mean: {relative_time:.3f} seconds") + # else: + # logging.info("🤖 handle_model_message | Waiting for at least 50 percent of models to calculate mean latency.") if source not in self._model_arrival_latency_data[round_id]: self._model_arrival_latency_data[round_id][source] = { "start_time": start_time, "relative_time": relative_time, } - logging.info(f"self.model_arrival_latency_data: {self._model_arrival_latency_data}") + # logging.info(f"self.model_arrival_latency_data: {self._model_arrival_latency_data}") logging.info(f"Node {source} | Time taken relative to time_0: {relative_time:.3f} seconds") if message.round == current_round: - save_data( - self.config.participant["scenario_args"]["name"], - "model_arrival_latency", - source, - self.get_addr(), - num_round=message.round, - latency=relative_time, - ) - # else: - # logging.info(f"🤖 handle_model_message | Received a model from a different round | Model round: {message.round} | Current round: {current_round}") - # if message.round > current_round: - # logging.info(f"🤖 handle_model_message | message.round > current_round") - # save_data( - # self.config.participant["scenario_args"]["name"], - # "model_arrival_latency", - # source, - # self.get_addr(), - # num_round=message.round, - # latency=relative_time, - # ) + logging.info(f"🤖 handle_model_message | message_round == current_round to node {source}") + elif message.round < current_round: + logging.info(f"🤖 handle_model_message | message_round <= current_round to node {source}") + else: + logging.info(f"🤖 handle_model_message | message_round > current_round to node {source}") + + save_data( + self.config.participant["scenario_args"]["name"], + "model_arrival_latency", + source, + self.get_addr(), + num_round=message.round, + latency=relative_time, + ) if message.round != current_round and message.round != -1: logging.info( diff --git a/nebula/core/network/connection.py b/nebula/core/network/connection.py index 33318c13b..d10b0beba 100755 --- a/nebula/core/network/connection.py +++ b/nebula/core/network/connection.py @@ -12,6 +12,10 @@ import lz4.frame from geopy import distance +from nebula.core.reputation.Reputation import ( + Reputation, +) + if TYPE_CHECKING: from nebula.core.network.communications import CommunicationsManager @@ -74,6 +78,9 @@ def __init__( self.MAX_CHUNK_SIZE = 1024 # 1 KB self.BUFFER_SIZE = 1024 # 1 KB + self.reputation_instance = Reputation(self.cm.engine) + self._model_arrival_latency_data = self.reputation_instance.model_arrival_latency_data + logging.info( f"Connection [established]: {self.addr} (id: {self.id}) (active: {self.active}) (direct: {self.direct})" ) @@ -283,29 +290,52 @@ async def handle_incoming_message(self) -> None: f"Received chunk {chunk_index} of message {message_id.hex()} | size: {len(chunk_data)} bytes" ) - # if chunk_index == 0: - # if round_id is not None: - # if round_id >= 0: - # start_time = time.time() - # chunk_bytes = chunk_data.tobytes() - # # logging.info(f"chunk data: {chunk_bytes}") - # comparation_with = b"\x01\x00\x00\x00x\x9c\x00" - # if chunk_bytes[: len(comparation_with)] == comparation_with: - # if round_id not in self._chunk_data: - # self._chunk_data[round_id] = {} - - # if "time_0" not in self._chunk_data[round_id]: - # self._chunk_data[round_id]["time_0"] = {"time": start_time, "source": source} + round_id = self.cm.get_round() + source = self.addr + comparation_with = b"\x01\x00\x00\x00x\x9c\x00" + + if round_id is not None and round_id >= 0: + chunk_bytes = chunk_data.tobytes() + # if round_id not in self._model_arrival_latency_data: + # self._model_arrival_latency_data[round_id] = {} + # if source not in self._model_arrival_latency_data[round_id]: + # self._model_arrival_latency_data[round_id][source] = {} + + if chunk_index == 0 and chunk_bytes[: len(comparation_with)] == comparation_with: + start_time = time.time() + # logging.info(f"start_time: {start_time:.3f} of source {source} for round {round_id}") + # self._model_arrival_latency_data[round_id][source]["start_time"] = start_time + + # if chunk_index == 1 and "start_time" in self._model_arrival_latency_data[round_id][source]: + # logging.info("Processing chunk_index == 1") + # end_time = time.time() + # self._model_arrival_latency_data[round_id][source]["end_time"] = end_time + # logging.info(f"end_time: {end_time} of source {source} for round {round_id}") + # start_time = self._model_arrival_latency_data[round_id][source]["start_time"] + # latency = end_time - start_time + # logging.info(f"Node {source} | Latency: {latency:.3f} seconds") + + # if ( + # "start_time" in self._model_arrival_latency_data[round_id][source] and + # "end_time" in self._model_arrival_latency_data[round_id][source] + # ): + # start_time = self._model_arrival_latency_data[round_id][source]["start_time"] + # end_time = self._model_arrival_latency_data[round_id][source]["end_time"] + # latency = end_time - start_time + # logging.info(f"Node {source} | Latency: {latency:.3f} seconds") + + # if "time_0" not in self._model_arrival_latency_data[round_id]: + # self._model_arrival_latency_data[round_id]["time_0"] = {"time": start_time, "source": source} # logging.info(f"start_time: {start_time} of source {self.addr} for round {round_id}") - # relative_time = start_time - self._chunk_data[round_id]["time_0"]["time"] + # relative_time = start_time - self._model_arrival_latency_data[round_id]["time_0"]["time"] - # if source not in self._chunk_data[round_id]: - # self._chunk_data[round_id][source] = { + # if source not in self._model_arrival_latency_data[round_id]: + # self._model_arrival_latency_data[round_id][source] = { # "start_time": start_time, # "relative_time": relative_time, # } - # logging.info(f"self.chunk_data: {self._chunk_data}") + # logging.info(f"self.chunk_data: {self._model_arrival_latency_data}") # logging.info(f"Node {source} | Time taken relative to time_0: {relative_time:.3f} seconds") # save_data( @@ -314,11 +344,8 @@ async def handle_incoming_message(self) -> None: # source, # self.cm.get_addr(), # num_round=round_id, - # message_id_decoded=message_id_decoded, # latency=relative_time, # ) - # else: - # logging.info(f"Node {source} save data at round {round_id} already exists") if is_last_chunk: await self._process_complete_message(message_id) diff --git a/nebula/core/reputation/Reputation.py b/nebula/core/reputation/Reputation.py index d31b4e54d..cab52c2dd 100644 --- a/nebula/core/reputation/Reputation.py +++ b/nebula/core/reputation/Reputation.py @@ -122,6 +122,7 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro """ logging.info(f"id_node: {id_node}, addr: {addr}, nei: {nei}") addr = addr.split(":")[0].strip() + nei_with_port = nei nei = nei.split(":")[0].strip() messages_time_message_normalized = 0 @@ -185,12 +186,52 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro round_latency = metric["model_arrival_latency"]["round"] latency = metric["model_arrival_latency"]["latency"] if round_latency == current_round: + logging.info("model_arrival_latency from round == current_round") messages_model_arrival_latency_normalized = Reputation.manage_model_arrival_latency( - round_latency, addr, nei, latency, scenario, self.model_arrival_latency_data + round_latency, + addr, + nei, + latency, + scenario, + self.model_arrival_latency_data, + current_round, ) logging.info( f"messages_model_arrival_latency_normalized: {messages_model_arrival_latency_normalized}" ) + if current_round - 1 in Reputation.model_arrival_latency_history: + if nei in Reputation.model_arrival_latency_history[current_round - 1]: + nei = Reputation.model_arrival_latency_history[current_round - 1][nei] + logging.info(f"Nei: {nei}") + if ( + "no_data" + in Reputation.model_arrival_latency_history[current_round - 1][nei] + ): + no_data = Reputation.model_arrival_latency_history[current_round - 1][nei][ + "no_data" + ] + logging.info(f"No data: {no_data}") + if no_data == True: + Reputation.model_arrival_latency_history[current_round][nei]["lantecy"] = ( + latency + ) + elif round_latency < current_round: + logging.info("model_arrival_latency from round < current_round") + for round in range(current_round, current_round + 1): + logging.info(f"Round to process: {round}") + latency = self.engine.config.participant["aggregator_args"]["aggregation_timeout"] + messages_model_arrival_latency_normalized = Reputation.manage_model_arrival_latency( + round_latency, + addr, + nei, + latency, + scenario, + self.model_arrival_latency_data, + current_round, + ) + logging.info( + f"messages_model_arrival_latency_normalized: {messages_model_arrival_latency_normalized}" + ) similarity_file = os.path.join(log_dir, f"participant_{id_node}_similarity.csv") similarity_reputation = Reputation.read_similarity_file(similarity_file, nei) @@ -199,6 +240,20 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro avg_model_arrival_latency = Reputation.save_model_arrival_latency_history( addr, nei, messages_model_arrival_latency_normalized, current_round ) + logging.info(f"Avg model_arrival_latency: {avg_model_arrival_latency}") + # if current_round >= 5 and (avg_model_arrival_latency < 0.9 or avg_model_arrival_latency > 1): + # self.engine.rejected_nodes.add((nei_with_port, current_round)) + # logging.info(f"Node rejected = {nei_with_port}") + # logging.info(f"Rejected nodes: {self.engine.rejected_nodes}") + + # self.engine.rejected_nodes = { + # (node, round_rejected) for node, round_rejected in self.engine.rejected_nodes + # if current_round - round_rejected < 2 + # } + # logging.info(f"Rejected nodes: {self.engine.rejected_nodes}") + # self.engine.rejected_nodes = { node for node, _ in self.engine.rejected_nodes } + # logging.info(f"Rejected nodes: {self.engine.rejected_nodes}") + if avg_model_arrival_latency is None and current_round > 4: avg_model_arrival_latency = Reputation.model_arrival_latency_history[(addr, nei)][ current_round - 1 @@ -288,21 +343,10 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro # Weights for each metric if current_round is not None: if current_round >= 5: # only for rounds 5 and later - # Weight to scenarios with delay - # weight_to_similarity = 0.1 - # weight_to_fraction = 0.1 - # weight_to_message_time_message = 0.1 - # weight_to_model_arrival_latency = 0.7 - # Weight to scenarios with noise injection - # weight_to_similarity = 0.4 - # weight_to_fraction = 0.4 - # weight_to_message_time_message = 0.1 - # weight_to_model_arrival_latency = 0.1 - # Weight to scenarios with flood - weight_to_similarity = 0.1 - weight_to_fraction = 0.1 - weight_to_message_time_message = 0.6 - weight_to_model_arrival_latency = 0.2 + weight_to_similarity = 0.25 + weight_to_fraction = 0.25 + weight_to_message_time_message = 0.25 + weight_to_model_arrival_latency = 0.25 elif current_round <= 4: # only for rounds 0, 1, 2, 3 weight_to_similarity = 1.0 weight_to_fraction = 0.0 @@ -483,11 +527,18 @@ def analyze_anomalies( prev_key = (addr, nei, round_num - 1) if prev_key not in Reputation.fraction_changed_history: for i in range(0, round_num + 1): + logging.info(f"Round: {round_num}, i = {i}") potential_prev_key = (addr, nei, round_num - i) + logging.info(f"Round: {round_num}, Potential previous key: {potential_prev_key}") if potential_prev_key in Reputation.fraction_changed_history: - prev_key = potential_prev_key - break - + mean_fraction_prev = Reputation.fraction_changed_history[potential_prev_key][ + "mean_fraction" + ] + if mean_fraction_prev is not None: + prev_key = potential_prev_key + break + + # logging.info(f"Round: {round_num}, Previous key: {prev_key}") if prev_key: mean_fraction_prev = Reputation.fraction_changed_history[prev_key]["mean_fraction"] std_dev_fraction_prev = Reputation.fraction_changed_history[prev_key]["std_dev_fraction"] @@ -636,7 +687,9 @@ def save_time_message_history(addr, nei, messages_time_message_normalized, curre return 0.0 @staticmethod - def manage_model_arrival_latency(round_num, addr, nei, latency, scenario, model_arrival_latency_data): + def manage_model_arrival_latency( + round_num, addr, nei, latency, scenario, model_arrival_latency_data, current_round + ): """ Manage the model_arrival_latency latency metric with persistent storage of mean latency. @@ -654,41 +707,40 @@ def manage_model_arrival_latency(round_num, addr, nei, latency, scenario, model_ try: current_key = nei - if round_num not in Reputation.model_arrival_latency_history: - Reputation.model_arrival_latency_history[round_num] = {} + if current_round not in Reputation.model_arrival_latency_history: + Reputation.model_arrival_latency_history[current_round] = {} - Reputation.model_arrival_latency_history[round_num][current_key] = { + Reputation.model_arrival_latency_history[current_round][current_key] = { "latency": latency, "score": 0.0, } - # logging.info(f"Reputation.model_arrival_latency_history: {Reputation.model_arrival_latency_history}") + logging.info(f"Reputation.model_arrival_latency_history: {Reputation.model_arrival_latency_history}") - if round_num >= 5: - if round_num > 5: + if current_round >= 5: + if current_round > 5: if ( - round_num - 1 in Reputation.model_arrival_latency_history - and current_key in Reputation.model_arrival_latency_history[round_num - 1] + current_round - 1 in Reputation.model_arrival_latency_history + and current_key in Reputation.model_arrival_latency_history[current_round - 1] ): - prev_mean_latency = Reputation.model_arrival_latency_history[round_num - 1][current_key].get( - "mean_latency", None - ) - prev_stdev_latency = Reputation.model_arrival_latency_history[round_num - 1][current_key].get( - "stdev_latency", None - ) - percentil_25 = Reputation.model_arrival_latency_history[round_num - 1][current_key].get( + prev_mean_latency = Reputation.model_arrival_latency_history[current_round - 1][ + current_key + ].get("mean_latency", None) + prev_stdev_latency = Reputation.model_arrival_latency_history[current_round - 1][ + current_key + ].get("stdev_latency", None) + percentil_25 = Reputation.model_arrival_latency_history[current_round - 1][current_key].get( "percentil_25", None ) - percentil_75 = Reputation.model_arrival_latency_history[round_num - 1][current_key].get( + percentil_75 = Reputation.model_arrival_latency_history[current_round - 1][current_key].get( "percentil_75", None ) - # logging.info(f"Round {round_num} | Node {nei} | Round {round_num - 1} | Previous Mean Latency: {prev_mean_latency} | Previous Stdev Latency: {prev_stdev_latency}") - # logging.info(f"Round {round_num} | Node {nei} | Round {round_num - 1} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}") + # logging.info(f"Round {current_round} | Node {nei} | Round {current_round - 1} | Previous Mean Latency: {prev_mean_latency} | Previous Stdev Latency: {prev_stdev_latency}") + # logging.info(f"Round {current_round} | Node {nei} | Round {current_round - 1} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}") - # Si no se encuentran datos en round_num - 1, recorrer las rondas previas desde round_num - 2 hasta 5 if prev_mean_latency is None or prev_stdev_latency is None: - for r in range(round_num - 2, 5 - 1, -1): + for r in range(current_round - 2, 5 - 1, -1): if ( r in Reputation.model_arrival_latency_history and current_key in Reputation.model_arrival_latency_history[r] @@ -706,56 +758,55 @@ def manage_model_arrival_latency(round_num, addr, nei, latency, scenario, model_ "percentil_75", None ) - # logging.info(f"Round {round_num} | Node {nei} | Round {r} | Previous Mean Latency: {prev_mean_latency} | Previous Stdev Latency: {prev_stdev_latency}") - # logging.info(f"Round {round_num} | Node {nei} | Round {r} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}") + # logging.info(f"Round {current_round} | Node {nei} | Round {r} | Previous Mean Latency: {prev_mean_latency} | Previous Stdev Latency: {prev_stdev_latency}") + # logging.info(f"Round {current_round} | Node {nei} | Round {r} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}") # Si se encuentran los datos, salir del bucle if prev_mean_latency is not None and prev_stdev_latency is not None: break else: logging.info( - f"Round {round_num} | Node {nei} | Round {r} | No previous data found." + f"Round {current_round} | Node {nei} | Round {r} | No previous data found." ) - # Si no se encontraron datos en ninguna ronda anterior, inicializar valores predeterminados if prev_mean_latency is None or prev_stdev_latency is None: prev_mean_latency = 0 prev_stdev_latency = 1 percentil_25 = 0 percentil_75 = 0 - # logging.info(f"Round {round_num} | Node {nei} | No previous latency data found, initializing defaults.") + # logging.info(f"Round {current_round} | Node {nei} | No previous latency data found, initializing defaults.") else: all_latencies = [] - for r in range(round_num - 1): + for r in range(current_round - 1): if r in Reputation.model_arrival_latency_history: for key, data in Reputation.model_arrival_latency_history[r].items(): if "latency" in data and data["latency"] != 0: all_latencies.append(data["latency"]) - logging.info(f"Round {round_num} | Node {nei} | All latencies: {all_latencies}") + logging.info(f"Round {current_round} | Node {nei} | All latencies: {all_latencies}") prev_mean_latency = np.mean(all_latencies) if all_latencies else 0 prev_stdev_latency = np.std(all_latencies) if all_latencies else 1 logging.info( - f"Round {round_num} | Node {nei} | Initial Mean Latency: {prev_mean_latency} | Initial Stdev Latency: {prev_stdev_latency}" + f"Round {current_round} | Node {nei} | Initial Mean Latency: {prev_mean_latency} | Initial Stdev Latency: {prev_stdev_latency}" ) percentil_25 = np.percentile(all_latencies, 25) if all_latencies else 0 percentil_75 = np.percentile(all_latencies, 75) if all_latencies else 0 logging.info( - f"Round {round_num} | Node {nei} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}" + f"Round {current_round} | Node {nei} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}" ) k = 0.5 prev_mean_latency = prev_mean_latency + k * (percentil_75 - percentil_25) - logging.info(f"Round {round_num} | Node {nei} | Mean latency with k: {prev_mean_latency}") - logging.info(f"Round {round_num} | Node {nei} | Latency: {latency}") + logging.info(f"Round {current_round} | Node {nei} | Mean latency with k: {prev_mean_latency}") + logging.info(f"Round {current_round} | Node {nei} | Latency: {latency}") if latency == 0.0: latency = 0.5 difference = latency - prev_mean_latency - logging.info(f"Round {round_num} | Node {nei} | Difference: {difference}") + logging.info(f"Round {current_round} | Node {nei} | Difference: {difference}") if latency <= prev_mean_latency: score = 1 @@ -764,39 +815,49 @@ def manage_model_arrival_latency(round_num, addr, nei, latency, scenario, model_ score = 1 else: score = 1 / (1 + np.exp(abs(difference) / prev_mean_latency)) - logging.info(f"Round {round_num} | Node {nei} | Score: {score}") - Reputation.model_arrival_latency_history[round_num][current_key]["score"] = score + logging.info(f"Round {current_round} | Node {nei} | Score: {score}") + + if round_num < current_round: + score += 1 / (1 + np.exp(abs(difference) / prev_mean_latency)) + Reputation.model_arrival_latency_history[current_round][current_key]["no_data"] = True + + logging.info(f"Round {current_round} | Node {nei} | Score penalization round: {score}") + Reputation.model_arrival_latency_history[current_round][current_key]["score"] = score - n = round_num if round_num else 1 - # logging.info(f"Round {round_num} | Node {nei} | N: {n}") + n = current_round if current_round else 1 + # logging.info(f"Round {current_round} | Node {nei} | N: {n}") update_mean = (prev_mean_latency * n + latency) / (n + 1) update_stdev = np.sqrt( ((prev_stdev_latency**2) + (latency - prev_mean_latency) * (latency - update_mean)) / (n + 1) ) logging.info( - f"Round {round_num} | Node {nei} | Mean Latency: {update_mean} | Stdev Latency: {update_stdev}" + f"Round {current_round} | Node {nei} | Mean Latency: {update_mean} | Stdev Latency: {update_stdev}" ) accumulated_latencies = [ data["latency"] - for r in range(round_num + 1) + for r in range(current_round + 1) if r in Reputation.model_arrival_latency_history for key, data in Reputation.model_arrival_latency_history[r].items() if "latency" in data and data["latency"] != 0 ] - logging.info(f"Round {round_num} | Node {nei} | Accumulated latencies: {accumulated_latencies}") + logging.info(f"Round {current_round} | Node {nei} | Accumulated latencies: {accumulated_latencies}") update_percentil_25 = np.percentile(accumulated_latencies, 25) if accumulated_latencies else 0 update_percentil_75 = np.percentile(accumulated_latencies, 75) if accumulated_latencies else 0 logging.info( - f"Round {round_num} | Node {nei} | Percentil 25: {update_percentil_25} | Percentil 75: {update_percentil_75}" + f"Round {current_round} | Node {nei} | Percentil 25: {update_percentil_25} | Percentil 75: {update_percentil_75}" ) - Reputation.model_arrival_latency_history[round_num][current_key]["mean_latency"] = update_mean - Reputation.model_arrival_latency_history[round_num][current_key]["stdev_latency"] = update_stdev - Reputation.model_arrival_latency_history[round_num][current_key]["percentil_25"] = update_percentil_25 - Reputation.model_arrival_latency_history[round_num][current_key]["percentil_75"] = update_percentil_75 + Reputation.model_arrival_latency_history[current_round][current_key]["mean_latency"] = update_mean + Reputation.model_arrival_latency_history[current_round][current_key]["stdev_latency"] = update_stdev + Reputation.model_arrival_latency_history[current_round][current_key]["percentil_25"] = ( + update_percentil_25 + ) + Reputation.model_arrival_latency_history[current_round][current_key]["percentil_75"] = ( + update_percentil_75 + ) else: # For rounds < 5, no scoring or updates @@ -806,13 +867,13 @@ def manage_model_arrival_latency(round_num, addr, nei, latency, scenario, model_ data = { "addr": addr, "nei": nei, - "round": round_num, + "round": current_round, "latency": latency, - "mean_latency": prev_mean_latency if round_num >= 5 else None, - "stdev_latency": prev_stdev_latency if round_num >= 5 else None, - "percentil_25": percentil_25 if round_num >= 5 else None, - "percentil_75": percentil_75 if round_num >= 5 else None, - "difference": difference if round_num >= 5 else None, + "mean_latency": prev_mean_latency if current_round >= 5 else None, + "stdev_latency": prev_stdev_latency if current_round >= 5 else None, + "percentil_25": percentil_25 if current_round >= 5 else None, + "percentil_75": percentil_75 if current_round >= 5 else None, + "difference": difference if current_round >= 5 else None, "score": score, } Reputation.metrics(scenario, data, addr, nei, "model_arrival_latency") @@ -965,7 +1026,7 @@ def save_model_arrival_latency_history(addr, nei, model_arrival_latency, round_n ) # logging.info(f"Avg model_arrival_latency latency IF: {avg_model_arrival_latency}") else: - avg_model_arrival_latency = model_arrival_latency - (model_arrival_latency * 0.1) + avg_model_arrival_latency = model_arrival_latency - (model_arrival_latency * 0.05) # logging.info(f"Avg model_arrival_latency latency ELSE: {avg_model_arrival_latency}") elif model_arrival_latency == 0 and round_num > 5: # logging.info(" elif model_arrival_latency == 0 and round_num > 5") @@ -1021,7 +1082,9 @@ def save_reputation_history_in_memory(addr, nei, reputation, current_round): decay_factor = Reputation.calculate_decay_rate(rep) ** i total_reputation += rep * decay_factor total_weights += decay_factor - logging.info(f"Round: {n_round}, Reputation: {rep}, Decay: {decay_factor}, Total reputation: {total_reputation}") + logging.info( + f"Round: {n_round}, Reputation: {rep}, Decay: {decay_factor}, Total reputation: {total_reputation}" + ) avg_reputation = total_reputation / total_weights if total_weights > 0: diff --git a/nebula/frontend/config/participant.json.example b/nebula/frontend/config/participant.json.example index db79127e6..bea50a0d6 100755 --- a/nebula/frontend/config/participant.json.example +++ b/nebula/frontend/config/participant.json.example @@ -88,7 +88,7 @@ }, "aggregator_args": { "algorithm": "FedAvg", - "aggregation_timeout": 300 + "aggregation_timeout": 20 }, "defense_args": { "with_reputation": false, From f9cef06bfe29d8e6161bb75772ca0e5fd5bd0e11 Mon Sep 17 00:00:00 2001 From: Isaac Marroqui Date: Thu, 16 Jan 2025 11:09:36 +0100 Subject: [PATCH 14/43] implemented dynamic score --- nebula/addons/attacks/attacks.py | 20 +- nebula/core/engine.py | 76 ++-- nebula/core/network/communications.py | 1 - nebula/core/reputation/Reputation.py | 382 ++++++++++++------ .../frontend/config/participant.json.example | 2 +- nebula/frontend/templates/deployment.html | 6 +- 6 files changed, 315 insertions(+), 172 deletions(-) diff --git a/nebula/addons/attacks/attacks.py b/nebula/addons/attacks/attacks.py index 69113a5a0..5e8ecd7c1 100755 --- a/nebula/addons/attacks/attacks.py +++ b/nebula/addons/attacks/attacks.py @@ -1,6 +1,5 @@ import asyncio import logging -import time from copy import deepcopy from typing import Any @@ -172,8 +171,8 @@ def __init__(self): async def attack(self): logging.info("[DelayerAttack] Performing delayer attack") - logging.info("Delaying time from 30 seconds") - time.sleep(30) + logging.info("Delaying time from 35 seconds") + await asyncio.sleep(35) logging.info("Delaying time finished") @@ -185,19 +184,24 @@ class FloodingAttack(Attack): def __init__(self): super().__init__() + self.scaled_repetitions = None async def attack(self, cm: CommunicationsManager, addr, num_round, repetitions=2, interval=0.05): logging.info("[FloodingAttack] Performing flood attack") - neighbors = set(await cm.get_addrs_current_connections(only_direct=True)) + neighbors = set(await cm.get_addrs_current_connections(only_direct=True, myself=True)) logging.info(f"Neighbors: {neighbors}") - logging.info(f"Round: {num_round}") logging.info(f"Interval: {interval}") logging.info(f"Repetitions: {repetitions}") - scaled_repetitions = len(neighbors) * repetitions - logging.info(f"Total repetitions: {scaled_repetitions}") + + if self.scaled_repetitions is None: + self.scaled_repetitions = len(neighbors) * repetitions + else: + self.scaled_repetitions = self.scaled_repetitions + (num_round - 3) + logging.info(f"Total repetitions: {self.scaled_repetitions}") + for nei in neighbors: - for i in range(scaled_repetitions): + for i in range(self.scaled_repetitions): message_data = cm.mm.generate_flood_attack_message( attacker_id=addr, frequency=int(i), diff --git a/nebula/core/engine.py b/nebula/core/engine.py index 097c0b26e..90d8fef16 100755 --- a/nebula/core/engine.py +++ b/nebula/core/engine.py @@ -7,7 +7,7 @@ from nebula.addons.attacks.attacks import create_attack from nebula.addons.functions import print_msg_box from nebula.addons.reporter import Reporter -from nebula.core.aggregation.aggregator import create_aggregator, create_malicious_aggregator, create_target_aggregator +from nebula.core.aggregation.aggregator import create_aggregator, create_malicious_aggregator from nebula.core.eventmanager import EventManager, event_handler from nebula.core.network.communications import CommunicationsManager from nebula.core.pb import nebula_pb2 @@ -120,18 +120,6 @@ def __init__( title="Logging information", ) - self.with_reputation = self.config.participant["defense_args"]["with_reputation"] - self.is_dynamic_topology = self.config.participant["defense_args"]["is_dynamic_topology"] - self.is_dynamic_aggregation = self.config.participant["defense_args"]["is_dynamic_aggregation"] - self.target_aggregation = ( - create_target_aggregator(config=self.config, engine=self) if self.is_dynamic_aggregation else None - ) - msg = f"Reputation system: {self.with_reputation}\nDynamic topology: {self.is_dynamic_topology}\nDynamic aggregation: {self.is_dynamic_aggregation}" - msg += ( - f"\nTarget aggregation: {self.target_aggregation.__class__.__name__}" if self.is_dynamic_aggregation else "" - ) - print_msg_box(msg=msg, indent=2, title="Defense information") - self.learning_cycle_lock = Locker(name="learning_cycle_lock", async_lock=True) self.federation_setup_lock = Locker(name="federation_setup_lock", async_lock=True) self.federation_ready_lock = Locker(name="federation_ready_lock", async_lock=True) @@ -157,11 +145,11 @@ def __init__( ] ) - # Register additional callbacks - self._event_manager.register_callback( - self._reputation_callback, - # ... add more callbacks here - ) + # # Register additional callbacks + # self._event_manager.register_callback( + # self._reputation_callback, + # # ... add more callbacks here + # ) # Reputation self.reputation_instance = Reputation(self) @@ -170,6 +158,25 @@ def __init__( self.rejected_nodes = set() self.change_weight_nodes = set() + self.with_reputation = self.config.participant["defense_args"]["with_reputation"] + msg = f"Reputation system: {self.with_reputation}" + print_msg_box(msg=msg, indent=2, title="Defense information") + # self.is_dynamic_topology = self.config.participant["defense_args"]["is_dynamic_topology"] + # self.is_dynamic_aggregation = self.config.participant["defense_args"]["is_dynamic_aggregation"] + # self.target_aggregation = ( + # create_target_aggregator(config=self.config, engine=self) if self.is_dynamic_aggregation else None + # ) + # msg = f"Reputation system: {self.with_reputation}\nDynamic topology: {self.is_dynamic_topology}\nDynamic aggregation: {self.is_dynamic_aggregation}" + # msg += ( + # f"\nTarget aggregation: {self.target_aggregation.__class__.__name__}" if self.is_dynamic_aggregation else "" + # ) + # print_msg_box(msg=msg, indent=2, title="Defense information") + + if self.with_reputation: + logging.info("Reputation system enabled") + federation = self.config.participant["network_args"]["neighbors"].split() + self.reputation_instance.init_reputation(federation_nodes=federation) + @property def cm(self): return self._cm @@ -290,18 +297,18 @@ async def _start_federation_callback(self, source, message): logging.info(f"📝 handle_federation_message | Trigger | Received start federation message from {source}") await self.create_trainer_module() - @event_handler(nebula_pb2.FederationMessage, nebula_pb2.FederationMessage.Action.REPUTATION) - async def _reputation_callback(self, source, message): - malicious_nodes = message.arguments # List of malicious nodes - if self.with_reputation: - if len(malicious_nodes) > 0 and not self._is_malicious: - if self.is_dynamic_topology: - await self._disrupt_connection_using_reputation(malicious_nodes) - if self.is_dynamic_aggregation and self.aggregator != self.target_aggregation: - await self._dynamic_aggregator( - self.aggregator.get_nodes_pending_models_to_aggregate(), - malicious_nodes, - ) + # @event_handler(nebula_pb2.FederationMessage, nebula_pb2.FederationMessage.Action.REPUTATION) + # async def _reputation_callback(self, source, message): + # malicious_nodes = message.arguments # List of malicious nodes + # if self.with_reputation: + # if len(malicious_nodes) > 0 and not self._is_malicious: + # if self.is_dynamic_topology: + # await self._disrupt_connection_using_reputation(malicious_nodes) + # if self.is_dynamic_aggregation and self.aggregator != self.target_aggregation: + # await self._dynamic_aggregator( + # self.aggregator.get_nodes_pending_models_to_aggregate(), + # malicious_nodes, + # ) @event_handler( nebula_pb2.FederationMessage, @@ -467,7 +474,8 @@ async def _waiting_model_updates(self): f"_waiting_model_updates | Aggregation done for round {self.round}, including parameters in local model." ) self.trainer.set_model_parameters(params) - await self.calculate_reputation() + if self.with_reputation: + await self.calculate_reputation() else: logging.error("Aggregation finished with no parameters") @@ -503,7 +511,7 @@ async def calculate_reputation(self): if self.reputation[nei]["reputation"] is not None: logging.info(f"Reputation of node {nei}: {self.reputation[nei]['reputation']}") - if self.reputation[nei]["reputation"] <= 0.6: + if self.reputation[nei]["reputation"] < 0.6: self.rejected_nodes.add(nei) logging.info(f"Rejected nodes: {self.rejected_nodes}") elif 0.6 < self.reputation[nei]["reputation"] < 0.8: @@ -760,8 +768,8 @@ def __init__( self.fit_time = 0.0 self.extra_time = 0.0 - self.round_start_attack = 6 - self.round_stop_attack = 9 + self.round_start_attack = 7 + self.round_stop_attack = 10 self.aggregator_bening = self._aggregator diff --git a/nebula/core/network/communications.py b/nebula/core/network/communications.py index 6b5d7805a..33f00ed40 100755 --- a/nebula/core/network/communications.py +++ b/nebula/core/network/communications.py @@ -205,7 +205,6 @@ async def handle_federation_message(self, source, message): ) try: await self.engine.event_manager.trigger_event(source, message) - # self.store_receive_timestamp(source, "federation") except Exception as e: logging.exception( f"📝 handle_federation_message | Error while processing: {message.action} {message.arguments} | {e}" diff --git a/nebula/core/reputation/Reputation.py b/nebula/core/reputation/Reputation.py index cab52c2dd..37d1124c7 100644 --- a/nebula/core/reputation/Reputation.py +++ b/nebula/core/reputation/Reputation.py @@ -108,11 +108,160 @@ class Reputation: def __init__(self, engine: "Engine"): self._engine = engine self.model_arrival_latency_data = {} + self.history_data = {} + self.metric_weights = {} @property def engine(self): return self._engine + def init_reputation(self, federation_nodes=None): + # logging.info("init_reputation | Reputation initialization started") + if not federation_nodes: + logging.error("init_reputation | No federation nodes provided") + return + + if self._engine.with_reputation: + # logging.info("init_reputation | Reputation system enabled") + neighbors = Reputation.is_valid_ip(federation_nodes) + + if not neighbors: + logging.error("init_reputation | No neighbors found") + return + + # logging.info(f"init_reputation | Neighbors: {neighbors}") + reputation_initialized = 0.6 + + for nei in neighbors: + if nei not in self._engine.reputation: + self._engine.reputation[nei] = { + "reputation": reputation_initialized, + "round": -1, + "last_feedback_round": -1, + } + # logging.info(f"init_reputation | Reputation initialized for node {nei} with {reputation_initialized}") + + def is_valid_ip(federation_nodes): + """ + Check if the IP addresses are valid. + """ + valid_ip = [] + for i in federation_nodes: + addr = f"{i.split(':')[0]}:{i.split(':')[1]}" + # logging.info(f"addr: {addr}") + valid_ip.append(addr) + + return valid_ip + + @staticmethod + def calculate_weighted_reputation( + avg_messages_time_message_normalized, + similarity_reputation, + fraction_score_asign, + avg_model_arrival_latency, + weight_to_message_time_message, + weight_to_similarity, + weight_to_fraction, + weight_to_model_arrival_latency, + history_data: dict, + metric_weights: dict, + current_round, + rep, + ): + """ + Calculate the reputation of a participant according to the dynamic weights of the metrics. + """ + # Weights for each metric + if current_round is not None: + required_keys = [ + "avg_messages_time_message_normalized", + "similarity_reputation", + "fraction_score_asign", + "avg_model_arrival_latency", + ] + + for key in required_keys: + if key not in history_data: + history_data[key] = [] + + logging.info(f"history_data: {history_data}") + logging.info(f"avg_messages_time_message_normalized: {avg_messages_time_message_normalized}") + logging.info(f"similarity_reputation: {similarity_reputation}") + logging.info(f"fraction_score_asign: {fraction_score_asign}") + logging.info(f"avg_model_arrival_latency: {avg_model_arrival_latency}") + + metric_weights = { + "avg_messages_time_message_normalized": weight_to_message_time_message, + "similarity_reputation": weight_to_similarity, + "fraction_score_asign": weight_to_fraction, + "avg_model_arrival_latency": weight_to_model_arrival_latency, + } + + if current_round >= 5: # only for rounds 5 and later + metric_weights = { + "avg_messages_time_message_normalized": 0.25, + "similarity_reputation": 0.25, + "fraction_score_asign": 0.25, + "avg_model_arrival_latency": 0.25, + } + + metrics = { + "avg_messages_time_message_normalized": avg_messages_time_message_normalized, + "similarity_reputation": similarity_reputation, + "fraction_score_asign": fraction_score_asign, + "avg_model_arrival_latency": avg_model_arrival_latency, + } + + desviations = {} + for metric_name, current_value in metrics.items(): + historical_values = history_data[metric_name] + logging.info(f"historical_values: {historical_values}") + + if historical_values: + mean_value = np.mean(historical_values) + std_value = np.std(historical_values) + else: + mean_value = current_value + std_value = 1 + + deviation = abs(current_value - mean_value) + desviations[metric_name] = deviation + + logging.info(f"{metric_name} - Mean: {mean_value}, Std: {std_value}, Deviation: {deviation}") + + max_desviation = max(desviations.values()) if desviations else 1 + normalized_weights = { + metric_name: (desviation / max_desviation) for metric_name, desviation in desviations.items() + } + logging.info(f"normalized_weights: {normalized_weights}") + + total_weight = sum(normalized_weights.values()) + normalized_weights = { + metric_name: weight / total_weight for metric_name, weight in normalized_weights.items() + } + logging.info(f"total_weight: {total_weight}, normalized_weights: {normalized_weights}") + + metric_weights = normalized_weights + logging.info(f"metric_weights: {metric_weights}") + + reputation = ( + metric_weights["avg_messages_time_message_normalized"] * avg_messages_time_message_normalized + + metric_weights["similarity_reputation"] * similarity_reputation + + metric_weights["fraction_score_asign"] * fraction_score_asign + + metric_weights["avg_model_arrival_latency"] * avg_model_arrival_latency + ) + else: + reputation = rep + + logging.info(f"reputation: {reputation}") + + history_data["avg_messages_time_message_normalized"].append(avg_messages_time_message_normalized) + history_data["similarity_reputation"].append(similarity_reputation) + history_data["fraction_score_asign"].append(fraction_score_asign) + history_data["avg_model_arrival_latency"].append(avg_model_arrival_latency) + + return reputation + def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_round=None): """ Calculate the reputation of each participant based on the data stored. @@ -134,6 +283,10 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro messages_model_arrival_latency_normalized = 0 avg_model_arrival_latency = 0 fraction_neighbors_scores = None + weight_to_message_time_message = 0 + weight_to_similarity = 0 + weight_to_fraction = 0 + weight_to_model_arrival_latency = 0 try: script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -185,8 +338,9 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro if "model_arrival_latency" in metric: round_latency = metric["model_arrival_latency"]["round"] latency = metric["model_arrival_latency"]["latency"] + score_asigned = None if round_latency == current_round: - logging.info("model_arrival_latency from round == current_round") + # logging.info("model_arrival_latency from round == current_round") messages_model_arrival_latency_normalized = Reputation.manage_model_arrival_latency( round_latency, addr, @@ -196,42 +350,41 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro self.model_arrival_latency_data, current_round, ) - logging.info( - f"messages_model_arrival_latency_normalized: {messages_model_arrival_latency_normalized}" - ) - if current_round - 1 in Reputation.model_arrival_latency_history: - if nei in Reputation.model_arrival_latency_history[current_round - 1]: - nei = Reputation.model_arrival_latency_history[current_round - 1][nei] - logging.info(f"Nei: {nei}") - if ( - "no_data" - in Reputation.model_arrival_latency_history[current_round - 1][nei] - ): - no_data = Reputation.model_arrival_latency_history[current_round - 1][nei][ - "no_data" - ] - logging.info(f"No data: {no_data}") - if no_data == True: - Reputation.model_arrival_latency_history[current_round][nei]["lantecy"] = ( - latency - ) + # logging.info( + # f"messages_model_arrival_latency_normalized: {messages_model_arrival_latency_normalized}" + # ) elif round_latency < current_round: - logging.info("model_arrival_latency from round < current_round") - for round in range(current_round, current_round + 1): - logging.info(f"Round to process: {round}") - latency = self.engine.config.participant["aggregator_args"]["aggregation_timeout"] - messages_model_arrival_latency_normalized = Reputation.manage_model_arrival_latency( - round_latency, - addr, - nei, - latency, - scenario, - self.model_arrival_latency_data, - current_round, - ) - logging.info( - f"messages_model_arrival_latency_normalized: {messages_model_arrival_latency_normalized}" - ) + # logging.info("model_arrival_latency from round < current_round") + if current_round in Reputation.model_arrival_latency_history: + # logging.info("model_arrival_latency from round < current_round") + if nei in Reputation.model_arrival_latency_history[current_round]: + # logging.info("nei in model_arrival_latency_history") + if "latency" in Reputation.model_arrival_latency_history[current_round][nei]: + # logging.info("latency in model_arrival_latency_history") + if "score" in Reputation.model_arrival_latency_history[current_round][nei]: + score_asigned = Reputation.model_arrival_latency_history[current_round][ + nei + ]["score"] + + if score_asigned is None: + for round in range(current_round, current_round + 1): + latency = self.engine.config.participant["aggregator_args"][ + "aggregation_timeout" + ] + messages_model_arrival_latency_normalized = ( + Reputation.manage_model_arrival_latency( + round_latency, + addr, + nei, + latency, + scenario, + self.model_arrival_latency_data, + current_round, + ) + ) + # logging.info( + # f"messages_model_arrival_latency_normalized: {messages_model_arrival_latency_normalized}" + # ) similarity_file = os.path.join(log_dir, f"participant_{id_node}_similarity.csv") similarity_reputation = Reputation.read_similarity_file(similarity_file, nei) @@ -240,20 +393,6 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro avg_model_arrival_latency = Reputation.save_model_arrival_latency_history( addr, nei, messages_model_arrival_latency_normalized, current_round ) - logging.info(f"Avg model_arrival_latency: {avg_model_arrival_latency}") - # if current_round >= 5 and (avg_model_arrival_latency < 0.9 or avg_model_arrival_latency > 1): - # self.engine.rejected_nodes.add((nei_with_port, current_round)) - # logging.info(f"Node rejected = {nei_with_port}") - # logging.info(f"Rejected nodes: {self.engine.rejected_nodes}") - - # self.engine.rejected_nodes = { - # (node, round_rejected) for node, round_rejected in self.engine.rejected_nodes - # if current_round - round_rejected < 2 - # } - # logging.info(f"Rejected nodes: {self.engine.rejected_nodes}") - # self.engine.rejected_nodes = { node for node, _ in self.engine.rejected_nodes } - # logging.info(f"Rejected nodes: {self.engine.rejected_nodes}") - if avg_model_arrival_latency is None and current_round > 4: avg_model_arrival_latency = Reputation.model_arrival_latency_history[(addr, nei)][ current_round - 1 @@ -319,51 +458,40 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro # logging.info(f"Fraction score previous round: {fraction_previous_round}") if fraction_previous_round is not None: - fraction_score_asign = fraction_previous_round - (fraction_previous_round * 0.1) + fraction_score_asign = fraction_previous_round - (fraction_previous_round * 0.5) # logging.info(f"Fraction score normalized: {fraction_score_asign}") + else: + if fraction_neighbors_scores is None: + fraction_neighbors_scores = {} + # logging.info(f"fraction_neighbors_scores: {fraction_neighbors_scores}") - if fraction_neighbors_scores is None: - fraction_neighbors_scores = {} - # logging.info(f"fraction_neighbors_scores: {fraction_neighbors_scores}") - - if fraction_previous_round is None: for key, value in Reputation.fraction_changed_history.items(): score = value.get("fraction_score") - # logging.info(f"Score: {score}") if score is not None: fraction_neighbors_scores[key] = score - if fraction_neighbors_scores: - fraction_score_asign = np.mean(list(fraction_neighbors_scores.values())) - else: - fraction_score_asign = 0 # O un valor predeterminado adecuado + if fraction_neighbors_scores: + fraction_score_asign = np.mean(list(fraction_neighbors_scores.values())) + else: + fraction_score_asign = 0 # O un valor predeterminado adecuado else: fraction_score_asign = 0 - # Weights for each metric - if current_round is not None: - if current_round >= 5: # only for rounds 5 and later - weight_to_similarity = 0.25 - weight_to_fraction = 0.25 - weight_to_message_time_message = 0.25 - weight_to_model_arrival_latency = 0.25 - elif current_round <= 4: # only for rounds 0, 1, 2, 3 - weight_to_similarity = 1.0 - weight_to_fraction = 0.0 - weight_to_message_time_message = 0.0 - weight_to_model_arrival_latency = 0.0 - - logging.info(f"Weight to similarity: {weight_to_similarity}") - logging.info(f"Weight to fraction: {weight_to_fraction}") - logging.info(f"Weight to message_time_message: {weight_to_message_time_message}") - logging.info(f"Weight to model_arrival_latency: {weight_to_model_arrival_latency}") - - # Reputation calculation - reputation = ( - weight_to_message_time_message * avg_messages_time_message_normalized - + weight_to_similarity * similarity_reputation - + weight_to_fraction * fraction_score_asign - + weight_to_model_arrival_latency * avg_model_arrival_latency + reputation = self.engine.reputation[nei_with_port]["reputation"] if current_round < 5 else 0 + + reputation = Reputation.calculate_weighted_reputation( + avg_messages_time_message_normalized, + similarity_reputation, + fraction_score_asign, + avg_model_arrival_latency, + weight_to_fraction, + weight_to_message_time_message, + weight_to_model_arrival_latency, + weight_to_similarity, + self.history_data, + self.metric_weights, + current_round, + reputation, ) # Create graphics to metrics @@ -527,9 +655,9 @@ def analyze_anomalies( prev_key = (addr, nei, round_num - 1) if prev_key not in Reputation.fraction_changed_history: for i in range(0, round_num + 1): - logging.info(f"Round: {round_num}, i = {i}") + # logging.info(f"Round: {round_num}, i = {i}") potential_prev_key = (addr, nei, round_num - i) - logging.info(f"Round: {round_num}, Potential previous key: {potential_prev_key}") + # logging.info(f"Round: {round_num}, Potential previous key: {potential_prev_key}") if potential_prev_key in Reputation.fraction_changed_history: mean_fraction_prev = Reputation.fraction_changed_history[potential_prev_key][ "mean_fraction" @@ -715,7 +843,7 @@ def manage_model_arrival_latency( "score": 0.0, } - logging.info(f"Reputation.model_arrival_latency_history: {Reputation.model_arrival_latency_history}") + # logging.info(f"Reputation.model_arrival_latency_history: {Reputation.model_arrival_latency_history}") if current_round >= 5: if current_round > 5: @@ -758,10 +886,13 @@ def manage_model_arrival_latency( "percentil_75", None ) - # logging.info(f"Round {current_round} | Node {nei} | Round {r} | Previous Mean Latency: {prev_mean_latency} | Previous Stdev Latency: {prev_stdev_latency}") - # logging.info(f"Round {current_round} | Node {nei} | Round {r} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}") + logging.info( + f"Round {current_round} | Node {nei} | Round {r} | Previous Mean Latency: {prev_mean_latency} | Previous Stdev Latency: {prev_stdev_latency}" + ) + logging.info( + f"Round {current_round} | Node {nei} | Round {r} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}" + ) - # Si se encuentran los datos, salir del bucle if prev_mean_latency is not None and prev_stdev_latency is not None: break else: @@ -783,21 +914,21 @@ def manage_model_arrival_latency( if "latency" in data and data["latency"] != 0: all_latencies.append(data["latency"]) - logging.info(f"Round {current_round} | Node {nei} | All latencies: {all_latencies}") + # logging.info(f"Round {current_round} | Node {nei} | All latencies: {all_latencies}") prev_mean_latency = np.mean(all_latencies) if all_latencies else 0 prev_stdev_latency = np.std(all_latencies) if all_latencies else 1 - logging.info( - f"Round {current_round} | Node {nei} | Initial Mean Latency: {prev_mean_latency} | Initial Stdev Latency: {prev_stdev_latency}" - ) + # logging.info( + # f"Round {current_round} | Node {nei} | Initial Mean Latency: {prev_mean_latency} | Initial Stdev Latency: {prev_stdev_latency}" + # ) percentil_25 = np.percentile(all_latencies, 25) if all_latencies else 0 percentil_75 = np.percentile(all_latencies, 75) if all_latencies else 0 - logging.info( - f"Round {current_round} | Node {nei} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}" - ) + # logging.info( + # f"Round {current_round} | Node {nei} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}" + # ) - k = 0.5 + k = 0.2 prev_mean_latency = prev_mean_latency + k * (percentil_75 - percentil_25) logging.info(f"Round {current_round} | Node {nei} | Mean latency with k: {prev_mean_latency}") logging.info(f"Round {current_round} | Node {nei} | Latency: {latency}") @@ -815,14 +946,13 @@ def manage_model_arrival_latency( score = 1 else: score = 1 / (1 + np.exp(abs(difference) / prev_mean_latency)) - logging.info(f"Round {current_round} | Node {nei} | Score: {score}") - - if round_num < current_round: - score += 1 / (1 + np.exp(abs(difference) / prev_mean_latency)) - Reputation.model_arrival_latency_history[current_round][current_key]["no_data"] = True - - logging.info(f"Round {current_round} | Node {nei} | Score penalization round: {score}") - Reputation.model_arrival_latency_history[current_round][current_key]["score"] = score + logging.info(f"Round {current_round} | Node {nei} | Score: {score}") + if round_num < current_round: + round_diff = current_round - round_num + penalty_factor = round_diff * 0.1 + penalty = penalty_factor * (1 - score) + score -= penalty * score + # logging.info(f"Round {current_round} | Node {nei} | Score penalization round: {score}") n = current_round if current_round else 1 # logging.info(f"Round {current_round} | Node {nei} | N: {n}") @@ -831,9 +961,9 @@ def manage_model_arrival_latency( update_stdev = np.sqrt( ((prev_stdev_latency**2) + (latency - prev_mean_latency) * (latency - update_mean)) / (n + 1) ) - logging.info( - f"Round {current_round} | Node {nei} | Mean Latency: {update_mean} | Stdev Latency: {update_stdev}" - ) + # logging.info( + # f"Round {current_round} | Node {nei} | Mean Latency: {update_mean} | Stdev Latency: {update_stdev}" + # ) accumulated_latencies = [ data["latency"] @@ -842,13 +972,13 @@ def manage_model_arrival_latency( for key, data in Reputation.model_arrival_latency_history[r].items() if "latency" in data and data["latency"] != 0 ] - logging.info(f"Round {current_round} | Node {nei} | Accumulated latencies: {accumulated_latencies}") + # logging.info(f"Round {current_round} | Node {nei} | Accumulated latencies: {accumulated_latencies}") update_percentil_25 = np.percentile(accumulated_latencies, 25) if accumulated_latencies else 0 update_percentil_75 = np.percentile(accumulated_latencies, 75) if accumulated_latencies else 0 - logging.info( - f"Round {current_round} | Node {nei} | Percentil 25: {update_percentil_25} | Percentil 75: {update_percentil_75}" - ) + # logging.info( + # f"Round {current_round} | Node {nei} | Percentil 25: {update_percentil_25} | Percentil 75: {update_percentil_75}" + # ) Reputation.model_arrival_latency_history[current_round][current_key]["mean_latency"] = update_mean Reputation.model_arrival_latency_history[current_round][current_key]["stdev_latency"] = update_stdev @@ -858,6 +988,7 @@ def manage_model_arrival_latency( Reputation.model_arrival_latency_history[current_round][current_key]["percentil_75"] = ( update_percentil_75 ) + # Reputation.model_arrival_latency_history[current_round][current_key]["score"] = score else: # For rounds < 5, no scoring or updates @@ -941,23 +1072,22 @@ def manage_metric_time_message(messages_time_message, addr, nei, current_round, normalized_messages = 0.0 - # Apply the normalizations using the percentiles - if previous_round >= 4: # Ensure there's enough data to calculate percentiles + if previous_round >= 4: percentile_25 = Reputation.previous_percentile_25_time_message.get(current_addr_nei, 0) - # logging.info(f"Round {current_round}. percentile_25: {percentile_25}") + logging.info(f"Round {current_round}. percentile_25: {percentile_25}") percentile_85 = Reputation.previous_percentile_85_time_message.get(current_addr_nei, 0) - # logging.info(f"Round {current_round}. percentile_85: {percentile_85}") + logging.info(f"Round {current_round}. percentile_85: {percentile_85}") - # logging.info(f"Round {current_round}. Messages count: {messages_count}") + logging.info(f"Round {current_round}. Messages count: {messages_count}") if percentile_85 - percentile_25 == 0: - relative_position = (messages_count - percentile_25) / (percentile_85) + relative_position = 1 # logging.info(f"Round {current_round}. Relative position: {relative_position}") else: relative_position = (messages_count - percentile_25) / (percentile_85 - percentile_25) - # logging.info(f"Round {current_round}. Relative position: {relative_position}") + logging.info(f"Round {current_round}. Relative position: {relative_position}") normalized_messages = 1 - 1 / (1 + np.exp(-(relative_position - 1))) - # logging.info(f"Round {current_round}. Normalized messages sin max: {normalized_messages}") + logging.info(f"Round {current_round}. Normalized messages sin max: {normalized_messages}") normalized_messages = max(0.01, normalized_messages) # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") @@ -1075,16 +1205,16 @@ def save_reputation_history_in_memory(addr, nei, reputation, current_round): total_reputation = 0 total_weights = 0 avg_reputation = 0 - rounds = sorted(Reputation.reputation_history[key].keys(), reverse=True)[:3] + rounds = sorted(Reputation.reputation_history[key].keys(), reverse=True)[:2] for i, n_round in enumerate(rounds, start=1): rep = Reputation.reputation_history[key][n_round] decay_factor = Reputation.calculate_decay_rate(rep) ** i total_reputation += rep * decay_factor total_weights += decay_factor - logging.info( - f"Round: {n_round}, Reputation: {rep}, Decay: {decay_factor}, Total reputation: {total_reputation}" - ) + # logging.info( + # f"Round: {n_round}, Reputation: {rep}, Decay: {decay_factor}, Total reputation: {total_reputation}" + # ) avg_reputation = total_reputation / total_weights if total_weights > 0: diff --git a/nebula/frontend/config/participant.json.example b/nebula/frontend/config/participant.json.example index bea50a0d6..ba0acdde2 100755 --- a/nebula/frontend/config/participant.json.example +++ b/nebula/frontend/config/participant.json.example @@ -88,7 +88,7 @@ }, "aggregator_args": { "algorithm": "FedAvg", - "aggregation_timeout": 20 + "aggregation_timeout": 30 }, "defense_args": { "with_reputation": false, diff --git a/nebula/frontend/templates/deployment.html b/nebula/frontend/templates/deployment.html index b442b6a29..6e3e75a47 100755 --- a/nebula/frontend/templates/deployment.html +++ b/nebula/frontend/templates/deployment.html @@ -196,8 +196,10 @@
Scenario title
Scenario description
- + +
From e6e1c58c19016c24dd45371f2986beda8f61eb95 Mon Sep 17 00:00:00 2001 From: Isaac Marroqui Date: Mon, 20 Jan 2025 12:01:36 +0100 Subject: [PATCH 15/43] reputational adjustments --- nebula/addons/attacks/attacks.py | 15 ++++++++------- nebula/core/engine.py | 2 +- nebula/core/reputation/Reputation.py | 18 ++++++++++++++---- .../frontend/config/participant.json.example | 2 +- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/nebula/addons/attacks/attacks.py b/nebula/addons/attacks/attacks.py index 5e8ecd7c1..21a8457f6 100755 --- a/nebula/addons/attacks/attacks.py +++ b/nebula/addons/attacks/attacks.py @@ -188,20 +188,21 @@ def __init__(self): async def attack(self, cm: CommunicationsManager, addr, num_round, repetitions=2, interval=0.05): logging.info("[FloodingAttack] Performing flood attack") - neighbors = set(await cm.get_addrs_current_connections(only_direct=True, myself=True)) + neighbors = set(await cm.get_addrs_current_connections(only_direct=True)) logging.info(f"Neighbors: {neighbors}") logging.info(f"Round: {num_round}") logging.info(f"Interval: {interval}") logging.info(f"Repetitions: {repetitions}") - if self.scaled_repetitions is None: - self.scaled_repetitions = len(neighbors) * repetitions - else: - self.scaled_repetitions = self.scaled_repetitions + (num_round - 3) - logging.info(f"Total repetitions: {self.scaled_repetitions}") + # if self.scaled_repetitions is None: + # self.scaled_repetitions = len(neighbors) * repetitions + # else: + # self.scaled_repetitions = self.scaled_repetitions + (num_round - 3) + scaled_repetitions = num_round * 2 + logging.info(f"Total repetitions: {scaled_repetitions}") for nei in neighbors: - for i in range(self.scaled_repetitions): + for i in range(scaled_repetitions): message_data = cm.mm.generate_flood_attack_message( attacker_id=addr, frequency=int(i), diff --git a/nebula/core/engine.py b/nebula/core/engine.py index 90d8fef16..e3a3cdb36 100755 --- a/nebula/core/engine.py +++ b/nebula/core/engine.py @@ -777,7 +777,7 @@ async def _extended_learning_cycle(self): if type(self.attack).__name__ == "FloodingAttack": logging.info("Running Flooding Attack") if self.round in range(self.round_start_attack, self.round_stop_attack): - await self.attack.attack(self.cm, self.addr, self.round, repetitions=2, interval=0.05) + await self.attack.attack(self.cm, self.addr, self.round, repetitions=2, interval=0.1) if type(self.attack).__name__ == "DelayerAttack": logging.info("Running Delayer Attack") diff --git a/nebula/core/reputation/Reputation.py b/nebula/core/reputation/Reputation.py index 37d1124c7..47a265ddf 100644 --- a/nebula/core/reputation/Reputation.py +++ b/nebula/core/reputation/Reputation.py @@ -928,7 +928,7 @@ def manage_model_arrival_latency( # f"Round {current_round} | Node {nei} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}" # ) - k = 0.2 + k = 0.15 prev_mean_latency = prev_mean_latency + k * (percentil_75 - percentil_25) logging.info(f"Round {current_round} | Node {nei} | Mean latency with k: {prev_mean_latency}") logging.info(f"Round {current_round} | Node {nei} | Latency: {latency}") @@ -1071,6 +1071,7 @@ def manage_metric_time_message(messages_time_message, addr, nei, current_round, ) normalized_messages = 0.0 + relative_position = 0 if previous_round >= 4: percentile_25 = Reputation.previous_percentile_25_time_message.get(current_addr_nei, 0) @@ -1080,13 +1081,22 @@ def manage_metric_time_message(messages_time_message, addr, nei, current_round, logging.info(f"Round {current_round}. Messages count: {messages_count}") if percentile_85 - percentile_25 == 0: - relative_position = 1 - # logging.info(f"Round {current_round}. Relative position: {relative_position}") + logging.info(f"Round {current_round}. Percentiles difference is 0") + normalized_messages = 1 + elif percentile_25 <= messages_count <= percentile_85: + logging.info(f"Round {current_round}. Messages count between percentiles") + normalized_messages = 1 + elif messages_count < percentile_25: + logging.info(f"Round {current_round}. Messages count below percentile_25") + relative_position = (messages_count - percentile_25) / (percentile_85 - percentile_25) + logging.info(f"Round {current_round}. Relative position: {relative_position}") + normalized_messages = 1 / (1 + np.exp(-relative_position)) else: + logging.info(f"Round {current_round}. Messages count out of percentiles") relative_position = (messages_count - percentile_25) / (percentile_85 - percentile_25) logging.info(f"Round {current_round}. Relative position: {relative_position}") + normalized_messages = 1 - 1 / (1 + np.exp(-(relative_position - 1))) - normalized_messages = 1 - 1 / (1 + np.exp(-(relative_position - 1))) logging.info(f"Round {current_round}. Normalized messages sin max: {normalized_messages}") normalized_messages = max(0.01, normalized_messages) # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") diff --git a/nebula/frontend/config/participant.json.example b/nebula/frontend/config/participant.json.example index ba0acdde2..402cbb0b9 100755 --- a/nebula/frontend/config/participant.json.example +++ b/nebula/frontend/config/participant.json.example @@ -88,7 +88,7 @@ }, "aggregator_args": { "algorithm": "FedAvg", - "aggregation_timeout": 30 + "aggregation_timeout": 25 }, "defense_args": { "with_reputation": false, From 81a88247f95e951fb174ad71d636ce6267edafd2 Mon Sep 17 00:00:00 2001 From: Isaac Marroqui Date: Tue, 28 Jan 2025 11:33:12 +0100 Subject: [PATCH 16/43] update config and dynamic reputation --- nebula/core/engine.py | 163 ++++- nebula/core/network/communications.py | 28 +- nebula/core/reputation/Reputation.py | 573 ++++++++---------- .../frontend/config/participant.json.example | 2 +- 4 files changed, 422 insertions(+), 344 deletions(-) diff --git a/nebula/core/engine.py b/nebula/core/engine.py index e3a3cdb36..08bbb322b 100755 --- a/nebula/core/engine.py +++ b/nebula/core/engine.py @@ -1,7 +1,7 @@ import asyncio import logging import os - +import numpy as np import docker from nebula.addons.attacks.attacks import create_attack @@ -172,10 +172,10 @@ def __init__( # ) # print_msg_box(msg=msg, indent=2, title="Defense information") - if self.with_reputation: - logging.info("Reputation system enabled") - federation = self.config.participant["network_args"]["neighbors"].split() - self.reputation_instance.init_reputation(federation_nodes=federation) + # if self.with_reputation: + # logging.info("Reputation system enabled") + # federation = self.config.participant["network_args"]["neighbors"].split() + # self.reputation_instance.init_reputation(federation_nodes=federation) @property def cm(self): @@ -492,9 +492,10 @@ async def calculate_reputation(self): current_round = self.get_round() neighbors = set(await self.cm.get_addrs_current_connections(only_direct=True)) + reputation_with_weights = None for nei in neighbors: - avg_reputation = self.reputation_instance.calculate_reputation( + metric_messages_time, metric_similarity, metric_fraction, metric_model_arrival_latency = self.reputation_instance.calculate_value_metrics( self.config.participant["scenario_args"]["name"], self.log_dir, self.idx, @@ -503,20 +504,140 @@ async def calculate_reputation(self): current_round=current_round, ) - if nei not in self.reputation: - self.reputation[nei] = {"reputation": avg_reputation, "round": self.round, "last_feedback_round": -1} - else: - self.reputation[nei]["reputation"] = avg_reputation - self.reputation[nei]["round"] = self.round - - if self.reputation[nei]["reputation"] is not None: - logging.info(f"Reputation of node {nei}: {self.reputation[nei]['reputation']}") - if self.reputation[nei]["reputation"] < 0.6: - self.rejected_nodes.add(nei) - logging.info(f"Rejected nodes: {self.rejected_nodes}") - elif 0.6 < self.reputation[nei]["reputation"] < 0.8: - logging.info(f"Change weight node: {nei}") - self.change_weight_nodes.add(nei) + logging.info(f"metric_messages_time at round {self.round}: {metric_messages_time}") + logging.info(f"metric_similarity at round {self.round}: {metric_similarity}") + logging.info(f"metric_fraction at round {self.round}: {metric_fraction}") + logging.info(f"metric_model_arrival_latency at round {self.round}: {metric_model_arrival_latency}") + + history_data = self.reputation_instance.history_data + self.reputation_instance.calculate_weighted_values(metric_messages_time, + metric_similarity, + metric_fraction, + metric_model_arrival_latency, + history_data, + current_round, + self.addr, + nei) + # logging.info(f"history_data after calculate_weighted_values at {self.round}: {history_data}") + + if current_round >= 5: + average_weights = {} + for metric_name in history_data.keys(): + valid_entries = [ + entry for entry in history_data[metric_name] + if entry["round"] >= current_round and entry.get("weight") not in [None, -1] + ] + # logging.info(f"valid_entries for {metric_name} at round {self.round}: {valid_entries}") + + if valid_entries: + average_weight = sum([entry["weight"] for entry in valid_entries]) / len(valid_entries) + average_weights[metric_name] = average_weight + # logging.info(f"average_weight for {metric_name} at round {self.round}: {average_weight}") + else: + average_weights[metric_name] = 0 + + for nei in neighbors: + metric_messages_time_history = None + metric_similarity_history = None + metric_fraction_history = None + metric_model_arrival_latency_history = None + + for metric_name in history_data.keys(): + for entry in history_data.get(metric_name, []): + if entry["round"] == current_round and entry["nei"] == nei: + if metric_name == "messages_time": + metric_messages_time_history = entry["metric_value"] + elif metric_name == "similarity": + metric_similarity_history = entry["metric_value"] + elif metric_name == "fraction": + metric_fraction_history = entry["metric_value"] + elif metric_name == "model_arrival_latency": + metric_model_arrival_latency_history = entry["metric_value"] + break + + logging.info(f"metric_messages_time_history at round {self.round}: {metric_messages_time_history}") + logging.info(f"metric_similarity_history at round {self.round}: {metric_similarity_history}") + logging.info(f"metric_fraction_history at round {self.round}: {metric_fraction_history}") + logging.info(f"metric_model_arrival_latency_history at round {self.round}: {metric_model_arrival_latency_history}") + + logging.info(f"average_weights at round {self.round}: {average_weights}") + + if metric_messages_time_history is not None and metric_similarity_history is not None and metric_fraction_history is not None and metric_model_arrival_latency_history is not None: + reputation_with_weights = ( + metric_messages_time_history * average_weights["messages_time"] + + metric_similarity_history * average_weights["similarity"] + + metric_fraction_history * average_weights["fraction"] + + metric_model_arrival_latency_history * average_weights["model_arrival_latency"] + ) + logging.info(f"Reputation with weights: {reputation_with_weights}") + + metrics_data = { + "addr": self.addr.split(":")[0].strip(), + "nei": nei.split(":")[0].strip(), + "round": self.round, + "average_time_messages": average_weights["messages_time"], + "average_similarity": average_weights["similarity"], + "average_fraction": average_weights["fraction"], + "average_model_arrival_latency": average_weights["model_arrival_latency"], + } + + self.reputation_instance.metrics( + self.experiment_name, + metrics_data, + self.addr.split(":")[0].strip(), + nei.split(":")[0].strip(), + "reputation", + update_field="reputation_without_feedback", + ) + # logging.info(f"reputation_with_weights at round {self.round}: {reputation_with_weights}") + else: + reputation_with_weights = None + # logging.info(f"reputation_with_weights at round {self.round}: {reputation_with_weights}") + + if reputation_with_weights is not None: + avg_reputation = self.reputation_instance.save_reputation_history_in_memory(self.addr, nei, reputation_with_weights, current_round) + logging.info(f"Average reputation for node {nei}: {avg_reputation}") + else: + avg_reputation = 0 + # logging.info(f"Average reputation for node {nei}: {avg_reputation}") + + if nei not in self.reputation: + self.reputation[nei] = {"reputation": avg_reputation, "round": current_round, "last_feedback_round": -1} + else: + self.reputation[nei]["reputation"] = avg_reputation + self.reputation[nei]["round"] = current_round + + if self.reputation[nei]["reputation"] is not None: + metrics_data = { + "addr": self.addr.split(":")[0].strip(), + "nei": nei.split(":")[0].strip(), + "round": self.round, + "reputation_without_feedback": self.reputation[nei]["reputation"], + } + + self.reputation_instance.metrics( + self.experiment_name, + metrics_data, + self.addr.split(":")[0].strip(), + nei.split(":")[0].strip(), + "reputation", + update_field="model_arrival_latency", + ) + + if self.reputation[nei]["reputation"] is not None: + logging.info(f"Reputation of node {nei}: {self.reputation[nei]['reputation']}") + if self.reputation[nei]["reputation"] < 0.6: + self.rejected_nodes.add(nei) + logging.info(f"Rejected nodes: {self.rejected_nodes}") + elif 0.6 < self.reputation[nei]["reputation"] < 0.8: + logging.info(f"Change weight node: {nei}") + self.change_weight_nodes.add(nei) + else: + # logging.info(f"No weights calculated at round {self.round}") + if self.with_reputation: + # logging.info("Reputation system enabled") + federation = self.config.participant["network_args"]["neighbors"].split() + self.reputation_instance.init_reputation(self.addr, federation_nodes=federation, round_num=current_round, last_feedback_round=-1, scenario=self.experiment_name) status = await self.include_feedback_in_reputation() if status: @@ -777,7 +898,7 @@ async def _extended_learning_cycle(self): if type(self.attack).__name__ == "FloodingAttack": logging.info("Running Flooding Attack") if self.round in range(self.round_start_attack, self.round_stop_attack): - await self.attack.attack(self.cm, self.addr, self.round, repetitions=2, interval=0.1) + await self.attack.attack(self.cm, self.addr, self.round, repetitions=2, interval=0.15) if type(self.attack).__name__ == "DelayerAttack": logging.info("Running Delayer Attack") diff --git a/nebula/core/network/communications.py b/nebula/core/network/communications.py index 33f00ed40..64d8c9ea8 100755 --- a/nebula/core/network/communications.py +++ b/nebula/core/network/communications.py @@ -139,16 +139,22 @@ async def handle_incoming_message(self, data, addr_from): message_wrapper = nebula_pb2.Wrapper() message_wrapper.ParseFromString(data) source = message_wrapper.source + message_round = None logging.debug(f"📥 handle_incoming_message | Received message from {addr_from} with source {source}") + if source == self.addr: return + if message_wrapper.HasField("discovery_message"): + message_type = "discovery" if await self.include_received_message_hash(hashlib.md5(data).hexdigest()): await self.forwarder.forward(data, addr_from=addr_from) await self.handle_discovery_message(source, message_wrapper.discovery_message) elif message_wrapper.HasField("control_message"): + message_type = "control" await self.handle_control_message(source, message_wrapper.control_message) elif message_wrapper.HasField("federation_message"): + message_type = "federation" if await self.include_received_message_hash(hashlib.md5(data).hexdigest()): if self.config.participant["device_args"][ "proxy" @@ -158,6 +164,7 @@ async def handle_incoming_message(self, data, addr_from): await self.forwarder.forward(data, addr_from=addr_from) await self.handle_federation_message(source, message_wrapper.federation_message) elif message_wrapper.HasField("model_message"): + message_type = "model" if await self.include_received_message_hash(hashlib.md5(data).hexdigest()): # TODO: Improve the technique. Now only forward model messages if the node is a proxy # Need to update the expected model messages receiving during the round @@ -166,15 +173,27 @@ async def handle_incoming_message(self, data, addr_from): await self.forwarder.forward(data, addr_from=addr_from) await self.handle_model_message(source, message_wrapper.model_message) elif message_wrapper.HasField("reputation_message"): + message_type = "reputation" + message_round = message_wrapper.reputation_message.round if await self.include_received_message_hash(hashlib.md5(data).hexdigest()): self.forwarder.forward(data, addr_from=addr_from) await self.handle_reputation_message(source, message_wrapper.reputation_message) elif message_wrapper.HasField("flood_attack_message"): + message_type = "flood_attack" await self.handle_flooding_attack_message(source, message_wrapper.flood_attack_message) elif message_wrapper.HasField("connection_message"): + message_type = "connection" await self.handle_connection_message(source, message_wrapper.connection_message) else: + message_type = "unknown" logging.info(f"Unknown handler for message: {message_wrapper}") + + if message_round is not None: + round_num = message_round + else: + round_num = self.get_round() + self.store_receive_timestamp(addr_from, message_type, round=round_num) + except Exception as e: logging.exception(f"📥 handle_incoming_message | Error while processing: {e}") logging.exception(traceback.format_exc()) @@ -203,6 +222,7 @@ async def handle_federation_message(self, source, message): logging.info( f"📝 handle_federation_message | Received [Action {message.action}] from {source} with arguments {message.arguments}" ) + self.store_receive_timestamp(source, "federation", self.engine.get_round()) try: await self.engine.event_manager.trigger_event(source, message) except Exception as e: @@ -281,7 +301,7 @@ async def handle_model_message(self, source, message): if "time_0" not in self._model_arrival_latency_data[round_id]: self._model_arrival_latency_data[round_id]["time_0"] = {"time": start_time, "source": source} - logging.info(f"start_time: {start_time} of source {self.addr} for round {round_id}") + # logging.info(f"start_time: {start_time} of source {source} for round {round_id}") relative_time = start_time - self._model_arrival_latency_data[round_id]["time_0"]["time"] # federation_nodes = await self.get_addrs_current_connections(only_direct=True, myself=True) @@ -419,7 +439,6 @@ async def handle_reputation_message(self, source, message): ) self.store_receive_timestamp(source, "reputation", message.round) - # self.calculate_latency(source, "reputation") current_node = self.addr nei = message.node_id @@ -444,8 +463,7 @@ async def handle_flooding_attack_message(self, source, message): logging.info( f"🔥 handle_flooding_attack_message | Received flooding attack message from {source} | Attacker: {message.attacker_id} | Frequency: {message.frequency} | Duration: {message.duration} | Target node: {message.target_node}" ) - current_round = self.engine.get_round() - self.store_receive_timestamp(source, "flooding_attack", current_round) + self.store_receive_timestamp(source, "flooding_attack", self.engine.get_round()) except Exception as e: logging.exception(f"🔥 handle_flooding_attack_message | Error while processing: {e}") @@ -848,7 +866,7 @@ def store_receive_timestamp(self, source, type_message, round=None): current_round = self.get_round() if current_time: if round is None: - round = self.get_round() + round = current_round save_data( self.config.participant["scenario_args"]["name"], "time_message", diff --git a/nebula/core/reputation/Reputation.py b/nebula/core/reputation/Reputation.py index 47a265ddf..4ff131554 100644 --- a/nebula/core/reputation/Reputation.py +++ b/nebula/core/reputation/Reputation.py @@ -115,8 +115,8 @@ def __init__(self, engine: "Engine"): def engine(self): return self._engine - def init_reputation(self, federation_nodes=None): - # logging.info("init_reputation | Reputation initialization started") + def init_reputation(self, addr, federation_nodes=None, round_num=None, last_feedback_round=None, scenario=None): + logging.info("init_reputation | Reputation initialization started") if not federation_nodes: logging.error("init_reputation | No federation nodes provided") return @@ -136,10 +136,28 @@ def init_reputation(self, federation_nodes=None): if nei not in self._engine.reputation: self._engine.reputation[nei] = { "reputation": reputation_initialized, - "round": -1, - "last_feedback_round": -1, + "round": round_num, + "last_feedback_round": last_feedback_round, } - # logging.info(f"init_reputation | Reputation initialized for node {nei} with {reputation_initialized}") + elif self._engine.reputation[nei].get("reputation") is None: + self._engine.reputation[nei]["reputation"] = reputation_initialized + self._engine.reputation[nei]["round"] = round_num + self._engine.reputation[nei]["last_feedback_round"] = last_feedback_round + + metrics_data = { + "addr": addr.split(":")[0].strip(), + "nei": nei.split(":")[0].strip(), + "round": round_num, + "reputation_without_feedback": reputation_initialized, + } + self.metrics( + scenario, + metrics_data, + addr.split(":")[0].strip(), + nei.split(":")[0].strip(), + "reputation", + update_field="model_arrival_latency", + ) def is_valid_ip(federation_nodes): """ @@ -154,115 +172,128 @@ def is_valid_ip(federation_nodes): return valid_ip @staticmethod - def calculate_weighted_reputation( + def calculate_weighted_values( avg_messages_time_message_normalized, similarity_reputation, fraction_score_asign, avg_model_arrival_latency, - weight_to_message_time_message, - weight_to_similarity, - weight_to_fraction, - weight_to_model_arrival_latency, history_data: dict, - metric_weights: dict, current_round, - rep, + addr, + nei ): """ - Calculate the reputation of a participant according to the dynamic weights of the metrics. + Calculate the weighted values for each metric. """ - # Weights for each metric if current_round is not None: + logging.info(f"Values before normalization") + logging.info(f"avg_messages_time_message_normalized: {avg_messages_time_message_normalized}") + logging.info(f"similarity_reputation: {similarity_reputation}") + logging.info(f"fraction_score_asign: {fraction_score_asign}") + logging.info(f"avg_model_arrival_latency: {avg_model_arrival_latency}") + + normalized_weights = {} required_keys = [ - "avg_messages_time_message_normalized", - "similarity_reputation", - "fraction_score_asign", - "avg_model_arrival_latency", + "messages_time", + "similarity", + "fraction", + "model_arrival_latency", ] for key in required_keys: if key not in history_data: history_data[key] = [] - logging.info(f"history_data: {history_data}") - logging.info(f"avg_messages_time_message_normalized: {avg_messages_time_message_normalized}") - logging.info(f"similarity_reputation: {similarity_reputation}") - logging.info(f"fraction_score_asign: {fraction_score_asign}") - logging.info(f"avg_model_arrival_latency: {avg_model_arrival_latency}") - - metric_weights = { - "avg_messages_time_message_normalized": weight_to_message_time_message, - "similarity_reputation": weight_to_similarity, - "fraction_score_asign": weight_to_fraction, - "avg_model_arrival_latency": weight_to_model_arrival_latency, + metrics = { + "messages_time": avg_messages_time_message_normalized, + "similarity": similarity_reputation, + "fraction": fraction_score_asign, + "model_arrival_latency": avg_model_arrival_latency, } - if current_round >= 5: # only for rounds 5 and later - metric_weights = { - "avg_messages_time_message_normalized": 0.25, - "similarity_reputation": 0.25, - "fraction_score_asign": 0.25, - "avg_model_arrival_latency": 0.25, - } + for metric_name, current_value in metrics.items(): + history_data[metric_name].append({ + "round": current_round, + "addr": addr, + "nei": nei, + "metric_name": metric_name, + "metric_value": current_value, + "weight": None + }) - metrics = { - "avg_messages_time_message_normalized": avg_messages_time_message_normalized, - "similarity_reputation": similarity_reputation, - "fraction_score_asign": fraction_score_asign, - "avg_model_arrival_latency": avg_model_arrival_latency, - } + adjusted_weights = {} + if current_round >= 5: desviations = {} for metric_name, current_value in metrics.items(): historical_values = history_data[metric_name] - logging.info(f"historical_values: {historical_values}") - if historical_values: - mean_value = np.mean(historical_values) - std_value = np.std(historical_values) + metric_values = [entry['metric_value'] for entry in historical_values if 'metric_value' in entry and entry["metric_value"] != 0] + logging.info(f"metric_name: {metric_name}, metric_values: {metric_values}") + + if metric_values: + mean_value = np.mean(metric_values) + std_value = np.std(metric_values) else: - mean_value = current_value + mean_value = -1 std_value = 1 deviation = abs(current_value - mean_value) desviations[metric_name] = deviation - + logging.info(f"Current value: {current_value}") logging.info(f"{metric_name} - Mean: {mean_value}, Std: {std_value}, Deviation: {deviation}") - max_desviation = max(desviations.values()) if desviations else 1 - normalized_weights = { - metric_name: (desviation / max_desviation) for metric_name, desviation in desviations.items() - } - logging.info(f"normalized_weights: {normalized_weights}") + logging.info(f"desviations: {desviations}") - total_weight = sum(normalized_weights.values()) - normalized_weights = { - metric_name: weight / total_weight for metric_name, weight in normalized_weights.items() - } - logging.info(f"total_weight: {total_weight}, normalized_weights: {normalized_weights}") + if all(deviation == 0.0 for deviation in desviations.values()): + weight_per_metric = 1 / len(metrics) + normalized_weights = {metric_name: weight_per_metric for metric_name in metrics} + logging.info(f"All deviations are 0.0. Distributing weights equally: {normalized_weights}") + else: + max_desviation = max(desviations.values()) if desviations else 1 + logging.info(f"max_desviation: {max_desviation}") + normalized_weights = { + metric_name: (desviation / max_desviation) for metric_name, desviation in desviations.items() + } - metric_weights = normalized_weights - logging.info(f"metric_weights: {metric_weights}") + total_weight = sum(normalized_weights.values()) + normalized_weights = { + metric_name: weight / total_weight for metric_name, weight in normalized_weights.items() + } + logging.info(f"total_weight: {total_weight}, normalized_weights: {normalized_weights}") - reputation = ( - metric_weights["avg_messages_time_message_normalized"] * avg_messages_time_message_normalized - + metric_weights["similarity_reputation"] * similarity_reputation - + metric_weights["fraction_score_asign"] * fraction_score_asign - + metric_weights["avg_model_arrival_latency"] * avg_model_arrival_latency - ) - else: - reputation = rep + mean_deviation = np.mean(list(desviations.values())) + dynamic_min_weight = max(0.1, mean_deviation / (mean_deviation + 1)) + logging.info(f"Dynamic minimum weight: {dynamic_min_weight}") - logging.info(f"reputation: {reputation}") + total_adjusted_weight = 0 - history_data["avg_messages_time_message_normalized"].append(avg_messages_time_message_normalized) - history_data["similarity_reputation"].append(similarity_reputation) - history_data["fraction_score_asign"].append(fraction_score_asign) - history_data["avg_model_arrival_latency"].append(avg_model_arrival_latency) + for metric_name, weight in normalized_weights.items(): + if weight < dynamic_min_weight: + adjusted_weights[metric_name] = dynamic_min_weight + else: + adjusted_weights[metric_name] = weight + total_adjusted_weight += adjusted_weights[metric_name] - return reputation + if total_adjusted_weight > 1: + for metric_name in adjusted_weights: + adjusted_weights[metric_name] /= total_adjusted_weight + total_adjusted_weight = 1 - def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_round=None): + logging.info(f"adjusted_weights: {adjusted_weights}, total_adjusted_weight: {total_adjusted_weight}") + else: + # Asignar pesos predeterminados si current_round < 5 + adjusted_weights = {metric_name: 1 / len(metrics) for metric_name in metrics} + logging.info(f"Initial round. Using default weights: {adjusted_weights}") + + # Ahora actualizar los pesos en history_data + for metric_name, current_value in metrics.items(): + weight = adjusted_weights.get(metric_name, -1) + for entry in history_data[metric_name]: + if entry["metric_name"] == metric_name and entry["round"] == current_round: + entry["weight"] = weight + + def calculate_value_metrics(self, scenario, log_dir, id_node, addr, nei, current_round=None): """ Calculate the reputation of each participant based on the data stored. @@ -283,10 +314,6 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro messages_model_arrival_latency_normalized = 0 avg_model_arrival_latency = 0 fraction_neighbors_scores = None - weight_to_message_time_message = 0 - weight_to_similarity = 0 - weight_to_fraction = 0 - weight_to_model_arrival_latency = 0 try: script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -386,8 +413,11 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro # f"messages_model_arrival_latency_normalized: {messages_model_arrival_latency_normalized}" # ) - similarity_file = os.path.join(log_dir, f"participant_{id_node}_similarity.csv") - similarity_reputation = Reputation.read_similarity_file(similarity_file, nei) + if current_round >= 5: + similarity_file = os.path.join(log_dir, f"participant_{id_node}_similarity.csv") + similarity_reputation = Reputation.read_similarity_file(similarity_file, nei) + else: + similarity_reputation = 0 if messages_model_arrival_latency_normalized >= 0: avg_model_arrival_latency = Reputation.save_model_arrival_latency_history( @@ -410,7 +440,7 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro addr, nei, messages_time_message_normalized, current_round ) # logging.info(f"Avg messages time_message normalized: {avg_messages_time_message_normalized}") - if avg_messages_time_message_normalized is None and current_round >= 4: + if avg_messages_time_message_normalized is None and current_round > 4: avg_messages_time_message_normalized = Reputation.time_message_history[(addr, nei)][ current_round - 1 ]["avg_time_message"] @@ -432,7 +462,7 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro # logging.info(f"Fraction score previous round: {fraction_previous_round}") if fraction_previous_round is not None: - fraction_score_asign = (fraction_score_normalized + fraction_previous_round) / 2 + fraction_score_asign = fraction_score_normalized * 0.8 + fraction_previous_round * 0.2 # logging.info(f"Fraction score normalized: {fraction_score_asign}") Reputation.fraction_changed_history[(addr, nei, current_round)]["fraction_score"] = ( fraction_score_asign @@ -477,23 +507,6 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro else: fraction_score_asign = 0 - reputation = self.engine.reputation[nei_with_port]["reputation"] if current_round < 5 else 0 - - reputation = Reputation.calculate_weighted_reputation( - avg_messages_time_message_normalized, - similarity_reputation, - fraction_score_asign, - avg_model_arrival_latency, - weight_to_fraction, - weight_to_message_time_message, - weight_to_model_arrival_latency, - weight_to_similarity, - self.history_data, - self.metric_weights, - current_round, - reputation, - ) - # Create graphics to metrics self.create_graphics_to_metrics( messages_time_message_count, @@ -506,16 +519,9 @@ def calculate_reputation(self, scenario, log_dir, id_node, addr, nei, current_ro current_round, self.engine.total_rounds, scenario, - reputation, - ) - - # Save history reputation - average_reputation = Reputation.save_reputation_history_in_memory( - addr, nei, reputation, current_round ) - logging.info(f"Average reputation to node {nei}: {average_reputation}") - return average_reputation + return avg_messages_time_message_normalized, similarity_reputation, fraction_score_asign, avg_model_arrival_latency except Exception as e: logging.exception(f"Error calculating reputation. Type: {type(e).__name__}") @@ -531,7 +537,6 @@ def create_graphics_to_metrics( current_round, total_rounds, scenario, - reputation, ): """ Create graphics to metrics. @@ -574,7 +579,6 @@ def create_graphics_to_metrics( "similarity": similarity, "fraction": fraction, "model_arrival_latency": model_arrival_latency, - "reputation_without_feedback": reputation, } Reputation.metrics(scenario, data, addr, nei, "reputation") @@ -593,6 +597,21 @@ def analyze_anomalies( ): """ Analyze anomalies in the fraction of parameters changed. + + Args: + addr (str): Source IP address. + nei (str): Destination IP address. + round_num (int): Round number. + current_round (int): Current round number. + fraction_changed (float): Fraction of parameters changed. + threshold (float): Threshold value. + changes_record (list): List of changes. + changed_params (int): Number of changed parameters. + total_params (int): Total number of parameters. + scenario (str): The scenario name for logging and metric storage. + + Returns: + float: The fraction score between 0 and 1. """ try: key = (addr, nei, round_num) @@ -679,9 +698,9 @@ def analyze_anomalies( # logging.info(f"Round: {round_num}, Current fraction: {current_fraction}, Current threshold: {current_threshold}") # low_mean_fraction_prev = mean_fraction_prev - std_dev_fraction_prev - upper_mean_fraction_prev = (mean_fraction_prev) + (std_dev_fraction_prev) + upper_mean_fraction_prev = (mean_fraction_prev + std_dev_fraction_prev) * 1.05 # low_mean_threshold_prev = mean_threshold_prev - std_dev_threshold_prev - upper_mean_threshold_prev = (mean_threshold_prev) + (std_dev_threshold_prev) + upper_mean_threshold_prev = (mean_threshold_prev + std_dev_threshold_prev) * 1.10 # logging.info(f"Round: {round_num}, Upper mean fraction: {upper_mean_fraction_prev}, Upper mean threshold: {upper_mean_threshold_prev}") # fraction_anomaly = not (low_mean_fraction_prev <= current_fraction <= upper_mean_fraction_prev) @@ -693,34 +712,41 @@ def analyze_anomalies( Reputation.fraction_changed_history[key]["fraction_anomaly"] = fraction_anomaly Reputation.fraction_changed_history[key]["threshold_anomaly"] = threshold_anomaly - # Calculate the fraction score - k_fraction = std_dev_fraction_prev if std_dev_fraction_prev != 0 else 1 - # logging.info(f"Round: {round_num}, K fraction: {k_fraction}") - k_threshold = std_dev_threshold_prev if std_dev_threshold_prev != 0 else 1 - # logging.info(f"Round: {round_num}, K threshold: {k_threshold}") - fraction_value = ( - 1 - (1 / (1 + np.exp(-k_fraction * (current_fraction - mean_fraction_prev)))) - if current_fraction is not None and mean_fraction_prev is not None - else 0 - ) - # logging.info(f"Round: {round_num}, Fraction_value: {fraction_value}") + penalization_factor_fraction = abs(current_fraction - mean_fraction_prev) / mean_fraction_prev if mean_fraction_prev != 0 else 1 + penalization_factor_threshold = abs(current_threshold - mean_threshold_prev) / mean_threshold_prev if mean_threshold_prev != 0 else 1 + # logging.info(f"Round: {round_num}, Penalization factor fraction: {penalization_factor_fraction}, Penalization factor threshold: {penalization_factor_threshold}") - threshold_value = ( - 1 - (1 / (1 + np.exp(-k_threshold * (current_threshold - mean_threshold_prev)))) - if current_threshold is not None and mean_threshold_prev is not None - else 0 - ) - # logging.info(f"Round: {round_num}, Threshold_value: {threshold_value}") - - # if threshold_anomaly: - # fraction_weight = 0.8 - # threshold_weight = 0.2 - # elif fraction_anomaly: - # fraction_weight = 0.4 - # threshold_weight = 0.6 - # else: - # fraction_weight = 0.5 - # threshold_weight = 0.5 + k_fraction = penalization_factor_fraction if penalization_factor_fraction != 0 else 1 + k_threshold = penalization_factor_threshold if penalization_factor_threshold != 0 else 1 + # logging.info(f"Round: {round_num}, K fraction: {k_fraction}, K threshold: {k_threshold}") + + if fraction_anomaly: + fraction_value = ( + 1 - (1 / (1 + np.exp(-k_fraction))) + if current_fraction is not None and mean_fraction_prev is not None + else 0 + ) + else: + fraction_value = ( + 1 - (1 / (1 + np.exp(k_fraction))) + if current_fraction is not None and mean_fraction_prev is not None + else 0 + ) + + if threshold_anomaly: + threshold_value = ( + 1 - (1 / (1 + np.exp(-k_threshold))) + if current_threshold is not None and mean_threshold_prev is not None + else 0 + ) + else: + threshold_value = ( + 1 - (1 / (1 + np.exp(k_threshold))) + if current_threshold is not None and mean_threshold_prev is not None + else 0 + ) + + # logging.info(f"Round: {round_num}, Fraction value: {fraction_value}, Threshold value: {threshold_value}") fraction_weight = 0.5 threshold_weight = 0.5 @@ -730,18 +756,10 @@ def analyze_anomalies( # Reputation.fraction_changed_history[key]["fraction_score"] = fraction_score # Upload the values to the history - Reputation.fraction_changed_history[key]["mean_fraction"] = ( - current_fraction + mean_fraction_prev - ) / 2 - Reputation.fraction_changed_history[key]["std_dev_fraction"] = np.sqrt( - ((current_fraction - mean_fraction_prev) ** 2 + std_dev_fraction_prev**2) / 2 - ) - Reputation.fraction_changed_history[key]["mean_threshold"] = ( - current_threshold + mean_threshold_prev - ) / 2 - Reputation.fraction_changed_history[key]["std_dev_threshold"] = np.sqrt( - ((0.1 * (current_threshold - mean_threshold_prev) ** 2) + std_dev_threshold_prev**2) / 2 - ) + Reputation.fraction_changed_history[key]["mean_fraction"] = (current_fraction + mean_fraction_prev) / 2 + Reputation.fraction_changed_history[key]["std_dev_fraction"] = np.sqrt(((current_fraction - mean_fraction_prev) ** 2 + std_dev_fraction_prev**2) / 2) + Reputation.fraction_changed_history[key]["mean_threshold"] = (current_threshold + mean_threshold_prev) / 2 + Reputation.fraction_changed_history[key]["std_dev_threshold"] = np.sqrt(((0.1 * (current_threshold - mean_threshold_prev) ** 2) + std_dev_threshold_prev**2) / 2) data = { "addr": addr, @@ -749,6 +767,20 @@ def analyze_anomalies( "round": current_round, "fraction_changed": current_fraction, "threshold": current_threshold, + "mean_fraction": mean_fraction_prev, + "std_dev_fraction": std_dev_fraction_prev, + "mean_threshold": mean_threshold_prev, + "std_dev_threshold": std_dev_threshold_prev, + "upper_mean_fraction": upper_mean_fraction_prev, + "upper_mean_threshold": upper_mean_threshold_prev, + "fraction_anomaly": fraction_anomaly, + "threshold_anomaly": threshold_anomaly, + "penalization_factor_fraction": penalization_factor_fraction, + "penalization_factor_threshold": penalization_factor_threshold, + "k_fraction": k_fraction, + "k_threshold": k_threshold, + "fraction_value": fraction_value, + "threshold_value": threshold_value, "fraction_score": fraction_score, } Reputation.metrics(scenario, data, addr, nei, "fraction_changed") @@ -788,13 +820,13 @@ def save_time_message_history(addr, nei, messages_time_message_normalized, curre # logging.info(f"time_message history: {Reputation.time_message_history}") # rounds = Reputation.time_message_history[key] - if messages_time_message_normalized != 0 and current_round >= 4: + if messages_time_message_normalized != 0 and current_round > 4: previous_avg = ( Reputation.time_message_history[key].get(current_round - 1, {}).get("avg_time_message", None) ) # logging.info(f"Previous avg time_message: {previous_avg}") if previous_avg is not None: - avg_time_message = (messages_time_message_normalized + previous_avg) / 2 + avg_time_message = messages_time_message_normalized * 0.8 + previous_avg * 0.2 # logging.info(f"Avg time_message in if: {avg_time_message}") else: avg_time_message = messages_time_message_normalized @@ -819,7 +851,7 @@ def manage_model_arrival_latency( round_num, addr, nei, latency, scenario, model_arrival_latency_data, current_round ): """ - Manage the model_arrival_latency latency metric with persistent storage of mean latency. + Manage the model_arrival_latency metric with persistent storage of mean latency. Args: round_num (int): The round number. @@ -828,6 +860,7 @@ def manage_model_arrival_latency( latency (float): Latency value for the current model_arrival_latency. scenario (str): The scenario name for logging and metric storage. model_arrival_latency_data (dict): model_arrival_latency-related data. + current_round (int): The current round of the program. Returns: float: Normalized model_arrival_latency latency value between 0 and 1. @@ -843,127 +876,50 @@ def manage_model_arrival_latency( "score": 0.0, } - # logging.info(f"Reputation.model_arrival_latency_history: {Reputation.model_arrival_latency_history}") + prev_mean_latency = 0 + prev_percentil_25 = 0 + prev_percentil_75 = 0 + difference = 0 if current_round >= 5: - if current_round > 5: - if ( - current_round - 1 in Reputation.model_arrival_latency_history - and current_key in Reputation.model_arrival_latency_history[current_round - 1] - ): - prev_mean_latency = Reputation.model_arrival_latency_history[current_round - 1][ - current_key - ].get("mean_latency", None) - prev_stdev_latency = Reputation.model_arrival_latency_history[current_round - 1][ - current_key - ].get("stdev_latency", None) - percentil_25 = Reputation.model_arrival_latency_history[current_round - 1][current_key].get( - "percentil_25", None - ) - percentil_75 = Reputation.model_arrival_latency_history[current_round - 1][current_key].get( - "percentil_75", None - ) - - # logging.info(f"Round {current_round} | Node {nei} | Round {current_round - 1} | Previous Mean Latency: {prev_mean_latency} | Previous Stdev Latency: {prev_stdev_latency}") - # logging.info(f"Round {current_round} | Node {nei} | Round {current_round - 1} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}") - - if prev_mean_latency is None or prev_stdev_latency is None: - for r in range(current_round - 2, 5 - 1, -1): - if ( - r in Reputation.model_arrival_latency_history - and current_key in Reputation.model_arrival_latency_history[r] - ): - prev_mean_latency = Reputation.model_arrival_latency_history[r][current_key].get( - "mean_latency", None - ) - prev_stdev_latency = Reputation.model_arrival_latency_history[r][current_key].get( - "stdev_latency", None - ) - percentil_25 = Reputation.model_arrival_latency_history[r][current_key].get( - "percentil_25", None - ) - percentil_75 = Reputation.model_arrival_latency_history[r][current_key].get( - "percentil_75", None - ) - - logging.info( - f"Round {current_round} | Node {nei} | Round {r} | Previous Mean Latency: {prev_mean_latency} | Previous Stdev Latency: {prev_stdev_latency}" - ) - logging.info( - f"Round {current_round} | Node {nei} | Round {r} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}" - ) - - if prev_mean_latency is not None and prev_stdev_latency is not None: - break - else: - logging.info( - f"Round {current_round} | Node {nei} | Round {r} | No previous data found." - ) - - if prev_mean_latency is None or prev_stdev_latency is None: - prev_mean_latency = 0 - prev_stdev_latency = 1 - percentil_25 = 0 - percentil_75 = 0 - # logging.info(f"Round {current_round} | Node {nei} | No previous latency data found, initializing defaults.") - else: - all_latencies = [] - for r in range(current_round - 1): - if r in Reputation.model_arrival_latency_history: - for key, data in Reputation.model_arrival_latency_history[r].items(): - if "latency" in data and data["latency"] != 0: - all_latencies.append(data["latency"]) + for r in range(current_round - 1, 4, -1): + if r in Reputation.model_arrival_latency_history and current_key in Reputation.model_arrival_latency_history[r]: + prev_mean_latency = Reputation.model_arrival_latency_history[r][current_key].get("mean_latency", 0) + prev_percentil_25 = Reputation.model_arrival_latency_history[r][current_key].get("percentil_25", 0) + prev_percentil_75 = Reputation.model_arrival_latency_history[r][current_key].get("percentil_75", 0) + if prev_mean_latency and prev_percentil_25 and prev_percentil_75: + break - # logging.info(f"Round {current_round} | Node {nei} | All latencies: {all_latencies}") + if current_round == 5: + all_latencies = [ + data["latency"] + for r in range(5) + if r in Reputation.model_arrival_latency_history + for key, data in Reputation.model_arrival_latency_history[r].items() + if "latency" in data and data["latency"] != 0 + ] prev_mean_latency = np.mean(all_latencies) if all_latencies else 0 - prev_stdev_latency = np.std(all_latencies) if all_latencies else 1 - # logging.info( - # f"Round {current_round} | Node {nei} | Initial Mean Latency: {prev_mean_latency} | Initial Stdev Latency: {prev_stdev_latency}" - # ) - - percentil_25 = np.percentile(all_latencies, 25) if all_latencies else 0 - percentil_75 = np.percentile(all_latencies, 75) if all_latencies else 0 - # logging.info( - # f"Round {current_round} | Node {nei} | Percentil 25: {percentil_25} | Percentil 75: {percentil_75}" - # ) + prev_percentil_25 = np.percentile(all_latencies, 25) if all_latencies else 0 + prev_percentil_75 = np.percentile(all_latencies, 75) if all_latencies else 0 k = 0.15 - prev_mean_latency = prev_mean_latency + k * (percentil_75 - percentil_25) - logging.info(f"Round {current_round} | Node {nei} | Mean latency with k: {prev_mean_latency}") - logging.info(f"Round {current_round} | Node {nei} | Latency: {latency}") + prev_mean_latency += k * (prev_percentil_75 - prev_percentil_25) if latency == 0.0: latency = 0.5 difference = latency - prev_mean_latency - logging.info(f"Round {current_round} | Node {nei} | Difference: {difference}") - - if latency <= prev_mean_latency: - score = 1 + if latency <= prev_mean_latency or abs(difference) <= prev_mean_latency: + score = 1.0 else: - if abs(difference) <= prev_mean_latency: - score = 1 - else: - score = 1 / (1 + np.exp(abs(difference) / prev_mean_latency)) - logging.info(f"Round {current_round} | Node {nei} | Score: {score}") - if round_num < current_round: - round_diff = current_round - round_num - penalty_factor = round_diff * 0.1 - penalty = penalty_factor * (1 - score) - score -= penalty * score - # logging.info(f"Round {current_round} | Node {nei} | Score penalization round: {score}") - - n = current_round if current_round else 1 - # logging.info(f"Round {current_round} | Node {nei} | N: {n}") - - update_mean = (prev_mean_latency * n + latency) / (n + 1) - update_stdev = np.sqrt( - ((prev_stdev_latency**2) + (latency - prev_mean_latency) * (latency - update_mean)) / (n + 1) - ) - # logging.info( - # f"Round {current_round} | Node {nei} | Mean Latency: {update_mean} | Stdev Latency: {update_stdev}" - # ) + score = 1 / (1 + np.exp(abs(difference) / prev_mean_latency)) + + if round_num < current_round: + round_diff = current_round - round_num + penalty_factor = round_diff * 0.1 + penalty = penalty_factor * (1 - score) + score -= penalty * score accumulated_latencies = [ data["latency"] @@ -972,38 +928,27 @@ def manage_model_arrival_latency( for key, data in Reputation.model_arrival_latency_history[r].items() if "latency" in data and data["latency"] != 0 ] - # logging.info(f"Round {current_round} | Node {nei} | Accumulated latencies: {accumulated_latencies}") - update_percentil_25 = np.percentile(accumulated_latencies, 25) if accumulated_latencies else 0 - update_percentil_75 = np.percentile(accumulated_latencies, 75) if accumulated_latencies else 0 - # logging.info( - # f"Round {current_round} | Node {nei} | Percentil 25: {update_percentil_25} | Percentil 75: {update_percentil_75}" - # ) - - Reputation.model_arrival_latency_history[current_round][current_key]["mean_latency"] = update_mean - Reputation.model_arrival_latency_history[current_round][current_key]["stdev_latency"] = update_stdev - Reputation.model_arrival_latency_history[current_round][current_key]["percentil_25"] = ( - update_percentil_25 - ) - Reputation.model_arrival_latency_history[current_round][current_key]["percentil_75"] = ( - update_percentil_75 - ) - # Reputation.model_arrival_latency_history[current_round][current_key]["score"] = score + updated_percentil_25 = np.percentile(accumulated_latencies, 25) if accumulated_latencies else 0 + updated_percentil_75 = np.percentile(accumulated_latencies, 75) if accumulated_latencies else 0 + Reputation.model_arrival_latency_history[current_round][current_key].update({ + "mean_latency": prev_mean_latency, + "percentil_25": updated_percentil_25, + "percentil_75": updated_percentil_75, + "score": score, + }) else: - # For rounds < 5, no scoring or updates score = 0.0 - # Store the metrics data = { "addr": addr, "nei": nei, "round": current_round, "latency": latency, "mean_latency": prev_mean_latency if current_round >= 5 else None, - "stdev_latency": prev_stdev_latency if current_round >= 5 else None, - "percentil_25": percentil_25 if current_round >= 5 else None, - "percentil_75": percentil_75 if current_round >= 5 else None, + "percentil_25": prev_percentil_25 if current_round >= 5 else None, + "percentil_75": prev_percentil_75 if current_round >= 5 else None, "difference": difference if current_round >= 5 else None, "score": score, } @@ -1012,7 +957,7 @@ def manage_model_arrival_latency( return score except Exception as e: - logging.exception(f"Error managing model_arrival_latency latency: {e}") + logging.exception(f"Error managing model_arrival_latency: {e}") return 0.0 @staticmethod @@ -1035,7 +980,7 @@ def manage_metric_time_message(messages_time_message, addr, nei, current_round, return 0.0, 0 previous_round = current_round - 1 - # logging.info(f"Round {current_round}. Previous round: {previous_round}") + logging.info(f"Round {current_round}. Previous round: {previous_round}") current_addr_nei = (addr, nei) relevant_messages = [ @@ -1044,16 +989,19 @@ def manage_metric_time_message(messages_time_message, addr, nei, current_round, if msg["key"] == current_addr_nei and msg["round"] == previous_round ] messages_count = len(relevant_messages) if relevant_messages else 0 - # logging.info(f"Round {current_round}. Messages count: {messages_count}") + logging.info(f"Round {current_round}. Messages count: {messages_count}") + rounds_to_consider = [] if previous_round >= 4: rounds_to_consider = [previous_round - 4, previous_round - 3, previous_round - 2, previous_round - 1] elif previous_round == 3: - rounds_to_consider = [0, 1, 2] + rounds_to_consider = [0, 1, 2, 3] elif previous_round == 2: + rounds_to_consider = [0, 1, 2] + elif previous_round == 1: rounds_to_consider = [0, 1] - else: - rounds_to_consider = list(range(current_round + 1)) + elif previous_round == 0: + rounds_to_consider = [0] # logging.info(f"Round {current_round}. Rounds to consider: {rounds_to_consider}") previous_counts = [ @@ -1067,37 +1015,24 @@ def manage_metric_time_message(messages_time_message, addr, nei, current_round, np.percentile(previous_counts, 25) if previous_counts else 0 ) Reputation.previous_percentile_85_time_message[current_addr_nei] = ( - np.percentile(previous_counts, 85) * 1.15 if previous_counts else 0 + np.percentile(previous_counts, 85) * 1.20 if previous_counts else 0 ) - normalized_messages = 0.0 + normalized_messages = 1 relative_position = 0 - if previous_round >= 4: + if previous_round > 4: percentile_25 = Reputation.previous_percentile_25_time_message.get(current_addr_nei, 0) - logging.info(f"Round {current_round}. percentile_25: {percentile_25}") + # logging.info(f"Round {current_round}. percentile_25: {percentile_25}") percentile_85 = Reputation.previous_percentile_85_time_message.get(current_addr_nei, 0) - logging.info(f"Round {current_round}. percentile_85: {percentile_85}") - - logging.info(f"Round {current_round}. Messages count: {messages_count}") - if percentile_85 - percentile_25 == 0: - logging.info(f"Round {current_round}. Percentiles difference is 0") - normalized_messages = 1 - elif percentile_25 <= messages_count <= percentile_85: - logging.info(f"Round {current_round}. Messages count between percentiles") - normalized_messages = 1 - elif messages_count < percentile_25: - logging.info(f"Round {current_round}. Messages count below percentile_25") - relative_position = (messages_count - percentile_25) / (percentile_85 - percentile_25) - logging.info(f"Round {current_round}. Relative position: {relative_position}") - normalized_messages = 1 / (1 + np.exp(-relative_position)) - else: - logging.info(f"Round {current_round}. Messages count out of percentiles") - relative_position = (messages_count - percentile_25) / (percentile_85 - percentile_25) - logging.info(f"Round {current_round}. Relative position: {relative_position}") - normalized_messages = 1 - 1 / (1 + np.exp(-(relative_position - 1))) + # logging.info(f"Round {current_round}. percentile_85: {percentile_85}") + + # logging.info(f"Round {current_round}. Messages count: {messages_count}") + if messages_count > percentile_85: + relative_position = (messages_count - percentile_85) / (percentile_85 - percentile_25) + # logging.info(f"Round {current_round}. Relative position: {relative_position}") + normalized_messages = np.exp(-relative_position) - logging.info(f"Round {current_round}. Normalized messages sin max: {normalized_messages}") normalized_messages = max(0.01, normalized_messages) # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") @@ -1279,10 +1214,10 @@ def read_similarity_file(file_path, nei): if source_ip == nei: try: # Design weights for each similarity metric - weight_cosine = 0.4 - weight_euclidean = 0.2 - weight_manhattan = 0.2 - weight_pearson = 0.2 + weight_cosine = 0.25 + weight_euclidean = 0.25 + weight_manhattan = 0.25 + weight_pearson = 0.25 # Retrieve and normalize metrics if necessary cosine = float(row["cosine"]) @@ -1336,6 +1271,10 @@ def metrics(scenario, data, addr, nei, type, update_field=None): "model_arrival_latency", "reputation_without_feedback", "reputation_with_feedback", + "average_model_arrival_latency", + "average_similarity", + "average_fraction", + "average_time_messages", ] if os.path.exists(csv_path): diff --git a/nebula/frontend/config/participant.json.example b/nebula/frontend/config/participant.json.example index 402cbb0b9..3759254dc 100755 --- a/nebula/frontend/config/participant.json.example +++ b/nebula/frontend/config/participant.json.example @@ -88,7 +88,7 @@ }, "aggregator_args": { "algorithm": "FedAvg", - "aggregation_timeout": 25 + "aggregation_timeout": 15 }, "defense_args": { "with_reputation": false, From 5503d3b903c427615ca976cd88051a06521af81a Mon Sep 17 00:00:00 2001 From: Isaac Marroqui Date: Tue, 28 Jan 2025 17:33:39 +0100 Subject: [PATCH 17/43] small adjustments after including the attacks of main branch --- nebula/core/reputation/Reputation.py | 57 +++++--- .../frontend/config/participant.json.example | 2 +- nebula/frontend/templates/deployment.html | 130 ++++-------------- 3 files changed, 66 insertions(+), 123 deletions(-) diff --git a/nebula/core/reputation/Reputation.py b/nebula/core/reputation/Reputation.py index 4ff131554..6b812deb5 100644 --- a/nebula/core/reputation/Reputation.py +++ b/nebula/core/reputation/Reputation.py @@ -939,7 +939,7 @@ def manage_model_arrival_latency( "score": score, }) else: - score = 0.0 + score = 0 data = { "addr": addr, @@ -958,7 +958,7 @@ def manage_model_arrival_latency( except Exception as e: logging.exception(f"Error managing model_arrival_latency: {e}") - return 0.0 + return 0 @staticmethod def manage_metric_time_message(messages_time_message, addr, nei, current_round, scenario): @@ -1018,7 +1018,7 @@ def manage_metric_time_message(messages_time_message, addr, nei, current_round, np.percentile(previous_counts, 85) * 1.20 if previous_counts else 0 ) - normalized_messages = 1 + normalized_messages = 1.0 relative_position = 0 if previous_round > 4: @@ -1147,25 +1147,42 @@ def save_reputation_history_in_memory(addr, nei, reputation, current_round): Reputation.reputation_history[key][current_round] = reputation - total_reputation = 0 - total_weights = 0 + # total_reputation = 0 + # total_weights = 0 avg_reputation = 0 rounds = sorted(Reputation.reputation_history[key].keys(), reverse=True)[:2] - for i, n_round in enumerate(rounds, start=1): - rep = Reputation.reputation_history[key][n_round] - decay_factor = Reputation.calculate_decay_rate(rep) ** i - total_reputation += rep * decay_factor - total_weights += decay_factor - # logging.info( - # f"Round: {n_round}, Reputation: {rep}, Decay: {decay_factor}, Total reputation: {total_reputation}" - # ) - - avg_reputation = total_reputation / total_weights - if total_weights > 0: - return avg_reputation + if len(rounds) >= 2: + current_round = rounds[0] + previous_round = rounds[1] + + current_rep = Reputation.reputation_history[key][current_round] + previous_rep = Reputation.reputation_history[key][previous_round] + logging.info(f"Current reputation: {current_rep}, Previous reputation: {previous_rep}") + + avg_reputation = (current_rep * 0.8) + (previous_rep * 0.2) + logging.info(f"Reputation ponderated: {avg_reputation}") else: - return -1 + logging.info(f"Reputation history: {Reputation.reputation_history}") + avg_reputation = Reputation.reputation_history[key][current_round] + logging.info(f"Current reputation: {avg_reputation}") + + return avg_reputation + + # for i, n_round in enumerate(rounds, start=1): + # rep = Reputation.reputation_history[key][n_round] + # decay_factor = Reputation.calculate_decay_rate(rep) ** i + # total_reputation += rep * decay_factor + # total_weights += decay_factor + # logging.info( + # f"Round: {n_round}, Reputation: {rep}, Decay: {decay_factor}, Total reputation: {total_reputation}" + # ) + + # avg_reputation = total_reputation / total_weights + # if total_weights > 0: + # return avg_reputation + # else: + # return -1 except Exception: logging.exception("Error saving reputation history") @@ -1185,10 +1202,12 @@ def calculate_decay_rate(reputation): if reputation > 0.8: return 0.9 # Muy bajo decaimiento + elif reputation > 0.7: + return 0.8 # Bajo decaimiento elif reputation > 0.6: return 0.6 # Bajo decaimiento elif reputation > 0.4: - return 0.3 # Alto decaimiento + return 0.2 # Alto decaimiento else: return 0.1 # Muy alto decaimiento diff --git a/nebula/frontend/config/participant.json.example b/nebula/frontend/config/participant.json.example index 4348dbbaf..f61dd1f61 100755 --- a/nebula/frontend/config/participant.json.example +++ b/nebula/frontend/config/participant.json.example @@ -88,7 +88,7 @@ }, "aggregator_args": { "algorithm": "FedAvg", - "aggregation_timeout": 15 + "aggregation_timeout": 25 }, "defense_args": { "with_reputation": false, diff --git a/nebula/frontend/templates/deployment.html b/nebula/frontend/templates/deployment.html index 4f6f82ec8..7c3a3cf57 100755 --- a/nebula/frontend/templates/deployment.html +++ b/nebula/frontend/templates/deployment.html @@ -196,10 +196,8 @@
Scenario title
Scenario description
- - +
@@ -247,7 +245,7 @@
Federation approach
Number of rounds
-
@@ -459,28 +457,6 @@
Reporting
Individual participants
- -
-
Advanced Deployment - - -
-
Accelerator definition
-
- -
-
Advanced Topology @@ -1126,7 +1102,18 @@
Schema of deployment
data["poisoned_node_percent"] = document.getElementById("poisoned-node-percent").value, data["poisoned_sample_percent"] = document.getElementById("poisoned-sample-percent").value, data["poisoned_noise_percent"] = document.getElementById("poisoned-noise-percent").value, - data["attack_params"] = getAttackParams(data["attacks"]) + data["attack_params"] = {} + data["attack_params"].poisoned_percent = document.getElementById("poisoned-sample-percent").value; + data["attack_params"].poisoned_ratio = document.getElementById("poisoned-noise-percent").value; + data["attack_params"].noise_type = document.getElementById("noise_type").value; + data["attack_params"].targeted = document.getElementById("targeted").checked; + data["attack_params"].target_label = document.getElementById("target_label").value; + data["attack_params"].target_changed_label = document.getElementById("target_changed_label").value; + data["attack_params"].strength = document.getElementById("strength").value; + data["attack_params"].layer_idx = document.getElementById("layer_idx").value; + data["attack_params"].delay = document.getElementById("delay").value; + data["attack_params"].round_start_attack = document.getElementById("start-attack").value + data["attack_params"].round_stop_attack = document.getElementById("stop-attack").value // Step 13 data["with_reputation"] = document.getElementById("with-reputation-btn").checked ? true : false data["is_dynamic_topology"] = document.getElementById("dynamic-topology-btn").checked ? true : false @@ -1230,7 +1217,17 @@
Schema of deployment
document.getElementById("poisoned-node-percent").value = data["poisoned_node_percent"] document.getElementById("poisoned-sample-percent").value = data["poisoned_sample_percent"] document.getElementById("poisoned-noise-percent").value = data["poisoned_noise_percent"] - setAttackParams(data["attacks"], data.attack_params) + document.getElementById("poisoned-sample-percent").value = data.attack_params.poisoned_percent; + document.getElementById("poisoned-noise-percent").value = data.attack_params.poisoned_ratio; + document.getElementById("noise_type").value = data.attack_params.noise_type; + document.getElementById("targeted").checked = data.attack_params.targeted; + document.getElementById("target_label").value = data.attack_params.target_label; + document.getElementById("target_changed_label").value = data.attack_params.target_changed_label; + document.getElementById("strength").value = data.attack_params.strength; + document.getElementById("layer_idx").value = data.attack_params.layer_idx; + document.getElementById("delay").value = data.attack_params.delay; + document.getElementById("start-attack").value = data.attack_params.round_start_attack + document.getElementById("stop-attack").value = data.attack_params.round_stop_attack if (data["poisoned_node_percent"] == 0) { @@ -1277,82 +1274,9 @@
Schema of deployment
} } } catch (error) { - console.error(error); + console.log(error); } } - - function getAttackParams(attack) { - let attack_params = { - noise_type: null, - targeted: null, - target_label: null, - target_changed_label: null, - strength: null, - layer_idx: null, - delay: null, - }; - - if (attack === "GLLNeuronInversionAttack") { - // no parameters needed - } else if (attack === "NoiseInjectionAttack") { - attack_params.strength = document.getElementById("strength").value || null; - } else if (attack === "SwappingWeightsAttack") { - attack_params.layer_idx = document.getElementById("layer_idx").value || null; - } else if (attack === "DelayerAttack") { - attack_params.delay = document.getElementById("delay").value || null; - } else if (["Label Flipping", "Sample Poisoning"].includes(attack)) { - attack_params.poisoned_percent = document.getElementById("poisoned-sample-percent").value || null; - attack_params.poisoned_ratio = document.getElementById("poisoned-noise-percent").value || null; - attack_params.noise_type = document.getElementById("noise_type").value || null; - attack_params.targeted = document.getElementById("targeted").checked ? true : false; - attack_params.target_label = document.getElementById("target_label").value || null; - attack_params.target_changed_label = document.getElementById("target_changed_label").value || null; - } else if (attack === "Model Poisoning") { - attack_params.poisoned_ratio = document.getElementById("poisoned-noise-percent").value || null; - attack_params.noise_type = document.getElementById("noise_type").value || null; - } - attack_params.round_start_attack = document.getElementById("start-attack").value - attack_params.round_stop_attack = document.getElementById("stop-attack").value - - return attack_params; - } - - function setAttackParams(attack, attack_params) { - const defaultParams = { - noise_type: "Salt", - targeted: false, - target_label: 0, - target_changed_label: 1, - strength: 1000, - layer_idx: 0, - delay: 0, - poisoned_percent: 0, - poisoned_ratio: 0, - }; - - if (attack === "NoiseInjectionAttack") { - document.getElementById("strength").value = attack_params.strength ?? defaultParams.strength; - } else if (attack === "SwappingWeightsAttack") { - document.getElementById("layer_idx").value = attack_params.layer_idx ?? defaultParams.layer_idx; - } else if (attack === "DelayerAttack") { - document.getElementById("delay").value = attack_params.delay ?? defaultParams.delay; - } else if (["Label Flipping", "Sample Poisoning"].includes(attack)) { - document.getElementById("poisoned-sample-percent").value = attack_params.poisoned_percent ?? defaultParams.poisoned_percent; - document.getElementById("poisoned-noise-percent").value = attack_params.poisoned_ratio ?? defaultParams.poisoned_ratio; - document.getElementById("noise_type").value = attack_params.noise_type ?? defaultParams.noise_type; - document.getElementById("targeted").checked = attack_params.targeted ?? defaultParams.targeted; - document.getElementById("targeted").dispatchEvent(new Event('change')); - document.getElementById("target_label").value = attack_params.target_label ?? defaultParams.target_label; - document.getElementById("target_changed_label").value = attack_params.target_changed_label ?? defaultParams.target_changed_label; - } else if (attack === "Model Poisoning") { - document.getElementById("poisoned-noise-percent").value = attack_params.poisoned_ratio ?? defaultParams.poisoned_ratio; - document.getElementById("noise_type").value = attack_params.noise_type ?? defaultParams.noise_type; - } - document.getElementById("start-attack").value = attack_params.round_start_attack - document.getElementById("stop-attack").value = attack_params.round_stop_attack - - return attack_params; - } From 25d79b1e82bbc2aba50adbcc4e7a502bc9c119bb Mon Sep 17 00:00:00 2001 From: isaac Date: Fri, 28 Feb 2025 18:01:42 +0100 Subject: [PATCH 20/43] add reputation message to new message optimization --- nebula/core/engine.py | 111 +++++++++------- nebula/core/network/actions.py | 4 + nebula/core/network/communications.py | 89 ++----------- nebula/core/network/messages.py | 11 +- nebula/core/pb/nebula.proto | 15 +-- nebula/core/pb/nebula/core/pb/nebula_pb2.py | 60 --------- nebula/core/pb/nebula_pb2.py | 140 +++++++++++++++----- 7 files changed, 192 insertions(+), 238 deletions(-) delete mode 100644 nebula/core/pb/nebula/core/pb/nebula_pb2.py mode change 100755 => 100644 nebula/core/pb/nebula_pb2.py diff --git a/nebula/core/engine.py b/nebula/core/engine.py index bb34a4481..0df259980 100755 --- a/nebula/core/engine.py +++ b/nebula/core/engine.py @@ -149,6 +149,7 @@ def __init__( self.reputation_instance = Reputation(self) self.reputation = {} self.reputation_with_feedback = {} + self.reputation_with_all_feedback = {} self.rejected_nodes = set() self.change_weight_nodes = set() @@ -225,9 +226,9 @@ def get_round_lock(self): def get_reputation(self): return self.reputation - @event_handler(nebula_pb2.DiscoveryMessage, nebula_pb2.DiscoveryMessage.Action.DISCOVER) def register_message_events_callbacks(self): me_dict = self.cm.get_messages_events() + logging.info(f"Registering message events: {me_dict}") message_events = [ (message_name, message_action) for (message_name, message_actions) in me_dict.items() @@ -301,18 +302,26 @@ async def _federation_federation_start_callback(self, source, message): logging.info(f"📝 handle_federation_message | Trigger | Received start federation message from {source}") await self.create_trainer_module() - # @event_handler(nebula_pb2.FederationMessage, nebula_pb2.FederationMessage.Action.REPUTATION) - # async def _reputation_callback(self, source, message): - # malicious_nodes = message.arguments # List of malicious nodes - # if self.with_reputation: - # if len(malicious_nodes) > 0 and not self._is_malicious: - # if self.is_dynamic_topology: - # await self._disrupt_connection_using_reputation(malicious_nodes) - # if self.is_dynamic_aggregation and self.aggregator != self.target_aggregation: - # await self._dynamic_aggregator( - # self.aggregator.get_nodes_pending_models_to_aggregate(), - # malicious_nodes, - # ) + async def _reputation_share_callback(self, source, message): + try: + logging.info(f"handle_reputation_message | Trigger | Received reputation message from {source} | Node: {message.node_id} | Score: {message.score} | Round: {message.round}") + self.cm.store_receive_timestamp(source, "reputation", message.round) + + current_node = self.addr + nei = message.node_id + + # Manage reputation + if current_node != nei: + key = (current_node, nei, message.round) + + if key not in self.reputation_with_all_feedback: + self.reputation_with_all_feedback[key] = [] + + self.reputation_with_all_feedback[key].append(message.score) + logging.info(f"Reputation with all feedback: {self.reputation_with_all_feedback}") + + except Exception as e: + logging.exception(f"Error handling reputation message: {e}") async def _federation_federation_models_included_callback(self, source, message): logging.info(f"📝 handle_federation_message | Trigger | Received aggregation finished message from {source}") @@ -511,10 +520,10 @@ async def calculate_reputation(self): ) ) - logging.info(f"metric_messages_time at round {self.round}: {metric_messages_time}") - logging.info(f"metric_similarity at round {self.round}: {metric_similarity}") - logging.info(f"metric_fraction at round {self.round}: {metric_fraction}") - logging.info(f"metric_model_arrival_latency at round {self.round}: {metric_model_arrival_latency}") + # logging.info(f"metric_messages_time at round {self.round}: {metric_messages_time}") + # logging.info(f"metric_similarity at round {self.round}: {metric_similarity}") + # logging.info(f"metric_fraction at round {self.round}: {metric_fraction}") + # logging.info(f"metric_model_arrival_latency at round {self.round}: {metric_model_arrival_latency}") history_data = self.reputation_instance.history_data self.reputation_instance.calculate_weighted_values( @@ -565,12 +574,12 @@ async def calculate_reputation(self): metric_model_arrival_latency_history = entry["metric_value"] break - logging.info(f"metric_messages_time_history at round {self.round}: {metric_messages_time_history}") - logging.info(f"metric_similarity_history at round {self.round}: {metric_similarity_history}") - logging.info(f"metric_fraction_history at round {self.round}: {metric_fraction_history}") - logging.info( - f"metric_model_arrival_latency_history at round {self.round}: {metric_model_arrival_latency_history}" - ) + # logging.info(f"metric_messages_time_history at round {self.round}: {metric_messages_time_history}") + # logging.info(f"metric_similarity_history at round {self.round}: {metric_similarity_history}") + # logging.info(f"metric_fraction_history at round {self.round}: {metric_fraction_history}") + # logging.info( + # f"metric_model_arrival_latency_history at round {self.round}: {metric_model_arrival_latency_history}" + # ) logging.info(f"average_weights at round {self.round}: {average_weights}") @@ -691,34 +700,34 @@ async def calculate_reputation(self): if data["reputation"] is not None: neighbors_to_send = [neighbor for neighbor in neighbors if neighbor != nei] - message_data = self.cm.mm.generate_reputation_message( - node_id=nei, - score=data["reputation"], - round=data["round"], - ) - - metrics_data = { - "addr": self.addr.split(":")[0].strip(), - "nei": nei.split(":")[0].strip(), - "round": self.round, - "reputation_with_feedback": data["reputation"], - } - - self.reputation_instance.metrics( - self.experiment_name, - metrics_data, - self.addr.split(":")[0].strip(), - nei.split(":")[0].strip(), - "reputation", - update_field="reputation_with_feedback", - ) + # message_data = self.cm.mm.generate_reputation_message( + # node_id=nei, + # score=data["reputation"], + # round=data["round"], + # ) + + # metrics_data = { + # "addr": self.addr.split(":")[0].strip(), + # "nei": nei.split(":")[0].strip(), + # "round": self.round, + # "reputation_with_feedback": data["reputation"], + # } + + # self.reputation_instance.metrics( + # self.experiment_name, + # metrics_data, + # self.addr.split(":")[0].strip(), + # nei.split(":")[0].strip(), + # "reputation", + # update_field="reputation_with_feedback", + # ) for neighbor in neighbors_to_send: - logging.info( - f"Sending reputation to node {nei} from node {neighbor} with reputation {data['reputation']}" - ) - # self.cm.store_send_timestamp(nei, current_round, "reputation") - await self.cm.send_message_to_neighbors(message_data, [neighbor]) + message = self.cm.create_message("reputation", "share", node_id=nei, score=data["reputation"], round=self.round) + await self.cm.send_message(neighbor, message) + logging.info(f"Sending reputation to node {nei} from node {neighbor} with reputation {data['reputation']}") + #await self.cm.send_message_to_neighbors(message_data, [neighbor]) + else: logging.info(f"Reputation already sent to node {nei}") @@ -727,9 +736,9 @@ async def include_feedback_in_reputation(self): weight_current_reputation = 0.9 weight_feedback = 0.1 - if self._cm.reputation_with_all_feedback is not None: + if self.reputation_with_all_feedback is not None: current_round = self.get_round() - for (current_node, node_ip, round_num), scores in self._cm.reputation_with_all_feedback.items(): + for (current_node, node_ip, round_num), scores in self.reputation_with_all_feedback.items(): if node_ip in self.reputation and "last_feedback_round" in self.reputation[node_ip]: if self.reputation[node_ip]["last_feedback_round"] >= round_num: continue diff --git a/nebula/core/network/actions.py b/nebula/core/network/actions.py index 356cd5bc7..af3bc2812 100644 --- a/nebula/core/network/actions.py +++ b/nebula/core/network/actions.py @@ -28,12 +28,16 @@ class ControlAction(Enum): RECOVERY = nebula_pb2.ControlMessage.Action.RECOVERY WEAK_LINK = nebula_pb2.ControlMessage.Action.WEAK_LINK +class ReputationAction(Enum): + SHARE = nebula_pb2.ReputationMessage.Action.SHARE + ACTION_CLASSES = { "connection": ConnectionAction, "federation": FederationAction, "discovery": DiscoveryAction, "control": ControlAction, + "reputation": ReputationAction, } diff --git a/nebula/core/network/communications.py b/nebula/core/network/communications.py index 1c2e8ce48..7e75ec47e 100755 --- a/nebula/core/network/communications.py +++ b/nebula/core/network/communications.py @@ -85,7 +85,6 @@ def __init__(self, engine: "Engine"): # Reputation self.reputation_instance = Reputation(self.engine) self._model_arrival_latency_data = self.reputation_instance.model_arrival_latency_data - self.reputation_with_all_feedback = {} self.message_timestamps = {} self.fraction_of_params_changed = {} @@ -140,6 +139,7 @@ def get_messages_events(self): async def handle_incoming_message(self, data, addr_from): await self.mm.process_message(data, addr_from) + async def forward_message(self, data, addr_from): await self.forwarder.forward(data, addr_from=addr_from) @@ -351,40 +351,14 @@ async def handle_connection_message(self, source, message): except Exception as e: logging.exception(f"🔗 handle_connection_message | Error while processing: {message.action} | {e}") - async def handle_reputation_message(self, source, message): - try: - logging.info( - f"handle_reputation_message | Reputation message received from {source} | Node: {message.node_id} | Score: {message.score} | Round: {message.round}" - ) - - self.store_receive_timestamp(source, "reputation", message.round) - - current_node = self.addr - nei = message.node_id - - # Manage reputation - if current_node != nei: - key = (current_node, nei, message.round) - - if key not in self.reputation_with_all_feedback: - self.reputation_with_all_feedback[key] = [] - - self.reputation_with_all_feedback[key].append(message.score) - # logging.info( - # f"handle_reputation_message | Reputation with all feedback: {self.reputation_with_all_feedback}" - # ) - - except Exception as e: - logging.exception(f"Error handling reputation message: {e}") - - async def handle_flooding_attack_message(self, source, message): - try: - logging.info( - f"🔥 handle_flooding_attack_message | Received flooding attack message from {source} | Attacker: {message.attacker_id} | Frequency: {message.frequency} | Duration: {message.duration} | Target node: {message.target_node}" - ) - self.store_receive_timestamp(source, "flooding_attack", self.engine.get_round()) - except Exception as e: - logging.exception(f"🔥 handle_flooding_attack_message | Error while processing: {e}") + # async def handle_flooding_attack_message(self, source, message): + # try: + # logging.info( + # f"🔥 handle_flooding_attack_message | Received flooding attack message from {source} | Attacker: {message.attacker_id} | Frequency: {message.frequency} | Duration: {message.duration} | Target node: {message.target_node}" + # ) + # self.store_receive_timestamp(source, "flooding_attack", self.engine.get_round()) + # except Exception as e: + # logging.exception(f"🔥 handle_flooding_attack_message | Error while processing: {e}") def fraction_of_parameters_changed(self, source, parameters_local, parameters_received, current_round): # logging.info(f"🤖 fraction_of_parameters_changed | Managing parameters of models") @@ -774,16 +748,6 @@ async def send_message(self, dest_addr, message): logging.exception(f"❗️ Cannot send message {message} to {dest_addr}. Error: {e!s}") await self.disconnect(dest_addr, mutual_disconnection=False) - # def store_send_timestamp(self, dest_addr, round_number, type_message): - # send_timestamp = datetime.now().strftime("%H:%M:%S") - # self.message_timestamps[(self.addr, dest_addr, type_message)] = { - # "send": send_timestamp, - # "receive": None, - # "latency": None, - # "round": round_number, - # "type": type_message, - # } - def store_receive_timestamp(self, source, type_message, round=None): current_time = time.time() current_round = self.get_round() @@ -801,41 +765,6 @@ def store_receive_timestamp(self, source, type_message, round=None): current_round=current_round, ) - # receive_timestamp = datetime.now().strftime("%H:%M:%S") - # if (self.addr, source, type_message) in self.message_timestamps: - # self.message_timestamps[(self.addr, source, type_message)]["receive"] = receive_timestamp - - # def calculate_latency(self, source, type_message): - # if (self.addr, source, type_message) in self.message_timestamps: - # send_time = self.message_timestamps[(self.addr, source, type_message)]["send"] - # receive_time = self.message_timestamps[(self.addr, source, type_message)]["receive"] - # round_number = self.message_timestamps[(self.addr, source, type_message)]["round"] - # current_round = self.get_round() - - # if send_time and receive_time and type_message == "model": - # send_time = datetime.strptime(send_time, "%H:%M:%S") - # receive_time = datetime.strptime(receive_time, "%H:%M:%S") - - # latency = (receive_time - send_time).total_seconds() - # logging.info( - # f"🕒 Latency from {source} with type message {type_message} in round {round_number}: {latency}" - # ) - - # self.message_timestamps[(self.addr, source, type_message)]["latency"] = latency - # save_data( - # self.config.participant["scenario_args"]["name"], - # "communication", - # source, - # self.addr, - # num_round=round_number, - # time=latency, - # type_message=type_message, - # current_round=current_round, - # ) - - # return latency - # return None - async def send_model(self, dest_addr, round, serialized_model, weight=1): async with self.semaphore_send_model: try: diff --git a/nebula/core/network/messages.py b/nebula/core/network/messages.py index 0ceca6add..d0ef49d90 100644 --- a/nebula/core/network/messages.py +++ b/nebula/core/network/messages.py @@ -48,14 +48,19 @@ def _define_message_templates(self): "weight": 1, }, }, - "reputation": {"parameters": ["reputation"], "defaults": {}}, + "reputation": { + "parameters": ["node_id", "score", "round", "action"], + "defaults": { + "round": None, + }, + } # Add additional message types here } def get_messages_events(self): message_events = {} for message_name in self._message_templates.keys(): - if message_name != "model" and message_name != "reputation": + if message_name != "model": message_events[message_name] = get_actions_names(message_name) return message_events @@ -79,6 +84,8 @@ async def process_message(self, data, addr_from): return logging.info(f"Message type received: {message_type}") + self.cm.store_receive_timestamp(addr_from, message_type, round=self.cm.get_round()) + message_data = getattr(message_wrapper, message_type) # Not required processing messages diff --git a/nebula/core/pb/nebula.proto b/nebula/core/pb/nebula.proto index 4c17b87f7..68120d6a9 100755 --- a/nebula/core/pb/nebula.proto +++ b/nebula/core/pb/nebula.proto @@ -24,7 +24,6 @@ message Wrapper { ConnectionMessage connection_message = 6; ResponseMessage response_message = 7; ReputationMessage reputation_message = 8; - FloodAttackMessage flood_attack_message = 10; } } @@ -84,14 +83,12 @@ message ResponseMessage { } message ReputationMessage { - string node_id = 1; //Id to node thats send the reputation + enum Action { + SHARE = 0; + } + + string node_id = 1; //Id of the node to which the reputation is sent float score = 2; //Score reputation int32 round = 3; //Round to send the reputation -} - -message FloodAttackMessage { - string attacker_id = 1; // ID of the attacker node - int32 frequency = 2; // Frequency of message sending in the attack - int32 duration = 3; // Duration of the attack in seconds - string target_node = 4; // Optional: ID of the target node (if specified) + Action action = 4; // Action type (default: SHARE) } diff --git a/nebula/core/pb/nebula/core/pb/nebula_pb2.py b/nebula/core/pb/nebula/core/pb/nebula_pb2.py deleted file mode 100644 index 6ff3b7eb1..000000000 --- a/nebula/core/pb/nebula/core/pb/nebula_pb2.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: nebula/core/pb/nebula.proto -# Protobuf Python Version: 5.28.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 5, - 28, - 1, - '', - 'nebula/core/pb/nebula.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bnebula/core/pb/nebula.proto\x12\x06nebula\"\xe7\x03\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\t \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x12\x37\n\x12reputation_message\x18\x08 \x01(\x0b\x32\x19.nebula.ReputationMessageH\x00\x12:\n\x14\x66lood_attack_message\x18\n \x01(\x0b\x32\x1a.nebula.FloodAttackMessageH\x00\x42\t\n\x07message\"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02\"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02\"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t\"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04\"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05\"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03\"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05\"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action\"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01\"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t\"B\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\"c\n\x12\x46loodAttackMessage\x12\x13\n\x0b\x61ttacker_id\x18\x01 \x01(\t\x12\x11\n\tfrequency\x18\x02 \x01(\x05\x12\x10\n\x08\x64uration\x18\x03 \x01(\x05\x12\x13\n\x0btarget_node\x18\x04 \x01(\tb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'nebula.core.pb.nebula_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_WRAPPER']._serialized_start=40 - _globals['_WRAPPER']._serialized_end=527 - _globals['_DISCOVERYMESSAGE']._serialized_start=530 - _globals['_DISCOVERYMESSAGE']._serialized_end=688 - _globals['_DISCOVERYMESSAGE_ACTION']._serialized_start=636 - _globals['_DISCOVERYMESSAGE_ACTION']._serialized_end=688 - _globals['_CONTROLMESSAGE']._serialized_start=691 - _globals['_CONTROLMESSAGE']._serialized_end=845 - _globals['_CONTROLMESSAGE_ACTION']._serialized_start=769 - _globals['_CONTROLMESSAGE_ACTION']._serialized_end=845 - _globals['_FEDERATIONMESSAGE']._serialized_start=848 - _globals['_FEDERATIONMESSAGE']._serialized_end=1053 - _globals['_FEDERATIONMESSAGE_ACTION']._serialized_start=953 - _globals['_FEDERATIONMESSAGE_ACTION']._serialized_end=1053 - _globals['_MODELMESSAGE']._serialized_start=1055 - _globals['_MODELMESSAGE']._serialized_end=1120 - _globals['_CONNECTIONMESSAGE']._serialized_start=1122 - _globals['_CONNECTIONMESSAGE']._serialized_end=1230 - _globals['_CONNECTIONMESSAGE_ACTION']._serialized_start=1193 - _globals['_CONNECTIONMESSAGE_ACTION']._serialized_end=1230 - _globals['_RESPONSEMESSAGE']._serialized_start=1232 - _globals['_RESPONSEMESSAGE']._serialized_end=1267 - _globals['_REPUTATIONMESSAGE']._serialized_start=1269 - _globals['_REPUTATIONMESSAGE']._serialized_end=1335 - _globals['_FLOODATTACKMESSAGE']._serialized_start=1337 - _globals['_FLOODATTACKMESSAGE']._serialized_end=1436 -# @@protoc_insertion_point(module_scope) diff --git a/nebula/core/pb/nebula_pb2.py b/nebula/core/pb/nebula_pb2.py old mode 100755 new mode 100644 index 11e9df1b0..e2a96f376 --- a/nebula/core/pb/nebula_pb2.py +++ b/nebula/core/pb/nebula_pb2.py @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE # source: nebula.proto -# Protobuf Python Version: 5.28.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -15,37 +14,106 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cnebula.proto\x12\x06nebula\"\xe7\x03\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\t \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x12\x37\n\x12reputation_message\x18\x08 \x01(\x0b\x32\x19.nebula.ReputationMessageH\x00\x12:\n\x14\x66lood_attack_message\x18\n \x01(\x0b\x32\x1a.nebula.FloodAttackMessageH\x00\x42\t\n\x07message\"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02\"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02\"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t\"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04\"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05\"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03\"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05\"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action\"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01\"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t\"B\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\"c\n\x12\x46loodAttackMessage\x12\x13\n\x0b\x61ttacker_id\x18\x01 \x01(\t\x12\x11\n\tfrequency\x18\x02 \x01(\x05\x12\x10\n\x08\x64uration\x18\x03 \x01(\x05\x12\x13\n\x0btarget_node\x18\x04 \x01(\tb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'nebula_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_WRAPPER']._serialized_start=25 - _globals['_WRAPPER']._serialized_end=512 - _globals['_DISCOVERYMESSAGE']._serialized_start=515 - _globals['_DISCOVERYMESSAGE']._serialized_end=673 - _globals['_DISCOVERYMESSAGE_ACTION']._serialized_start=621 - _globals['_DISCOVERYMESSAGE_ACTION']._serialized_end=673 - _globals['_CONTROLMESSAGE']._serialized_start=676 - _globals['_CONTROLMESSAGE']._serialized_end=830 - _globals['_CONTROLMESSAGE_ACTION']._serialized_start=754 - _globals['_CONTROLMESSAGE_ACTION']._serialized_end=830 - _globals['_FEDERATIONMESSAGE']._serialized_start=833 - _globals['_FEDERATIONMESSAGE']._serialized_end=1038 - _globals['_FEDERATIONMESSAGE_ACTION']._serialized_start=938 - _globals['_FEDERATIONMESSAGE_ACTION']._serialized_end=1038 - _globals['_MODELMESSAGE']._serialized_start=1040 - _globals['_MODELMESSAGE']._serialized_end=1105 - _globals['_CONNECTIONMESSAGE']._serialized_start=1107 - _globals['_CONNECTIONMESSAGE']._serialized_end=1215 - _globals['_CONNECTIONMESSAGE_ACTION']._serialized_start=1178 - _globals['_CONNECTIONMESSAGE_ACTION']._serialized_end=1215 - _globals['_RESPONSEMESSAGE']._serialized_start=1217 - _globals['_RESPONSEMESSAGE']._serialized_end=1252 - _globals['_REPUTATIONMESSAGE']._serialized_start=1254 - _globals['_REPUTATIONMESSAGE']._serialized_end=1320 - _globals['_FLOODATTACKMESSAGE']._serialized_start=1322 - _globals['_FLOODATTACKMESSAGE']._serialized_end=1421 +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cnebula.proto\x12\x06nebula\"\xab\x03\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\t \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x12\x37\n\x12reputation_message\x18\x08 \x01(\x0b\x32\x19.nebula.ReputationMessageH\x00\x42\t\n\x07message\"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02\"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02\"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t\"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04\"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05\"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03\"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05\"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action\"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01\"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t\"\x89\x01\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\x12\x30\n\x06\x61\x63tion\x18\x04 \x01(\x0e\x32 .nebula.ReputationMessage.Action\"\x13\n\x06\x41\x63tion\x12\t\n\x05SHARE\x10\x00\x62\x06proto3') + + + +_WRAPPER = DESCRIPTOR.message_types_by_name['Wrapper'] +_DISCOVERYMESSAGE = DESCRIPTOR.message_types_by_name['DiscoveryMessage'] +_CONTROLMESSAGE = DESCRIPTOR.message_types_by_name['ControlMessage'] +_FEDERATIONMESSAGE = DESCRIPTOR.message_types_by_name['FederationMessage'] +_MODELMESSAGE = DESCRIPTOR.message_types_by_name['ModelMessage'] +_CONNECTIONMESSAGE = DESCRIPTOR.message_types_by_name['ConnectionMessage'] +_RESPONSEMESSAGE = DESCRIPTOR.message_types_by_name['ResponseMessage'] +_REPUTATIONMESSAGE = DESCRIPTOR.message_types_by_name['ReputationMessage'] +_DISCOVERYMESSAGE_ACTION = _DISCOVERYMESSAGE.enum_types_by_name['Action'] +_CONTROLMESSAGE_ACTION = _CONTROLMESSAGE.enum_types_by_name['Action'] +_FEDERATIONMESSAGE_ACTION = _FEDERATIONMESSAGE.enum_types_by_name['Action'] +_CONNECTIONMESSAGE_ACTION = _CONNECTIONMESSAGE.enum_types_by_name['Action'] +_REPUTATIONMESSAGE_ACTION = _REPUTATIONMESSAGE.enum_types_by_name['Action'] +Wrapper = _reflection.GeneratedProtocolMessageType('Wrapper', (_message.Message,), { + 'DESCRIPTOR' : _WRAPPER, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.Wrapper) + }) +_sym_db.RegisterMessage(Wrapper) + +DiscoveryMessage = _reflection.GeneratedProtocolMessageType('DiscoveryMessage', (_message.Message,), { + 'DESCRIPTOR' : _DISCOVERYMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.DiscoveryMessage) + }) +_sym_db.RegisterMessage(DiscoveryMessage) + +ControlMessage = _reflection.GeneratedProtocolMessageType('ControlMessage', (_message.Message,), { + 'DESCRIPTOR' : _CONTROLMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.ControlMessage) + }) +_sym_db.RegisterMessage(ControlMessage) + +FederationMessage = _reflection.GeneratedProtocolMessageType('FederationMessage', (_message.Message,), { + 'DESCRIPTOR' : _FEDERATIONMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.FederationMessage) + }) +_sym_db.RegisterMessage(FederationMessage) + +ModelMessage = _reflection.GeneratedProtocolMessageType('ModelMessage', (_message.Message,), { + 'DESCRIPTOR' : _MODELMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.ModelMessage) + }) +_sym_db.RegisterMessage(ModelMessage) + +ConnectionMessage = _reflection.GeneratedProtocolMessageType('ConnectionMessage', (_message.Message,), { + 'DESCRIPTOR' : _CONNECTIONMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.ConnectionMessage) + }) +_sym_db.RegisterMessage(ConnectionMessage) + +ResponseMessage = _reflection.GeneratedProtocolMessageType('ResponseMessage', (_message.Message,), { + 'DESCRIPTOR' : _RESPONSEMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.ResponseMessage) + }) +_sym_db.RegisterMessage(ResponseMessage) + +ReputationMessage = _reflection.GeneratedProtocolMessageType('ReputationMessage', (_message.Message,), { + 'DESCRIPTOR' : _REPUTATIONMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.ReputationMessage) + }) +_sym_db.RegisterMessage(ReputationMessage) + +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _WRAPPER._serialized_start=25 + _WRAPPER._serialized_end=452 + _DISCOVERYMESSAGE._serialized_start=455 + _DISCOVERYMESSAGE._serialized_end=613 + _DISCOVERYMESSAGE_ACTION._serialized_start=561 + _DISCOVERYMESSAGE_ACTION._serialized_end=613 + _CONTROLMESSAGE._serialized_start=616 + _CONTROLMESSAGE._serialized_end=770 + _CONTROLMESSAGE_ACTION._serialized_start=694 + _CONTROLMESSAGE_ACTION._serialized_end=770 + _FEDERATIONMESSAGE._serialized_start=773 + _FEDERATIONMESSAGE._serialized_end=978 + _FEDERATIONMESSAGE_ACTION._serialized_start=878 + _FEDERATIONMESSAGE_ACTION._serialized_end=978 + _MODELMESSAGE._serialized_start=980 + _MODELMESSAGE._serialized_end=1045 + _CONNECTIONMESSAGE._serialized_start=1047 + _CONNECTIONMESSAGE._serialized_end=1155 + _CONNECTIONMESSAGE_ACTION._serialized_start=1118 + _CONNECTIONMESSAGE_ACTION._serialized_end=1155 + _RESPONSEMESSAGE._serialized_start=1157 + _RESPONSEMESSAGE._serialized_end=1192 + _REPUTATIONMESSAGE._serialized_start=1195 + _REPUTATIONMESSAGE._serialized_end=1332 + _REPUTATIONMESSAGE_ACTION._serialized_start=1313 + _REPUTATIONMESSAGE_ACTION._serialized_end=1332 # @@protoc_insertion_point(module_scope) From 4d117758eb93e147c35eda028ecba4cefd36bed2 Mon Sep 17 00:00:00 2001 From: isaac Date: Fri, 28 Feb 2025 18:02:00 +0100 Subject: [PATCH 21/43] add reputation message to new message optimization --- nebula/core/engine.py | 15 +- nebula/core/network/actions.py | 1 + nebula/core/network/communications.py | 1 - nebula/core/network/messages.py | 2 +- nebula/core/pb/nebula_pb2.py | 207 +++++++++++++++----------- 5 files changed, 131 insertions(+), 95 deletions(-) diff --git a/nebula/core/engine.py b/nebula/core/engine.py index 0df259980..8818fe045 100755 --- a/nebula/core/engine.py +++ b/nebula/core/engine.py @@ -10,7 +10,6 @@ from nebula.core.aggregation.aggregator import create_aggregator from nebula.core.eventmanager import EventManager from nebula.core.network.communications import CommunicationsManager -from nebula.core.pb import nebula_pb2 from nebula.core.reputation.Reputation import Reputation from nebula.core.utils.locker import Locker @@ -304,7 +303,9 @@ async def _federation_federation_start_callback(self, source, message): async def _reputation_share_callback(self, source, message): try: - logging.info(f"handle_reputation_message | Trigger | Received reputation message from {source} | Node: {message.node_id} | Score: {message.score} | Round: {message.round}") + logging.info( + f"handle_reputation_message | Trigger | Received reputation message from {source} | Node: {message.node_id} | Score: {message.score} | Round: {message.round}" + ) self.cm.store_receive_timestamp(source, "reputation", message.round) current_node = self.addr @@ -723,10 +724,14 @@ async def calculate_reputation(self): # ) for neighbor in neighbors_to_send: - message = self.cm.create_message("reputation", "share", node_id=nei, score=data["reputation"], round=self.round) + message = self.cm.create_message( + "reputation", "share", node_id=nei, score=data["reputation"], round=self.round + ) await self.cm.send_message(neighbor, message) - logging.info(f"Sending reputation to node {nei} from node {neighbor} with reputation {data['reputation']}") - #await self.cm.send_message_to_neighbors(message_data, [neighbor]) + logging.info( + f"Sending reputation to node {nei} from node {neighbor} with reputation {data['reputation']}" + ) + # await self.cm.send_message_to_neighbors(message_data, [neighbor]) else: logging.info(f"Reputation already sent to node {nei}") diff --git a/nebula/core/network/actions.py b/nebula/core/network/actions.py index af3bc2812..556b4f0ae 100644 --- a/nebula/core/network/actions.py +++ b/nebula/core/network/actions.py @@ -28,6 +28,7 @@ class ControlAction(Enum): RECOVERY = nebula_pb2.ControlMessage.Action.RECOVERY WEAK_LINK = nebula_pb2.ControlMessage.Action.WEAK_LINK + class ReputationAction(Enum): SHARE = nebula_pb2.ReputationMessage.Action.SHARE diff --git a/nebula/core/network/communications.py b/nebula/core/network/communications.py index 7e75ec47e..da352a125 100755 --- a/nebula/core/network/communications.py +++ b/nebula/core/network/communications.py @@ -139,7 +139,6 @@ def get_messages_events(self): async def handle_incoming_message(self, data, addr_from): await self.mm.process_message(data, addr_from) - async def forward_message(self, data, addr_from): await self.forwarder.forward(data, addr_from=addr_from) diff --git a/nebula/core/network/messages.py b/nebula/core/network/messages.py index d0ef49d90..ed50708e1 100644 --- a/nebula/core/network/messages.py +++ b/nebula/core/network/messages.py @@ -53,7 +53,7 @@ def _define_message_templates(self): "defaults": { "round": None, }, - } + }, # Add additional message types here } diff --git a/nebula/core/pb/nebula_pb2.py b/nebula/core/pb/nebula_pb2.py index e2a96f376..64dafca46 100644 --- a/nebula/core/pb/nebula_pb2.py +++ b/nebula/core/pb/nebula_pb2.py @@ -1,119 +1,150 @@ -# -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: nebula.proto """Generated protocol buffer code.""" + from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database + # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cnebula.proto\x12\x06nebula\"\xab\x03\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\t \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x12\x37\n\x12reputation_message\x18\x08 \x01(\x0b\x32\x19.nebula.ReputationMessageH\x00\x42\t\n\x07message\"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02\"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02\"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t\"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04\"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05\"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03\"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05\"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action\"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01\"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t\"\x89\x01\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\x12\x30\n\x06\x61\x63tion\x18\x04 \x01(\x0e\x32 .nebula.ReputationMessage.Action\"\x13\n\x06\x41\x63tion\x12\t\n\x05SHARE\x10\x00\x62\x06proto3') - - - -_WRAPPER = DESCRIPTOR.message_types_by_name['Wrapper'] -_DISCOVERYMESSAGE = DESCRIPTOR.message_types_by_name['DiscoveryMessage'] -_CONTROLMESSAGE = DESCRIPTOR.message_types_by_name['ControlMessage'] -_FEDERATIONMESSAGE = DESCRIPTOR.message_types_by_name['FederationMessage'] -_MODELMESSAGE = DESCRIPTOR.message_types_by_name['ModelMessage'] -_CONNECTIONMESSAGE = DESCRIPTOR.message_types_by_name['ConnectionMessage'] -_RESPONSEMESSAGE = DESCRIPTOR.message_types_by_name['ResponseMessage'] -_REPUTATIONMESSAGE = DESCRIPTOR.message_types_by_name['ReputationMessage'] -_DISCOVERYMESSAGE_ACTION = _DISCOVERYMESSAGE.enum_types_by_name['Action'] -_CONTROLMESSAGE_ACTION = _CONTROLMESSAGE.enum_types_by_name['Action'] -_FEDERATIONMESSAGE_ACTION = _FEDERATIONMESSAGE.enum_types_by_name['Action'] -_CONNECTIONMESSAGE_ACTION = _CONNECTIONMESSAGE.enum_types_by_name['Action'] -_REPUTATIONMESSAGE_ACTION = _REPUTATIONMESSAGE.enum_types_by_name['Action'] -Wrapper = _reflection.GeneratedProtocolMessageType('Wrapper', (_message.Message,), { - 'DESCRIPTOR' : _WRAPPER, - '__module__' : 'nebula_pb2' - # @@protoc_insertion_point(class_scope:nebula.Wrapper) - }) +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x0cnebula.proto\x12\x06nebula"\xab\x03\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\t \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x12\x37\n\x12reputation_message\x18\x08 \x01(\x0b\x32\x19.nebula.ReputationMessageH\x00\x42\t\n\x07message"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t"\x89\x01\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\x12\x30\n\x06\x61\x63tion\x18\x04 \x01(\x0e\x32 .nebula.ReputationMessage.Action"\x13\n\x06\x41\x63tion\x12\t\n\x05SHARE\x10\x00\x62\x06proto3' +) + + +_WRAPPER = DESCRIPTOR.message_types_by_name["Wrapper"] +_DISCOVERYMESSAGE = DESCRIPTOR.message_types_by_name["DiscoveryMessage"] +_CONTROLMESSAGE = DESCRIPTOR.message_types_by_name["ControlMessage"] +_FEDERATIONMESSAGE = DESCRIPTOR.message_types_by_name["FederationMessage"] +_MODELMESSAGE = DESCRIPTOR.message_types_by_name["ModelMessage"] +_CONNECTIONMESSAGE = DESCRIPTOR.message_types_by_name["ConnectionMessage"] +_RESPONSEMESSAGE = DESCRIPTOR.message_types_by_name["ResponseMessage"] +_REPUTATIONMESSAGE = DESCRIPTOR.message_types_by_name["ReputationMessage"] +_DISCOVERYMESSAGE_ACTION = _DISCOVERYMESSAGE.enum_types_by_name["Action"] +_CONTROLMESSAGE_ACTION = _CONTROLMESSAGE.enum_types_by_name["Action"] +_FEDERATIONMESSAGE_ACTION = _FEDERATIONMESSAGE.enum_types_by_name["Action"] +_CONNECTIONMESSAGE_ACTION = _CONNECTIONMESSAGE.enum_types_by_name["Action"] +_REPUTATIONMESSAGE_ACTION = _REPUTATIONMESSAGE.enum_types_by_name["Action"] +Wrapper = _reflection.GeneratedProtocolMessageType( + "Wrapper", + (_message.Message,), + { + "DESCRIPTOR": _WRAPPER, + "__module__": "nebula_pb2", + # @@protoc_insertion_point(class_scope:nebula.Wrapper) + }, +) _sym_db.RegisterMessage(Wrapper) -DiscoveryMessage = _reflection.GeneratedProtocolMessageType('DiscoveryMessage', (_message.Message,), { - 'DESCRIPTOR' : _DISCOVERYMESSAGE, - '__module__' : 'nebula_pb2' - # @@protoc_insertion_point(class_scope:nebula.DiscoveryMessage) - }) +DiscoveryMessage = _reflection.GeneratedProtocolMessageType( + "DiscoveryMessage", + (_message.Message,), + { + "DESCRIPTOR": _DISCOVERYMESSAGE, + "__module__": "nebula_pb2", + # @@protoc_insertion_point(class_scope:nebula.DiscoveryMessage) + }, +) _sym_db.RegisterMessage(DiscoveryMessage) -ControlMessage = _reflection.GeneratedProtocolMessageType('ControlMessage', (_message.Message,), { - 'DESCRIPTOR' : _CONTROLMESSAGE, - '__module__' : 'nebula_pb2' - # @@protoc_insertion_point(class_scope:nebula.ControlMessage) - }) +ControlMessage = _reflection.GeneratedProtocolMessageType( + "ControlMessage", + (_message.Message,), + { + "DESCRIPTOR": _CONTROLMESSAGE, + "__module__": "nebula_pb2", + # @@protoc_insertion_point(class_scope:nebula.ControlMessage) + }, +) _sym_db.RegisterMessage(ControlMessage) -FederationMessage = _reflection.GeneratedProtocolMessageType('FederationMessage', (_message.Message,), { - 'DESCRIPTOR' : _FEDERATIONMESSAGE, - '__module__' : 'nebula_pb2' - # @@protoc_insertion_point(class_scope:nebula.FederationMessage) - }) +FederationMessage = _reflection.GeneratedProtocolMessageType( + "FederationMessage", + (_message.Message,), + { + "DESCRIPTOR": _FEDERATIONMESSAGE, + "__module__": "nebula_pb2", + # @@protoc_insertion_point(class_scope:nebula.FederationMessage) + }, +) _sym_db.RegisterMessage(FederationMessage) -ModelMessage = _reflection.GeneratedProtocolMessageType('ModelMessage', (_message.Message,), { - 'DESCRIPTOR' : _MODELMESSAGE, - '__module__' : 'nebula_pb2' - # @@protoc_insertion_point(class_scope:nebula.ModelMessage) - }) +ModelMessage = _reflection.GeneratedProtocolMessageType( + "ModelMessage", + (_message.Message,), + { + "DESCRIPTOR": _MODELMESSAGE, + "__module__": "nebula_pb2", + # @@protoc_insertion_point(class_scope:nebula.ModelMessage) + }, +) _sym_db.RegisterMessage(ModelMessage) -ConnectionMessage = _reflection.GeneratedProtocolMessageType('ConnectionMessage', (_message.Message,), { - 'DESCRIPTOR' : _CONNECTIONMESSAGE, - '__module__' : 'nebula_pb2' - # @@protoc_insertion_point(class_scope:nebula.ConnectionMessage) - }) +ConnectionMessage = _reflection.GeneratedProtocolMessageType( + "ConnectionMessage", + (_message.Message,), + { + "DESCRIPTOR": _CONNECTIONMESSAGE, + "__module__": "nebula_pb2", + # @@protoc_insertion_point(class_scope:nebula.ConnectionMessage) + }, +) _sym_db.RegisterMessage(ConnectionMessage) -ResponseMessage = _reflection.GeneratedProtocolMessageType('ResponseMessage', (_message.Message,), { - 'DESCRIPTOR' : _RESPONSEMESSAGE, - '__module__' : 'nebula_pb2' - # @@protoc_insertion_point(class_scope:nebula.ResponseMessage) - }) +ResponseMessage = _reflection.GeneratedProtocolMessageType( + "ResponseMessage", + (_message.Message,), + { + "DESCRIPTOR": _RESPONSEMESSAGE, + "__module__": "nebula_pb2", + # @@protoc_insertion_point(class_scope:nebula.ResponseMessage) + }, +) _sym_db.RegisterMessage(ResponseMessage) -ReputationMessage = _reflection.GeneratedProtocolMessageType('ReputationMessage', (_message.Message,), { - 'DESCRIPTOR' : _REPUTATIONMESSAGE, - '__module__' : 'nebula_pb2' - # @@protoc_insertion_point(class_scope:nebula.ReputationMessage) - }) +ReputationMessage = _reflection.GeneratedProtocolMessageType( + "ReputationMessage", + (_message.Message,), + { + "DESCRIPTOR": _REPUTATIONMESSAGE, + "__module__": "nebula_pb2", + # @@protoc_insertion_point(class_scope:nebula.ReputationMessage) + }, +) _sym_db.RegisterMessage(ReputationMessage) if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - _WRAPPER._serialized_start=25 - _WRAPPER._serialized_end=452 - _DISCOVERYMESSAGE._serialized_start=455 - _DISCOVERYMESSAGE._serialized_end=613 - _DISCOVERYMESSAGE_ACTION._serialized_start=561 - _DISCOVERYMESSAGE_ACTION._serialized_end=613 - _CONTROLMESSAGE._serialized_start=616 - _CONTROLMESSAGE._serialized_end=770 - _CONTROLMESSAGE_ACTION._serialized_start=694 - _CONTROLMESSAGE_ACTION._serialized_end=770 - _FEDERATIONMESSAGE._serialized_start=773 - _FEDERATIONMESSAGE._serialized_end=978 - _FEDERATIONMESSAGE_ACTION._serialized_start=878 - _FEDERATIONMESSAGE_ACTION._serialized_end=978 - _MODELMESSAGE._serialized_start=980 - _MODELMESSAGE._serialized_end=1045 - _CONNECTIONMESSAGE._serialized_start=1047 - _CONNECTIONMESSAGE._serialized_end=1155 - _CONNECTIONMESSAGE_ACTION._serialized_start=1118 - _CONNECTIONMESSAGE_ACTION._serialized_end=1155 - _RESPONSEMESSAGE._serialized_start=1157 - _RESPONSEMESSAGE._serialized_end=1192 - _REPUTATIONMESSAGE._serialized_start=1195 - _REPUTATIONMESSAGE._serialized_end=1332 - _REPUTATIONMESSAGE_ACTION._serialized_start=1313 - _REPUTATIONMESSAGE_ACTION._serialized_end=1332 + DESCRIPTOR._options = None + _WRAPPER._serialized_start = 25 + _WRAPPER._serialized_end = 452 + _DISCOVERYMESSAGE._serialized_start = 455 + _DISCOVERYMESSAGE._serialized_end = 613 + _DISCOVERYMESSAGE_ACTION._serialized_start = 561 + _DISCOVERYMESSAGE_ACTION._serialized_end = 613 + _CONTROLMESSAGE._serialized_start = 616 + _CONTROLMESSAGE._serialized_end = 770 + _CONTROLMESSAGE_ACTION._serialized_start = 694 + _CONTROLMESSAGE_ACTION._serialized_end = 770 + _FEDERATIONMESSAGE._serialized_start = 773 + _FEDERATIONMESSAGE._serialized_end = 978 + _FEDERATIONMESSAGE_ACTION._serialized_start = 878 + _FEDERATIONMESSAGE_ACTION._serialized_end = 978 + _MODELMESSAGE._serialized_start = 980 + _MODELMESSAGE._serialized_end = 1045 + _CONNECTIONMESSAGE._serialized_start = 1047 + _CONNECTIONMESSAGE._serialized_end = 1155 + _CONNECTIONMESSAGE_ACTION._serialized_start = 1118 + _CONNECTIONMESSAGE_ACTION._serialized_end = 1155 + _RESPONSEMESSAGE._serialized_start = 1157 + _RESPONSEMESSAGE._serialized_end = 1192 + _REPUTATIONMESSAGE._serialized_start = 1195 + _REPUTATIONMESSAGE._serialized_end = 1332 + _REPUTATIONMESSAGE_ACTION._serialized_start = 1313 + _REPUTATIONMESSAGE_ACTION._serialized_end = 1332 # @@protoc_insertion_point(module_scope) From 2602fb2f63e4f2e927c38b0830a5eae74734d571 Mon Sep 17 00:00:00 2001 From: isaac Date: Wed, 12 Mar 2025 14:06:06 +0100 Subject: [PATCH 22/43] changes to make everything work correctly after the merge with the main branch --- .../updatehandlers/dflupdatehandler.py | 18 +- nebula/core/engine.py | 123 ++++++++- nebula/core/network/communications.py | 103 -------- nebula/core/network/messages.py | 5 +- nebula/core/pb/nebula.proto | 1 - nebula/core/pb/nebula_pb2.py | 246 +++++++++--------- 6 files changed, 264 insertions(+), 232 deletions(-) diff --git a/nebula/core/aggregation/updatehandlers/dflupdatehandler.py b/nebula/core/aggregation/updatehandlers/dflupdatehandler.py index 4b3ddfcc6..67e706621 100644 --- a/nebula/core/aggregation/updatehandlers/dflupdatehandler.py +++ b/nebula/core/aggregation/updatehandlers/dflupdatehandler.py @@ -52,6 +52,9 @@ def us(self): @property def agg(self): return self._aggregator + + async def get_rejected_nodes(self): + return self._aggregator.engine.rejected_nodes async def init(self, config=None): await EventManager.get_instance().subscribe_node_event(UpdateNeighborEvent, self.notify_federation_update) @@ -97,6 +100,12 @@ async def _check_updates_already_received(self): async def storage_update(self, updt_received_event: UpdateReceivedEvent): time_received = time.time() (model, weight, source, round, _) = await updt_received_event.get_event_data() + + rejected_nodes = await self.get_rejected_nodes() + if source in rejected_nodes: + logging.info(f"Discard update | source: {source} is rejected and its update will not be stored.") + return + if source in self._sources_expected: updt = Update(model, weight, source, round, time_received) await self._updates_storage_lock.acquire_async() @@ -125,14 +134,18 @@ async def storage_update(self, updt_received_event: UpdateReceivedEvent): logging.info(f"Discard update | source: {source} not in expected updates for this Round") async def get_round_updates(self): + rejected_nodes = await self.get_rejected_nodes() await self._updates_storage_lock.acquire_async() - updates_missing = self._sources_expected.difference(self._sources_received) + updates_missing = self._sources_expected.difference(self._sources_received).difference(rejected_nodes) if updates_missing: self._missing_ones = updates_missing logging.info(f"Missing updates from sources: {updates_missing}") self._nodes_using_historic.clear() updates = {} for sr in self._sources_received: + if sr in rejected_nodes: + logging.info(f"Discard update | source: {sr} is rejected") + continue source_historic = self.us[sr][1] last_updt_received = self.us[sr][0] updt: Update = None @@ -202,7 +215,8 @@ async def _notify(self): await self.agg.notify_all_updates_received() async def _all_updates_received(self): - updates_left = self._sources_expected.difference(self._sources_received) + rejected_nodes = await self.get_rejected_nodes() + updates_left = self._sources_expected.difference(self._sources_received).difference(rejected_nodes) all_received = False if len(updates_left) == 0: logging.info("All updates have been received this round") diff --git a/nebula/core/engine.py b/nebula/core/engine.py index 4efcfb7cf..d9eb50403 100644 --- a/nebula/core/engine.py +++ b/nebula/core/engine.py @@ -5,6 +5,7 @@ import docker +from datetime import datetime from nebula.addons.attacks.attacks import create_attack from nebula.addons.functions import print_msg_box from nebula.addons.reporter import Reporter @@ -14,6 +15,18 @@ from nebula.core.nebulaevents import AggregationEvent, RoundStartEvent, UpdateNeighborEvent, UpdateReceivedEvent from nebula.core.network.communications import CommunicationsManager from nebula.core.utils.locker import Locker +from nebula.core.reputation.Reputation import ( + Reputation, + save_data, +) +from nebula.core.utils.helper import ( + cosine_metric, + euclidean_metric, + jaccard_metric, + manhattan_metric, + minkowski_metric, + pearson_correlation_metric, +) logging.getLogger("requests").setLevel(logging.WARNING) logging.getLogger("urllib3").setLevel(logging.WARNING) @@ -131,6 +144,18 @@ def __init__( self._addon_manager = AddonManager(self, self.config) + self.reputation_instance = Reputation(self) + self.reputation = {} + self.reputation_with_feedback = {} + self.reputation_with_all_feedback = {} + self.rejected_nodes = set() + self.change_weight_nodes = set() + self._model_arrival_latency_data = self.reputation_instance.model_arrival_latency_data + + self.with_reputation = self.config.participant["defense_args"]["with_reputation"] + msg = f"Reputation system: {self.with_reputation}" + print_msg_box(msg=msg, indent=2, title="Defense information") + @property def cm(self): return self._cm @@ -219,6 +244,96 @@ async def model_update_callback(self, source, message): updt_received_event = UpdateReceivedEvent(decoded_model, message.weight, source, message.round) await EventManager.get_instance().publish_node_event(updt_received_event) + if self.with_reputation: + if self.config.participant["adaptive_args"]["model_similarity"]: + logging.info("🤖 handle_model_message | Checking model similarity") + cosine_value = cosine_metric( + self.trainer.get_model_parameters(), + decoded_model, + similarity=True, + ) + euclidean_value = euclidean_metric( + self.trainer.get_model_parameters(), + decoded_model, + similarity=True, + ) + minkowski_value = minkowski_metric( + self.trainer.get_model_parameters(), + decoded_model, + p=2, + similarity=True, + ) + manhattan_value = manhattan_metric( + self.trainer.get_model_parameters(), + decoded_model, + similarity=True, + ) + pearson_correlation_value = pearson_correlation_metric( + self.trainer.get_model_parameters(), + decoded_model, + similarity=True, + ) + jaccard_value = jaccard_metric( + self.trainer.get_model_parameters(), + decoded_model, + similarity=True, + ) + file = f"{self.log_dir}/participant_{self.idx}_similarity.csv" + directory = os.path.dirname(file) + os.makedirs(directory, exist_ok=True) + if not os.path.isfile(file): + with open(file, "w") as f: + f.write( + "timestamp,source_ip,round,current_round,cosine,euclidean,minkowski,manhattan,pearson_correlation,jaccard\n" + ) + with open(file, "a") as f: + f.write( + f"{datetime.now()}, {source}, {message.round}, {self.get_round()}, {cosine_value}, {euclidean_value}, {minkowski_value}, {manhattan_value}, {pearson_correlation_value}, {jaccard_value}\n" + ) + + # Manage parameters of models + parameters_local = self.trainer.get_model_parameters() + self.cm.fraction_of_parameters_changed(source, parameters_local, decoded_model, message.round) + + # Manage model_arrival_latency latency + start_time = time.time() + round_id = message.round + if round_id not in self._model_arrival_latency_data: + self._model_arrival_latency_data[round_id] = {} + + if "time_0" not in self._model_arrival_latency_data[round_id]: + self._model_arrival_latency_data[round_id]["time_0"] = {"time": start_time, "source": source} + + relative_time = start_time - self._model_arrival_latency_data[round_id]["time_0"]["time"] + + if source not in self._model_arrival_latency_data[round_id]: + self._model_arrival_latency_data[round_id][source] = { + "start_time": start_time, + "relative_time": relative_time, + } + # logging.info(f"self.model_arrival_latency_data: {self._model_arrival_latency_data}") + logging.info(f"Node {source} | Time taken relative to time_0: {relative_time:.3f} seconds") + + if message.round == self.get_round(): + logging.info(f"🤖 handle_model_message | message_round == current_round to node {source}") + elif message.round < self.get_round(): + logging.info(f"🤖 handle_model_message | message_round <= current_round to node {source}") + else: + logging.info(f"🤖 handle_model_message | message_round > current_round to node {source}") + + save_data( + self.config.participant["scenario_args"]["name"], + "model_arrival_latency", + source, + self.get_addr(), + num_round=message.round, + latency=relative_time, + ) + + if cosine_value < 0.6: + logging.info("🤖 handle_model_message | Model similarity is less than 0.6") + self.rejected_nodes.add(source) + """ ############################## # General callbacks # @@ -287,10 +402,8 @@ async def _federation_federation_start_callback(self, source, message): async def _reputation_share_callback(self, source, message): try: - logging.info( - f"handle_reputation_message | Trigger | Received reputation message from {source} | Node: {message.node_id} | Score: {message.score} | Round: {message.round}" - ) - self.cm.store_receive_timestamp(source, "reputation", message.round) + logging.info(f"handle_reputation_message | Trigger | Received reputation message from {source} | Node: {message.node_id} | Score: {message.score} | Round: {message.round}") + #self.cm.store_receive_timestamp(source, "reputation", message.round) current_node = self.addr nei = message.node_id @@ -303,7 +416,7 @@ async def _reputation_share_callback(self, source, message): self.reputation_with_all_feedback[key] = [] self.reputation_with_all_feedback[key].append(message.score) - logging.info(f"Reputation with all feedback: {self.reputation_with_all_feedback}") + #logging.info(f"Reputation with all feedback: {self.reputation_with_all_feedback}") except Exception as e: logging.exception(f"Error handling reputation message: {e}") diff --git a/nebula/core/network/communications.py b/nebula/core/network/communications.py index 3a33f744e..78afd7f1a 100755 --- a/nebula/core/network/communications.py +++ b/nebula/core/network/communications.py @@ -4,7 +4,6 @@ import os import sys import time -from datetime import datetime from typing import TYPE_CHECKING import requests @@ -21,14 +20,6 @@ Reputation, save_data, ) -from nebula.core.utils.helper import ( - cosine_metric, - euclidean_metric, - jaccard_metric, - manhattan_metric, - minkowski_metric, - pearson_correlation_metric, -) from nebula.core.utils.locker import Locker if TYPE_CHECKING: @@ -82,7 +73,6 @@ def __init__(self, engine: "Engine"): # Reputation self.reputation_instance = Reputation(self.engine) - self._model_arrival_latency_data = self.reputation_instance.model_arrival_latency_data self.message_timestamps = {} self.fraction_of_params_changed = {} @@ -146,99 +136,6 @@ async def handle_model_message(self, source, message): if message.round == -1: model_init_event = MessageEvent(("model", "initialization"), source, message) asyncio.create_task(EventManager.get_instance().publish(model_init_event)) - - if self._engine.with_reputation: - if not self.engine.get_federation_ready_lock().locked() or self.engine.get_initialization_status(): - decoded_model = self.engine.trainer.deserialize_model(message.parameters) - if self.config.participant["adaptive_args"]["model_similarity"]: - logging.info("🤖 handle_model_message | Checking model similarity") - cosine_value = cosine_metric( - self.engine.trainer.get_model_parameters(), - decoded_model, - similarity=True, - ) - euclidean_value = euclidean_metric( - self.engine.trainer.get_model_parameters(), - decoded_model, - similarity=True, - ) - minkowski_value = minkowski_metric( - self.engine.trainer.get_model_parameters(), - decoded_model, - p=2, - similarity=True, - ) - manhattan_value = manhattan_metric( - self.engine.trainer.get_model_parameters(), - decoded_model, - similarity=True, - ) - pearson_correlation_value = pearson_correlation_metric( - self.engine.trainer.get_model_parameters(), - decoded_model, - similarity=True, - ) - jaccard_value = jaccard_metric( - self.engine.trainer.get_model_parameters(), - decoded_model, - similarity=True, - ) - file = f"{self.engine.log_dir}/participant_{self.engine.idx}_similarity.csv" - directory = os.path.dirname(file) - os.makedirs(directory, exist_ok=True) - if not os.path.isfile(file): - with open(file, "w") as f: - f.write( - "timestamp,source_ip,round,current_round,cosine,euclidean,minkowski,manhattan,pearson_correlation,jaccard\n" - ) - with open(file, "a") as f: - f.write( - f"{datetime.now()}, {source}, {message.round}, {self.get_round()}, {cosine_value}, {euclidean_value}, {minkowski_value}, {manhattan_value}, {pearson_correlation_value}, {jaccard_value}\n" - ) - - # Manage parameters of models - parameters_local = self.engine.trainer.get_model_parameters() - self.fraction_of_parameters_changed(source, parameters_local, decoded_model, message.round) - - # Manage model_arrival_latency latency - start_time = time.time() - round_id = message.round - if round_id not in self._model_arrival_latency_data: - self._model_arrival_latency_data[round_id] = {} - - if "time_0" not in self._model_arrival_latency_data[round_id]: - self._model_arrival_latency_data[round_id]["time_0"] = {"time": start_time, "source": source} - - relative_time = start_time - self._model_arrival_latency_data[round_id]["time_0"]["time"] - - if source not in self._model_arrival_latency_data[round_id]: - self._model_arrival_latency_data[round_id][source] = { - "start_time": start_time, - "relative_time": relative_time, - } - # logging.info(f"self.model_arrival_latency_data: {self._model_arrival_latency_data}") - logging.info(f"Node {source} | Time taken relative to time_0: {relative_time:.3f} seconds") - - if message.round == self.get_round(): - logging.info(f"🤖 handle_model_message | message_round == current_round to node {source}") - elif message.round < self.get_round(): - logging.info(f"🤖 handle_model_message | message_round <= current_round to node {source}") - else: - logging.info(f"🤖 handle_model_message | message_round > current_round to node {source}") - - save_data( - self.config.participant["scenario_args"]["name"], - "model_arrival_latency", - source, - self.get_addr(), - num_round=message.round, - latency=relative_time, - ) - - if cosine_value < 0.6: - logging.info("🤖 handle_model_message | Model similarity is less than 0.6") - self.engine.rejected_nodes.add(source) - return else: model_updt_event = MessageEvent(("model", "update"), source, message) asyncio.create_task(EventManager.get_instance().publish(model_updt_event)) diff --git a/nebula/core/network/messages.py b/nebula/core/network/messages.py index e0dd796f2..51ece9abd 100644 --- a/nebula/core/network/messages.py +++ b/nebula/core/network/messages.py @@ -61,7 +61,7 @@ def _define_message_templates(self): def get_messages_events(self): message_events = {} for message_name in self._message_templates: - if message_name != "model" and message_name != "reputation": + if message_name != "model": message_events[message_name] = get_actions_names(message_name) return message_events @@ -84,7 +84,8 @@ async def process_message(self, data, addr_from): logging.warning("Received message with no active field in the 'oneof'") return - self.cm.store_receive_timestamp(addr_from, message_type, round=self.cm.get_round()) + if self.cm.engine.with_reputation: + self.cm.store_receive_timestamp(addr_from, message_type, round=self.cm.get_round()) message_data = getattr(message_wrapper, message_type) diff --git a/nebula/core/pb/nebula.proto b/nebula/core/pb/nebula.proto index 12793a482..4436be3dc 100755 --- a/nebula/core/pb/nebula.proto +++ b/nebula/core/pb/nebula.proto @@ -121,7 +121,6 @@ message ReputationMessage { enum Action { SHARE = 0; } - string node_id = 1; //Id of the node to which the reputation is sent float score = 2; //Score reputation int32 round = 3; //Round to send the reputation diff --git a/nebula/core/pb/nebula_pb2.py b/nebula/core/pb/nebula_pb2.py index 64dafca46..b181f94d3 100644 --- a/nebula/core/pb/nebula_pb2.py +++ b/nebula/core/pb/nebula_pb2.py @@ -1,150 +1,158 @@ +# -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: nebula.proto """Generated protocol buffer code.""" - from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database - # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( - b'\n\x0cnebula.proto\x12\x06nebula"\xab\x03\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\t \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x12\x37\n\x12reputation_message\x18\x08 \x01(\x0b\x32\x19.nebula.ReputationMessageH\x00\x42\t\n\x07message"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05"l\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action"%\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t"\x89\x01\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\x12\x30\n\x06\x61\x63tion\x18\x04 \x01(\x0e\x32 .nebula.ReputationMessage.Action"\x13\n\x06\x41\x63tion\x12\t\n\x05SHARE\x10\x00\x62\x06proto3' -) - - -_WRAPPER = DESCRIPTOR.message_types_by_name["Wrapper"] -_DISCOVERYMESSAGE = DESCRIPTOR.message_types_by_name["DiscoveryMessage"] -_CONTROLMESSAGE = DESCRIPTOR.message_types_by_name["ControlMessage"] -_FEDERATIONMESSAGE = DESCRIPTOR.message_types_by_name["FederationMessage"] -_MODELMESSAGE = DESCRIPTOR.message_types_by_name["ModelMessage"] -_CONNECTIONMESSAGE = DESCRIPTOR.message_types_by_name["ConnectionMessage"] -_RESPONSEMESSAGE = DESCRIPTOR.message_types_by_name["ResponseMessage"] -_REPUTATIONMESSAGE = DESCRIPTOR.message_types_by_name["ReputationMessage"] -_DISCOVERYMESSAGE_ACTION = _DISCOVERYMESSAGE.enum_types_by_name["Action"] -_CONTROLMESSAGE_ACTION = _CONTROLMESSAGE.enum_types_by_name["Action"] -_FEDERATIONMESSAGE_ACTION = _FEDERATIONMESSAGE.enum_types_by_name["Action"] -_CONNECTIONMESSAGE_ACTION = _CONNECTIONMESSAGE.enum_types_by_name["Action"] -_REPUTATIONMESSAGE_ACTION = _REPUTATIONMESSAGE.enum_types_by_name["Action"] -Wrapper = _reflection.GeneratedProtocolMessageType( - "Wrapper", - (_message.Message,), - { - "DESCRIPTOR": _WRAPPER, - "__module__": "nebula_pb2", - # @@protoc_insertion_point(class_scope:nebula.Wrapper) - }, -) + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cnebula.proto\x12\x06nebula\"\xae\x04\n\x07Wrapper\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x35\n\x11\x64iscovery_message\x18\x02 \x01(\x0b\x32\x18.nebula.DiscoveryMessageH\x00\x12\x31\n\x0f\x63ontrol_message\x18\x03 \x01(\x0b\x32\x16.nebula.ControlMessageH\x00\x12\x37\n\x12\x66\x65\x64\x65ration_message\x18\x04 \x01(\x0b\x32\x19.nebula.FederationMessageH\x00\x12-\n\rmodel_message\x18\x05 \x01(\x0b\x32\x14.nebula.ModelMessageH\x00\x12\x37\n\x12\x63onnection_message\x18\x06 \x01(\x0b\x32\x19.nebula.ConnectionMessageH\x00\x12\x33\n\x10response_message\x18\x07 \x01(\x0b\x32\x17.nebula.ResponseMessageH\x00\x12\x37\n\x12reputation_message\x18\x08 \x01(\x0b\x32\x19.nebula.ReputationMessageH\x00\x12\x33\n\x10\x64iscover_message\x18\t \x01(\x0b\x32\x17.nebula.DiscoverMessageH\x00\x12-\n\roffer_message\x18\n \x01(\x0b\x32\x14.nebula.OfferMessageH\x00\x12+\n\x0clink_message\x18\x0b \x01(\x0b\x32\x13.nebula.LinkMessageH\x00\x42\t\n\x07message\"\x9e\x01\n\x10\x44iscoveryMessage\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.nebula.DiscoveryMessage.Action\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02\"4\n\x06\x41\x63tion\x12\x0c\n\x08\x44ISCOVER\x10\x00\x12\x0c\n\x08REGISTER\x10\x01\x12\x0e\n\nDEREGISTER\x10\x02\"\x9a\x01\n\x0e\x43ontrolMessage\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.nebula.ControlMessage.Action\x12\x0b\n\x03log\x18\x02 \x01(\t\"L\n\x06\x41\x63tion\x12\t\n\x05\x41LIVE\x10\x00\x12\x0c\n\x08OVERHEAD\x10\x01\x12\x0c\n\x08MOBILITY\x10\x02\x12\x0c\n\x08RECOVERY\x10\x03\x12\r\n\tWEAK_LINK\x10\x04\"\xcd\x01\n\x11\x46\x65\x64\x65rationMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.FederationMessage.Action\x12\x11\n\targuments\x18\x02 \x03(\t\x12\r\n\x05round\x18\x03 \x01(\x05\"d\n\x06\x41\x63tion\x12\x14\n\x10\x46\x45\x44\x45RATION_START\x10\x00\x12\x0e\n\nREPUTATION\x10\x01\x12\x1e\n\x1a\x46\x45\x44\x45RATION_MODELS_INCLUDED\x10\x02\x12\x14\n\x10\x46\x45\x44\x45RATION_READY\x10\x03\"A\n\x0cModelMessage\x12\x12\n\nparameters\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x03\x12\r\n\x05round\x18\x03 \x01(\x05\"\x8f\x01\n\x11\x43onnectionMessage\x12\x30\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32 .nebula.ConnectionMessage.Action\"H\n\x06\x41\x63tion\x12\x0b\n\x07\x43ONNECT\x10\x00\x12\x0e\n\nDISCONNECT\x10\x01\x12\x10\n\x0cLATE_CONNECT\x10\x02\x12\x0f\n\x0bRESTRUCTURE\x10\x03\"r\n\x0f\x44iscoverMessage\x12.\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1e.nebula.DiscoverMessage.Action\"/\n\x06\x41\x63tion\x12\x11\n\rDISCOVER_JOIN\x10\x00\x12\x12\n\x0e\x44ISCOVER_NODES\x10\x01\"\xce\x01\n\x0cOfferMessage\x12+\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1b.nebula.OfferMessage.Action\x12\x13\n\x0bn_neighbors\x18\x02 \x01(\x02\x12\x0c\n\x04loss\x18\x03 \x01(\x02\x12\x12\n\nparameters\x18\x04 \x01(\x0c\x12\x0e\n\x06rounds\x18\x05 \x01(\x05\x12\r\n\x05round\x18\x06 \x01(\x05\x12\x0e\n\x06\x65pochs\x18\x07 \x01(\x05\"+\n\x06\x41\x63tion\x12\x0f\n\x0bOFFER_MODEL\x10\x00\x12\x10\n\x0cOFFER_METRIC\x10\x01\"w\n\x0bLinkMessage\x12*\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1a.nebula.LinkMessage.Action\x12\r\n\x05\x61\x64\x64rs\x18\x02 \x01(\t\"-\n\x06\x41\x63tion\x12\x0e\n\nCONNECT_TO\x10\x00\x12\x13\n\x0f\x44ISCONNECT_FROM\x10\x01\"#\n\x0fResponseMessage\x12\x10\n\x08response\x18\x01 \x01(\t\"\x89\x01\n\x11ReputationMessage\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x12\r\n\x05round\x18\x03 \x01(\x05\x12\x30\n\x06\x61\x63tion\x18\x04 \x01(\x0e\x32 .nebula.ReputationMessage.Action\"\x13\n\x06\x41\x63tion\x12\t\n\x05SHARE\x10\x00\x62\x06proto3') + + + +_WRAPPER = DESCRIPTOR.message_types_by_name['Wrapper'] +_DISCOVERYMESSAGE = DESCRIPTOR.message_types_by_name['DiscoveryMessage'] +_CONTROLMESSAGE = DESCRIPTOR.message_types_by_name['ControlMessage'] +_FEDERATIONMESSAGE = DESCRIPTOR.message_types_by_name['FederationMessage'] +_MODELMESSAGE = DESCRIPTOR.message_types_by_name['ModelMessage'] +_CONNECTIONMESSAGE = DESCRIPTOR.message_types_by_name['ConnectionMessage'] +_DISCOVERMESSAGE = DESCRIPTOR.message_types_by_name['DiscoverMessage'] +_OFFERMESSAGE = DESCRIPTOR.message_types_by_name['OfferMessage'] +_LINKMESSAGE = DESCRIPTOR.message_types_by_name['LinkMessage'] +_RESPONSEMESSAGE = DESCRIPTOR.message_types_by_name['ResponseMessage'] +_REPUTATIONMESSAGE = DESCRIPTOR.message_types_by_name['ReputationMessage'] +_DISCOVERYMESSAGE_ACTION = _DISCOVERYMESSAGE.enum_types_by_name['Action'] +_CONTROLMESSAGE_ACTION = _CONTROLMESSAGE.enum_types_by_name['Action'] +_FEDERATIONMESSAGE_ACTION = _FEDERATIONMESSAGE.enum_types_by_name['Action'] +_CONNECTIONMESSAGE_ACTION = _CONNECTIONMESSAGE.enum_types_by_name['Action'] +_DISCOVERMESSAGE_ACTION = _DISCOVERMESSAGE.enum_types_by_name['Action'] +_OFFERMESSAGE_ACTION = _OFFERMESSAGE.enum_types_by_name['Action'] +_LINKMESSAGE_ACTION = _LINKMESSAGE.enum_types_by_name['Action'] +_REPUTATIONMESSAGE_ACTION = _REPUTATIONMESSAGE.enum_types_by_name['Action'] +Wrapper = _reflection.GeneratedProtocolMessageType('Wrapper', (_message.Message,), { + 'DESCRIPTOR' : _WRAPPER, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.Wrapper) + }) _sym_db.RegisterMessage(Wrapper) -DiscoveryMessage = _reflection.GeneratedProtocolMessageType( - "DiscoveryMessage", - (_message.Message,), - { - "DESCRIPTOR": _DISCOVERYMESSAGE, - "__module__": "nebula_pb2", - # @@protoc_insertion_point(class_scope:nebula.DiscoveryMessage) - }, -) +DiscoveryMessage = _reflection.GeneratedProtocolMessageType('DiscoveryMessage', (_message.Message,), { + 'DESCRIPTOR' : _DISCOVERYMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.DiscoveryMessage) + }) _sym_db.RegisterMessage(DiscoveryMessage) -ControlMessage = _reflection.GeneratedProtocolMessageType( - "ControlMessage", - (_message.Message,), - { - "DESCRIPTOR": _CONTROLMESSAGE, - "__module__": "nebula_pb2", - # @@protoc_insertion_point(class_scope:nebula.ControlMessage) - }, -) +ControlMessage = _reflection.GeneratedProtocolMessageType('ControlMessage', (_message.Message,), { + 'DESCRIPTOR' : _CONTROLMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.ControlMessage) + }) _sym_db.RegisterMessage(ControlMessage) -FederationMessage = _reflection.GeneratedProtocolMessageType( - "FederationMessage", - (_message.Message,), - { - "DESCRIPTOR": _FEDERATIONMESSAGE, - "__module__": "nebula_pb2", - # @@protoc_insertion_point(class_scope:nebula.FederationMessage) - }, -) +FederationMessage = _reflection.GeneratedProtocolMessageType('FederationMessage', (_message.Message,), { + 'DESCRIPTOR' : _FEDERATIONMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.FederationMessage) + }) _sym_db.RegisterMessage(FederationMessage) -ModelMessage = _reflection.GeneratedProtocolMessageType( - "ModelMessage", - (_message.Message,), - { - "DESCRIPTOR": _MODELMESSAGE, - "__module__": "nebula_pb2", - # @@protoc_insertion_point(class_scope:nebula.ModelMessage) - }, -) +ModelMessage = _reflection.GeneratedProtocolMessageType('ModelMessage', (_message.Message,), { + 'DESCRIPTOR' : _MODELMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.ModelMessage) + }) _sym_db.RegisterMessage(ModelMessage) -ConnectionMessage = _reflection.GeneratedProtocolMessageType( - "ConnectionMessage", - (_message.Message,), - { - "DESCRIPTOR": _CONNECTIONMESSAGE, - "__module__": "nebula_pb2", - # @@protoc_insertion_point(class_scope:nebula.ConnectionMessage) - }, -) +ConnectionMessage = _reflection.GeneratedProtocolMessageType('ConnectionMessage', (_message.Message,), { + 'DESCRIPTOR' : _CONNECTIONMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.ConnectionMessage) + }) _sym_db.RegisterMessage(ConnectionMessage) -ResponseMessage = _reflection.GeneratedProtocolMessageType( - "ResponseMessage", - (_message.Message,), - { - "DESCRIPTOR": _RESPONSEMESSAGE, - "__module__": "nebula_pb2", - # @@protoc_insertion_point(class_scope:nebula.ResponseMessage) - }, -) +DiscoverMessage = _reflection.GeneratedProtocolMessageType('DiscoverMessage', (_message.Message,), { + 'DESCRIPTOR' : _DISCOVERMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.DiscoverMessage) + }) +_sym_db.RegisterMessage(DiscoverMessage) + +OfferMessage = _reflection.GeneratedProtocolMessageType('OfferMessage', (_message.Message,), { + 'DESCRIPTOR' : _OFFERMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.OfferMessage) + }) +_sym_db.RegisterMessage(OfferMessage) + +LinkMessage = _reflection.GeneratedProtocolMessageType('LinkMessage', (_message.Message,), { + 'DESCRIPTOR' : _LINKMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.LinkMessage) + }) +_sym_db.RegisterMessage(LinkMessage) + +ResponseMessage = _reflection.GeneratedProtocolMessageType('ResponseMessage', (_message.Message,), { + 'DESCRIPTOR' : _RESPONSEMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.ResponseMessage) + }) _sym_db.RegisterMessage(ResponseMessage) -ReputationMessage = _reflection.GeneratedProtocolMessageType( - "ReputationMessage", - (_message.Message,), - { - "DESCRIPTOR": _REPUTATIONMESSAGE, - "__module__": "nebula_pb2", - # @@protoc_insertion_point(class_scope:nebula.ReputationMessage) - }, -) +ReputationMessage = _reflection.GeneratedProtocolMessageType('ReputationMessage', (_message.Message,), { + 'DESCRIPTOR' : _REPUTATIONMESSAGE, + '__module__' : 'nebula_pb2' + # @@protoc_insertion_point(class_scope:nebula.ReputationMessage) + }) _sym_db.RegisterMessage(ReputationMessage) if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _WRAPPER._serialized_start = 25 - _WRAPPER._serialized_end = 452 - _DISCOVERYMESSAGE._serialized_start = 455 - _DISCOVERYMESSAGE._serialized_end = 613 - _DISCOVERYMESSAGE_ACTION._serialized_start = 561 - _DISCOVERYMESSAGE_ACTION._serialized_end = 613 - _CONTROLMESSAGE._serialized_start = 616 - _CONTROLMESSAGE._serialized_end = 770 - _CONTROLMESSAGE_ACTION._serialized_start = 694 - _CONTROLMESSAGE_ACTION._serialized_end = 770 - _FEDERATIONMESSAGE._serialized_start = 773 - _FEDERATIONMESSAGE._serialized_end = 978 - _FEDERATIONMESSAGE_ACTION._serialized_start = 878 - _FEDERATIONMESSAGE_ACTION._serialized_end = 978 - _MODELMESSAGE._serialized_start = 980 - _MODELMESSAGE._serialized_end = 1045 - _CONNECTIONMESSAGE._serialized_start = 1047 - _CONNECTIONMESSAGE._serialized_end = 1155 - _CONNECTIONMESSAGE_ACTION._serialized_start = 1118 - _CONNECTIONMESSAGE_ACTION._serialized_end = 1155 - _RESPONSEMESSAGE._serialized_start = 1157 - _RESPONSEMESSAGE._serialized_end = 1192 - _REPUTATIONMESSAGE._serialized_start = 1195 - _REPUTATIONMESSAGE._serialized_end = 1332 - _REPUTATIONMESSAGE_ACTION._serialized_start = 1313 - _REPUTATIONMESSAGE_ACTION._serialized_end = 1332 + + DESCRIPTOR._options = None + _WRAPPER._serialized_start=25 + _WRAPPER._serialized_end=583 + _DISCOVERYMESSAGE._serialized_start=586 + _DISCOVERYMESSAGE._serialized_end=744 + _DISCOVERYMESSAGE_ACTION._serialized_start=692 + _DISCOVERYMESSAGE_ACTION._serialized_end=744 + _CONTROLMESSAGE._serialized_start=747 + _CONTROLMESSAGE._serialized_end=901 + _CONTROLMESSAGE_ACTION._serialized_start=825 + _CONTROLMESSAGE_ACTION._serialized_end=901 + _FEDERATIONMESSAGE._serialized_start=904 + _FEDERATIONMESSAGE._serialized_end=1109 + _FEDERATIONMESSAGE_ACTION._serialized_start=1009 + _FEDERATIONMESSAGE_ACTION._serialized_end=1109 + _MODELMESSAGE._serialized_start=1111 + _MODELMESSAGE._serialized_end=1176 + _CONNECTIONMESSAGE._serialized_start=1179 + _CONNECTIONMESSAGE._serialized_end=1322 + _CONNECTIONMESSAGE_ACTION._serialized_start=1250 + _CONNECTIONMESSAGE_ACTION._serialized_end=1322 + _DISCOVERMESSAGE._serialized_start=1324 + _DISCOVERMESSAGE._serialized_end=1438 + _DISCOVERMESSAGE_ACTION._serialized_start=1391 + _DISCOVERMESSAGE_ACTION._serialized_end=1438 + _OFFERMESSAGE._serialized_start=1441 + _OFFERMESSAGE._serialized_end=1647 + _OFFERMESSAGE_ACTION._serialized_start=1604 + _OFFERMESSAGE_ACTION._serialized_end=1647 + _LINKMESSAGE._serialized_start=1649 + _LINKMESSAGE._serialized_end=1768 + _LINKMESSAGE_ACTION._serialized_start=1723 + _LINKMESSAGE_ACTION._serialized_end=1768 + _RESPONSEMESSAGE._serialized_start=1770 + _RESPONSEMESSAGE._serialized_end=1805 + _REPUTATIONMESSAGE._serialized_start=1808 + _REPUTATIONMESSAGE._serialized_end=1945 + _REPUTATIONMESSAGE_ACTION._serialized_start=1926 + _REPUTATIONMESSAGE_ACTION._serialized_end=1945 # @@protoc_insertion_point(module_scope) From 220d8493d8647c168dfa07272d53732c2368aefd Mon Sep 17 00:00:00 2001 From: isaac Date: Thu, 20 Mar 2025 11:35:41 +0100 Subject: [PATCH 23/43] new configuration at front and changes in back to dynamic and static weights --- nebula/core/engine.py | 479 +++++------ nebula/core/network/communications.py | 2 +- nebula/core/network/messages.py | 2 +- nebula/core/reputation/Reputation.py | 807 +++++++++--------- .../frontend/config/participant.json.example | 8 +- nebula/frontend/templates/deployment.html | 188 ++-- nebula/scenarios.py | 53 +- 7 files changed, 799 insertions(+), 740 deletions(-) diff --git a/nebula/core/engine.py b/nebula/core/engine.py index d9eb50403..ec9e06589 100644 --- a/nebula/core/engine.py +++ b/nebula/core/engine.py @@ -149,11 +149,28 @@ def __init__( self.reputation_with_feedback = {} self.reputation_with_all_feedback = {} self.rejected_nodes = set() - self.change_weight_nodes = set() self._model_arrival_latency_data = self.reputation_instance.model_arrival_latency_data self.with_reputation = self.config.participant["defense_args"]["with_reputation"] + self.reputation_metrics = self.config.participant["defense_args"]["reputation_metrics"] + self.initial_reputation = float(self.config.participant["defense_args"]["initial_reputation"]) + self.weighting_factor = self.config.participant["defense_args"]["weighting_factor"] + self.weight_model_arrival_latency = float(self.config.participant["defense_args"]["weight_model_arrival_latency"]) + self.weight_model_similarity = float(self.config.participant["defense_args"]["weight_model_similarity"]) + self.weight_num_messages = float(self.config.participant["defense_args"]["weight_num_messages"]) + self.weight_fraction_params_changed = float(self.config.participant["defense_args"]["weight_fraction_params_changed"]) + + logging.info(f"model_arrival_latency: {self.reputation_metrics.get('model_arrival_latency')}") + msg = f"Reputation system: {self.with_reputation}" + msg += f"\nReputation metrics: {self.reputation_metrics}" + msg += f"\nInitial reputation: {self.initial_reputation}" + msg += f"\nWeighting factor: {self.weighting_factor}" + if self.weighting_factor == "static": + msg += f"\nWeight model arrival latency: {self.weight_model_arrival_latency}" + msg += f"\nWeight model similarity: {self.weight_model_similarity}" + msg += f"\nWeight number of messages: {self.weight_num_messages}" + msg += f"\nWeight fraction of parameters changed: {self.weight_fraction_params_changed}" print_msg_box(msg=msg, indent=2, title="Defense information") @property @@ -244,7 +261,7 @@ async def model_update_callback(self, source, message): updt_received_event = UpdateReceivedEvent(decoded_model, message.weight, source, message.round) await EventManager.get_instance().publish_node_event(updt_received_event) - if self.with_reputation: + if self.with_reputation and self.reputation_metrics.get("model_similarity"): if self.config.participant["adaptive_args"]["model_similarity"]: logging.info("🤖 handle_model_message | Checking model similarity") cosine_value = cosine_metric( @@ -291,48 +308,55 @@ async def model_update_callback(self, source, message): f"{datetime.now()}, {source}, {message.round}, {self.get_round()}, {cosine_value}, {euclidean_value}, {minkowski_value}, {manhattan_value}, {pearson_correlation_value}, {jaccard_value}\n" ) + if cosine_value < 0.6: + logging.info("🤖 handle_model_message | Model similarity is less than 0.6") + self.rejected_nodes.add(source) + # Manage parameters of models - parameters_local = self.trainer.get_model_parameters() - self.cm.fraction_of_parameters_changed(source, parameters_local, decoded_model, message.round) + if self.reputation_metrics.get("fraction_parameters_changed"): + parameters_local = self.trainer.get_model_parameters() + self.cm.fraction_of_parameters_changed(source, parameters_local, decoded_model, message.round) # Manage model_arrival_latency latency - start_time = time.time() - round_id = message.round - if round_id not in self._model_arrival_latency_data: - self._model_arrival_latency_data[round_id] = {} + if self.reputation_metrics.get("model_arrival_latency"): + start_time = time.time() + round_id = message.round + if round_id not in self._model_arrival_latency_data: + self._model_arrival_latency_data[round_id] = {} - if "time_0" not in self._model_arrival_latency_data[round_id]: - self._model_arrival_latency_data[round_id]["time_0"] = {"time": start_time, "source": source} + if "time_0" not in self._model_arrival_latency_data[round_id]: + self._model_arrival_latency_data[round_id]["time_0"] = {"time": start_time, "source": source} - relative_time = start_time - self._model_arrival_latency_data[round_id]["time_0"]["time"] + relative_time = start_time - self._model_arrival_latency_data[round_id]["time_0"]["time"] - if source not in self._model_arrival_latency_data[round_id]: - self._model_arrival_latency_data[round_id][source] = { - "start_time": start_time, - "relative_time": relative_time, - } - # logging.info(f"self.model_arrival_latency_data: {self._model_arrival_latency_data}") - logging.info(f"Node {source} | Time taken relative to time_0: {relative_time:.3f} seconds") + if source not in self._model_arrival_latency_data[round_id]: + self._model_arrival_latency_data[round_id][source] = { + "start_time": start_time, + "relative_time": relative_time, + } + # logging.info(f"self.model_arrival_latency_data: {self._model_arrival_latency_data}") + logging.info(f"Node {source} | Time taken relative to time_0: {relative_time:.3f} seconds") - if message.round == self.get_round(): - logging.info(f"🤖 handle_model_message | message_round == current_round to node {source}") - elif message.round < self.get_round(): - logging.info(f"🤖 handle_model_message | message_round <= current_round to node {source}") + if message.round == self.get_round(): + logging.info(f"🤖 handle_model_message | message_round == current_round to node {source}") + elif message.round < self.get_round(): + logging.info(f"🤖 handle_model_message | message_round <= current_round to node {source}") + else: + logging.info(f"🤖 handle_model_message | message_round > current_round to node {source}") + + save_data( + self.config.participant["scenario_args"]["name"], + "model_arrival_latency", + source, + self.get_addr(), + num_round=message.round, + current_round=self.get_round(), + latency=relative_time, + ) else: - logging.info(f"🤖 handle_model_message | message_round > current_round to node {source}") - - save_data( - self.config.participant["scenario_args"]["name"], - "model_arrival_latency", - source, - self.get_addr(), - num_round=message.round, - latency=relative_time, - ) + logging.info("🤖 handle_model_message | Model arrival latency is disabled") - if cosine_value < 0.6: - logging.info("🤖 handle_model_message | Model similarity is less than 0.6") - self.rejected_nodes.add(source) + """ ############################## @@ -651,22 +675,18 @@ def learning_cycle_finished(self): return not (self.round < self.total_rounds) async def calculate_reputation(self): + logging.info(f"Calculating reputation at round {self.round}") + logging.info(f"Active metrics: {self.reputation_metrics}") logging.info(f"rejected nodes at round {self.round}: {self.rejected_nodes}") - if self.rejected_nodes is not None: - self.rejected_nodes.clear() + self.rejected_nodes.clear() logging.info(f"rejected nodes after clear at round {self.round}: {self.rejected_nodes}") - logging.info(f"change weight nodes at round {self.round}: {self.change_weight_nodes}") - if self.change_weight_nodes is not None: - self.change_weight_nodes.clear() - logging.info(f"change weight nodes after clear at round {self.round}: {self.change_weight_nodes}") - current_round = self.get_round() neighbors = set(await self.cm.get_addrs_current_connections(only_direct=True)) - reputation_with_weights = None + history_data = self.reputation_instance.history_data for nei in neighbors: - metric_messages_time, metric_similarity, metric_fraction, metric_model_arrival_latency = ( + metric_messages_number, metric_similarity, metric_fraction, metric_model_arrival_latency = ( self.reputation_instance.calculate_value_metrics( self.config.participant["scenario_args"]["name"], self.log_dir, @@ -674,165 +694,53 @@ async def calculate_reputation(self): self.addr, nei, current_round=current_round, + metrics_active=self.reputation_metrics, ) ) + + if self.weighting_factor == "dynamic": + self.reputation_instance.calculate_weighted_values( + metric_messages_number, + metric_similarity, + metric_fraction, + metric_model_arrival_latency, + history_data, + current_round, + self.addr, + nei, + self.reputation_metrics, + ) - # logging.info(f"metric_messages_time at round {self.round}: {metric_messages_time}") - # logging.info(f"metric_similarity at round {self.round}: {metric_similarity}") - # logging.info(f"metric_fraction at round {self.round}: {metric_fraction}") - # logging.info(f"metric_model_arrival_latency at round {self.round}: {metric_model_arrival_latency}") - - history_data = self.reputation_instance.history_data - self.reputation_instance.calculate_weighted_values( - metric_messages_time, - metric_similarity, - metric_fraction, - metric_model_arrival_latency, - history_data, - current_round, - self.addr, - nei, - ) - # logging.info(f"history_data after calculate_weighted_values at {self.round}: {history_data}") - - if current_round >= 5: - average_weights = {} - for metric_name in history_data.keys(): - valid_entries = [ - entry - for entry in history_data[metric_name] - if entry["round"] >= current_round and entry.get("weight") not in [None, -1] - ] - # logging.info(f"valid_entries for {metric_name} at round {self.round}: {valid_entries}") - - if valid_entries: - average_weight = sum([entry["weight"] for entry in valid_entries]) / len(valid_entries) - average_weights[metric_name] = average_weight - # logging.info(f"average_weight for {metric_name} at round {self.round}: {average_weight}") - else: - average_weights[metric_name] = 0 - - for nei in neighbors: - metric_messages_time_history = None - metric_similarity_history = None - metric_fraction_history = None - metric_model_arrival_latency_history = None - - for metric_name in history_data.keys(): - for entry in history_data.get(metric_name, []): - if entry["round"] == current_round and entry["nei"] == nei: - if metric_name == "messages_time": - metric_messages_time_history = entry["metric_value"] - elif metric_name == "similarity": - metric_similarity_history = entry["metric_value"] - elif metric_name == "fraction": - metric_fraction_history = entry["metric_value"] - elif metric_name == "model_arrival_latency": - metric_model_arrival_latency_history = entry["metric_value"] - break - - # logging.info(f"metric_messages_time_history at round {self.round}: {metric_messages_time_history}") - # logging.info(f"metric_similarity_history at round {self.round}: {metric_similarity_history}") - # logging.info(f"metric_fraction_history at round {self.round}: {metric_fraction_history}") - # logging.info( - # f"metric_model_arrival_latency_history at round {self.round}: {metric_model_arrival_latency_history}" - # ) - - logging.info(f"average_weights at round {self.round}: {average_weights}") - - if ( - metric_messages_time_history is not None - and metric_similarity_history is not None - and metric_fraction_history is not None - and metric_model_arrival_latency_history is not None - ): - reputation_with_weights = ( - metric_messages_time_history * average_weights["messages_time"] - + metric_similarity_history * average_weights["similarity"] - + metric_fraction_history * average_weights["fraction"] - + metric_model_arrival_latency_history * average_weights["model_arrival_latency"] - ) - logging.info(f"Reputation with weights: {reputation_with_weights}") - - metrics_data = { - "addr": self.addr.split(":")[0].strip(), - "nei": nei.split(":")[0].strip(), - "round": self.round, - "average_time_messages": average_weights["messages_time"], - "average_similarity": average_weights["similarity"], - "average_fraction": average_weights["fraction"], - "average_model_arrival_latency": average_weights["model_arrival_latency"], - } - - self.reputation_instance.metrics( - self.experiment_name, - metrics_data, - self.addr.split(":")[0].strip(), - nei.split(":")[0].strip(), - "reputation", - update_field="reputation_without_feedback", - ) - # logging.info(f"reputation_with_weights at round {self.round}: {reputation_with_weights}") - else: - reputation_with_weights = None - # logging.info(f"reputation_with_weights at round {self.round}: {reputation_with_weights}") - - if reputation_with_weights is not None: - avg_reputation = self.reputation_instance.save_reputation_history_in_memory( - self.addr, nei, reputation_with_weights, current_round - ) - logging.info(f"Average reputation for node {nei}: {avg_reputation}") - else: - avg_reputation = 0 - # logging.info(f"Average reputation for node {nei}: {avg_reputation}") - - if nei not in self.reputation: - self.reputation[nei] = { - "reputation": avg_reputation, - "round": current_round, - "last_feedback_round": -1, - } - else: - self.reputation[nei]["reputation"] = avg_reputation - self.reputation[nei]["round"] = current_round - - if self.reputation[nei]["reputation"] is not None: - metrics_data = { - "addr": self.addr.split(":")[0].strip(), - "nei": nei.split(":")[0].strip(), - "round": self.round, - "reputation_without_feedback": self.reputation[nei]["reputation"], - } - - self.reputation_instance.metrics( - self.experiment_name, - metrics_data, - self.addr.split(":")[0].strip(), - nei.split(":")[0].strip(), - "reputation", - update_field="model_arrival_latency", - ) - - if self.reputation[nei]["reputation"] is not None: - logging.info(f"Reputation of node {nei}: {self.reputation[nei]['reputation']}") - if self.reputation[nei]["reputation"] < 0.6: - self.rejected_nodes.add(nei) - logging.info(f"Rejected nodes: {self.rejected_nodes}") - # elif 0.6 < self.reputation[nei]["reputation"] < 0.8: - # logging.info(f"Change weight node: {nei}") - # self.change_weight_nodes.add(nei) - else: - # logging.info(f"No weights calculated at round {self.round}") - if self.with_reputation: - # logging.info("Reputation system enabled") - federation = self.config.participant["network_args"]["neighbors"].split() - self.reputation_instance.init_reputation( + if self.weighting_factor == "static" and current_round >= 5: + self.reputation_instance._calculate_static_reputation( self.addr, - federation_nodes=federation, - round_num=current_round, - last_feedback_round=-1, - scenario=self.experiment_name, + nei, + metric_messages_number, + metric_similarity, + metric_fraction, + metric_model_arrival_latency, + current_round, + self.weight_num_messages, + self.weight_model_similarity, + self.weight_fraction_params_changed, + self.weight_model_arrival_latency, ) + + if self.weighting_factor == "dynamic" and current_round >= 5: + await self.reputation_instance._calculate_dynamic_reputation(self.addr, + current_round, + neighbors) + + if current_round < 5 and self.with_reputation: + federation = self.config.participant["network_args"]["neighbors"].split() + self.reputation_instance.init_reputation( + self.addr, + federation_nodes=federation, + round_num=current_round, + last_feedback_round=-1, + scenario=self.experiment_name, + init_reputation = self.initial_reputation, + ) status = await self.include_feedback_in_reputation() if status: @@ -853,95 +761,125 @@ async def calculate_reputation(self): self.trainer._logger.log_data(reputation_dict_with_values, step=self.round) for nei, data in self.reputation.items(): - if nei not in self.reputation[nei]: - if data["reputation"] is not None: - neighbors_to_send = [neighbor for neighbor in neighbors if neighbor != nei] - - # message_data = self.cm.mm.generate_reputation_message( - # node_id=nei, - # score=data["reputation"], - # round=data["round"], - # ) + if data["reputation"] is not None: + neighbors_to_send = [neighbor for neighbor in neighbors if neighbor != nei] - # metrics_data = { - # "addr": self.addr.split(":")[0].strip(), - # "nei": nei.split(":")[0].strip(), - # "round": self.round, - # "reputation_with_feedback": data["reputation"], - # } - - # self.reputation_instance.metrics( - # self.experiment_name, - # metrics_data, - # self.addr.split(":")[0].strip(), - # nei.split(":")[0].strip(), - # "reputation", - # update_field="reputation_with_feedback", - # ) + for neighbor in neighbors_to_send: + message = self.cm.create_message( + "reputation", "share", node_id=nei, score=float(data["reputation"]), round=self.round + ) + await self.cm.send_message(neighbor, message) + logging.info( + f"Sending reputation to node {nei} from node {neighbor} with reputation {data['reputation']}" + ) - for neighbor in neighbors_to_send: - message = self.cm.create_message( - "reputation", "share", node_id=nei, score=data["reputation"], round=self.round - ) - await self.cm.send_message(neighbor, message) - logging.info( - f"Sending reputation to node {nei} from node {neighbor} with reputation {data['reputation']}" - ) - # await self.cm.send_message_to_neighbors(message_data, [neighbor]) + metrics_data = { + "addr": self.addr, + "nei": nei, + "round": current_round, + "reputation_with_feedback": data["reputation"], + } + + self.reputation_instance.metrics( + self.config.participant["scenario_args"]["name"], + metrics_data, + self.addr, + nei, + "reputation", + update_field="reputation_with_feedback", + ) else: logging.info(f"Reputation already sent to node {nei}") async def include_feedback_in_reputation(self): - data = None weight_current_reputation = 0.9 weight_feedback = 0.1 - if self.reputation_with_all_feedback is not None: - current_round = self.get_round() - for (current_node, node_ip, round_num), scores in self.reputation_with_all_feedback.items(): - if node_ip in self.reputation and "last_feedback_round" in self.reputation[node_ip]: - if self.reputation[node_ip]["last_feedback_round"] >= round_num: - continue + if self.reputation_with_all_feedback is None: + logging.info("No feedback received.") + return False + + current_round = self.get_round() + updated = False - logging.info( - f"current_node: {current_node} | node_ip: {node_ip} | round_num: {round_num} | scores: {scores}" - ) + for(current_node, node_ip, round_num), scores in self.reputation_with_all_feedback.items(): + if not scores: + logging.info(f"No feedback received for node {node_ip} in round {round_num}") + continue - if scores: - avg_feedback = sum(scores) / len(scores) - logging.info(f"Receive feedback to node {node_ip} with average score {avg_feedback}") - - logging.info(f"self.reputation: {self.reputation}") - if node_ip in self.reputation: - current_reputation = self.reputation[node_ip]["reputation"] - logging.info(f"Current reputation for node {node_ip}: {current_reputation}") - else: - logging.info(f"No node {node_ip} in reputation history.") - return False - - if current_reputation: - combined_reputation = (current_reputation * weight_current_reputation) + ( - avg_feedback * weight_feedback - ) - logging.info( - f"Combined reputation for node {node_ip} in round {round_num}: {combined_reputation}" - ) - else: - combined_reputation = current_reputation - logging.info(f"No reputation calculate for node {node_ip}.") + if node_ip not in self.reputation: + logging.info(f"No reputation for node {node_ip}") + continue - self.reputation[node_ip] = { - "reputation": combined_reputation, - "round": current_round, - "last_feedback_round": round_num, - } + if "last_feedback_round" in self.reputation[node_ip] and self.reputation[node_ip]["last_feedback_round"] >= round_num: + continue - logging.info(f"Updated self.reputation for {node_ip}: {self.reputation[node_ip]}") + avg_feedback = sum(scores) / len(scores) + logging.info(f"Receive feedback to node {node_ip} with average score {avg_feedback}") + current_reputation = self.reputation[node_ip]["reputation"] + if current_reputation is None: + logging.info(f"No reputation calculate for node {node_ip}.") + continue + + combined_reputation = (current_reputation * weight_current_reputation) + (avg_feedback * weight_feedback) + logging.info(f"Combined reputation for node {node_ip} in round {round_num}: {combined_reputation}") + + self.reputation[node_ip] = { + "reputation": combined_reputation, + "round": current_round, + "last_feedback_round": round_num, + } + updated = True + logging.info(f"Updated self.reputation for {node_ip}: {self.reputation[node_ip]}") + + if updated: return True else: - return False, None + return False + + # if self.reputation_with_all_feedback is not None: + # current_round = self.get_round() + # for (current_node, node_ip, round_num), scores in self.reputation_with_all_feedback.items(): + # if node_ip in self.reputation and "last_feedback_round" in self.reputation[node_ip]: + # if self.reputation[node_ip]["last_feedback_round"] >= round_num: + # continue + + # logging.info( + # f"current_node: {current_node} | node_ip: {node_ip} | round_num: {round_num} | scores: {scores}" + # ) + + # if scores: + # avg_feedback = sum(scores) / len(scores) + # logging.info(f"Receive feedback to node {node_ip} with average score {avg_feedback}") + + # logging.info(f"self.reputation: {self.reputation}") + # if node_ip in self.reputation: + # current_reputation = self.reputation[node_ip]["reputation"] + # logging.info(f"Current reputation for node {node_ip}: {current_reputation}") + # else: + # logging.info(f"No node {node_ip} in reputation history.") + # return False + + # if current_reputation: + # combined_reputation = (current_reputation * weight_current_reputation) + ( + # avg_feedback * weight_feedback + # ) + # logging.info( + # f"Combined reputation for node {node_ip} in round {round_num}: {combined_reputation}" + # ) + # else: + # combined_reputation = current_reputation + # logging.info(f"No reputation calculate for node {node_ip}.") + + # self.reputation[node_ip] = { + # "reputation": combined_reputation, + # "round": current_round, + # "last_feedback_round": round_num, + # } + + # logging.info(f"Updated self.reputation for {node_ip}: {self.reputation[node_ip]}") async def _learning_cycle(self): while self.round is not None and self.round < self.total_rounds: @@ -1060,7 +998,6 @@ async def _extended_learning_cycle(self): # ) # await self.cm.send_message_to_neighbors(message) - class MaliciousNode(Engine): def __init__( self, diff --git a/nebula/core/network/communications.py b/nebula/core/network/communications.py index 78afd7f1a..53a727210 100755 --- a/nebula/core/network/communications.py +++ b/nebula/core/network/communications.py @@ -440,7 +440,7 @@ def store_receive_timestamp(self, source, type_message, round=None): round = current_round save_data( self.config.participant["scenario_args"]["name"], - "time_message", + "number_message", source, self.addr, num_round=round, diff --git a/nebula/core/network/messages.py b/nebula/core/network/messages.py index 51ece9abd..f66ee3ea0 100644 --- a/nebula/core/network/messages.py +++ b/nebula/core/network/messages.py @@ -84,7 +84,7 @@ async def process_message(self, data, addr_from): logging.warning("Received message with no active field in the 'oneof'") return - if self.cm.engine.with_reputation: + if self.cm.engine.with_reputation and self.cm.engine.reputation_metrics.get("num_messages"): self.cm.store_receive_timestamp(addr_from, message_type, round=self.cm.get_round()) message_data = getattr(message_wrapper, message_type) diff --git a/nebula/core/reputation/Reputation.py b/nebula/core/reputation/Reputation.py index a0e018920..628faf213 100644 --- a/nebula/core/reputation/Reputation.py +++ b/nebula/core/reputation/Reputation.py @@ -2,6 +2,7 @@ import json import logging import os +import random from typing import TYPE_CHECKING, ClassVar import numpy as np @@ -24,7 +25,6 @@ def save_data( changed_params=None, threshold=None, changes_record=None, - message_id_decoded=None, latency=None, ): """ @@ -37,14 +37,11 @@ def save_data( time (float): Time taken to process the data. """ - source_ip = source_ip.split(":")[0] - addr = addr.split(":")[0] - try: combined_data = {} - if type_data == "time_message": - combined_data["time_message"] = { + if type_data == "number_message": + combined_data["number_message"] = { "time": time, "type_message": type_message, "round": num_round, @@ -63,6 +60,7 @@ def save_data( combined_data["model_arrival_latency"] = { "latency": latency, "round": num_round, + "round_received": current_round, } script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -87,23 +85,22 @@ def save_data( except Exception: logging.exception("Error saving data") - class Reputation: """ Class to define the reputation of a participant. """ reputation_history: ClassVar[dict] = {} - time_message_history: ClassVar[dict] = {} + number_message_history: ClassVar[dict] = {} neighbor_reputation_history: ClassVar[dict] = {} fraction_changed_history: ClassVar[dict] = {} - messages_time_message: ClassVar[list] = [] - previous_threshold_time_message: ClassVar[dict] = {} - previous_std_dev_time_message: ClassVar[dict] = {} + messages_number_message: ClassVar[list] = [] + previous_threshold_number_message: ClassVar[dict] = {} + previous_std_dev_number_message: ClassVar[dict] = {} messages_model_arrival_latency: ClassVar[dict] = {} model_arrival_latency_history: ClassVar[dict] = {} - previous_percentile_25_time_message: ClassVar[dict] = {} - previous_percentile_85_time_message: ClassVar[dict] = {} + previous_percentile_25_number_message: ClassVar[dict] = {} + previous_percentile_85_number_message: ClassVar[dict] = {} def __init__(self, engine: "Engine"): self._engine = engine @@ -115,8 +112,10 @@ def __init__(self, engine: "Engine"): def engine(self): return self._engine - def init_reputation(self, addr, federation_nodes=None, round_num=None, last_feedback_round=None, scenario=None): - logging.info("init_reputation | Reputation initialization started") + def init_reputation(self, addr, federation_nodes=None, round_num=None, last_feedback_round=None, scenario=None, init_reputation=None): + """ + Initialize the reputation system. + """ if not federation_nodes: logging.error("init_reputation | No federation nodes provided") return @@ -129,34 +128,36 @@ def init_reputation(self, addr, federation_nodes=None, round_num=None, last_feed logging.error("init_reputation | No neighbors found") return - # logging.info(f"init_reputation | Neighbors: {neighbors}") - reputation_initialized = 0.6 - for nei in neighbors: if nei not in self._engine.reputation: self._engine.reputation[nei] = { - "reputation": reputation_initialized, + "reputation": init_reputation, "round": round_num, "last_feedback_round": last_feedback_round, } elif self._engine.reputation[nei].get("reputation") is None: - self._engine.reputation[nei]["reputation"] = reputation_initialized + self._engine.reputation[nei]["reputation"] = init_reputation self._engine.reputation[nei]["round"] = round_num self._engine.reputation[nei]["last_feedback_round"] = last_feedback_round + avg_reputation = self.save_reputation_history_in_memory( + self._engine.addr, nei, init_reputation, round_num + ) + metrics_data = { - "addr": addr.split(":")[0].strip(), - "nei": nei.split(":")[0].strip(), + "addr": addr, + "nei": nei, "round": round_num, - "reputation_without_feedback": reputation_initialized, + "reputation_without_feedback": avg_reputation, } + self.metrics( scenario, metrics_data, - addr.split(":")[0].strip(), - nei.split(":")[0].strip(), + addr, + nei, "reputation", - update_field="model_arrival_latency", + update_field="reputation_without_feedback", ) def is_valid_ip(federation_nodes): @@ -164,39 +165,182 @@ def is_valid_ip(federation_nodes): Check if the IP addresses are valid. """ valid_ip = [] - for i in federation_nodes: - addr = f"{i.split(':')[0]}:{i.split(':')[1]}" - # logging.info(f"addr: {addr}") - valid_ip.append(addr) + for i in federation_nodes: + valid_ip.append(i) return valid_ip + def _calculate_static_reputation(self, addr, nei, metric_messages_number, metric_similarity, metric_fraction, metric_model_arrival_latency, current_round, + weight_messages_number, weight_similarity, weight_fraction, weight_model_arrival_latency): + """ + Calculate the static reputation of a participant. + + Args: + addr (str): The IP address of the participant. + nei (str): The IP address of the participant. + metric_messages_number (float): The number of messages. + metric_similarity (float): The similarity between models. + metric_fraction (float): The fraction of parameters changed. + metric_model_arrival_latency (float): The model arrival latency. + current_round (int): The current round number. + weight_messages_number (float): The weight of the number of messages. + weight_similarity (float): The weight of the similarity. + weight_fraction (float): The weight of the fraction. + weight_model_arrival_latency (float): The weight of the model arrival latency. + + Returns: + float: The static reputation of the participant. + """ + + static_weights = { + "num_messages": weight_messages_number, + "model_similarity": weight_similarity, + "fraction_parameters_changed": weight_fraction, + "model_arrival_latency": weight_model_arrival_latency, + } + + metric_values = { + "num_messages": metric_messages_number, + "model_similarity": metric_similarity, + "fraction_parameters_changed": metric_fraction, + "model_arrival_latency": metric_model_arrival_latency, + } + + reputation_static = sum( + metric_values[metric_name] * static_weights[metric_name] for metric_name in static_weights + ) + logging.info(f"Static reputation for node {nei} at round {self.engine.get_round()}: {reputation_static}") + + avg_reputation = self.save_reputation_history_in_memory( + self.engine.addr, nei, reputation_static, current_round + ) + + metrics_data = { + "addr": addr, + "nei": nei, + "round": self.engine.get_round(), + "reputation_without_feedback": avg_reputation, + } + + for metric_name in metric_values: + metrics_data[f"average_{metric_name}"] = static_weights[metric_name] + + self._update_reputation_record(nei, avg_reputation, current_round, metrics_data) + + async def _calculate_dynamic_reputation(self, addr, current_round, neighbors): + """ + Calculate the dynamic reputation of a participant. + + Args: + addr (str): The IP address of the participant. + current_round (int): The current round number. + neighbors (list): The list of neighbors. + + Returns: + dict: The dynamic reputation of the participant. + """ + average_weights = {} + + for metric_name in self.history_data.keys(): + if self.engine.reputation_metrics.get(metric_name, False): + valid_entries = [ + entry for entry in self.history_data[metric_name] + if entry["round"] >= current_round and entry.get("weight") not in [None, -1] + ] + + if valid_entries: + average_weight = sum([entry["weight"] for entry in valid_entries]) / len(valid_entries) + average_weights[metric_name] = average_weight + else: + average_weights[metric_name] = 0 + + for nei in neighbors: + metric_values = {} + for metric_name in self.history_data.keys(): + if self.engine.reputation_metrics.get(metric_name, False): + for entry in self.history_data.get(metric_name, []): + if entry["round"] == current_round and entry["metric_name"] == metric_name and entry["nei"] == nei: + metric_values[metric_name] = entry["metric_value"] + break + + if all(metric_name in metric_values for metric_name in average_weights): + reputation_with_weights = sum( + metric_values.get(metric_name, 0) * average_weights[metric_name] + for metric_name in average_weights + ) + logging.info(f"Dynamic reputation with weights for {nei} at round {self.engine.get_round()}: {reputation_with_weights}") + + avg_reputation = self.save_reputation_history_in_memory( + self.engine.addr, nei, reputation_with_weights, current_round + ) + + metrics_data = { + "addr": addr, + "nei": nei, + "round": self.engine.get_round(), + "reputation_without_feedback": avg_reputation, + } + + for metric_name in metric_values: + metrics_data[f"average_{metric_name}"] = average_weights[metric_name] + + self._update_reputation_record(nei, avg_reputation, current_round, metrics_data) + + def _update_reputation_record(self, nei, reputation, current_round, data): + """ + Update the reputation record of a participant. + + Args: + nei (str): The IP address of the participant. + reputation (float): The reputation of the participant. + current_round (int): The current round number. + """ + if nei not in self.engine.reputation: + self.engine.reputation[nei] = { + "reputation": reputation, + "round": current_round, + "last_feedback_round": -1, + } + else: + self.engine.reputation[nei]["reputation"] = reputation + self.engine.reputation[nei]["round"] = current_round + + logging.info(f"Reputation of node {nei}: {self.engine.reputation[nei]['reputation']}") + if self.engine.reputation[nei]["reputation"] < 0.6: + self.engine.rejected_nodes.add(nei) + logging.info(f"Rejected node {nei} at round {self.engine.get_round()}") + + self.metrics( + self.engine.experiment_name, + data, + self.engine.addr, + nei, + "reputation", + update_field="reputation_without_feedback", + ) + @staticmethod def calculate_weighted_values( - avg_messages_time_message_normalized, + avg_messages_number_message_normalized, similarity_reputation, fraction_score_asign, avg_model_arrival_latency, history_data: dict, current_round, addr, - nei + nei, + reputation_metrics ): """ Calculate the weighted values for each metric. """ if current_round is not None: - logging.info(f"Values before normalization") - logging.info(f"avg_messages_time_message_normalized: {avg_messages_time_message_normalized}") - logging.info(f"similarity_reputation: {similarity_reputation}") - logging.info(f"fraction_score_asign: {fraction_score_asign}") - logging.info(f"avg_model_arrival_latency: {avg_model_arrival_latency}") normalized_weights = {} required_keys = [ - "messages_time", - "similarity", - "fraction", + "num_messages", + "model_similarity", + "fraction_parameters_changed", "model_arrival_latency", ] @@ -205,13 +349,16 @@ def calculate_weighted_values( history_data[key] = [] metrics = { - "messages_time": avg_messages_time_message_normalized, - "similarity": similarity_reputation, - "fraction": fraction_score_asign, + "num_messages": avg_messages_number_message_normalized, + "model_similarity": similarity_reputation, + "fraction_parameters_changed": fraction_score_asign, "model_arrival_latency": avg_model_arrival_latency, } - for metric_name, current_value in metrics.items(): + active_metrics = {k: v for k, v in metrics.items() if reputation_metrics.get(k, False)} + num_active_metrics = len(active_metrics) + + for metric_name, current_value in active_metrics.items(): history_data[metric_name].append({ "round": current_round, "addr": addr, @@ -223,48 +370,41 @@ def calculate_weighted_values( adjusted_weights = {} - if current_round >= 5: + if current_round >= 5 and num_active_metrics > 0: desviations = {} - for metric_name, current_value in metrics.items(): + for metric_name, current_value in active_metrics.items(): historical_values = history_data[metric_name] metric_values = [entry['metric_value'] for entry in historical_values if 'metric_value' in entry and entry["metric_value"] != 0] - logging.info(f"metric_name: {metric_name}, metric_values: {metric_values}") if metric_values: mean_value = np.mean(metric_values) - std_value = np.std(metric_values) else: - mean_value = -1 - std_value = 1 + mean_value = 0 deviation = abs(current_value - mean_value) desviations[metric_name] = deviation - logging.info(f"Current value: {current_value}") - logging.info(f"{metric_name} - Mean: {mean_value}, Std: {std_value}, Deviation: {deviation}") - - logging.info(f"desviations: {desviations}") if all(deviation == 0.0 for deviation in desviations.values()): - weight_per_metric = 1 / len(metrics) - normalized_weights = {metric_name: weight_per_metric for metric_name in metrics} - logging.info(f"All deviations are 0.0. Distributing weights equally: {normalized_weights}") + random_weights = [random.random() for _ in range(num_active_metrics)] + total_random_weight = sum(random_weights) + normalized_weights = {metric_name: weight / total_random_weight for metric_name, weight in zip(active_metrics, random_weights)} else: max_desviation = max(desviations.values()) if desviations else 1 - logging.info(f"max_desviation: {max_desviation}") normalized_weights = { metric_name: (desviation / max_desviation) for metric_name, desviation in desviations.items() } total_weight = sum(normalized_weights.values()) - normalized_weights = { - metric_name: weight / total_weight for metric_name, weight in normalized_weights.items() - } - logging.info(f"total_weight: {total_weight}, normalized_weights: {normalized_weights}") + if total_weight > 0: + normalized_weights = { + metric_name: weight / total_weight for metric_name, weight in normalized_weights.items() + } + else: + normalized_weights = {metric_name: 1 / num_active_metrics for metric_name in active_metrics} mean_deviation = np.mean(list(desviations.values())) dynamic_min_weight = max(0.1, mean_deviation / (mean_deviation + 1)) - logging.info(f"Dynamic minimum weight: {dynamic_min_weight}") total_adjusted_weight = 0 @@ -279,42 +419,36 @@ def calculate_weighted_values( for metric_name in adjusted_weights: adjusted_weights[metric_name] /= total_adjusted_weight total_adjusted_weight = 1 - - logging.info(f"adjusted_weights: {adjusted_weights}, total_adjusted_weight: {total_adjusted_weight}") else: - # Asignar pesos predeterminados si current_round < 5 - adjusted_weights = {metric_name: 1 / len(metrics) for metric_name in metrics} - logging.info(f"Initial round. Using default weights: {adjusted_weights}") + adjusted_weights = {metric_name: 1 / num_active_metrics for metric_name in active_metrics} - # Ahora actualizar los pesos en history_data - for metric_name, current_value in metrics.items(): + for metric_name, current_value in active_metrics.items(): weight = adjusted_weights.get(metric_name, -1) for entry in history_data[metric_name]: - if entry["metric_name"] == metric_name and entry["round"] == current_round: + if entry["metric_name"] == metric_name and entry["round"] == current_round and entry["nei"] == nei: entry["weight"] = weight - def calculate_value_metrics(self, scenario, log_dir, id_node, addr, nei, current_round=None): + def calculate_value_metrics(self, scenario, log_dir, id_node, addr, nei, current_round=None, metrics_active=None): """ Calculate the reputation of each participant based on the data stored. Args: scenario (str): Scenario name. """ - logging.info(f"id_node: {id_node}, addr: {addr}, nei: {nei}") - addr = addr.split(":")[0].strip() - nei_with_port = nei - nei = nei.split(":")[0].strip() - - messages_time_message_normalized = 0 - messages_time_message_count = 0 - avg_messages_time_message_normalized = 0 + + messages_number_message_normalized = 0 + messages_number_message_count = 0 + avg_messages_number_message_normalized = 0 fraction_score = 0 fraction_score_normalized = 0 fraction_score_asign = 0 messages_model_arrival_latency_normalized = 0 avg_model_arrival_latency = 0 + similarity_reputation = 0 fraction_neighbors_scores = None + logging.info(f"Metrics active: {metrics_active}") + try: script_dir = os.path.dirname(os.path.abspath(__file__)) file_name = f"{addr}_storing_{nei}_info.json" @@ -325,29 +459,25 @@ def calculate_value_metrics(self, scenario, log_dir, id_node, addr, nei, current with open(full_file_path) as json_file: all_metrics = json.load(json_file) for metric in all_metrics: - if "time_message" in metric: - round_time = metric["time_message"]["round"] - current_round_time = metric["time_message"]["current_round"] - time = metric["time_message"]["time"] - type_message = metric["time_message"]["type_message"] + if "number_message" in metric and metrics_active.get("num_messages", False): + round_time = metric["number_message"]["round"] + current_round_time = metric["number_message"]["current_round"] + time = metric["number_message"]["time"] + type_message = metric["number_message"]["type_message"] previous_round_time = current_round - 1 - # logging.info(f"Current round time: {current_round_time}, previous round time: {previous_round_time}") if round_time == previous_round_time: - # logging.info(f"Time message: {time}, type message: {type_message}, round: {round_time}") - Reputation.messages_time_message.append({ - "time_message": time, + Reputation.messages_number_message.append({ + "number_message": time, "type_message": type_message, "round": round_time, "current_round": current_round_time, "key": (addr, nei), }) - if "fraction_of_params_changed" in metric: + + if "fraction_of_params_changed" in metric and metrics_active.get("fraction_parameters_changed", False): round_fraction = metric["fraction_of_params_changed"]["round"] - total_params = metric["fraction_of_params_changed"]["total_params"] - changed_params = metric["fraction_of_params_changed"]["changed_params"] fraction_changed = metric["fraction_of_params_changed"]["fraction_changed"] threshold = metric["fraction_of_params_changed"]["threshold"] - changes_record = metric["fraction_of_params_changed"]["changes_record"] if round_fraction == current_round: fraction_score_normalized = Reputation.analyze_anomalies( addr, @@ -356,18 +486,15 @@ def calculate_value_metrics(self, scenario, log_dir, id_node, addr, nei, current current_round, fraction_changed, threshold, - changes_record, - changed_params, - total_params, scenario, ) - # logging.info(f"Fraction score normalized: {fraction_score_normalized}") - if "model_arrival_latency" in metric: + + if "model_arrival_latency" in metric and metrics_active.get("model_arrival_latency", False): round_latency = metric["model_arrival_latency"]["round"] + round_received = metric["model_arrival_latency"]["round_received"] latency = metric["model_arrival_latency"]["latency"] score_asigned = None - if round_latency == current_round: - # logging.info("model_arrival_latency from round == current_round") + if round_received == current_round: messages_model_arrival_latency_normalized = Reputation.manage_model_arrival_latency( round_latency, addr, @@ -376,46 +503,37 @@ def calculate_value_metrics(self, scenario, log_dir, id_node, addr, nei, current scenario, self.model_arrival_latency_data, current_round, + self.engine.config.participant["aggregator_args"]["aggregation_timeout"], ) - # logging.info( - # f"messages_model_arrival_latency_normalized: {messages_model_arrival_latency_normalized}" - # ) - elif round_latency < current_round: - # logging.info("model_arrival_latency from round < current_round") - if current_round in Reputation.model_arrival_latency_history: - # logging.info("model_arrival_latency from round < current_round") - if nei in Reputation.model_arrival_latency_history[current_round]: - # logging.info("nei in model_arrival_latency_history") - if "latency" in Reputation.model_arrival_latency_history[current_round][nei]: - # logging.info("latency in model_arrival_latency_history") - if "score" in Reputation.model_arrival_latency_history[current_round][nei]: - score_asigned = Reputation.model_arrival_latency_history[current_round][ - nei - ]["score"] - - if score_asigned is None: - for round in range(current_round, current_round + 1): - latency = self.engine.config.participant["aggregator_args"][ - "aggregation_timeout" - ] - messages_model_arrival_latency_normalized = ( - Reputation.manage_model_arrival_latency( - round_latency, - addr, - nei, - latency, - scenario, - self.model_arrival_latency_data, - current_round, - ) - ) - # logging.info( - # f"messages_model_arrival_latency_normalized: {messages_model_arrival_latency_normalized}" - # ) - - if current_round >= 5: + # elif round_latency < current_round: + # if current_round in Reputation.model_arrival_latency_history: + # if nei in Reputation.model_arrival_latency_history[current_round]: + # if "latency" in Reputation.model_arrival_latency_history[current_round][nei]: + # if "score" in Reputation.model_arrival_latency_history[current_round][nei]: + # score_asigned = Reputation.model_arrival_latency_history[current_round][ + # nei + # ]["score"] + + # if score_asigned is None: + # for round in range(current_round, current_round + 1): + # latency = self.engine.config.participant["aggregator_args"][ + # "aggregation_timeout" + # ] + # messages_model_arrival_latency_normalized = ( + # Reputation.manage_model_arrival_latency( + # round_latency, + # addr, + # nei, + # latency, + # scenario, + # self.model_arrival_latency_data, + # current_round, + # ) + # ) + + if current_round >= 5 and metrics_active.get("model_similarity", False): similarity_file = os.path.join(log_dir, f"participant_{id_node}_similarity.csv") - similarity_reputation = Reputation.read_similarity_file(similarity_file, nei) + similarity_reputation = Reputation.read_similarity_file(similarity_file, nei, current_round) else: similarity_reputation = 0 @@ -427,73 +545,46 @@ def calculate_value_metrics(self, scenario, log_dir, id_node, addr, nei, current avg_model_arrival_latency = Reputation.model_arrival_latency_history[(addr, nei)][ current_round - 1 ]["score"] - # logging.info(f"Avg model_arrival_latency latency is None and current_round = {current_round}, model_arrival_latency: {avg_model_arrival_latency}") - if Reputation.messages_time_message is not None: - messages_time_message_normalized, messages_time_message_count = ( - Reputation.manage_metric_time_message( - Reputation.messages_time_message, addr, nei, current_round, scenario + if Reputation.messages_number_message is not None: + messages_number_message_normalized, messages_number_message_count = ( + Reputation.manage_metric_number_message( + Reputation.messages_number_message, addr, nei, current_round, scenario, metrics_active.get("num_messages", False) ) ) - # logging.info(f"Messages time_message normalized: {messages_time_message_normalized}") - avg_messages_time_message_normalized = Reputation.save_time_message_history( - addr, nei, messages_time_message_normalized, current_round + avg_messages_number_message_normalized = Reputation.save_number_message_history( + addr, nei, messages_number_message_normalized, current_round ) - # logging.info(f"Avg messages time_message normalized: {avg_messages_time_message_normalized}") - if avg_messages_time_message_normalized is None and current_round > 4: - avg_messages_time_message_normalized = Reputation.time_message_history[(addr, nei)][ - current_round - 1 - ]["avg_time_message"] - # (f"Avg messages time_message is None and curret_round = {current_round}, avg_messages_time_message_normalized: {avg_messages_time_message_normalized}") + if avg_messages_number_message_normalized is None and current_round > 4: + avg_messages_number_message_normalized = Reputation.number_message_history[(addr, nei)][current_round - 1]["avg_number_message"] if current_round >= 5: if fraction_score_normalized > 0: key_previous_round = (addr, nei, current_round - 1) if current_round - 1 > 0 else None fraction_previous_round = None - if ( - key_previous_round is not None - and key_previous_round in Reputation.fraction_changed_history - ): - fraction_score = Reputation.fraction_changed_history[key_previous_round].get( - "fraction_score" - ) + if (key_previous_round is not None and key_previous_round in Reputation.fraction_changed_history): + fraction_score = Reputation.fraction_changed_history[key_previous_round].get("fraction_score") fraction_previous_round = fraction_score if fraction_score is not None else None - # logging.info(f"Fraction score previous round: {fraction_previous_round}") if fraction_previous_round is not None: fraction_score_asign = fraction_score_normalized * 0.8 + fraction_previous_round * 0.2 - # logging.info(f"Fraction score normalized: {fraction_score_asign}") - Reputation.fraction_changed_history[(addr, nei, current_round)]["fraction_score"] = ( - fraction_score_asign - ) + Reputation.fraction_changed_history[(addr, nei, current_round)]["fraction_score"] = (fraction_score_asign) else: fraction_score_asign = fraction_score_normalized - # logging.info(f"Fraction score normalized: {fraction_score_asign}") - Reputation.fraction_changed_history[(addr, nei, current_round)]["fraction_score"] = ( - fraction_score_asign - ) + Reputation.fraction_changed_history[(addr, nei, current_round)]["fraction_score"] = (fraction_score_asign) else: - # logging.info(f"No fraction score to assign") fraction_previous_round = None key_previous_round = (addr, nei, current_round - 1) if current_round - 1 > 0 else None - if ( - key_previous_round is not None - and key_previous_round in Reputation.fraction_changed_history - ): - fraction_score = Reputation.fraction_changed_history[key_previous_round].get( - "fraction_score" - ) + if (key_previous_round is not None and key_previous_round in Reputation.fraction_changed_history): + fraction_score = Reputation.fraction_changed_history[key_previous_round].get("fraction_score") fraction_previous_round = fraction_score if fraction_score is not None else None - # logging.info(f"Fraction score previous round: {fraction_previous_round}") if fraction_previous_round is not None: fraction_score_asign = fraction_previous_round - (fraction_previous_round * 0.5) - # logging.info(f"Fraction score normalized: {fraction_score_asign}") else: if fraction_neighbors_scores is None: fraction_neighbors_scores = {} - # logging.info(f"fraction_neighbors_scores: {fraction_neighbors_scores}") for key, value in Reputation.fraction_changed_history.items(): score = value.get("fraction_score") @@ -509,8 +600,8 @@ def calculate_value_metrics(self, scenario, log_dir, id_node, addr, nei, current # Create graphics to metrics self.create_graphics_to_metrics( - messages_time_message_count, - avg_messages_time_message_normalized, + messages_number_message_count, + avg_messages_number_message_normalized, similarity_reputation, fraction_score_asign, avg_model_arrival_latency, @@ -521,14 +612,14 @@ def calculate_value_metrics(self, scenario, log_dir, id_node, addr, nei, current scenario, ) - return avg_messages_time_message_normalized, similarity_reputation, fraction_score_asign, avg_model_arrival_latency + return avg_messages_number_message_normalized, similarity_reputation, fraction_score_asign, avg_model_arrival_latency except Exception as e: logging.exception(f"Error calculating reputation. Type: {type(e).__name__}") def create_graphics_to_metrics( self, - time_message_count, - time_message_norm, + number_message_count, + number_message_norm, similarity, fraction, model_arrival_latency, @@ -543,23 +634,18 @@ def create_graphics_to_metrics( """ if current_round is not None and current_round < total_rounds: - model_arrival_latency_dict = {f"R-Model_arrival_latency_reputation/{addr}": {nei: model_arrival_latency}} - - messages_time_message_count_dict = { - f"R-Count_messages_time_message_reputation/{addr}": {nei: time_message_count} - } - - messages_time_message_norm_dict = {f"R-time_message_reputation/{addr}": {nei: time_message_norm}} + model_arrival_latency_dict = {f"R-Model_arrival_latency_reputation/{addr}": {nei: model_arrival_latency}} + messages_number_message_count_dict = {f"R-Count_messages_number_message_reputation/{addr}": {nei: number_message_count}} + messages_number_message_norm_dict = {f"R-number_message_reputation/{addr}": {nei: number_message_norm}} similarity_dict = {f"R-Similarity_reputation/{addr}": {nei: similarity}} - fraction_dict = {f"R-Fraction_reputation/{addr}": {nei: fraction}} - if messages_time_message_count_dict is not None: - self.engine.trainer._logger.log_data(messages_time_message_count_dict, step=current_round) + if messages_number_message_count_dict is not None: + self.engine.trainer._logger.log_data(messages_number_message_count_dict, step=current_round) - if messages_time_message_norm_dict is not None: - self.engine.trainer._logger.log_data(messages_time_message_norm_dict, step=current_round) + if messages_number_message_norm_dict is not None: + self.engine.trainer._logger.log_data(messages_number_message_norm_dict, step=current_round) if similarity_dict is not None: self.engine.trainer._logger.log_data(similarity_dict, step=current_round) @@ -574,8 +660,8 @@ def create_graphics_to_metrics( "addr": addr, "nei": nei, "round": current_round, - "time_message_count": time_message_count, - "time_message_norm": time_message_norm, + "number_message_count": number_message_count, + "number_message_norm": number_message_norm, "similarity": similarity, "fraction": fraction, "model_arrival_latency": model_arrival_latency, @@ -590,9 +676,6 @@ def analyze_anomalies( current_round, fraction_changed, threshold, - changes_record, - changed_params, - total_params, scenario, ): """ @@ -605,9 +688,6 @@ def analyze_anomalies( current_round (int): Current round number. fraction_changed (float): Fraction of parameters changed. threshold (float): Threshold value. - changes_record (list): List of changes. - changed_params (int): Number of changed parameters. - total_params (int): Total number of parameters. scenario (str): The scenario name for logging and metric storage. Returns: @@ -640,7 +720,6 @@ def analyze_anomalies( "std_dev_threshold": None, } - # Calcular y almacenar estadísticas solo hasta la ronda 4 if round_num < 5: past_fractions = [] past_thresholds = [] @@ -653,17 +732,13 @@ def analyze_anomalies( if past_fractions: mean_fraction = np.mean(past_fractions) - # logging.info(f"Round: {round_num}, Mean fraction: {mean_fraction}") std_dev_fraction = np.std(past_fractions) - # logging.info(f"Round: {round_num}, Std dev fraction: {std_dev_fraction}") Reputation.fraction_changed_history[key]["mean_fraction"] = mean_fraction Reputation.fraction_changed_history[key]["std_dev_fraction"] = std_dev_fraction if past_thresholds: mean_threshold = np.mean(past_thresholds) - # logging.info(f"Round: {round_num}, Mean threshold: {mean_threshold}") std_dev_threshold = np.std(past_thresholds) - # logging.info(f"Round: {round_num}, Std dev threshold: {std_dev_threshold}") Reputation.fraction_changed_history[key]["mean_threshold"] = mean_threshold Reputation.fraction_changed_history[key]["std_dev_threshold"] = std_dev_threshold @@ -674,9 +749,7 @@ def analyze_anomalies( prev_key = (addr, nei, round_num - 1) if prev_key not in Reputation.fraction_changed_history: for i in range(0, round_num + 1): - # logging.info(f"Round: {round_num}, i = {i}") potential_prev_key = (addr, nei, round_num - i) - # logging.info(f"Round: {round_num}, Potential previous key: {potential_prev_key}") if potential_prev_key in Reputation.fraction_changed_history: mean_fraction_prev = Reputation.fraction_changed_history[potential_prev_key][ "mean_fraction" @@ -685,40 +758,29 @@ def analyze_anomalies( prev_key = potential_prev_key break - # logging.info(f"Round: {round_num}, Previous key: {prev_key}") if prev_key: mean_fraction_prev = Reputation.fraction_changed_history[prev_key]["mean_fraction"] std_dev_fraction_prev = Reputation.fraction_changed_history[prev_key]["std_dev_fraction"] mean_threshold_prev = Reputation.fraction_changed_history[prev_key]["mean_threshold"] std_dev_threshold_prev = Reputation.fraction_changed_history[prev_key]["std_dev_threshold"] - # logging.info(f"Round: {round_num}, Mean fraction: {mean_fraction_prev}, Std dev fraction: {std_dev_fraction_prev}, Mean threshold: {mean_threshold_prev}, Std dev threshold: {std_dev_threshold_prev}") current_fraction = Reputation.fraction_changed_history[key]["fraction_changed"] current_threshold = Reputation.fraction_changed_history[key]["threshold"] - # logging.info(f"Round: {round_num}, Current fraction: {current_fraction}, Current threshold: {current_threshold}") - # low_mean_fraction_prev = mean_fraction_prev - std_dev_fraction_prev upper_mean_fraction_prev = (mean_fraction_prev + std_dev_fraction_prev) * 1.05 - # low_mean_threshold_prev = mean_threshold_prev - std_dev_threshold_prev upper_mean_threshold_prev = (mean_threshold_prev + std_dev_threshold_prev) * 1.10 - # logging.info(f"Round: {round_num}, Upper mean fraction: {upper_mean_fraction_prev}, Upper mean threshold: {upper_mean_threshold_prev}") - # fraction_anomaly = not (low_mean_fraction_prev <= current_fraction <= upper_mean_fraction_prev) - # threshold_anomaly = not (low_mean_threshold_prev <= current_threshold <= upper_mean_threshold_prev) fraction_anomaly = current_fraction > upper_mean_fraction_prev threshold_anomaly = current_threshold > upper_mean_threshold_prev - # logging.info(f"Round: {round_num}, Fraction anomaly: {fraction_anomaly}, Threshold anomaly: {threshold_anomaly}") Reputation.fraction_changed_history[key]["fraction_anomaly"] = fraction_anomaly Reputation.fraction_changed_history[key]["threshold_anomaly"] = threshold_anomaly penalization_factor_fraction = abs(current_fraction - mean_fraction_prev) / mean_fraction_prev if mean_fraction_prev != 0 else 1 penalization_factor_threshold = abs(current_threshold - mean_threshold_prev) / mean_threshold_prev if mean_threshold_prev != 0 else 1 - # logging.info(f"Round: {round_num}, Penalization factor fraction: {penalization_factor_fraction}, Penalization factor threshold: {penalization_factor_threshold}") k_fraction = penalization_factor_fraction if penalization_factor_fraction != 0 else 1 k_threshold = penalization_factor_threshold if penalization_factor_threshold != 0 else 1 - # logging.info(f"Round: {round_num}, K fraction: {k_fraction}, K threshold: {k_threshold}") if fraction_anomaly: fraction_value = ( @@ -746,16 +808,12 @@ def analyze_anomalies( else 0 ) - # logging.info(f"Round: {round_num}, Fraction value: {fraction_value}, Threshold value: {threshold_value}") fraction_weight = 0.5 threshold_weight = 0.5 fraction_score = fraction_weight * fraction_value + threshold_weight * threshold_value - # logging.info(f"Round: {round_num}, Fraction score: {fraction_score}") - # Reputation.fraction_changed_history[key]["fraction_score"] = fraction_score - # Upload the values to the history Reputation.fraction_changed_history[key]["mean_fraction"] = (current_fraction + mean_fraction_prev) / 2 Reputation.fraction_changed_history[key]["std_dev_fraction"] = np.sqrt(((current_fraction - mean_fraction_prev) ** 2 + std_dev_fraction_prev**2) / 2) Reputation.fraction_changed_history[key]["mean_threshold"] = (current_threshold + mean_threshold_prev) / 2 @@ -792,63 +850,9 @@ def analyze_anomalies( logging.exception("Error analyzing anomalies") return -1 - @staticmethod - def save_time_message_history(addr, nei, messages_time_message_normalized, current_round): - """ - Save the time_message history of a participant (addr) regarding its neighbor (nei) in memory. - - Args: - addr (str): The identifier of the node whose time_message history is being saved. - nei (str): The neighboring node involved. - messages_time_message_normalized (float): The time_message value to be saved. - current_round (int): The current round number. - - Returns: - float: The cumulative time_message including the current round. - """ - - try: - key = (addr, nei) - avg_time_message = 0 - - if key not in Reputation.time_message_history: - Reputation.time_message_history[key] = {} - - Reputation.time_message_history[key][current_round] = {"time_message": messages_time_message_normalized} - - # logging.info(f"time_message: {messages_time_message_normalized}") - # logging.info(f"time_message history: {Reputation.time_message_history}") - - # rounds = Reputation.time_message_history[key] - if messages_time_message_normalized != 0 and current_round > 4: - previous_avg = ( - Reputation.time_message_history[key].get(current_round - 1, {}).get("avg_time_message", None) - ) - # logging.info(f"Previous avg time_message: {previous_avg}") - if previous_avg is not None: - avg_time_message = messages_time_message_normalized * 0.8 + previous_avg * 0.2 - # logging.info(f"Avg time_message in if: {avg_time_message}") - else: - avg_time_message = messages_time_message_normalized - # logging.info(f"Avg time_message in else: {avg_time_message}") - - Reputation.time_message_history[key][current_round]["avg_time_message"] = avg_time_message - else: - avg_time_message = 0 - - # logging.info(f"Avg time_message: {avg_time_message}") - return avg_time_message - except Exception: - logging.exception("Error saving time_message history") - return -1 - - except Exception as e: - logging.exception(f"Error managing model_arrival_latency latency: {e}") - return 0.0 - @staticmethod def manage_model_arrival_latency( - round_num, addr, nei, latency, scenario, model_arrival_latency_data, current_round + round_num, addr, nei, latency, scenario, model_arrival_latency_data, current_round, aggregation_timeout ): """ Manage the model_arrival_latency metric with persistent storage of mean latency. @@ -910,12 +914,14 @@ def manage_model_arrival_latency( latency = 0.5 difference = latency - prev_mean_latency - if latency <= prev_mean_latency or abs(difference) <= prev_mean_latency: + # if latency <= prev_mean_latency or abs(difference) <= prev_mean_latency: + if latency <= prev_mean_latency or latency <= aggregation_timeout: score = 1.0 else: score = 1 / (1 + np.exp(abs(difference) / prev_mean_latency)) if round_num < current_round: + logging.info(f"Round: {round_num} < Current round: {current_round} to node {nei}") round_diff = current_round - round_num penalty_factor = round_diff * 0.1 penalty = penalty_factor * (1 - score) @@ -961,35 +967,96 @@ def manage_model_arrival_latency( return 0 @staticmethod - def manage_metric_time_message(messages_time_message, addr, nei, current_round, scenario): + def save_model_arrival_latency_history(addr, nei, model_arrival_latency, round_num): + """ + Save the model_arrival_latency history of a participant (addr) regarding its neighbor (nei) in memory. + + Args: + addr (str): The identifier of the node whose model_arrival_latency history is being saved. + nei (str): The neighboring node involved. + model_arrival_latency (float): The model_arrival_latency value to be saved. + current_round (int): The current round number. + + Returns: + float: The cumulative model_arrival_latency including the current round. + """ + try: + current_key = nei + + if round_num not in Reputation.model_arrival_latency_history: + Reputation.model_arrival_latency_history[round_num] = {} + + if current_key not in Reputation.model_arrival_latency_history[round_num]: + Reputation.model_arrival_latency_history[round_num][current_key] = {} + + Reputation.model_arrival_latency_history[round_num][current_key].update({ + "score": model_arrival_latency, + }) + + if model_arrival_latency > 0 and round_num > 5: + previous_avg = ( + Reputation.model_arrival_latency_history.get(round_num - 1, {}) + .get(current_key, {}) + .get("avg_model_arrival_latency", None) + ) + + if previous_avg is not None: + avg_model_arrival_latency = ( + model_arrival_latency * 0.8 + previous_avg * 0.2 + if previous_avg is not None + else model_arrival_latency + ) + else: + avg_model_arrival_latency = model_arrival_latency - (model_arrival_latency * 0.05) + elif model_arrival_latency == 0 and round_num > 5: + previous_avg = ( + Reputation.model_arrival_latency_history.get(round_num - 1, {}) + .get(current_key, {}) + .get("avg_model_arrival_latency", None) + ) + avg_model_arrival_latency = previous_avg - (previous_avg * 0.05) + else: + avg_model_arrival_latency = model_arrival_latency + + Reputation.model_arrival_latency_history[round_num][current_key]["avg_model_arrival_latency"] = ( + avg_model_arrival_latency + ) + + return avg_model_arrival_latency + except Exception: + logging.exception("Error saving model_arrival_latency history") + + @staticmethod + def manage_metric_number_message(messages_number_message, addr, nei, current_round, scenario, metric_active=True): """ - Manage the time_message metric using percentiles for normalization, considering the last 5 rounds dynamically. + Manage the number_message metric using percentiles for normalization, considering the last 5 rounds dynamically. Args: - messages_time_message (list): List of messages time_message. + messages_number_message (list): List of messages number_message. addr (str): Source IP address. nei (str): Destination IP address. current_round (int): Current round number. Returns: - float: Normalized time_message value. + float: Normalized number_message value. int: Messages count. """ try: if current_round == 0: return 0.0, 0 + if not metric_active: + return 0.0, 0 + previous_round = current_round - 1 - logging.info(f"Round {current_round}. Previous round: {previous_round}") current_addr_nei = (addr, nei) relevant_messages = [ msg - for msg in messages_time_message + for msg in messages_number_message if msg["key"] == current_addr_nei and msg["round"] == previous_round ] messages_count = len(relevant_messages) if relevant_messages else 0 - logging.info(f"Round {current_round}. Messages count: {messages_count}") rounds_to_consider = [] if previous_round >= 4: @@ -1002,19 +1069,16 @@ def manage_metric_time_message(messages_time_message, addr, nei, current_round, rounds_to_consider = [0, 1] elif previous_round == 0: rounds_to_consider = [0] - # logging.info(f"Round {current_round}. Rounds to consider: {rounds_to_consider}") previous_counts = [ - len([m for m in messages_time_message if m["key"] == current_addr_nei and m["round"] == r]) + len([m for m in messages_number_message if m["key"] == current_addr_nei and m["round"] == r]) for r in rounds_to_consider ] - # logging.info(f"Round {current_round}. Total counts of rounds_to_consider: {previous_counts}") - # Calculate the 25th and 75th percentiles based on the selected rounds - Reputation.previous_percentile_25_time_message[current_addr_nei] = ( + Reputation.previous_percentile_25_number_message[current_addr_nei] = ( np.percentile(previous_counts, 25) if previous_counts else 0 ) - Reputation.previous_percentile_85_time_message[current_addr_nei] = ( + Reputation.previous_percentile_85_number_message[current_addr_nei] = ( np.percentile(previous_counts, 85) * 1.20 if previous_counts else 0 ) @@ -1022,19 +1086,14 @@ def manage_metric_time_message(messages_time_message, addr, nei, current_round, relative_position = 0 if previous_round > 4: - percentile_25 = Reputation.previous_percentile_25_time_message.get(current_addr_nei, 0) - # logging.info(f"Round {current_round}. percentile_25: {percentile_25}") - percentile_85 = Reputation.previous_percentile_85_time_message.get(current_addr_nei, 0) - # logging.info(f"Round {current_round}. percentile_85: {percentile_85}") + percentile_25 = Reputation.previous_percentile_25_number_message.get(current_addr_nei, 0) + percentile_85 = Reputation.previous_percentile_85_number_message.get(current_addr_nei, 0) - # logging.info(f"Round {current_round}. Messages count: {messages_count}") if messages_count > percentile_85: relative_position = (messages_count - percentile_85) / (percentile_85 - percentile_25) - # logging.info(f"Round {current_round}. Relative position: {relative_position}") normalized_messages = np.exp(-relative_position) normalized_messages = max(0.01, normalized_messages) - # logging.info(f"Round {current_round}. Normalized messages: {normalized_messages}") data = { "addr": addr, @@ -1042,88 +1101,63 @@ def manage_metric_time_message(messages_time_message, addr, nei, current_round, "round": current_round, "messages_count": messages_count, "normalized_messages": normalized_messages, - "percentile_25": Reputation.previous_percentile_25_time_message[current_addr_nei], - "percentile_85": Reputation.previous_percentile_85_time_message[current_addr_nei], + "percentile_25": Reputation.previous_percentile_25_number_message[current_addr_nei], + "percentile_85": Reputation.previous_percentile_85_number_message[current_addr_nei], } - Reputation.metrics(scenario, data, addr, nei, "time_message") + Reputation.metrics(scenario, data, addr, nei, "number_message") return normalized_messages, messages_count except Exception: - logging.exception("Error managing metric time_message") + logging.exception("Error managing metric number_message") return 0.0, 0 - + @staticmethod - def save_model_arrival_latency_history(addr, nei, model_arrival_latency, round_num): + def save_number_message_history(addr, nei, messages_number_message_normalized, current_round): """ - Save the model_arrival_latency history of a participant (addr) regarding its neighbor (nei) in memory. + Save the number_message history of a participant (addr) regarding its neighbor (nei) in memory. Args: - addr (str): The identifier of the node whose model_arrival_latency history is being saved. + addr (str): The identifier of the node whose number_message history is being saved. nei (str): The neighboring node involved. - model_arrival_latency (float): The model_arrival_latency value to be saved. + messages_number_message_normalized (float): The number_message value to be saved. current_round (int): The current round number. Returns: - float: The cumulative model_arrival_latency including the current round. + float: The cumulative number_message including the current round. """ - try: - # Define the current key for the neighbor node (nei) - current_key = nei - # Initialize the history for the current round if it doesn't exist - if round_num not in Reputation.model_arrival_latency_history: - Reputation.model_arrival_latency_history[round_num] = {} - - # Store the latency value for the current round and neighbor - if current_key not in Reputation.model_arrival_latency_history[round_num]: - Reputation.model_arrival_latency_history[round_num][current_key] = {} + try: + key = (addr, nei) + avg_number_message = 0 - Reputation.model_arrival_latency_history[round_num][current_key].update({ - "score": model_arrival_latency, - }) + if key not in Reputation.number_message_history: + Reputation.number_message_history[key] = {} - # logging.info(f"model_arrival_latency history: {Reputation.model_arrival_latency_history} | model_arrival_latency: {model_arrival_latency} | current_round: {current_round}") + Reputation.number_message_history[key][current_round] = {"number_message": messages_number_message_normalized} - if model_arrival_latency > 0 and round_num > 5: - # logging.info(" if model_arrival_latency >= 0 and round_num > 5") + + if messages_number_message_normalized != 0 and current_round > 4: previous_avg = ( - Reputation.model_arrival_latency_history.get(round_num - 1, {}) - .get(current_key, {}) - .get("avg_model_arrival_latency", None) + Reputation.number_message_history[key].get(current_round - 1, {}).get("avg_number_message", None) ) - # logging.info(f"Previous avg model_arrival_latency latency: {previous_avg}") - if previous_avg is not None: - avg_model_arrival_latency = ( - model_arrival_latency * 0.8 + previous_avg * 0.2 - if previous_avg is not None - else model_arrival_latency - ) - # logging.info(f"Avg model_arrival_latency latency IF: {avg_model_arrival_latency}") + avg_number_message = messages_number_message_normalized * 0.8 + previous_avg * 0.2 else: - avg_model_arrival_latency = model_arrival_latency - (model_arrival_latency * 0.05) - # logging.info(f"Avg model_arrival_latency latency ELSE: {avg_model_arrival_latency}") - elif model_arrival_latency == 0 and round_num > 5: - # logging.info(" elif model_arrival_latency == 0 and round_num > 5") - previous_avg = ( - Reputation.model_arrival_latency_history.get(round_num - 1, {}) - .get(current_key, {}) - .get("avg_model_arrival_latency", None) - ) - # logging.info(f"Previous avg model_arrival_latency latency: {previous_avg}") - avg_model_arrival_latency = previous_avg - (previous_avg * 0.05) - # logging.info(f"Avg model_arrival_latency latency ELIF: {avg_model_arrival_latency}") - else: - avg_model_arrival_latency = model_arrival_latency + avg_number_message = messages_number_message_normalized - Reputation.model_arrival_latency_history[round_num][current_key]["avg_model_arrival_latency"] = ( - avg_model_arrival_latency - ) + Reputation.number_message_history[key][current_round]["avg_number_message"] = avg_number_message + else: + avg_number_message = 0 - return avg_model_arrival_latency + return avg_number_message except Exception: - logging.exception("Error saving model_arrival_latency history") + logging.exception("Error saving number_message history") + return -1 + except Exception as e: + logging.exception(f"Error managing model_arrival_latency latency: {e}") + return 0.0 + @staticmethod def save_reputation_history_in_memory(addr, nei, reputation, current_round): """ @@ -1147,10 +1181,9 @@ def save_reputation_history_in_memory(addr, nei, reputation, current_round): Reputation.reputation_history[key][current_round] = reputation - # total_reputation = 0 - # total_weights = 0 avg_reputation = 0 rounds = sorted(Reputation.reputation_history[key].keys(), reverse=True)[:2] + # logging.info(f"Rounds in save_reputation_history: {rounds}") if len(rounds) >= 2: current_round = rounds[0] @@ -1212,54 +1245,53 @@ def calculate_decay_rate(reputation): return 0.1 # Muy alto decaimiento @staticmethod - def read_similarity_file(file_path, nei): + def read_similarity_file(file_path, nei, current_round): """ Read a similarity file and extract relevant data for each IP. Args: file_path (str): Path to the similarity file. + nei (str): The IP address of the neighbor. + current_round (int): The current round number. Returns: - dict: A dictionary containing relevant data for each IP extracted from the file. - Each IP will have a dictionary containing cosine, euclidean, minkowski, - manhattan, pearson_correlation, and jaccard values. + float: The similarity value. """ - nei = nei.split(":")[0].strip() similarity = 0.0 - with open(file_path) as file: - reader = csv.DictReader(file) - for row in reader: - source_ip = row["source_ip"].split(":")[0].strip() - if source_ip == nei: - try: - # Design weights for each similarity metric + try: + with open(file_path, "r") as file: + reader = csv.DictReader(file) + for row in reader: + source_ip = row["source_ip"].strip() + round_in_file = int(row.get("round", -1).strip()) + if source_ip == nei and round_in_file == current_round: weight_cosine = 0.25 weight_euclidean = 0.25 weight_manhattan = 0.25 weight_pearson = 0.25 - # Retrieve and normalize metrics if necessary cosine = float(row["cosine"]) euclidean = float(row["euclidean"]) manhattan = float(row["manhattan"]) pearson_correlation = float(row["pearson_correlation"]) - # Calculate similarity similarity = ( weight_cosine * cosine + weight_euclidean * euclidean + weight_manhattan * manhattan + weight_pearson * pearson_correlation - ) - except Exception: - logging.exception("Error reading similarity file") - return similarity + ) + except FileNotFoundError: + logging.error(f"File {file_path} not found.") + except Exception as e: + logging.exception(f"Error reading similarity file: {e}") + return similarity + @staticmethod def metrics(scenario, data, addr, nei, type, update_field=None): current_dir = os.path.dirname(os.path.realpath(__file__)) csv_path = os.path.join(current_dir, f"{scenario}/metrics/{type}/{addr}_{nei}_{type}.csv") - # logging.info(f"Round {current_round}. CSV path: {csv_path}") csv_dir = os.path.dirname(csv_path) if not os.path.exists(csv_dir): @@ -1273,9 +1305,8 @@ def metrics(scenario, data, addr, nei, type, update_field=None): writer.writeheader() writer.writerow(data) except Exception: - logging.exception("Error saving messages time_message data to CSV") + logging.exception("Error saving messages number_message data to CSV") else: - # logging.info(f"Reputation data received for round {data['round']}: {data}") rows = [] updated = False @@ -1283,38 +1314,34 @@ def metrics(scenario, data, addr, nei, type, update_field=None): "addr", "nei", "round", - "time_message_count", - "time_message_norm", + "number_message_count", + "number_message_norm", "similarity", "fraction", "model_arrival_latency", "reputation_without_feedback", "reputation_with_feedback", "average_model_arrival_latency", - "average_similarity", - "average_fraction", - "average_time_messages", + "average_model_similarity", + "average_fraction_parameters_changed", + "average_num_messages", ] if os.path.exists(csv_path): with open(csv_path, newline="") as file: rows = list(csv.DictReader(file)) - # logging.info(f"Existing rows in CSV: {rows}") if update_field: for row in rows: if int(row["round"]) == int(data["round"]): - # logging.info(f"Updating row for round {data['round']}: {row}") row.update(data) updated = True break if not updated: rows.append(data) - # logging.info(f"Appended new data for round {data['round']}: {data}") with open(csv_path, mode="w", newline="") as file: writer = csv.DictWriter(file, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) - # logging.info(f"Final rows written to CSV: {rows}") diff --git a/nebula/frontend/config/participant.json.example b/nebula/frontend/config/participant.json.example index db3ec0d34..b72b4de6b 100755 --- a/nebula/frontend/config/participant.json.example +++ b/nebula/frontend/config/participant.json.example @@ -90,14 +90,14 @@ }, "aggregator_args": { "algorithm": "FedAvg", - "aggregation_timeout": 60, + "aggregation_timeout": 25, "aggregation_push": "slow" }, "defense_args": { "with_reputation": false, - "is_dynamic_topology": false, - "is_dynamic_aggregation": false, - "target_aggregation": false + "reputation_metrics": {}, + "initial_reputation": 0.6, + "weighting_factor": "dynamic" }, "adversarial_args": { "attacks": "No Attack", diff --git a/nebula/frontend/templates/deployment.html b/nebula/frontend/templates/deployment.html index f23df67a5..94af6b317 100755 --- a/nebula/frontend/templates/deployment.html +++ b/nebula/frontend/templates/deployment.html @@ -621,50 +621,62 @@