refactor: subclasses for Config

This commit is contained in:
Youngjoon Lee 2024-05-17 14:54:23 +09:00
parent fa4635b749
commit 66e5549fdc
No known key found for this signature in database
GPG Key ID: 09B750B5BD6F08A2
7 changed files with 101 additions and 60 deletions

View File

@ -34,9 +34,9 @@ class Adversary:
def is_around_message_interval(self, time: SimTime):
now_frac, now_int = math.modf(time)
return now_int % self.config.message_interval == 0 and now_frac <= self.config.max_message_prep_time
return now_int % self.config.mixnet.message_interval == 0 and now_frac <= self.config.mixnet.max_message_prep_time
def update_observation_window(self):
while True:
self.mixed_msgs_per_window.append(defaultdict(int))
yield self.env.timeout(self.config.io_observation_window)
yield self.env.timeout(self.config.adversary.io_observation_window)

View File

@ -20,7 +20,7 @@ class Analysis:
def messages_emitted_around_interval(self):
df = pd.DataFrame(
[(node.id, cnt, node.id < len(self.sim.config.real_message_prob_weights))
[(node.id, cnt, node.id < len(self.sim.config.mixnet.real_message_prob_weights))
for node, cnt in self.sim.p2p.adversary.senders_around_interval.items()],
columns=["node_id", "msg_count", "expected"]
)

View File

@ -1,3 +1,4 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Self
@ -7,7 +8,36 @@ import yaml
@dataclass
class Config:
simulation: SimulationConfig
mixnet: MixnetConfig
p2p: P2pConfig
adversary: AdversaryConfig
@classmethod
def load(cls, yaml_path: str) -> Self:
with open(yaml_path, "r") as f:
data = yaml.safe_load(f)
config = dacite.from_dict(data_class=Config, data=data)
# Validations
config.simulation.validate()
config.mixnet.validate()
config.p2p.validate()
config.adversary.validate()
return config
@dataclass
class SimulationConfig:
running_time: int
def validate(self):
assert self.running_time > 0
@dataclass
class MixnetConfig:
num_nodes: int
num_mix_layers: int
# An interval of sending a new real/cover message
@ -23,31 +53,35 @@ class Config:
cover_message_prob: float
# A maximum preparation time (delay) before sending the message
max_message_prep_time: float
# A maximum network latency between nodes directly connected with each other
max_network_latency: float
# A maximum delay of messages mixed in a mix node
max_mix_delay: float
def validate(self):
assert self.num_nodes > 0
assert 0 < self.num_mix_layers <= self.num_nodes
assert self.message_interval > 0
assert self.real_message_prob > 0
assert len(self.real_message_prob_weights) <= self.num_nodes
for weight in self.real_message_prob_weights:
assert weight >= 1
assert self.cover_message_prob >= 0
assert self.max_message_prep_time >= 0
assert self.max_mix_delay >= 0
@dataclass
class P2pConfig:
# A maximum network latency between nodes directly connected with each other
max_network_latency: float
def validate(self):
assert self.max_network_latency >= 0
@dataclass
class AdversaryConfig:
# A discrete time window for the adversary to observe inputs and outputs of a certain node
io_observation_window: int
@classmethod
def load(cls, yaml_path: str) -> Self:
with open(yaml_path, "r") as f:
data = yaml.safe_load(f)
config = dacite.from_dict(data_class=Config, data=data)
# Validations
assert config.running_time > 0
assert config.num_nodes > 0
assert 0 < config.num_mix_layers <= config.num_nodes
assert config.message_interval > 0
assert config.real_message_prob > 0
assert len(config.real_message_prob_weights) <= config.num_nodes
for weight in config.real_message_prob_weights:
assert weight >= 1
assert config.cover_message_prob >= 0
assert config.max_message_prep_time >= 0
assert config.max_network_latency >= 0
assert config.io_observation_window >= 0
return config
def validate(self):
assert self.io_observation_window >= 0

View File

@ -1,23 +1,30 @@
# The simulation uses a virtual time. Please see README for more details.
running_time: 30
num_nodes: 100
num_mix_layers: 3
# An interval of sending a new real/cover message
# A probability of actually sending a message depends on the following parameters.
message_interval: 1
# A probability of sending a real message within a cycle
real_message_prob: 0.1
# A weight of real message emission probability of some nodes
# Each weight is assigned to each node in the order of the node ID.
# The length of the list should be <= num_nodes. i.e. some nodes won't have a weight.
real_message_prob_weights: [10, 8, 12]
# A probability of sending a cover message within a cycle if not sending a real message
cover_message_prob: 0.2
# A maximum preparation time (delay) before sending the message
max_message_prep_time: 0.3
# A maximum network latency between nodes directly connected with each other
max_network_latency: 0.5
# A maximum delay of messages mixed in a mix node
max_mix_delay: 3
# A discrete time window for the adversary to observe inputs and outputs of a certain node
io_observation_window: 1
simulation:
# The simulation uses a virtual time. Please see README for more details.
running_time: 30
mixnet:
num_nodes: 100
num_mix_layers: 3
# An interval of sending a new real/cover message
# A probability of actually sending a message depends on the following parameters.
message_interval: 1
# A probability of sending a real message within a cycle
real_message_prob: 0.1
# A weight of real message emission probability of some nodes
# Each weight is assigned to each node in the order of the node ID.
# The length of the list should be <= p2p.num_nodes. i.e. some nodes won't have a weight.
real_message_prob_weights: [10, 8, 12]
# A probability of sending a cover message within a cycle if not sending a real message
cover_message_prob: 0.2
# A maximum preparation time (delay) before sending the message
max_message_prep_time: 0.3
# A maximum delay of messages mixed in a mix node
max_mix_delay: 3
p2p:
# A maximum network latency between nodes directly connected with each other
max_network_latency: 0.5
adversary:
# A discrete time window for the adversary to observe inputs and outputs of a certain node
io_observation_window: 1

View File

@ -29,13 +29,13 @@ class Node:
Creates/encapsulate a message and send it to the network through the mixnet
"""
while True:
yield self.env.timeout(self.config.message_interval)
yield self.env.timeout(self.config.mixnet.message_interval)
payload = self.payload_to_send()
if payload is None: # nothing to send in this turn
continue
prep_time = random.uniform(0, self.config.max_message_prep_time)
prep_time = random.uniform(0, self.config.mixnet.max_message_prep_time)
yield self.env.timeout(prep_time)
self.log("Sending a message to the mixnet")
@ -46,22 +46,22 @@ class Node:
rnd = random.random()
if rnd < self.real_message_prob():
return self.REAL_PAYLOAD
elif rnd < self.config.cover_message_prob:
elif rnd < self.config.mixnet.cover_message_prob:
return self.COVER_PAYLOAD
else:
return None
def real_message_prob(self):
weight = self.config.real_message_prob_weights[self.id] \
if self.id < len(self.config.real_message_prob_weights) else 0
return self.config.real_message_prob * weight
weight = self.config.mixnet.real_message_prob_weights[self.id] \
if self.id < len(self.config.mixnet.real_message_prob_weights) else 0
return self.config.mixnet.real_message_prob * weight
def create_message(self, payload: bytes) -> SphinxPacket:
"""
Creates a message using the Sphinx format
@return:
"""
mixes = self.p2p.get_nodes(self.config.num_mix_layers)
mixes = self.p2p.get_nodes(self.config.mixnet.num_mix_layers)
public_keys = [mix.public_key for mix in mixes]
# TODO: replace with realistic tx
incentive_txs = [Node.create_incentive_tx(mix.public_key) for mix in mixes]
@ -89,7 +89,7 @@ class Node:
self.log("Dropping a cover message: %s" % msg.payload)
else:
# TODO: use Poisson delay or something else, if necessary
yield self.env.timeout(random.uniform(0, self.config.max_mix_delay))
yield self.env.timeout(random.uniform(0, self.config.mixnet.max_mix_delay))
self.env.process(self.p2p.broadcast(self, msg))
else:
self.log("Receiving SphinxPacket, but not mine")

View File

@ -44,7 +44,7 @@ class P2p:
def send(self, msg: SphinxPacket | bytes, node):
# simulate network latency
yield self.env.timeout(random.uniform(0, self.config.max_network_latency))
yield self.env.timeout(random.uniform(0, self.config.p2p.max_network_latency))
self.adversary.observe_incoming_message(node)
self.env.process(node.receive_message(msg))

View File

@ -13,8 +13,8 @@ class Simulation:
self.config = config
self.env = simpy.Environment()
self.p2p = P2p(self.env, config)
self.nodes = [Node(i, self.env, self.p2p, config) for i in range(config.num_nodes)]
self.nodes = [Node(i, self.env, self.p2p, config) for i in range(config.mixnet.num_nodes)]
self.p2p.add_node(self.nodes)
def run(self):
self.env.run(until=self.config.running_time)
self.env.run(until=self.config.simulation.running_time)