From 236a677c86509bd5a6bad639d16e4df14737efcc Mon Sep 17 00:00:00 2001 From: David Rusu Date: Fri, 21 Mar 2025 00:57:54 +0400 Subject: [PATCH] cryptarchia: remove orphan proofs from block headers --- cryptarchia/cryptarchia.py | 181 +++++----------- cryptarchia/sync.py | 6 +- cryptarchia/test_common.py | 8 +- cryptarchia/test_ledger_state_update.py | 44 ---- cryptarchia/test_orphaned_proofs.py | 251 ----------------------- cryptarchia/test_stake_relativization.py | 10 +- 6 files changed, 58 insertions(+), 442 deletions(-) delete mode 100644 cryptarchia/test_orphaned_proofs.py diff --git a/cryptarchia/cryptarchia.py b/cryptarchia/cryptarchia.py index e8de31a..35cf3b5 100644 --- a/cryptarchia/cryptarchia.py +++ b/cryptarchia/cryptarchia.py @@ -13,9 +13,6 @@ import numpy as np logger = logging.getLogger(__name__) -Id: TypeAlias = bytes - - class Hash(bytes): def __new__(cls, dst, *data): assert isinstance(dst, bytes) @@ -178,7 +175,7 @@ class Note: self.zone_id, ) - def nullifier(self) -> Id: + def nullifier(self) -> Hash: return Hash(b"NOMOS_NOTE_NF", self.commitment(), self.encode_sk()) @@ -186,7 +183,7 @@ class Note: class MockLeaderProof: note: Note slot: Slot - parent: Id + parent: Hash @property def commitment(self): @@ -200,7 +197,7 @@ class MockLeaderProof: def evolved_commitment(self): return self.note.evolve().commitment() - def verify(self, slot: Slot, parent: Id): + def verify(self, slot: Slot, parent: Hash): # TODO: verification not implemented return slot == self.slot and parent == self.parent @@ -208,52 +205,29 @@ class MockLeaderProof: @dataclass class BlockHeader: slot: Slot - parent: Id + parent: Hash content_size: int - content_id: Id + content_id: Hash leader_proof: MockLeaderProof - orphaned_proofs: List["BlockHeader"] = field(default_factory=list) - - def update_header_hash(self, h): - # version byte - h.update(b"\x01") - - # content size - h.update(int.to_bytes(self.content_size, length=4, byteorder="big")) - - # content id - assert len(self.content_id) == 32 - h.update(self.content_id) - - # slot - h.update(self.slot.encode()) - - # parent - assert len(self.parent) == 32 - h.update(self.parent) - - # leader proof - assert len(self.leader_proof.commitment) == 32 - h.update(self.leader_proof.commitment) - assert len(self.leader_proof.nullifier) == 32 - h.update(self.leader_proof.nullifier) - assert len(self.leader_proof.evolved_commitment) == 32 - h.update(self.leader_proof.evolved_commitment) - - # orphaned proofs - h.update(int.to_bytes(len(self.orphaned_proofs), length=4, byteorder="big")) - for proof in self.orphaned_proofs: - proof.update_header_hash(h) # **Attention**: - # The ID of a block header is defined as the 32byte blake2b hash of its fields + # The ID of a block header is defined as the hash of its fields # as serialized in the format specified by the 'HEADER' rule in 'messages.abnf'. # # The following code is to be considered as a reference implementation, mostly to be used for testing. - def id(self) -> Id: - h = blake2b(digest_size=32) - self.update_header_hash(h) - return h.digest() + def id(self) -> Hash: + return Hash( + b"BLOCK_ID", + b"\x01", # version + int.to_bytes(self.content_size, length=4, byteorder="big"), # content size + self.content_id, # content id + self.slot.encode(), # slot + self.parent, # parent + # leader proof + self.leader_proof.commitment, + self.leader_proof.nullifier, + self.leader_proof.evolved_commitment, + ) def __hash__(self): return hash(self.id()) @@ -273,19 +247,19 @@ class LedgerState: # # NOTE that this does not prevent nonce grinding at the last slot # when the nonce snapshot is taken - nonce: Id = None + nonce: Hash = None # set of commitments - commitments_spend: set[Id] = field(default_factory=set) + commitments_spend: set[Hash] = field(default_factory=set) # set of commitments eligible to lead - commitments_lead: set[Id] = field(default_factory=set) + commitments_lead: set[Hash] = field(default_factory=set) # set of nullified notes - nullifiers: set[Id] = field(default_factory=set) + nullifiers: set[Hash] = field(default_factory=set) # -- Stake Relativization State - # The number of observed leaders (blocks + orphans), this measurement is + # The number of observed leaders, this measurement is # used in inferring total active stake in the network. leader_count: int = 0 @@ -302,13 +276,13 @@ class LedgerState: def replace(self, **kwarg) -> "LedgerState": return replace(self, **kwarg) - def verify_eligible_to_spend(self, commitment: Id) -> bool: + def verify_eligible_to_spend(self, commitment: Hash) -> bool: return commitment in self.commitments_spend - def verify_eligible_to_lead(self, commitment: Id) -> bool: + def verify_eligible_to_lead(self, commitment: Hash) -> bool: return commitment in self.commitments_lead - def verify_unspent(self, nullifier: Id) -> bool: + def verify_unspent(self, nullifier: Hash) -> bool: return nullifier not in self.nullifiers def apply(self, block: BlockHeader): @@ -320,9 +294,8 @@ class LedgerState: block.leader_proof.nullifier, block.slot.encode(), ) + self.apply_leader_proof(block.leader_proof) self.block = block - for proof in itertools.chain(block.orphaned_proofs, [block]): - self.apply_leader_proof(proof.leader_proof) def apply_leader_proof(self, proof: MockLeaderProof): self.nullifiers.add(proof.nullifier) @@ -347,7 +320,7 @@ class EpochState: # leadership lottery. inferred_total_active_stake: int - def verify_eligible_to_lead_due_to_age(self, commitment: Id) -> bool: + def verify_eligible_to_lead_due_to_age(self, commitment: Hash) -> bool: # A note is eligible to lead if it was committed to before the the stake # distribution snapshot was taken or it was produced by a leader proof # since the snapshot was taken. @@ -371,7 +344,7 @@ class EpochState: class Follower: def __init__(self, genesis_state: LedgerState, config: Config): self.config = config - self.forks: list[Id] = [] + self.forks: list[Hash] = [] self.local_chain = genesis_state.block.id() self.genesis_state = genesis_state self.ledger_state = {genesis_state.block.id(): genesis_state.copy()} @@ -384,39 +357,10 @@ class Follower: current_state = self.ledger_state[block.parent].copy() - # We use the proposed block epoch state to validate orphans as well. - # For very old orphans, these states may be different. epoch_state = self.compute_epoch_state( block.slot.epoch(self.config), block.parent ) - # first, we verify adopted leadership transactions - for orphan in block.orphaned_proofs: - # orphan proofs are checked in two ways - # 1. ensure they are valid locally in their original branch - # 2. ensure it does not conflict with current state - - # We take a shortcut for (1.) by restricting orphans to proofs we've - # already processed in other branches. - if orphan.id() not in self.ledger_state: - raise MissingOrphanProof - - # (2.) is satisfied by verifying the proof against current state ensuring: - # - it is a valid proof - # - and the nullifier has not already been spent - if not self.verify_slot_leader( - orphan.slot, - orphan.parent, - orphan.leader_proof, - epoch_state, - current_state, - ): - raise InvalidOrphanProof - - # if an adopted leadership proof is valid we need to apply its - # effects to the ledger state - current_state.apply_leader_proof(orphan.leader_proof) - # TODO: this is not the full block validation spec, only slot leader is verified if not self.verify_slot_leader( block.slot, @@ -430,7 +374,7 @@ class Follower: def verify_slot_leader( self, slot: Slot, - parent: Id, + parent: Hash, proof: MockLeaderProof, # notes are old enough if their commitment is in the stake distribution snapshot epoch_state: EpochState, @@ -491,29 +435,8 @@ class Follower: self.forks.remove(checkpoint_block_id) self.local_chain = checkpoint_block_id - def unimported_orphans(self) -> list[BlockHeader]: - """ - Returns all unimported orphans w.r.t. the given tip's state. - Orphans are returned in the order that they should be imported. - """ - tip_state = self.tip_state().copy() - tip = tip_state.block.id() - - orphans = [] - - for fork in self.forks: - _, _, fork_depth, fork_suffix = common_prefix_depth( - tip, fork, self.ledger_state - ) - for b in fork_suffix: - if b.leader_proof.nullifier not in tip_state.nullifiers: - tip_state.nullifiers.add(b.leader_proof.nullifier) - orphans += [b] - - return orphans - # Evaluate the fork choice rule and return the chain we should be following - def fork_choice(self) -> Id: + def fork_choice(self) -> Hash: return maxvalid_bg( self.local_chain, self.forks, @@ -525,13 +448,13 @@ class Follower: def tip(self) -> BlockHeader: return self.tip_state().block - def tip_id(self) -> Id: + def tip_id(self) -> Hash: return self.local_chain def tip_state(self) -> LedgerState: return self.ledger_state[self.tip_id()] - def state_at_slot_beginning(self, tip: Id, slot: Slot) -> LedgerState: + def state_at_slot_beginning(self, tip: Hash, slot: Slot) -> LedgerState: for state in iter_chain(tip, self.ledger_state): if state.block.slot < slot: return state @@ -540,7 +463,7 @@ class Follower: def epoch_start_slot(self, epoch) -> Slot: return Slot(epoch.epoch * self.config.epoch_length) - def stake_distribution_snapshot(self, epoch, tip: Id): + def stake_distribution_snapshot(self, epoch, tip: Hash): # stake distribution snapshot happens at the beginning of the previous epoch, # i.e. for epoch e, the snapshot is taken at the last block of epoch e-2 slot = Slot(epoch.prev().epoch * self.config.epoch_length) @@ -555,7 +478,7 @@ class Follower: ) return self.state_at_slot_beginning(tip, slot) - def compute_epoch_state(self, epoch: Epoch, tip: Id) -> EpochState: + def compute_epoch_state(self, epoch: Epoch, tip: Hash) -> EpochState: if epoch.epoch == 0: return EpochState( stake_distribution_snapshot=self.genesis_state, @@ -663,7 +586,7 @@ class Leader: note: Note def try_prove_slot_leader( - self, epoch: EpochState, slot: Slot, parent: Id + self, epoch: EpochState, slot: Slot, parent: Hash ) -> MockLeaderProof | None: if self._is_slot_leader(epoch, slot): return MockLeaderProof(self.note, slot, parent) @@ -679,7 +602,7 @@ class Leader: def iter_chain( - tip: Id, states: Dict[Id, LedgerState] + tip: Hash, states: Dict[Hash, LedgerState] ) -> Generator[LedgerState, None, None]: while tip in states: yield states[tip] @@ -687,14 +610,14 @@ def iter_chain( def iter_chain_blocks( - tip: Id, states: Dict[Id, LedgerState] + tip: Hash, states: Dict[Hash, LedgerState] ) -> Generator[BlockHeader, None, None]: for state in iter_chain(tip, states): yield state.block def common_prefix_depth( - a: Id, b: Id, states: Dict[Id, LedgerState] + a: Hash, b: Hash, states: Dict[Hash, LedgerState] ) -> tuple[int, list[BlockHeader], int, list[BlockHeader]]: return common_prefix_depth_from_chains( iter_chain_blocks(a, states), iter_chain_blocks(b, states) @@ -752,7 +675,7 @@ def chain_density(chain: list[BlockHeader], slot: Slot) -> int: return sum(1 for b in chain if b.slot < slot) -def block_children(states: Dict[Id, LedgerState]) -> Dict[Id, set[Id]]: +def block_children(states: Dict[Hash, LedgerState]) -> Dict[Hash, set[Hash]]: children = defaultdict(set) for c, state in states.items(): children[state.block.parent].add(c) @@ -768,14 +691,14 @@ def block_children(states: Dict[Id, LedgerState]) -> Dict[Id, set[Id]]: # k defines the forking depth of a chain at which point we switch phases. # s defines the length of time (unit of slots) after the fork happened we will inspect for chain density def maxvalid_bg( - local_chain: Id, - forks: List[Id], + local_chain: Hash, + forks: List[Hash], k: int, s: int, - states: Dict[Id, LedgerState], -) -> Id: - assert type(local_chain) == Id - assert all(type(f) == Id for f in forks) + states: Dict[Hash, LedgerState], +) -> Hash: + assert type(local_chain) == Hash, type(local_chain) + assert all(type(f) == Hash for f in forks) cmax = local_chain for fork in forks: @@ -806,16 +729,6 @@ class ParentNotFound(Exception): return "Parent not found" -class MissingOrphanProof(Exception): - def __str__(self): - return "Missing orphan proof" - - -class InvalidOrphanProof(Exception): - def __str__(self): - return "Invalid orphan proof" - - class InvalidLeaderProof(Exception): def __str__(self): return "Invalid leader proof" diff --git a/cryptarchia/sync.py b/cryptarchia/sync.py index 6921c3b..cea6f21 100644 --- a/cryptarchia/sync.py +++ b/cryptarchia/sync.py @@ -4,7 +4,7 @@ from typing import Generator from cryptarchia.cryptarchia import ( BlockHeader, Follower, - Id, + Hash, ParentNotFound, Slot, common_prefix_depth_from_chains, @@ -19,7 +19,7 @@ def sync(local: Follower, peers: list[Follower]): # Repeat the sync process until no peer has a tip ahead of the local tip, # because peers' tips may advance during the sync process. block_fetcher = BlockFetcher(peers) - rejected_blocks: set[Id] = set() + rejected_blocks: set[Hash] = set() while True: # Fetch blocks from the peers in the range of slots from the local tip to the latest tip. # Gather orphaned blocks, which are blocks from forks that are absent in the local block tree. @@ -145,7 +145,7 @@ class BlockFetcher: continue def fetch_chain_backward( - self, tip: Id, local: Follower + self, tip: Hash, local: Follower ) -> Generator[BlockHeader, None, None]: # Fetches a chain of blocks from the peers, starting from the given tip to the genesis. # Attempts to extend the chain as much as possible by querying multiple peers, diff --git a/cryptarchia/test_common.py b/cryptarchia/test_common.py index e9bda5e..e930132 100644 --- a/cryptarchia/test_common.py +++ b/cryptarchia/test_common.py @@ -7,6 +7,7 @@ from .cryptarchia import ( MockLeaderProof, Leader, Follower, + Hash, ) @@ -29,7 +30,6 @@ class TestNode: return BlockHeader( parent=parent, slot=slot, - orphaned_proofs=self.follower.unimported_orphans(), leader_proof=leader_proof, content_size=0, content_id=bytes(32), @@ -67,19 +67,17 @@ def mk_genesis_state(initial_stake_distribution: list[Note]) -> LedgerState: def mk_block( - parent: BlockHeader, slot: int, note: Note, content=bytes(32), orphaned_proofs=[] + parent: BlockHeader, slot: int, note: Note, content=bytes(32) ) -> BlockHeader: assert type(parent) == BlockHeader, type(parent) assert type(slot) == int, type(slot) - from hashlib import sha256 return BlockHeader( slot=Slot(slot), parent=parent.id(), content_size=len(content), - content_id=sha256(content).digest(), + content_id=Hash(b"CONTENT_ID", content), leader_proof=MockLeaderProof(note, Slot(slot), parent=parent.id()), - orphaned_proofs=orphaned_proofs, ) diff --git a/cryptarchia/test_ledger_state_update.py b/cryptarchia/test_ledger_state_update.py index 79a8ef9..4c5649d 100644 --- a/cryptarchia/test_ledger_state_update.py +++ b/cryptarchia/test_ledger_state_update.py @@ -4,7 +4,6 @@ from .cryptarchia import ( Note, Follower, InvalidLeaderProof, - MissingOrphanProof, ParentNotFound, iter_chain, ) @@ -263,46 +262,3 @@ class TestLedgerStateUpdate(TestCase): block_2_1 = mk_block(slot=40, parent=block_2_0, note=note_new.evolve()) follower.on_block(block_2_1) assert follower.tip() == block_2_1 - - def test_orphaned_proofs(self): - note, note_orphan = Note(sk=0, value=100), Note(sk=1, value=100) - genesis = mk_genesis_state([note, note_orphan]) - - follower = Follower(genesis, mk_config([note, note_orphan])) - - block_0_0 = mk_block(slot=0, parent=genesis.block, note=note) - follower.on_block(block_0_0) - assert follower.tip() == block_0_0 - - note_new = note.evolve() - note_new_new = note_new.evolve() - block_0_1 = mk_block(slot=1, parent=block_0_0, note=note_new_new) - with self.assertRaises(InvalidLeaderProof): - follower.on_block(block_0_1) - # the note evolved twice should not be accepted as it is not in the lead commitments - assert follower.tip() == block_0_0 - - # An orphaned proof will not be accepted until a node first sees the corresponding block. - # - # Also, notice that the block is using the evolved orphan note which is not present on the main - # branch. The evolved orphan commitment is added from the orphan prior to validating the block - # header as part of orphan importing process - orphan = mk_block(parent=genesis.block, slot=0, note=note_orphan) - block_0_1 = mk_block( - slot=1, - parent=block_0_0, - note=note_orphan.evolve(), - orphaned_proofs=[orphan], - ) - with self.assertRaises(MissingOrphanProof): - follower.on_block(block_0_1) - - # since follower had not seen this orphan prior to being included as - # an orphan proof, it will be rejected - assert follower.tip() == block_0_0 - - # but all is fine if the follower first sees the orphan block, and then - # is imported into the main chain - follower.on_block(orphan) - follower.on_block(block_0_1) - assert follower.tip() == block_0_1 diff --git a/cryptarchia/test_orphaned_proofs.py b/cryptarchia/test_orphaned_proofs.py deleted file mode 100644 index 00c2a20..0000000 --- a/cryptarchia/test_orphaned_proofs.py +++ /dev/null @@ -1,251 +0,0 @@ -from unittest import TestCase - -from cryptarchia.cryptarchia import Note, Follower - -from .test_common import mk_config, mk_genesis_state, mk_block - - -class TestOrphanedProofs(TestCase): - def test_simple_orphan_import(self): - n_a, n_b = Note(sk=0, value=10), Note(sk=1, value=10) - coins = [n_a, n_b] - config = mk_config(coins) - genesis = mk_genesis_state(coins) - follower = Follower(genesis, config) - - # -- fork -- - # - # b2 == tip - # / - # b1 - # \ - # b3 - # - - b1, n_a = mk_block(genesis.block, 1, n_a), n_a.evolve() - b2, n_a = mk_block(b1, 2, n_a), n_a.evolve() - b3, n_b = mk_block(b1, 2, n_b), n_b.evolve() - - for b in [b1, b2, b3]: - follower.on_block(b) - - assert follower.tip() == b2 - assert [f for f in follower.forks] == [b3.id()] - assert follower.unimported_orphans() == [b3] - - # -- extend with import -- - # - # b2 - b4 - # / / - # b1 / - # \ / - # b3 - # - b4, n_a = mk_block(b2, 3, n_a, orphaned_proofs=[b3]), n_a.evolve() - follower.on_block(b4) - - assert follower.tip() == b4 - assert [f for f in follower.forks] == [b3.id()] - assert follower.unimported_orphans() == [] - - def test_orphan_proof_import_from_long_running_fork(self): - n_a, n_b = Note(sk=0, value=10), Note(sk=1, value=10) - coins = [n_a, n_b] - config = mk_config(coins) - genesis = mk_genesis_state(coins) - follower = Follower(genesis, config) - - # -- fork -- - # - # b2 - b3 == tip - # / - # b1 - # \ - # b4 - b5 - # - - b1, n_a = mk_block(genesis.block, 1, n_a), n_a.evolve() - - b2, n_a = mk_block(b1, 2, n_a), n_a.evolve() - b3, n_a = mk_block(b2, 3, n_a), n_a.evolve() - - b4, n_b = mk_block(b1, 2, n_b), n_b.evolve() - b5, n_b = mk_block(b4, 3, n_b), n_b.evolve() - - for b in [b1, b2, b3, b4, b5]: - follower.on_block(b) - - assert follower.tip() == b3 - assert [f for f in follower.forks] == [b5.id()] - assert follower.unimported_orphans() == [b4, b5] - - # -- extend b3, importing the fork -- - # - # b2 - b3 - b6 == tip - # / ___/ - # b1 ___/ / - # \ / / - # b4 - b5 - - b6, n_a = mk_block(b3, 4, n_a, orphaned_proofs=[b4, b5]), n_a.evolve() - follower.on_block(b6) - - assert follower.tip() == b6 - assert [f for f in follower.forks] == [b5.id()] - - def test_orphan_proof_import_from_fork_without_direct_shared_parent(self): - coins = [Note(sk=i, value=10) for i in range(2)] - n_a, n_b = coins - config = mk_config(coins) - genesis = mk_genesis_state(coins) - follower = Follower(genesis, config) - - # -- forks -- - # - # b2 - b3 - b4 == tip - # / - # b1 - # \ - # b5 - b6 - b7 - - b1, n_a = mk_block(genesis.block, 1, n_a), n_a.evolve() - - b2, n_a = mk_block(b1, 2, n_a), n_a.evolve() - b3, n_a = mk_block(b2, 3, n_a), n_a.evolve() - b4, n_a = mk_block(b3, 4, n_a), n_a.evolve() - - b5, n_b = mk_block(b1, 2, n_b), n_b.evolve() - b6, n_b = mk_block(b5, 3, n_b), n_b.evolve() - b7, n_b = mk_block(b6, 4, n_b), n_b.evolve() - - for b in [b1, b2, b3, b4, b5, b6, b7]: - follower.on_block(b) - - assert follower.tip() == b4 - assert [f for f in follower.forks] == [b7.id()] - assert follower.unimported_orphans() == [b5, b6, b7] - - # -- extend b4, importing the forks -- - # - # b2 - b3 - b4 - b8 == tip - # / _______/ - # b1 ____/______/ - # \ / / / - # b5 - b6 - b7 - # - # Earlier implementations of orphan proof validation failed to - # validate b7 as an orphan here. - - b8, n_a = mk_block(b4, 5, n_a, orphaned_proofs=[b5, b6, b7]), n_a.evolve() - follower.on_block(b8) - - assert follower.tip() == b8 - assert [f for f in follower.forks] == [b7.id()] - assert follower.unimported_orphans() == [] - - def test_unimported_orphans(self): - # Given the following fork graph: - # - # b2 - b3 - # / - # b1 - # \ - # b4 - b5 - # \ - # -- b6 - # - # Orphans w.r.t. to b3 are b4..6, thus extending from b3 with b7 would - # give the following fork graph - # - # b2 - b3 --- b7== tip - # / ____/ - # b1 ____/ __/ - # \ / / / - # b4 - b5 / - # \ / - # -- b6 - # - - coins = [Note(sk=i, value=10) for i in range(3)] - n_a, n_b, n_c = coins - config = mk_config(coins) - genesis = mk_genesis_state(coins) - follower = Follower(genesis, config) - - b1, n_a = mk_block(genesis.block, 1, n_a), n_a.evolve() - - b2, n_a = mk_block(b1, 2, n_a), n_a.evolve() - b3, n_a = mk_block(b2, 3, n_a), n_a.evolve() - - b4, n_b = mk_block(b1, 2, n_b), n_b.evolve() - b5, n_b = mk_block(b4, 3, n_b), n_b.evolve() - - b6, n_c = mk_block(b4, 3, n_c), n_c.evolve() - - for b in [b1, b2, b3, b4, b5, b6]: - follower.on_block(b) - - assert follower.tip() == b3 - assert [f for f in follower.forks] == [b5.id(), b6.id()] - assert follower.unimported_orphans() == [b4, b5, b6] - - b7, n_a = mk_block(b3, 4, n_a, orphaned_proofs=[b4, b5, b6]), n_a.evolve() - - follower.on_block(b7) - assert follower.tip() == b7 - - def test_transitive_orphan_reimports(self): - # Two forks, one after the other, with some complicated orphan imports. - # I don't have different line colors to differentiate orphans from parents - # so I've added o=XX to differentiate orphans from parents. - # - # - The first fork at b3(a) is not too interesting. - # - The second fork at b4(b) has both b6 and b7 importing b5 - # - crucially b7 uses the evolved commitment from b5 - # - Then finally b8 imports b7. - # - # proper orphan proof importing will be able to deal with the fact that - # b7's commitment was produced outside of the main branch AND the commitment - # is not part of the current list of orphans in b8 - # (b5 had already been imported, therefore it is not included as an orphan in b8) - # - # b1(a) - b2(a) - b3(a) - b4(b) - b6(b, o=b5) - b8(b, o=b7) - # \ \___ __/ __/ - # \ _x_ __/ - # \ / \_ / - # -b5(a)-----\-b7(a, o=b5) - - coins = [Note(sk=i, value=10) for i in range(2)] - n_a, n_b = coins - config = mk_config(coins) - genesis = mk_genesis_state(coins) - follower = Follower(genesis, config) - - b1, n_a = mk_block(genesis.block, 1, n_a), n_a.evolve() - b2, n_a = mk_block(b1, 2, n_a), n_a.evolve() - b3, n_a = mk_block(b2, 3, n_a), n_a.evolve() - - b4, n_b = mk_block(b3, 4, n_b), n_b.evolve() - b5, n_a = mk_block(b3, 4, n_a), n_a.evolve() - - b6, n_b = mk_block(b4, 5, n_b, orphaned_proofs=[b5]), n_b.evolve() - b7, n_a = mk_block(b4, 5, n_a, orphaned_proofs=[b5]), n_a.evolve() - - b8, n_b = mk_block(b6, 6, n_b, orphaned_proofs=[b7]), n_b.evolve() - - for b in [b1, b2, b3, b4, b5]: - follower.on_block(b) - - assert follower.tip() == b4 - assert follower.unimported_orphans() == [b5] - - for b in [b6, b7]: - follower.on_block(b) - - assert follower.tip() == b6 - assert follower.unimported_orphans() == [b7] - - follower.on_block(b8) - - assert follower.tip() == b8 - assert follower.unimported_orphans() == [] diff --git a/cryptarchia/test_stake_relativization.py b/cryptarchia/test_stake_relativization.py index 3a58f76..7448dbf 100644 --- a/cryptarchia/test_stake_relativization.py +++ b/cryptarchia/test_stake_relativization.py @@ -26,16 +26,16 @@ class TestStakeRelativization(TestCase): assert follower.tip_state().leader_count == 1 # on fork, tip state is not updated - orphan = mk_block(genesis.block, slot=1, note=n_b) - follower.on_block(orphan) + fork = mk_block(genesis.block, slot=1, note=n_b) + follower.on_block(fork) assert follower.tip_state().block == b1 assert follower.tip_state().leader_count == 1 - # after orphan is adopted, leader count should jumpy by 2 (each orphan counts as a leader) - b2 = mk_block(b1, slot=2, note=n_a.evolve(), orphaned_proofs=[orphan]) + # continuing the chain increments the leader count + b2 = mk_block(b1, slot=2, note=n_a.evolve()) follower.on_block(b2) assert follower.tip_state().block == b2 - assert follower.tip_state().leader_count == 3 + assert follower.tip_state().leader_count == 2 def test_inference_on_empty_genesis_epoch(self): note = Note(sk=0, value=10)