def __init__(self, validator_set: ValidatorSet, ticker: Ticker = None): self.validator_set: ValidatorSet = validator_set self.buffer: Buffer = Buffer() if ticker is None: self.ticker = Ticker() else: self.ticker = ticker
def __init__(self, config: Config): self.config = config self.ticker = Ticker() validator_set = ValidatorSet.with_random_weight( config.validator_num, self.ticker) self.network = NetworkModel(validator_set, self.ticker) self.checkpoint_rotation_count: Dict[int, int] = dict()
def __init__(self, config: Config): self.config = config self.ticker = Ticker() self.network = NetworkModel( ValidatorSet.with_equal_weight(config.validator_num, self.ticker), config.validator_num, config.rotation_ratio, self.ticker) self.checkpoint_rotation_count: Dict[int, int] = dict()
class BroadCastAndReceiveSimulator(Iterator[NetworkModel]): """ A simulator where at each slot, a single validator chosen randomly sends a message and all validators receive messages w.r.t. network latency. """ def __init__(self, config: Config): self.config = config self.ticker = Ticker() self.network = NetworkModel( ValidatorSet.with_equal_weight(config.validator_num, self.ticker), config.validator_num, config.rotation_ratio, self.ticker) self.checkpoint_rotation_count: Dict[int, int] = dict() def __iter__(self): return self def __next__(self) -> NetworkModel: i = self.ticker.current() if i > self.config.max_slot: raise StopIteration self.broadcast_from_random_validator() self.all_validators_receive_messages() self.calc_clique_size() self.ticker.tick() return self.network def broadcast_from_random_validator(self): rn: int = r.randint( 0, self.config.validator_num - 1) # Global random oracle for block proposer election for validator in self.network.validator_set.validators: message: Message = validator.create_message(rn) if not message: # This validator is not the proposer in this slot # FIXME: Remove this validator ("go offline") if she is confident of the success of exit continue if message.estimate.height > 0 and message.estimate.height % self.config.checkpoint_interval == 0: self.checkpoint_rotation_count.setdefault( message.estimate.height, 0) message.estimate.active_validators = self.network.validator_rotation( message.estimate.active_validators, "{}.{}".format( int(message.estimate.height / self.config.checkpoint_interval), self.checkpoint_rotation_count[ message.estimate.height])) self.checkpoint_rotation_count[message.estimate.height] += 1 res = validator.add_message(message) assert res.is_ok(), res.value self.network.broadcast(message, validator) def all_validators_receive_messages(self): for receiver in self.network.validator_set.all(): self.network.receive(receiver) def calc_clique_size(self): self.network.update_clique_size()
class Model: def __init__(self, validator_set: ValidatorSet, ticker: Ticker = None): self.validator_set: ValidatorSet = validator_set self.buffer: Buffer = Buffer() if ticker is None: self.ticker = Ticker() else: self.ticker = ticker def send(self, message: Message, sender: Validator, receiver: Validator): current_slot = self.ticker.current() packet = Packet(message, sender, receiver, current_slot) # FIXME: Decide delay w.r.t. network topology arrival_slot = packet.slot + r.randint(DELAY_MIN, DELAY_MAX) self.buffer.add_packet_to_be_arrived(receiver, arrival_slot, packet) def receive(self, receiver: Validator) -> List[Packet]: to_be_received: List[Packet] = [] packets = self.buffer.read(receiver, self.ticker.current()) for packet in packets: if receiver.message_is_to_be_pending(packet.message): self.buffer.add_packet_to_be_arrived(receiver, self.ticker.current() + 1, packet) else: to_be_received.append(packet) return to_be_received def broadcast(self, message: Message, sender: Validator): for receiver in self.validator_set.all(): if sender != receiver: self.send(message, sender, receiver) def join(self, new_validator: Validator, source_validator: Optional[Validator] = None): assert new_validator not in self.validator_set.validators, "{} already exists".format(new_validator.name) if source_validator is None: source_validator = self.validator_set.validators[0] if new_validator.state.store.genesis is None: res = new_validator.add_message(source_validator.state.store.genesis) assert res.is_ok(), res.value # FIXME: Decide appropriate delay for each message for message in source_validator.state.store.messages.values(): self.send(message, source_validator, new_validator) self.validator_set.validators.append(new_validator) def exit(self, validator: Validator): assert validator in self.validator_set.validators, "{} does not exist".format(validator.name) self.validator_set.validators.remove(validator) def dump(self): return { "validators": self.validator_set.dump(), "slot": self.ticker.current() }
class BroadCastAndReceiveSimulator(Iterator[NetworkModel]): def __init__(self, config: Config): self.config = config self.ticker = Ticker() validator_set = ValidatorSet.with_random_weight( config.validator_num, self.ticker) self.network = NetworkModel(validator_set, self.ticker) self.checkpoint_rotation_count: Dict[int, int] = dict() def __iter__(self): return self def __next__(self) -> NetworkModel: i = self.ticker.current() if i > self.config.max_slot: raise StopIteration self.broadcast_from_random_validator() self.all_validators_receive_all_packets() self.ticker.tick() return self.network def validator_rotation(self, slot: int, message: Message): self.checkpoint_rotation_count.setdefault(message.estimate.height, 0) name = Validator.gen_name( message, slot, self.config.checkpoint_interval, self.checkpoint_rotation_count[message.estimate.height]) # NOTE: Now, we assume the oldest validator exit for simplicity oldest_validator = self.network.validator_set.validators[0] self.network.exit(oldest_validator) new_validator = Validator(name, 1.0, self.ticker) self.network.join(new_validator) self.checkpoint_rotation_count[message.estimate.height] += 1 def broadcast_from_random_validator(self): slot: int = self.ticker.current() sender = BlockProposer.choose(slot, self.network.validator_set.validators) assert sender is not None, "no block proposer" message = sender.create_message() if message.estimate.is_checkpoint(self.config.checkpoint_interval): self.validator_rotation(slot, message) message.estimate.active_validators = self.network.validator_set.validators res = sender.add_message(message) assert res.is_ok(), res.value self.network.broadcast(message, sender) def all_validators_receive_all_packets(self): for receiver in self.network.validator_set.all(): packets = self.network.receive(receiver) for packet in packets: res = receiver.add_message(packet.message) assert res.is_ok(), "{} ({})".format(res.value, receiver.name)
def __init__(self, validator_set: ValidatorSet, validator_num: int, rotation_ratio: float, ticker: Ticker = None): self.validator_set: ValidatorSet = validator_set self.validator_num: int = validator_num self.rotation_ratio: float = rotation_ratio self.broadcast_slot_to_message: Dict[int, Message] = dict() self.arrival_slot_to_messages: Dict[Validator, Dict[int, List[Message]]] = dict() if ticker is None: self.ticker = Ticker() else: self.ticker = ticker
class State: def __init__(self, ticker: Optional[Ticker] = None): self.store: Store = Store() if ticker is None: self.ticker = Ticker() else: self.ticker = ticker def transition(self, message: Message) -> Result[Error, bool]: # TODO: implement checked = self.check_message(message) if checked.is_err(): return checked finalized = self.justify_message(message) if finalized.is_err(): return finalized return Ok(True) def check_message(self, message: Message) -> Result[Error, bool]: validated = MessageValidator.validate(self, message) if validated.is_err(): return validated safety = CliqueOracle.check_safety(message.estimate, self, None) if safety.is_err(): return safety return Ok(True) def justify_message(self, message: Message) -> Result[Error, bool]: message.receiver_slot = self.ticker.current() self.store.add(message) return Ok(True) def justification(self) -> Justification: return Justification(self.store.latest_message_hashes()) def dump(self): return {"messages": self.store.dump(self)} def tick(self): self.ticker.tick() def current_slot(self) -> int: return self.ticker.current()
def test_choose(): ticker: Ticker = Ticker() for slot in range(1, 100): validators = ValidatorSet.with_equal_weight(slot, ticker).validators first = BlockProposer.choose(slot, validators) second = BlockProposer.choose(slot, validators) # Same proposer is chosen if slot and validators are same assert first == second
def test_head(): ticker = Ticker() v_set = ValidatorSet.with_random_weight(3, ticker) messages = dict() receiver = v_set.choice_one() senders = [validator for validator in v_set.all() if validator != receiver] for sender in senders: m = sender.create_message() receiver.add_message(m) messages[sender] = m justification = receiver.state.justification() head = LMDGhostEstimator.head(receiver.state, justification) most_weighted_validator = max(senders, key=lambda v: v.weight) # Most weighted validator's block is chosen as parent. assert head.hash == messages[most_weighted_validator].estimate.hash
def test_head_when_tie_exists(): ticker = Ticker() v_set = ValidatorSet.with_equal_weight(3, ticker) messages = dict() receiver = v_set.choice_one() senders = [validator for validator in v_set.all() if validator != receiver] for sender in senders: m = sender.create_message() receiver.add_message(m) messages[sender] = m justification = receiver.state.justification() head = LMDGhostEstimator.head(receiver.state, justification) block_hashes = [m.estimate.hash for m in messages.values()] smallest_block_hash = min(block_hashes, key=lambda h: h) # The block with the smallest hash is chosen when tie exists. assert head.hash == smallest_block_hash
def __init__(self, ticker: Optional[Ticker] = None): self.store: Store = Store() if ticker is None: self.ticker = Ticker() else: self.ticker = ticker
def test_store(): ticker = Ticker() validator = Validator("v0", 1.0, ticker) genesis = Message.genesis(validator) store = Store() store.add(genesis) b1 = Block(0, genesis.estimate.hash) child = Message(validator, b1, Justification(store.latest_message_hashes()), 0) store.add(child) message_history = {validator: [genesis.hash, child.hash]} assert store.message_history == message_history messages = {genesis.hash: genesis, child.hash: child} assert store.messages == messages children = {genesis.hash: [child.hash]} assert store.children == children parent = {child.hash: genesis.hash} assert store.parent == parent block_to_message_in_hash = { genesis.estimate.hash: genesis.hash, child.estimate.hash: child.hash } assert store.block_to_message_in_hash == block_to_message_in_hash assert store.genesis == genesis assert store.message(child.hash) == child assert store.message(genesis.hash) == genesis assert store.message(000) is None latest_message_hashes = {validator: child.hash} assert store.latest_message_hashes() == latest_message_hashes latest_messages = {validator: child} assert store.latest_messages() == latest_messages assert store.parent_message(child) == genesis assert store.parent_message(genesis) is None assert store.children_messages(child) == [] assert store.children_messages(genesis) == [child] assert store.parent_block(genesis.estimate) is None assert store.parent_block(child.estimate) == genesis.estimate assert store.children_blocks(child.estimate) == [] assert store.children_blocks(genesis.estimate) == [child.estimate] assert store.has_children_blocks(genesis.estimate) assert not store.has_children_blocks(child.estimate) assert store.justified(genesis) assert store.justified(child) assert not store.justified(000) assert store.genesis_block() == genesis.estimate
def test_tick(): ticker = Ticker() assert ticker.current() == 0 ticker.tick() assert ticker.current() == 1
class Model: def __init__(self, validator_set: ValidatorSet, validator_num: int, rotation_ratio: float, ticker: Ticker = None): self.validator_set: ValidatorSet = validator_set self.validator_num: int = validator_num self.rotation_ratio: float = rotation_ratio self.broadcast_slot_to_message: Dict[int, Message] = dict() self.arrival_slot_to_messages: Dict[Validator, Dict[int, List[Message]]] = dict() if ticker is None: self.ticker = Ticker() else: self.ticker = ticker def send(self, message: Message, sender: Validator, receiver: Validator): current_slot = self.ticker.current() packet = Packet(message, sender, receiver, current_slot) # FIXME: Decide delay w.r.t. network topology arrival_slot = packet.slot + Delay.get(DELAY_MIN, DELAY_MAX) self.add_message_to_be_arrived(receiver, arrival_slot, packet.message) def receive(self, receiver: Validator): # FIXME: Rename this with Validator.add_message() messages = self.arrival_slot_to_messages.get(receiver, dict()).get( self.ticker.current(), []) for message in messages: self.add_message_or_pending(receiver, message) def broadcast(self, message: Message, sender: Validator): self.broadcast_slot_to_message[self.ticker.current()] = message for receiver in self.validator_set.all(): if sender != receiver: # NOTE: Sender already received this message in broadcast_from_random_validator() self.send(message, sender, receiver) def update_clique_size(self): for validator in self.validator_set.validators: for message in validator.state.store.messages.values(): clique_oracle = CliqueOracle(message.estimate, validator.state) message.clique_size = clique_oracle.biggest_clique_weight() def validator_rotation(self, validators: List[Validator], name_prefix: str) -> List[Validator]: # NOTE: Now, we assume older validators exit for simplicity of visualization validators: List[Validator] = validators[int(self.validator_num * self.rotation_ratio) + 1:] new_validators: List[Validator] = [] for i in range(self.validator_num - len(validators)): validator = Validator("v{}.{}".format(name_prefix, i), 1.0, self.ticker) # Add genesis message FIXME: Add genesis as default res = validator.add_message(self.validator_set.genesis) assert res.is_ok(), res.value # Do simulation of message arrival from the start for new validator for past_slot in range(self.ticker.current()): past_message = self.broadcast_slot_to_message[past_slot] # Sending in past arrival_slot = past_slot + Delay.get(DELAY_MIN, DELAY_MAX) self.add_message_to_be_arrived(validator, arrival_slot, past_message) # Receiving in past messages = self.arrival_slot_to_messages.get( validator, dict()).get(past_slot, []) for message in messages: res = validator.add_message(message) if not res.is_ok(): # If the message is not valid, skip that for the next round (assuming the reason is reordering) self.add_message_to_be_arrived(validator, past_slot + 1, message) new_validators.append(validator) validators.append(validator) assert len(validators) == self.validator_num self.validator_set.validators += new_validators return validators def add_message_or_pending(self, receiver: Validator, message: Message): res = receiver.add_message(message) if not res.is_ok(): # If the message is not valid, skip that for the next round (assuming the reason is reordering) self.add_message_to_be_arrived(receiver, self.ticker.current() + 1, message) def add_message_to_be_arrived(self, receiver: Validator, arrival_slot: int, message: Message): if receiver in self.arrival_slot_to_messages: self.arrival_slot_to_messages[receiver][arrival_slot] = \ self.arrival_slot_to_messages[receiver].get(arrival_slot, []) + [message] return self.arrival_slot_to_messages[receiver] = {arrival_slot: [message]} def dump(self): return { "validators": self.validator_set.dump(), "slot": self.ticker.current() }