def test_chain(self): G = Block.genesis() C = Chain() C.append(G) b = C.mine() self.assertEqual(len(C), 2) self.assertEqual(C[0], G) self.assertEqual(C[-1], b) self.assertEqual(C[0:2], C) self.assertEqual(len(C.slice(G, b)), 1) self.assertEqual(C[0].height, 0) self.assertEqual(C[1].height, 1) self.assertTrue(C.is_chained())
class Adversary(Party): def __init__(self, genesis, k, m, mu_B): self.attack_phase = AttackPhase.DOUBLE_SPEND self.m = m self.k = k self.mu_B = mu_B self.bypass_level = mu_B self.bypass_level_blocks_seen = 0 self.min_bypass_level = 2 self.honest_chain = Chain(genesis) self.last_good_honest_block = None self.double_spend_block = None self.stitch_block = None self.blocks_mined = [] self.need_bypass = False self.adversarial_nipopow = NIPoPoW(k, m) self.adversarial_nipopow.chain.append(genesis) super().__init__(genesis) def play(self): if self.attack_phase == AttackPhase.DOUBLE_SPEND: self.double_spend_block = self.chain.mine(True) self.blocks_mined.append(self.double_spend_block) self.adversarial_nipopow.chain.append(self.double_spend_block) self.attack_phase = AttackPhase.STITCH return None # withhold double spending block if self.attack_phase == AttackPhase.STITCH: self.stitch_block = self.honest_chain.mine(True) self.stitch_block.set_thorny_interlink([self.double_spend_block]) self.last_good_honest_block = self.stitch_block self.blocks_mined.append(self.stitch_block) self.adversarial_nipopow.chain.append(self.stitch_block) self.attack_phase = AttackPhase.GROW return self.honest_chain if self.attack_phase == AttackPhase.GROW: # discard = True if self.need_bypass: b = Block.mine(self.honest_chain[-1]) b.adversarial = True b.set_thorny_interlink([self.last_good_honest_block]) self.blocks_mined.append(b) if b.level < self.bypass_level: # Our block is of low enough level to stay under the radar # so we can use it for bypassing # Bypass any blocks between last_good_honest_block and b, # as they are blocks of a higher level than what we want. self.last_good_honest_block = b self.honest_chain.append(b) self.adversarial_nipopow.chain.append(b) # Bypass was successful. self.need_bypass = False # discard = False return self.honest_chain else: # Unfortunately, we mined a block of high level and this # cannot be used for bypassing (as b would then be included # by the honest prover into their proof), so we have to discard # this block and try again # discard = True pass # if not discard: # return b if self.attack_phase == AttackPhase.SUFFIX: b = self.chain.mine(True) self.blocks_mined.append(b) self.adversarial_nipopow.chain.append(b) self.suffix_size += 1 if self.suffix_size >= k and self.growth_completed(): self.attack_phase = AttackPhase.DONE return None # withhold suffix def block_arrival(self, chain): block = chain[-1] if self.attack_phase > AttackPhase.STITCH and self.attack_phase < AttackPhase.SUFFIX: if block.level >= self.bypass_level: # A superblock has appeared which is of higher level than we want. # We need to bypass it. # (There could be multiple of these in a row) self.need_bypass = True self.bypass_level_blocks_seen += 1 if self.bypass_level_blocks_seen >= m: self.bypass_level -= 1 self.bypass_level_blocks_seen = 0 if self.bypass_level < self.min_bypass_level: self.attack_phase = AttackPhase.SUFFIX self.chain = Chain(self.last_good_honest_block) self.suffix_size = 0 else: if not self.need_bypass: self.last_good_honest_block = block self.adversarial_nipopow.chain.append(block) self.honest_chain = chain def growth_completed(self): m = self.m mu_B = self.mu_B C = self.honest_chain.slice(self.stitch_block) if C.count_upchain(mu_B) < 2 * m: return False b = C[0] for mu in range(mu_B - 1, 0, -1): b = C.upchain(mu + 1)[m] C = C.slice(b) if C.count_upchain(mu) < 2 * m: return False return True def ready(self): return self.attack_phase == AttackPhase.DONE