Esempio n. 1
0
 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
Esempio n. 2
0
 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()
Esempio n. 5
0
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()
        }
Esempio n. 6
0
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)
Esempio n. 7
0
 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
Esempio n. 8
0
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
Esempio n. 10
0
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
Esempio n. 11
0
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
Esempio n. 12
0
 def __init__(self, ticker: Optional[Ticker] = None):
     self.store: Store = Store()
     if ticker is None:
         self.ticker = Ticker()
     else:
         self.ticker = ticker
Esempio n. 13
0
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
Esempio n. 15
0
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()
        }