From 17bec9b570bb232f7089defcbe599e54cb7abcd7 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Fri, 21 Mar 2025 01:23:34 +0400 Subject: [PATCH] cryptarchia: maintain a single commitment set in ledger state --- cryptarchia/cryptarchia.py | 98 +++++++++----------------------------- cryptarchia/test_common.py | 3 +- 2 files changed, 23 insertions(+), 78 deletions(-) diff --git a/cryptarchia/cryptarchia.py b/cryptarchia/cryptarchia.py index 35cf3b5..d5aa4c8 100644 --- a/cryptarchia/cryptarchia.py +++ b/cryptarchia/cryptarchia.py @@ -185,21 +185,19 @@ class MockLeaderProof: slot: Slot parent: Hash - @property - def commitment(self): - return self.note.commitment() + def epoch_nonce_contribution(self) -> Hash: + return Hash(b"NOMOS_NONCE_CONTRIB", self.slot.encode(), self.note.nullifier()) - @property - def nullifier(self): - return self.note.nullifier() - - @property - def evolved_commitment(self): - return self.note.evolve().commitment() - - def verify(self, slot: Slot, parent: Hash): - # TODO: verification not implemented - return slot == self.slot and parent == self.parent + def verify( + self, slot: Slot, parent: Hash, commitments: set[Hash], nullifiers: set[Hash] + ): + # TODO: verify slot lottery + return ( + slot == self.slot + and parent == self.parent + and self.note.commitment() in commitments + and self.note.nullifier() not in nullifiers + ) @dataclass @@ -224,9 +222,8 @@ class BlockHeader: self.slot.encode(), # slot self.parent, # parent # leader proof - self.leader_proof.commitment, - self.leader_proof.nullifier, - self.leader_proof.evolved_commitment, + self.leader_proof.epoch_nonce_contribution(), + # self.leader_proof -- the proof itself needs to be include in the hash ) def __hash__(self): @@ -249,11 +246,8 @@ class LedgerState: # when the nonce snapshot is taken nonce: Hash = None - # set of commitments - commitments_spend: set[Hash] = field(default_factory=set) - - # set of commitments eligible to lead - commitments_lead: set[Hash] = field(default_factory=set) + # set of note commitments + commitments: set[Hash] = field(default_factory=set) # set of nullified notes nullifiers: set[Hash] = field(default_factory=set) @@ -267,8 +261,7 @@ class LedgerState: return LedgerState( block=self.block, nonce=self.nonce, - commitments_spend=deepcopy(self.commitments_spend), - commitments_lead=deepcopy(self.commitments_lead), + commitments=deepcopy(self.commitments), nullifiers=deepcopy(self.nullifiers), leader_count=self.leader_count, ) @@ -276,32 +269,17 @@ class LedgerState: def replace(self, **kwarg) -> "LedgerState": return replace(self, **kwarg) - def verify_eligible_to_spend(self, commitment: Hash) -> bool: - return commitment in self.commitments_spend - - def verify_eligible_to_lead(self, commitment: Hash) -> bool: - return commitment in self.commitments_lead - - def verify_unspent(self, nullifier: Hash) -> bool: - return nullifier not in self.nullifiers - def apply(self, block: BlockHeader): assert block.parent == self.block.id() self.nonce = Hash( b"EPOCH_NONCE", self.nonce, - block.leader_proof.nullifier, + block.leader_proof.epoch_nonce_contribution(), block.slot.encode(), ) - self.apply_leader_proof(block.leader_proof) - self.block = block - - def apply_leader_proof(self, proof: MockLeaderProof): - self.nullifiers.add(proof.nullifier) - self.commitments_spend.add(proof.evolved_commitment) - self.commitments_lead.add(proof.evolved_commitment) self.leader_count += 1 + self.block = block @dataclass @@ -320,16 +298,6 @@ class EpochState: # leadership lottery. inferred_total_active_stake: int - 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. - # - # This verification is checking that first condition. - # - # NOTE: `ledger_state.commitments_spend` is a super-set of `ledger_state.commitments_lead` - return self.stake_distribution_snapshot.verify_eligible_to_spend(commitment) - def total_active_stake(self) -> int: """ Returns the inferred total stake participating in consensus. @@ -362,36 +330,14 @@ class Follower: ) # TODO: this is not the full block validation spec, only slot leader is verified - if not self.verify_slot_leader( + if not block.leader_proof.verify( block.slot, block.parent, - block.leader_proof, - epoch_state, - current_state, + epoch_state.stake_distribution_snapshot.commitments, + current_state.nullifiers, ): raise InvalidLeaderProof - def verify_slot_leader( - self, - slot: Slot, - parent: Hash, - proof: MockLeaderProof, - # notes are old enough if their commitment is in the stake distribution snapshot - epoch_state: EpochState, - # nullifiers (and commitments) are checked against the current state. - # For now, we assume proof parent state and current state are identical. - # This will change once we start putting merkle roots in headers - current_state: LedgerState, - ) -> bool: - return ( - proof.verify(slot, parent) # verify slot leader proof - and ( - current_state.verify_eligible_to_lead(proof.commitment) - or epoch_state.verify_eligible_to_lead_due_to_age(proof.commitment) - ) - and current_state.verify_unspent(proof.nullifier) - ) - def apply_block_to_ledger_state(self, block: BlockHeader) -> bool: if block.id() in self.ledger_state: logger.warning("dropping already processed block") diff --git a/cryptarchia/test_common.py b/cryptarchia/test_common.py index e930132..cc958e0 100644 --- a/cryptarchia/test_common.py +++ b/cryptarchia/test_common.py @@ -60,8 +60,7 @@ def mk_genesis_state(initial_stake_distribution: list[Note]) -> LedgerState: ), ), nonce=bytes(32), - commitments_spend={n.commitment() for n in initial_stake_distribution}, - commitments_lead={n.commitment() for n in initial_stake_distribution}, + commitments={n.commitment() for n in initial_stake_distribution}, nullifiers=set(), )