def random_block_generator(genesis, initial_leaf_number, block_number): """ Randomly generates blocks according to the given parameters. """ max_initial_leaf_number = initial_leaf_number max_block_number = block_number all_blocks = set() all_blocks.add(hash(genesis)) leaves = set() block_counter = 1 yield genesis while block_counter <= max_block_number: if block_counter <= max_initial_leaf_number + 1: parents = {hash(genesis)} else: parents = frozenset( sample(leaves, randint(1, max(1, round(len(leaves) / 5))))) cur_block = Block(block_counter, parents) leaves -= cur_block.get_parents() leaves.add(hash(cur_block)) all_blocks.add(hash(cur_block)) block_counter += 1 yield cur_block
def add(self, block: Block, is_malicious: bool = False): super().add(block) global_id = hash(block) if is_malicious: self._malicious_blocks_to_add_to_honest_dag.append(global_id) if self.did_attack_fail(): self._first_parallel_block_gid = global_id # The malicious attack generates a chain, so the new tip is the current block self._competing_chain_tip_gid = global_id self._competing_chain_tip_antipast -= self._G.node[global_id][self._BLUE_DIFF_PAST_ORDER_KEY].keys() self._competing_chain_tip_antipast -= self._G.node[global_id][self._RED_DIFF_PAST_ORDER_KEY].keys() # Because we are under the assumption that a selfish miner has zero network latency and the # simulation design, the assumption is that no new blocks are mined between the moment a new # selfish block is mined and the moment it is added to the DAG self._virtual_competing_chain_block_parents = \ self._get_competing_chain_tip_parents(global_id, self._competing_chain_tip_antipast, block.get_parents()) else: # Add malicious blocks to the honest DAG as soon as possible if self.did_attack_fail(): self._add_malicious_blocks_to_honest_dag() # This is possible because this is a competing chain attack, # where the honest chain doesn't include any malicious blocks self._honest_dag.add(block) if self.did_attack_succeed(): self._add_malicious_blocks_to_honest_dag() if not self.did_attack_fail(): # need to update the data structure only if in the middle of a (seemingly) successful attack self._competing_chain_tip_antipast.add(global_id) if global_id == self._competing_chain_tip_gid or \ self._is_a_bluer_than_b(self._competing_chain_tip_gid, global_id): self._virtual_competing_chain_block_parents -= block.get_parents() self._virtual_competing_chain_block_parents.add(global_id) elif not self._is_attack_viable(): self._stop_attack()
def test_blocks_with_missing_parents(self, miner, genesis): """ Tests adding a fork with missing parents. """ # Creating a fork that looks like this: # 0 <- 1 <- 2 <- 3 # 0 <- 1 <- 4 <- 5 block1 = Block(uuid.uuid4().int, {hash(genesis)}) block2 = Block(uuid.uuid4().int, {hash(block1)}) block3 = Block(uuid.uuid4().int, {hash(block2)}) block4 = Block(uuid.uuid4().int, {hash(block1)}) block5 = Block(uuid.uuid4().int, {hash(block4)}) # Add the blocks in the following order: # 3 (and then the miner will fetch 2) # 4 (and then the miner will fetch 1) # 5 # 1 (and then the miner will add 4, 5) # 2 (and then the miner will add 3) assert miner.add_block(block3) is False assert hash(block3) not in miner assert miner.add_block(block4) is False assert hash(block4) not in miner assert miner.add_block(block5) is False assert hash(block5) not in miner assert miner.add_block(block1) is True assert hash(block1) in miner assert hash(block4) in miner assert hash(block5) in miner assert hash(block2) not in miner assert hash(block3) not in miner assert miner.add_block(block2) is True assert hash(block2) in miner assert hash(block3) in miner
def mine_block(self) -> Union[Block, None]: """ :return: the mined block or None if mining was unsuccessful. """ gid = hash(uuid.uuid4().int) block = Block(global_id=gid, parents=self._dag.get_virtual_block_parents().copy(), size=self._block_size, # assume for the simulation's purposes that blocks are maximal data=self._name) # use the data field to hold the miner's name for better logs if not self.add_block(block): return None if not self._broadcast_added_blocks: # The block will be broadcast by _basic_block_add self._broadcast_block(block) self._mined_blocks_gids.add(gid) return block
def add(self, block: Block): global_id = hash(block) parents = block.get_parents() # add the block to the phantom self._G.add_node(global_id) self._G.node[global_id][self._BLOCK_DATA_KEY] = block for parent in parents: self._G.add_edge(global_id, parent) # update the leaves self._leaves -= parents self._leaves.add(global_id) # update the coloring of the graph and everything related (the blue anticones and the topological order too) self._update_coloring_incrementally(global_id) self._update_topological_order_incrementally(global_id)
def test_coloring_advanced(self, greedy_dag, genesis, block1, block2, block3, block4, block5, block6, block7, block8, block9): """ Tests coloring a big DAG. """ greedy_dag.add(genesis) greedy_dag.add(block1) greedy_dag.add(block2) greedy_dag.add(block3) # gids: 0 <- 1, 2 <- 3 # gids: 0 <- 4 greedy_dag.add(block4) assert greedy_dag._k_chain == ({ hash(genesis), hash(block1), hash(block3) }, 0) # gids: 0 <- 1, 2 <- 3 # gids: 0 <- 4 <- 5 greedy_dag.add(block5) assert greedy_dag._k_chain == ({ hash(genesis), hash(block1), hash(block3) }, 0) # gids: 0 <- 1, 2 <- 3 # gids: 0 <- 4 <- 5 <- 6 greedy_dag.add(block6) assert greedy_dag._k_chain == ({ hash(genesis), hash(block1), hash(block3) }, 0) # test colorings for various K values greedy_dag.set_k(0) assert greedy_dag._coloring_tip_gid == hash(block6) assert greedy_dag._k_chain == ({hash(block6)}, 3) assert greedy_dag._coloring_chain == { hash(genesis), hash(block4), hash(block5), hash(block6) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block4), hash(block5), hash(block6) } TestGreedyColoring.assert_coloring( greedy_dag, {hash(genesis), hash(block4), hash(block5), hash(block6)}) greedy_dag.set_k(1) assert greedy_dag._coloring_tip_gid == hash(block3) assert greedy_dag._k_chain == ({hash(block3)}, 2) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3) } TestGreedyColoring.assert_coloring( greedy_dag, {hash(genesis), hash(block1), hash(block2), hash(block3)}) greedy_dag.set_k(2) assert greedy_dag._coloring_tip_gid == hash(block3) assert greedy_dag._k_chain == ({hash(block1), hash(block3)}, 1) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3) } TestGreedyColoring.assert_coloring( greedy_dag, {hash(genesis), hash(block1), hash(block2), hash(block3)}) greedy_dag.set_k(3) assert greedy_dag._coloring_tip_gid == hash(block3) assert greedy_dag._k_chain == ({ hash(genesis), hash(block1), hash(block3) }, 0) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6) }) greedy_dag.set_k(4) assert greedy_dag._coloring_tip_gid == hash(block3) assert greedy_dag._k_chain == ({ hash(genesis), hash(block1), hash(block3) }, 0) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6) }) # gids: 0 <- 1, 2 <- 3 <- 7 # gids: 0 <- 4 <- 5 <- 6 greedy_dag.add(block7) assert greedy_dag._k_chain == ({ hash(genesis), hash(block1), hash(block3), hash(block7) }, 0) # gids: 0 <- 1, 2 <- 3 <- 7 <- 8 # gids: 0 <- 4 <- 5 <- 6 greedy_dag.add(block8) assert greedy_dag._k_chain == ({ hash(block1), hash(block3), hash(block7), hash(block8) }, 1) # gids: 0 <- 1, 2 <- 3 <- 7 <- 8 <- 9 # gids: 0 <- 4 <- 5 <- 6 greedy_dag.add(block9) assert greedy_dag._k_chain == ({ hash(block3), hash(block7), hash(block8), hash(block9) }, 2) # test colorings for various K values greedy_dag.set_k(0) assert greedy_dag._coloring_tip_gid == hash(block9) assert greedy_dag._k_chain == ({hash(block9)}, 5) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) }) greedy_dag.set_k(1) assert greedy_dag._coloring_tip_gid == hash(block9) assert greedy_dag._k_chain == ({hash(block8), hash(block9)}, 4) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9) }) greedy_dag.set_k(2) assert greedy_dag._coloring_tip_gid == hash(block9) assert greedy_dag._k_chain == ({ hash(block7), hash(block8), hash(block9) }, 3) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9) }) greedy_dag.set_k(3) assert greedy_dag._coloring_tip_gid == hash(block9) assert greedy_dag._k_chain == ({ hash(block3), hash(block7), hash(block8), hash(block9) }, 2) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9) }) greedy_dag.set_k(4) assert greedy_dag._coloring_tip_gid == hash(block9) assert greedy_dag._k_chain == ({ hash(block3), hash(block7), hash(block8), hash(block9) }, 2) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9) }) greedy_dag.set_k(5) assert greedy_dag._coloring_tip_gid == hash(block9) assert greedy_dag._k_chain == ({ hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) }, 1) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9) }) greedy_dag.set_k(6) assert greedy_dag._coloring_tip_gid == hash(block9) assert greedy_dag._k_chain == ({ hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) }, 0) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6), hash(block7), hash(block8), hash(block9) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6), hash(block7), hash(block8), hash(block9) }) # gids: 0 <- 1, 2 <- 3 <- 7 <- 8 <- 9 # gids: 0 <- 1, 2 <- 3 <- 7 <- 10 # gids: 0 <- 4 <- 5 <- 6 <- 10 block10 = Block(10, {hash(block6), hash(block7)}) greedy_dag.add(block10) greedy_dag.set_k(0) assert greedy_dag._coloring_tip_gid == hash(block9) assert greedy_dag._k_chain == ({hash(block9)}, 5) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) }) greedy_dag.set_k(1) assert greedy_dag._coloring_tip_gid == hash(block9) assert greedy_dag._k_chain == ({hash(block8), hash(block9)}, 4) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9) }) greedy_dag.set_k(2) assert greedy_dag._coloring_tip_gid == hash(block9) assert greedy_dag._k_chain == ({ hash(block7), hash(block8), hash(block9) }, 3) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9), hash(block10) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9), hash(block10) }) greedy_dag.set_k(3) assert greedy_dag._coloring_tip_gid == hash(block9) assert greedy_dag._k_chain == ({ hash(block3), hash(block7), hash(block8), hash(block9) }, 2) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block8), hash(block9) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9), hash(block10) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block7), hash(block8), hash(block9), hash(block10) }) greedy_dag.set_k(4) assert greedy_dag._coloring_tip_gid == hash(block10) assert greedy_dag._k_chain == ({hash(block7), hash(block10)}, 3) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block10) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6), hash(block7), hash(block8), hash(block9), hash(block10) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6), hash(block7), hash(block8), hash(block9), hash(block10) }) greedy_dag.set_k(5) assert greedy_dag._coloring_tip_gid == hash(block10) assert greedy_dag._k_chain == ({ hash(block3), hash(block7), hash(block10) }, 2) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block10) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6), hash(block7), hash(block8), hash(block9), hash(block10) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6), hash(block7), hash(block8), hash(block9), hash(block10) }) greedy_dag.set_k(6) assert greedy_dag._coloring_tip_gid == hash(block10) assert greedy_dag._k_chain == ({ hash(block3), hash(block7), hash(block10) }, 2) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block10) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6), hash(block7), hash(block8), hash(block9), hash(block10) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6), hash(block7), hash(block8), hash(block9), hash(block10) }) greedy_dag.set_k(7) assert greedy_dag._coloring_tip_gid == hash(block10) assert greedy_dag._k_chain == ({ hash(block1), hash(block3), hash(block7), hash(block10) }, 1) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block10) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6), hash(block7), hash(block8), hash(block9), hash(block10) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6), hash(block7), hash(block8), hash(block9), hash(block10) }) greedy_dag.set_k(8) assert greedy_dag._coloring_tip_gid == hash(block10) assert greedy_dag._k_chain == ({ hash(genesis), hash(block1), hash(block3), hash(block7), hash(block10) }, 0) assert greedy_dag._coloring_chain == { hash(genesis), hash(block1), hash(block3), hash(block7), hash(block10) } assert greedy_dag._get_coloring() == { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6), hash(block7), hash(block8), hash(block9), hash(block10) } TestGreedyColoring.assert_coloring( greedy_dag, { hash(genesis), hash(block1), hash(block2), hash(block3), hash(block4), hash(block5), hash(block6), hash(block7), hash(block8), hash(block9), hash(block10) })
def block5(self, block4): # graph should look like this: 0 <- 2 <- 4 <- 5 return Block(hash(block4) + 1, {hash(block4)})
def test_complex_attack(self, competing_chain_greedy_dag, genesis, block1, block2, block3): """ Tests a complex attack scenario. """ competing_chain_greedy_dag.add(genesis) # gids: 0 <- 1 competing_chain_greedy_dag.add(block1) # gids: 0 <- 1 # gids: 0 <- 4 block4 = Block( 4, competing_chain_greedy_dag.get_virtual_block_parents( is_malicious=True)) competing_chain_greedy_dag.add(block4, is_malicious=True) assert competing_chain_greedy_dag._currently_attacked_block_gid == hash( block1) assert competing_chain_greedy_dag._competing_chain_tip_gid == hash( block4) assert competing_chain_greedy_dag._competing_chain_tip_antipast == { hash(block1), hash(block4) } assert not competing_chain_greedy_dag.did_attack_succeed() assert competing_chain_greedy_dag.get_virtual_block_parents( is_malicious=True) == {hash(block4)} assert competing_chain_greedy_dag._currently_attacked_block_gid == hash( block1) assert competing_chain_greedy_dag._competing_chain_tip_gid == hash( block4) assert competing_chain_greedy_dag._competing_chain_tip_antipast == { hash(block1), hash(block4) } assert not competing_chain_greedy_dag.did_attack_succeed() # gids: 0 <- 1, 2 # gids: 0 <- 4 competing_chain_greedy_dag.add(block2) assert competing_chain_greedy_dag._currently_attacked_block_gid == hash( block1) assert competing_chain_greedy_dag._competing_chain_tip_gid == hash( block4) assert competing_chain_greedy_dag._competing_chain_tip_antipast == { hash(block1), hash(block2), hash(block4) } assert not competing_chain_greedy_dag.did_attack_succeed() assert competing_chain_greedy_dag.get_virtual_block_parents( is_malicious=True) == {hash(block4)} assert competing_chain_greedy_dag._currently_attacked_block_gid == hash( block1) assert competing_chain_greedy_dag._competing_chain_tip_gid == hash( block4) assert competing_chain_greedy_dag._competing_chain_tip_antipast == { hash(block1), hash(block2), hash(block4) } assert not competing_chain_greedy_dag.did_attack_succeed() # gids: 0 <- 1, 2 # gids: 0 <- 4 <- 5 block5 = Block( 5, competing_chain_greedy_dag.get_virtual_block_parents( is_malicious=True)) competing_chain_greedy_dag.add(block5, is_malicious=True) assert competing_chain_greedy_dag._currently_attacked_block_gid == hash( block1) assert competing_chain_greedy_dag._competing_chain_tip_gid == hash( block5) assert competing_chain_greedy_dag._competing_chain_tip_antipast == { hash(block1), hash(block2), hash(block5) } assert not competing_chain_greedy_dag.did_attack_succeed() assert competing_chain_greedy_dag.get_virtual_block_parents( is_malicious=True) == {hash(block1), hash(block2), hash(block5)} assert competing_chain_greedy_dag._currently_attacked_block_gid == hash( block1) assert competing_chain_greedy_dag._competing_chain_tip_gid == hash( block5) assert competing_chain_greedy_dag._competing_chain_tip_antipast == { hash(block1), hash(block2), hash(block5) } assert not competing_chain_greedy_dag.did_attack_succeed( ) # block1 is still unconfirmed # gids: 0 <- 1, 2 <- 3 # gids: 0 <- 4 <- 5 competing_chain_greedy_dag.add(block3) assert competing_chain_greedy_dag._currently_attacked_block_gid == hash( block1) assert competing_chain_greedy_dag._competing_chain_tip_gid == hash( block5) assert competing_chain_greedy_dag._competing_chain_tip_antipast == { hash(block1), hash(block2), hash(block3), hash(block5) } assert not competing_chain_greedy_dag.did_attack_succeed() assert competing_chain_greedy_dag.get_virtual_block_parents( is_malicious=True) == {hash(block1), hash(block2), hash(block5)} assert competing_chain_greedy_dag._currently_attacked_block_gid == hash( block1) assert competing_chain_greedy_dag._competing_chain_tip_gid == hash( block5) assert competing_chain_greedy_dag._competing_chain_tip_antipast == { hash(block1), hash(block2), hash(block3), hash(block5) } assert not competing_chain_greedy_dag.did_attack_succeed() # gids: 0 <- 1, 2 <- 3 # gids: 0 <- 1, 2 <- 6 # gids: 0 <- 4 <- 5 <- 6 block6 = Block( 6, competing_chain_greedy_dag.get_virtual_block_parents( is_malicious=True)) competing_chain_greedy_dag.add(block6, is_malicious=True) assert competing_chain_greedy_dag._currently_attacked_block_gid == hash( block1) assert competing_chain_greedy_dag._competing_chain_tip_gid == hash( block6) assert competing_chain_greedy_dag._competing_chain_tip_antipast == { hash(block3), hash(block6) } assert competing_chain_greedy_dag.did_attack_succeed()
def block7(block3, block6): # graph should look like this: 3 <- 7 return Block(hash(block6) + 1, {hash(block3)})
def block6(block5): # graph should look like this: 5 <- 6 return Block(hash(block5) + 1, {hash(block5)})
def block3(self, block1, block2): # graph should look like this: 0 <- 1 <- 3 return Block(hash(block2) + 1, {hash(block1)})
def block4(self, block2, block3): # graph should look like this: 0 <- 2 <- 4 return Block(hash(block3) + 1, {hash(block2)})
class Network: """ The network connecting the various miners. """ # Dictionary key for the miner data of each miner _MINER_KEY = 'miner' # Dictionary key for the hash rate of each miner _HASH_RATE_KEY = 'hash_rate' # Dictionary key for the edge weight _EDGE_WEIGHT_KEY = 'weight' # The default genesis block _GENESIS_BLOCK = Block(global_id=0, size=0, data="Genesis") def __init__(self, propagation_delay_parameter: int = 0, median_speed: Block.BlockSize = 1 << 20, no_delay_for_malicious_miners: bool = True, completely_connected_malicious_miners: bool = True, total_network_dag: DAG = None, simulation: "Simulation" = None): """ Initializes the network. """ self._network_graph = nx.DiGraph() self._propagation_delay_parameter = propagation_delay_parameter self._median_speed = median_speed self._no_delay_for_malicious_miners = no_delay_for_malicious_miners self._completely_connected_malicious_miners = completely_connected_malicious_miners self._simulation = simulation self._total_network_dag = total_network_dag self._total_network_dag.add(self._GENESIS_BLOCK) self._removed_miners: Set[str] = set() self._malicious_miner_names: Set[str] = set() def __contains__(self, miner_name: "Miner.Name") -> bool: return miner_name in self._network_graph def __getitem__(self, miner_name: "Miner.Name") -> "Miner": if miner_name not in self._network_graph: return None return self._network_graph.node[miner_name][self._MINER_KEY] def __iter__(self) -> Iterator["Miner.Name"]: return iter(self._network_graph) def __len__(self) -> int: return len(self._network_graph) @staticmethod def get_random_ip() -> "Miner.Name": """ :return: a random IP address as a string. """ return ".".join(str(random.randint(0, 255)) for _ in range(4)) def get_random_miner(self, according_to_hash_rate: bool = True) -> "Miner": """ :return: a random miner, randomness is distributed according to the given parameter. """ miners = [] hash_rates = [] total_hash_rate = 0 for miner_name in self: miners.append(miner_name) miner_hash_rate = self._network_graph.node[miner_name][self._HASH_RATE_KEY] hash_rates.append(miner_hash_rate) total_hash_rate += miner_hash_rate if according_to_hash_rate: miner_name = numpy.random.choice(miners, p=numpy.array(hash_rates) / total_hash_rate) else: miner_name = numpy.random.choice(miners) return self[miner_name] def add_miner(self, miner: "Miner", hash_rate: float, is_malicious: bool = False, discover_peers: bool = True): """ Adds a miner with the given hash rate to the network. """ miner_name = miner.get_name() self._network_graph.add_node(miner_name) self._network_graph.node[miner_name][Network._MINER_KEY] = miner self._network_graph.node[miner_name][Network._HASH_RATE_KEY] = hash_rate miner.set_network(self) miner.add_block(self._GENESIS_BLOCK) if is_malicious: self._malicious_miner_names.add(miner_name) if discover_peers: for miner_name in self: self[miner_name].discover_peers() def remove_miner(self, name: "Miner.Name"): """ Removes a miner from the network. """ peer_names = set(self._network_graph.predecessors(name)) self._removed_miners.add(self[name]) self._network_graph.remove_node(name) if name in self._malicious_miner_names: self._malicious_miner_names.remove(name) for peer_name in peer_names: self[peer_name].discover_peers() def discover_peers(self, miner_name: "Miner.Name", max_peer_num: Union[int, float]): """ :return: a random collection of miner_num miner names. """ new_peers = set() old_peers = set(self._network_graph.neighbors(miner_name)) | {miner_name} while len(new_peers) < min(len(self._network_graph) - len(old_peers), max_peer_num - len(old_peers) + 1): potential_peer = random.choice(list(self._network_graph.nodes())) if potential_peer not in old_peers: new_peers.add(potential_peer) return new_peers def _is_there_delay(self, miner_name: "Miner.Name") -> bool: """ :return: True iff the given miner suffers from network delay. """ return not ((miner_name in self._malicious_miner_names) and self._no_delay_for_malicious_miners) def _get_delay(self, sender_name: "Miner.Name", recipient_name: "Miner.Name") -> float: """ :return: if there is an edge between the miners, the actual delay between them. If not, generates a random delay. """ if self._network_graph.has_edge(sender_name, recipient_name): return self._network_graph[sender_name][recipient_name][self._EDGE_WEIGHT_KEY] return numpy.random.poisson(self._propagation_delay_parameter) * self._is_there_delay(sender_name) * \ self._is_there_delay(recipient_name) def add_peers(self, miner_name: "Miner.Name", peers: Set["Miner.Name"]): """ Adds the given miners as peers to the given miner. """ self._network_graph.add_weighted_edges_from( [(miner_name, peer_name, self._get_delay(miner_name, peer_name)) for peer_name in (peers - {miner_name})]) def remove_peers(self, miner_name: "Miner.Name", peers: Iterable["Miner.Name"]): """ Removes the given miners as peers to the given miner. """ self._network_graph.remove_edges_from([(miner_name, peer) for peer in peers]) def attack_success(self): """ A method that allows miners to notify the network of a successful attack. """ self._simulation.attack_success() def add_block(self, block: Block): """ Adds the given block to the total network DAG. """ if hash(block) not in self._total_network_dag: self._total_network_dag.add(block) def send_block(self, sender_name: "Miner.Name", recipient_name: "Miner.Name", block: Block): """ Sends the given block to the given miner. """ if sender_name not in self._network_graph or recipient_name not in self._network_graph: return delay_lambda = round(self._get_delay(sender_name, recipient_name) * sys.getsizeof(block) / self._median_speed) delay_time = min(numpy.random.poisson(delay_lambda), self._propagation_delay_parameter) self._simulation.send_block(sender_name, recipient_name, block, delay_time) def broadcast_block(self, miner_name: "Miner.Name", block: Block): """ Broadcasts the given block from the given miner to its peers. """ self.add_block(block) peers = set(self._network_graph.neighbors(miner_name)) if self._completely_connected_malicious_miners: if miner_name in self._malicious_miner_names: peers |= self._network_graph.nodes() else: peers |= self._malicious_miner_names for peer_name in peers: self.send_block(miner_name, peer_name, block) def fetch_block(self, miner_name: "Miner.Name", gid: Block.GlobalID): """ :return: retrieves the block with the given global id from the network for the given miner. """ for peer in self._network_graph.neighbors(miner_name): self.send_block(peer, miner_name, self._total_network_dag[gid]) def draw_total_network_dag(self, with_labels: bool = False): """ :return: draws the "total" DAG of all the blocks in the network. """ self._total_network_dag.draw(with_labels=with_labels) def draw_network(self, with_labels: bool = False): """ Draws the network. """ plt.figure() pos = nx.spectral_layout(self._network_graph) nx.draw_networkx(self._network_graph, pos=pos, font_size=8) if with_labels: edge_labels = nx.get_edge_attributes(self._network_graph, self._EDGE_WEIGHT_KEY) nx.draw_networkx_edge_labels(self._network_graph, pos=pos, edge_labels=edge_labels, font_size=8) plt.show() def __str__(self): """ :return: a string representation of the network. """ network_params = ', '.join([ "network info: delay lambda: " + str(self._propagation_delay_parameter), "median speed: " + str(self._median_speed), "no delay for malicious miners: " + str(self._no_delay_for_malicious_miners), "malicious miners are completely connected: " + str(self._completely_connected_malicious_miners) + ".\n" ]) network_graph_str = "Network graph: " + str(list(self._network_graph.edges(data=True))) + "\n" total_dag_str = "Total network DAG: " + str(self._total_network_dag) + ".\n" miners_str = "Active miners in the network:\n" + \ '\n'.join([ str(self[miner_name]) + ", hash rate: " + str(self._network_graph.node[miner_name][self._HASH_RATE_KEY]) + ", " + str(len(self[miner_name].get_mined_blocks()) / len(self._total_network_dag)) + " of network blocks. Its peers are: " + ', '.join([str(peer_name) + " with delay: " + str(self._network_graph[miner_name][peer_name][self._EDGE_WEIGHT_KEY]) for peer_name in self._network_graph.neighbors(miner_name)]) for miner_name in self]) return network_params + network_graph_str + total_dag_str + miners_str + "\n"
def block2(genesis, block1): # graph should look like this: 0 <- 2 return Block(hash(block1) + 1, {hash(genesis)})
def block1(genesis): # graph should look like this: 0 <- 1 return Block(hash(genesis) + 1, {hash(genesis)})
def block9(block8): # graph should look like this: 8 <- 9 return Block(hash(block8) + 1, {hash(block8)})
def block8(block7): # graph should look like this: 7 <- 8 return Block(hash(block7) + 1, {hash(block7)})
def genesis(): return Block()
def block5(block4): # graph should look like this: 4 <- 5 return Block(hash(block4) + 1, {hash(block4)})
def block3(block1, block2): # graph should look like this: 1, 2 <- 3 return Block(hash(block2) + 1, {hash(block1), hash(block2)})
def block6(self, block3, block5): # graph should look like this: 0 <- 1 <- 3 <- 6 return Block(hash(block5) + 1, {hash(block3)})
def block4(genesis, block3): # graph should look like this: 0 <- 4 return Block(hash(block3) + 1, {hash(genesis)})
def genesis(self): return Block()