cryptarchia: maintain a single commitment set in ledger state

This commit is contained in:
David Rusu 2025-03-21 01:23:34 +04:00
parent 236a677c86
commit 17bec9b570
2 changed files with 23 additions and 78 deletions

View File

@ -185,21 +185,19 @@ class MockLeaderProof:
slot: Slot slot: Slot
parent: Hash parent: Hash
@property def epoch_nonce_contribution(self) -> Hash:
def commitment(self): return Hash(b"NOMOS_NONCE_CONTRIB", self.slot.encode(), self.note.nullifier())
return self.note.commitment()
@property def verify(
def nullifier(self): self, slot: Slot, parent: Hash, commitments: set[Hash], nullifiers: set[Hash]
return self.note.nullifier() ):
# TODO: verify slot lottery
@property return (
def evolved_commitment(self): slot == self.slot
return self.note.evolve().commitment() and parent == self.parent
and self.note.commitment() in commitments
def verify(self, slot: Slot, parent: Hash): and self.note.nullifier() not in nullifiers
# TODO: verification not implemented )
return slot == self.slot and parent == self.parent
@dataclass @dataclass
@ -224,9 +222,8 @@ class BlockHeader:
self.slot.encode(), # slot self.slot.encode(), # slot
self.parent, # parent self.parent, # parent
# leader proof # leader proof
self.leader_proof.commitment, self.leader_proof.epoch_nonce_contribution(),
self.leader_proof.nullifier, # self.leader_proof -- the proof itself needs to be include in the hash
self.leader_proof.evolved_commitment,
) )
def __hash__(self): def __hash__(self):
@ -249,11 +246,8 @@ class LedgerState:
# when the nonce snapshot is taken # when the nonce snapshot is taken
nonce: Hash = None nonce: Hash = None
# set of commitments # set of note commitments
commitments_spend: set[Hash] = field(default_factory=set) commitments: set[Hash] = field(default_factory=set)
# set of commitments eligible to lead
commitments_lead: set[Hash] = field(default_factory=set)
# set of nullified notes # set of nullified notes
nullifiers: set[Hash] = field(default_factory=set) nullifiers: set[Hash] = field(default_factory=set)
@ -267,8 +261,7 @@ class LedgerState:
return LedgerState( return LedgerState(
block=self.block, block=self.block,
nonce=self.nonce, nonce=self.nonce,
commitments_spend=deepcopy(self.commitments_spend), commitments=deepcopy(self.commitments),
commitments_lead=deepcopy(self.commitments_lead),
nullifiers=deepcopy(self.nullifiers), nullifiers=deepcopy(self.nullifiers),
leader_count=self.leader_count, leader_count=self.leader_count,
) )
@ -276,32 +269,17 @@ class LedgerState:
def replace(self, **kwarg) -> "LedgerState": def replace(self, **kwarg) -> "LedgerState":
return replace(self, **kwarg) 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): def apply(self, block: BlockHeader):
assert block.parent == self.block.id() assert block.parent == self.block.id()
self.nonce = Hash( self.nonce = Hash(
b"EPOCH_NONCE", b"EPOCH_NONCE",
self.nonce, self.nonce,
block.leader_proof.nullifier, block.leader_proof.epoch_nonce_contribution(),
block.slot.encode(), 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.leader_count += 1
self.block = block
@dataclass @dataclass
@ -320,16 +298,6 @@ class EpochState:
# leadership lottery. # leadership lottery.
inferred_total_active_stake: int 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: def total_active_stake(self) -> int:
""" """
Returns the inferred total stake participating in consensus. 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 # 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.slot,
block.parent, block.parent,
block.leader_proof, epoch_state.stake_distribution_snapshot.commitments,
epoch_state, current_state.nullifiers,
current_state,
): ):
raise InvalidLeaderProof 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: def apply_block_to_ledger_state(self, block: BlockHeader) -> bool:
if block.id() in self.ledger_state: if block.id() in self.ledger_state:
logger.warning("dropping already processed block") logger.warning("dropping already processed block")

View File

@ -60,8 +60,7 @@ def mk_genesis_state(initial_stake_distribution: list[Note]) -> LedgerState:
), ),
), ),
nonce=bytes(32), nonce=bytes(32),
commitments_spend={n.commitment() for n in initial_stake_distribution}, commitments={n.commitment() for n in initial_stake_distribution},
commitments_lead={n.commitment() for n in initial_stake_distribution},
nullifiers=set(), nullifiers=set(),
) )