Merge 2b5ce42e5808ca9b23667b21c503f1197c43604c into 242c9f4f77cf1d14b2136417a5e5534f58db9fb5

This commit is contained in:
davidrusu 2026-03-26 01:15:11 +02:00 committed by GitHub
commit 5d537ce1ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 160 additions and 161 deletions

View File

@ -73,36 +73,36 @@ class Config:
def cryptarchia_v0_0_1(initial_total_active_stake) -> "Config": def cryptarchia_v0_0_1(initial_total_active_stake) -> "Config":
return Config( return Config(
k=2160, k=2160,
active_slot_coeff=0.05, active_slot_coeff=1 / 30,
epoch_stake_distribution_stabilization=3, epoch_stake_distribution_stabilization=3,
epoch_period_nonce_buffer=3, epoch_period_nonce_buffer=3,
epoch_period_nonce_stabilization=4, epoch_period_nonce_stabilization=4,
initial_total_active_stake=initial_total_active_stake, initial_total_active_stake=initial_total_active_stake,
total_active_stake_learning_rate=0.8, total_active_stake_learning_rate=1.0,
time=TimeConfig( time=TimeConfig(
slot_duration=1, slot_duration=1,
chain_start_time=0, chain_start_time=0,
), ),
) )
@property @functools.cached_property
def base_period_length(self) -> int: def base_period_length(self) -> int:
return int(floor(self.k / self.active_slot_coeff)) return int(floor(self.k / self.active_slot_coeff))
@property @functools.cached_property
def epoch_relative_nonce_slot(self) -> int: def epoch_relative_nonce_slot(self) -> int:
return ( return (
self.epoch_stake_distribution_stabilization + self.epoch_period_nonce_buffer self.epoch_stake_distribution_stabilization + self.epoch_period_nonce_buffer
) * self.base_period_length ) * self.base_period_length
@property @functools.cached_property
def epoch_length(self) -> int: def epoch_length(self) -> int:
return ( return (
self.epoch_relative_nonce_slot self.epoch_relative_nonce_slot
+ self.epoch_period_nonce_stabilization * self.base_period_length + self.epoch_period_nonce_stabilization * self.base_period_length
) )
@property @functools.cached_property
def s(self): def s(self):
""" """
The Security Paramater. This paramter controls how many slots one must The Security Paramater. This paramter controls how many slots one must
@ -162,6 +162,7 @@ class Note:
def encode_pk(self) -> bytes: def encode_pk(self) -> bytes:
return int.to_bytes(self.pk, length=32, byteorder="big") return int.to_bytes(self.pk, length=32, byteorder="big")
@functools.cached_property
def commitment(self) -> Hash: def commitment(self) -> Hash:
value_bytes = int.to_bytes(self.value, length=32, byteorder="big") value_bytes = int.to_bytes(self.value, length=32, byteorder="big")
return Hash( return Hash(
@ -174,8 +175,9 @@ class Note:
self.zone_id, self.zone_id,
) )
@functools.cached_property
def nullifier(self) -> Hash: def nullifier(self) -> Hash:
return Hash(b"NOMOS_NOTE_NF", self.commitment(), self.encode_sk()) return Hash(b"NOMOS_NOTE_NF", self.commitment, self.encode_sk())
@dataclass @dataclass
@ -188,7 +190,7 @@ class MockLeaderProof:
return Hash( return Hash(
b"NOMOS_NONCE_CONTRIB", b"NOMOS_NONCE_CONTRIB",
self.slot.encode(), self.slot.encode(),
self.note.commitment(), self.note.commitment,
self.note.encode_sk(), self.note.encode_sk(),
) )
@ -199,8 +201,8 @@ class MockLeaderProof:
return ( return (
slot == self.slot slot == self.slot
and parent == self.parent and parent == self.parent
and self.note.commitment() in commitments and self.note.commitment in commitments
and self.note.nullifier() not in nullifiers and self.note.nullifier not in nullifiers
) )
@ -217,6 +219,7 @@ class BlockHeader:
# as serialized in the format specified by the 'HEADER' rule in 'messages.abnf'. # 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. # The following code is to be considered as a reference implementation, mostly to be used for testing.
@functools.cached_property
def id(self) -> Hash: def id(self) -> Hash:
return Hash( return Hash(
b"BLOCK_ID", b"BLOCK_ID",
@ -231,7 +234,7 @@ class BlockHeader:
) )
def __hash__(self): def __hash__(self):
return hash(self.id()) return hash(self.id)
@dataclass @dataclass
@ -271,7 +274,7 @@ class LedgerState:
return replace(self, **kwarg) return replace(self, **kwarg)
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",
@ -317,12 +320,12 @@ class Follower:
def __init__(self, genesis_state: LedgerState, config: Config): def __init__(self, genesis_state: LedgerState, config: Config):
self.config = config self.config = config
self.forks: list[Hash] = [] self.forks: list[Hash] = []
self.local_chain = genesis_state.block.id() self.local_chain = genesis_state.block.id
self.genesis_state = genesis_state self.genesis_state = genesis_state
self.ledger_state = {genesis_state.block.id(): genesis_state.copy()} self.ledger_state = {genesis_state.block.id: genesis_state.copy()}
self.epoch_state = {} self.epoch_state = {}
self.state = State.BOOTSTRAPPING self.state = State.BOOTSTRAPPING
self.lib = genesis_state.block.id() # Last immutable block, initially the genesis block self.lib = genesis_state.block.id # Last immutable block, initially the genesis block
def to_online(self): def to_online(self):
""" """
@ -359,7 +362,7 @@ class Follower:
raise InvalidLeaderProof raise InvalidLeaderProof
def on_block(self, block: BlockHeader): def on_block(self, block: BlockHeader):
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")
return return
@ -367,14 +370,14 @@ class Follower:
new_state = self.ledger_state[block.parent].copy() new_state = self.ledger_state[block.parent].copy()
new_state.apply(block) new_state.apply(block)
self.ledger_state[block.id()] = new_state self.ledger_state[block.id] = new_state
if block.parent == self.local_chain: if block.parent == self.local_chain:
# simply extending the local chain # simply extending the local chain
self.local_chain = block.id() self.local_chain = block.id
else: else:
# otherwise, this block creates a fork # otherwise, this block creates a fork
self.forks.append(block.id()) self.forks.append(block.id)
# remove any existing fork that is superceded by this block # remove any existing fork that is superceded by this block
if block.parent in self.forks: if block.parent in self.forks:
@ -389,7 +392,6 @@ class Follower:
if self.state == State.ONLINE: if self.state == State.ONLINE:
self.update_lib() self.update_lib()
# Update the lib, and prune forks that do not descend from it. # Update the lib, and prune forks that do not descend from it.
def update_lib(self): def update_lib(self):
""" """
@ -401,7 +403,7 @@ class Follower:
return return
# prune forks that do not descend from the last immutable block, this is needed to avoid Genesis rule to roll back # prune forks that do not descend from the last immutable block, this is needed to avoid Genesis rule to roll back
# past the LIB # past the LIB
self.lib = next(islice(iter_chain(self.local_chain, self.ledger_state), self.config.k, None), self.genesis_state).block.id() self.lib = next(islice(iter_chain(self.local_chain, self.ledger_state), self.config.k, None), self.genesis_state).block.id
self.forks = [ self.forks = [
f for f in self.forks if is_ancestor(self.lib, f, self.ledger_state) f for f in self.forks if is_ancestor(self.lib, f, self.ledger_state)
] ]
@ -411,7 +413,6 @@ class Follower:
if is_ancestor(self.lib, k, self.ledger_state) or is_ancestor(k, self.lib, self.ledger_state) if is_ancestor(self.lib, k, self.ledger_state) or is_ancestor(k, self.lib, self.ledger_state)
} }
# Evaluate the fork choice rule and return the chain we should be following # Evaluate the fork choice rule and return the chain we should be following
def fork_choice(self) -> Hash: def fork_choice(self) -> Hash:
if self.state == State.BOOTSTRAPPING: if self.state == State.BOOTSTRAPPING:
@ -450,20 +451,18 @@ class Follower:
def epoch_start_slot(self, epoch) -> Slot: def epoch_start_slot(self, epoch) -> Slot:
return Slot(epoch.epoch * self.config.epoch_length) return Slot(epoch.epoch * self.config.epoch_length)
def stake_distribution_snapshot(self, epoch, tip: Hash): def stake_distribution_snapshot_slot(self, epoch):
# stake distribution snapshot happens at the beginning of the previous epoch, # 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 # 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) return self.epoch_start_slot(epoch.prev())
return self.state_at_slot_beginning(tip, slot)
def nonce_snapshot(self, epoch, tip): def nonce_snapshot_slot(self, epoch):
# nonce snapshot happens partway through the previous epoch after the # nonce snapshot happens partway through the previous epoch after the
# stake distribution has stabilized # stake distribution has stabilized
slot = Slot( return Slot(
self.config.epoch_relative_nonce_slot self.config.epoch_relative_nonce_slot
+ self.epoch_start_slot(epoch.prev()).absolute_slot + self.epoch_start_slot(epoch.prev()).absolute_slot
) )
return self.state_at_slot_beginning(tip, slot)
def compute_epoch_state(self, epoch: Epoch, tip: Hash) -> EpochState: def compute_epoch_state(self, epoch: Epoch, tip: Hash) -> EpochState:
if epoch.epoch == 0: if epoch.epoch == 0:
@ -473,18 +472,21 @@ class Follower:
inferred_total_active_stake=self.config.initial_total_active_stake, inferred_total_active_stake=self.config.initial_total_active_stake,
) )
stake_distribution_snapshot = self.stake_distribution_snapshot(epoch, tip) if state := self.epoch_state.get((epoch, tip)):
nonce_snapshot = self.nonce_snapshot(epoch, tip)
# we memoize epoch states to avoid recursion killing our performance
memo_block_id = nonce_snapshot.block.id()
if state := self.epoch_state.get((epoch, memo_block_id)):
return state return state
nonce_slot = self.nonce_snapshot_slot(epoch)
stake_distribution_slot = self.stake_distribution_snapshot_slot(epoch)
stake_distribution_snapshot = self.state_at_slot_beginning(
tip, stake_distribution_slot
)
nonce_snapshot = self.state_at_slot_beginning(tip, nonce_slot)
# To update our inference of total stake, we need the prior estimate which # To update our inference of total stake, we need the prior estimate which
# was calculated last epoch. Thus we recurse here to retreive the previous # was calculated last epoch. Thus we recurse here to retreive the previous
# estimate of total stake. # estimate of total stake.
prev_epoch = self.compute_epoch_state(epoch.prev(), tip) prev_epoch = self.compute_epoch_state(epoch.prev(), nonce_snapshot.block.id)
inferred_total_active_stake = self._infer_total_active_stake( inferred_total_active_stake = self._infer_total_active_stake(
prev_epoch, nonce_snapshot, stake_distribution_snapshot prev_epoch, nonce_snapshot, stake_distribution_snapshot
) )
@ -495,7 +497,7 @@ class Follower:
inferred_total_active_stake=inferred_total_active_stake, inferred_total_active_stake=inferred_total_active_stake,
) )
self.epoch_state[(epoch, memo_block_id)] = state self.epoch_state[(epoch, tip)] = state
return state return state
def _infer_total_active_stake( def _infer_total_active_stake(
@ -509,19 +511,19 @@ class Follower:
# Since we need a stable inference of total stake for the start of this epoch, # Since we need a stable inference of total stake for the start of this epoch,
# we limit our look back period to the start of last epoch until when the nonce # we limit our look back period to the start of last epoch until when the nonce
# snapshot was taken. # snapshot was taken.
block_proposals_last_epoch = ( period_block_density = (
nonce_snapshot.leader_count - stake_distribution_snapshot.leader_count nonce_snapshot.leader_count - stake_distribution_snapshot.leader_count
) )
T = self.config.epoch_relative_nonce_slot # Use epoch_relative_nonce_slot as this is the actual observation window
mean_blocks_per_slot = block_proposals_last_epoch / T # (the slot range from stake_distribution_snapshot to nonce_snapshot)
expected_blocks_per_slot = np.log(1 / (1 - self.config.active_slot_coeff)) period = self.config.epoch_relative_nonce_slot
blocks_per_slot_err = expected_blocks_per_slot - mean_blocks_per_slot f = self.config.active_slot_coeff
h = ( beta = self.config.total_active_stake_learning_rate
self.config.total_active_stake_learning_rate total_stake_estimate = prev_epoch.inferred_total_active_stake
* prev_epoch.inferred_total_active_stake
/ expected_blocks_per_slot slot_activation_error = 1 - period_block_density / (period * f)
) coefficient = total_stake_estimate * beta
return int(prev_epoch.inferred_total_active_stake - h * blocks_per_slot_err) return max(1, int(total_stake_estimate - coefficient * slot_activation_error))
def blocks_by_slot(self, from_slot: Slot) -> Generator[BlockHeader, None, None]: def blocks_by_slot(self, from_slot: Slot) -> Generator[BlockHeader, None, None]:
# Returns blocks in the given range of slots in order of slot # Returns blocks in the given range of slots in order of slot
@ -564,7 +566,7 @@ class Leader:
b"LEAD", b"LEAD",
epoch.nonce(), epoch.nonce(),
slot.encode(), slot.encode(),
self.note.commitment(), self.note.commitment,
self.note.encode_sk(), self.note.encode_sk(),
) )
ticket = int.from_bytes(ticket) ticket = int.from_bytes(ticket)
@ -605,7 +607,7 @@ def is_ancestor(a: Hash, b: Hash, states: Dict[Hash, LedgerState]) -> bool:
Returns True if `a` is an ancestor of `b` in the chain. Returns True if `a` is an ancestor of `b` in the chain.
""" """
for state in iter_chain(b, states): for state in iter_chain(b, states):
if state.block.id() == a: if state.block.id == a:
return True return True
return False return False
@ -623,7 +625,7 @@ def common_prefix_depth(
try: try:
a_block = next(a_blocks) a_block = next(a_blocks)
a_suffix.append(a_block) a_suffix.append(a_block)
a_block_id = a_block.id() a_block_id = a_block.id
if a_block_id in seen: if a_block_id in seen:
# we had seen this block from the fork chain # we had seen this block from the fork chain
return ( return (
@ -640,7 +642,7 @@ def common_prefix_depth(
try: try:
b_block = next(b_blocks) b_block = next(b_blocks)
b_suffix.append(b_block) b_suffix.append(b_block)
b_block_id = b_block.id() b_block_id = b_block.id
if b_block_id in seen: if b_block_id in seen:
# we had seen the fork in the local chain # we had seen the fork in the local chain
return ( return (

View File

@ -39,8 +39,8 @@ def sync(local: Follower, peers: list[Follower], checkpoint: LedgerState | None
num_blocks += 1 num_blocks += 1
# Reject blocks that have been rejected in the past # Reject blocks that have been rejected in the past
# or whose parent has been rejected. # or whose parent has been rejected.
if {block.id(), block.parent} & rejected_blocks: if {block.id, block.parent} & rejected_blocks:
rejected_blocks.add(block.id()) rejected_blocks.add(block.id)
continue continue
try: try:
@ -49,7 +49,7 @@ def sync(local: Follower, peers: list[Follower], checkpoint: LedgerState | None
except ParentNotFound: except ParentNotFound:
orphans.add(block) orphans.add(block)
except Exception: except Exception:
rejected_blocks.add(block.id()) rejected_blocks.add(block.id)
# Finish the sync process if no block has been fetched, # Finish the sync process if no block has been fetched,
# which means that no peer has a tip ahead of the local tip. # which means that no peer has a tip ahead of the local tip.
@ -63,14 +63,11 @@ def sync(local: Follower, peers: list[Follower], checkpoint: LedgerState | None
# Skip the orphan block if it has been processed during the previous backfillings # Skip the orphan block if it has been processed during the previous backfillings
# (i.e. if it has been already added to the local block tree). # (i.e. if it has been already added to the local block tree).
# Or, skip if it has been rejected during the previous backfillings. # Or, skip if it has been rejected during the previous backfillings.
if ( if orphan.id not in local.ledger_state and orphan.id not in rejected_blocks:
orphan.id() not in local.ledger_state
and orphan.id() not in rejected_blocks
):
try: try:
backfill_fork(local, orphan, block_fetcher) backfill_fork(local, orphan, block_fetcher)
except InvalidBlockFromBackfillFork as e: except InvalidBlockFromBackfillFork as e:
rejected_blocks.update(block.id() for block in e.invalid_suffix) rejected_blocks.update(block.id for block in e.invalid_suffix)
def backfill_fork( def backfill_fork(
@ -83,7 +80,7 @@ def backfill_fork(
suffix = find_missing_part( suffix = find_missing_part(
local, local,
block_fetcher.fetch_chain_backward(fork_tip.id(), local), block_fetcher.fetch_chain_backward(fork_tip.id, local),
) )
# Add blocks in the fork suffix with applying fork choice rule. # Add blocks in the fork suffix with applying fork choice rule.
@ -105,7 +102,7 @@ def find_missing_part(
suffix: list[BlockHeader] = [] suffix: list[BlockHeader] = []
for block in fork: for block in fork:
if block.id() in local.ledger_state: if block.id in local.ledger_state:
break break
suffix.append(block) suffix.append(block)
suffix.reverse() suffix.reverse()
@ -167,7 +164,7 @@ class BlockFetcher:
# First, try to iterate the chain from the local block tree. # First, try to iterate the chain from the local block tree.
for block in iter_chain_blocks(id, local.ledger_state): for block in iter_chain_blocks(id, local.ledger_state):
yield block yield block
if block.id() == local.genesis_state.block.id(): if block.id == local.genesis_state.block.id:
return return
id = block.parent id = block.parent
@ -175,7 +172,7 @@ class BlockFetcher:
for peer in self.peers: for peer in self.peers:
for block in iter_chain_blocks(id, peer.ledger_state): for block in iter_chain_blocks(id, peer.ledger_state):
yield block yield block
if block.id() == local.genesis_state.block.id(): if block.id == local.genesis_state.block.id:
return return
id = block.parent id = block.parent

View File

@ -59,7 +59,7 @@ def mk_genesis_state(initial_stake_distribution: list[Note]) -> LedgerState:
), ),
), ),
nonce=bytes(32), nonce=bytes(32),
commitments={n.commitment() for n in initial_stake_distribution}, commitments={n.commitment for n in initial_stake_distribution},
nullifiers=set(), nullifiers=set(),
) )
@ -72,10 +72,10 @@ def mk_block(
return BlockHeader( return BlockHeader(
slot=Slot(slot), slot=Slot(slot),
parent=parent.id(), parent=parent.id,
content_size=len(content), content_size=len(content),
content_id=Hash(b"CONTENT_ID", content), content_id=Hash(b"CONTENT_ID", content),
leader_proof=MockLeaderProof(note, Slot(slot), parent=parent.id()), leader_proof=MockLeaderProof(note, Slot(slot), parent=parent.id),
) )

View File

@ -31,137 +31,135 @@ class TestForkChoice(TestCase):
b4, b5 = mk_chain(b0, Note(sk=2, value=1), slots=[1, 2]) b4, b5 = mk_chain(b0, Note(sk=2, value=1), slots=[1, 2])
b6, b7 = mk_chain(b2, Note(sk=3, value=1), slots=[3, 4]) b6, b7 = mk_chain(b2, Note(sk=3, value=1), slots=[3, 4])
states = { states = {b.id: LedgerState(block=b) for b in [b0, b1, b2, b3, b4, b5, b6, b7]}
b.id(): LedgerState(block=b) for b in [b0, b1, b2, b3, b4, b5, b6, b7]
}
assert (d := common_prefix_depth(b0.id(), b0.id(), states)) == ( assert (d := common_prefix_depth(b0.id, b0.id, states)) == (
0, 0,
[b0], [b0],
0, 0,
[b0], [b0],
), d ), d
assert (d := common_prefix_depth(b1.id(), b0.id(), states)) == ( assert (d := common_prefix_depth(b1.id, b0.id, states)) == (
1, 1,
[b0, b1], [b0, b1],
0, 0,
[b0], [b0],
), d ), d
assert (d := common_prefix_depth(b0.id(), b1.id(), states)) == ( assert (d := common_prefix_depth(b0.id, b1.id, states)) == (
0, 0,
[b0], [b0],
1, 1,
[b0, b1], [b0, b1],
), d ), d
assert (d := common_prefix_depth(b1.id(), b1.id(), states)) == ( assert (d := common_prefix_depth(b1.id, b1.id, states)) == (
0, 0,
[b1], [b1],
0, 0,
[b1], [b1],
), d ), d
assert (d := common_prefix_depth(b2.id(), b0.id(), states)) == ( assert (d := common_prefix_depth(b2.id, b0.id, states)) == (
2, 2,
[b0, b1, b2], [b0, b1, b2],
0, 0,
[b0], [b0],
), d ), d
assert (d := common_prefix_depth(b0.id(), b2.id(), states)) == ( assert (d := common_prefix_depth(b0.id, b2.id, states)) == (
0, 0,
[b0], [b0],
2, 2,
[b0, b1, b2], [b0, b1, b2],
), d ), d
assert (d := common_prefix_depth(b3.id(), b0.id(), states)) == ( assert (d := common_prefix_depth(b3.id, b0.id, states)) == (
3, 3,
[b0, b1, b2, b3], [b0, b1, b2, b3],
0, 0,
[b0], [b0],
), d ), d
assert (d := common_prefix_depth(b0.id(), b3.id(), states)) == ( assert (d := common_prefix_depth(b0.id, b3.id, states)) == (
0, 0,
[b0], [b0],
3, 3,
[b0, b1, b2, b3], [b0, b1, b2, b3],
), d ), d
assert (d := common_prefix_depth(b1.id(), b4.id(), states)) == ( assert (d := common_prefix_depth(b1.id, b4.id, states)) == (
1, 1,
[b0, b1], [b0, b1],
1, 1,
[b0, b4], [b0, b4],
), d ), d
assert (d := common_prefix_depth(b4.id(), b1.id(), states)) == ( assert (d := common_prefix_depth(b4.id, b1.id, states)) == (
1, 1,
[b0, b4], [b0, b4],
1, 1,
[b0, b1], [b0, b1],
), d ), d
assert (d := common_prefix_depth(b1.id(), b5.id(), states)) == ( assert (d := common_prefix_depth(b1.id, b5.id, states)) == (
1, 1,
[b0, b1], [b0, b1],
2, 2,
[b0, b4, b5], [b0, b4, b5],
), d ), d
assert (d := common_prefix_depth(b5.id(), b1.id(), states)) == ( assert (d := common_prefix_depth(b5.id, b1.id, states)) == (
2, 2,
[b0, b4, b5], [b0, b4, b5],
1, 1,
[b0, b1], [b0, b1],
), d ), d
assert (d := common_prefix_depth(b2.id(), b5.id(), states)) == ( assert (d := common_prefix_depth(b2.id, b5.id, states)) == (
2, 2,
[b0, b1, b2], [b0, b1, b2],
2, 2,
[b0, b4, b5], [b0, b4, b5],
), d ), d
assert (d := common_prefix_depth(b5.id(), b2.id(), states)) == ( assert (d := common_prefix_depth(b5.id, b2.id, states)) == (
2, 2,
[b0, b4, b5], [b0, b4, b5],
2, 2,
[b0, b1, b2], [b0, b1, b2],
), d ), d
assert (d := common_prefix_depth(b3.id(), b5.id(), states)) == ( assert (d := common_prefix_depth(b3.id, b5.id, states)) == (
3, 3,
[b0, b1, b2, b3], [b0, b1, b2, b3],
2, 2,
[b0, b4, b5], [b0, b4, b5],
), d ), d
assert (d := common_prefix_depth(b5.id(), b3.id(), states)) == ( assert (d := common_prefix_depth(b5.id, b3.id, states)) == (
2, 2,
[b0, b4, b5], [b0, b4, b5],
3, 3,
[b0, b1, b2, b3], [b0, b1, b2, b3],
), d ), d
assert (d := common_prefix_depth(b3.id(), b6.id(), states)) == ( assert (d := common_prefix_depth(b3.id, b6.id, states)) == (
1, 1,
[b2, b3], [b2, b3],
1, 1,
[b2, b6], [b2, b6],
), d ), d
assert (d := common_prefix_depth(b6.id(), b3.id(), states)) == ( assert (d := common_prefix_depth(b6.id, b3.id, states)) == (
1, 1,
[b2, b6], [b2, b6],
1, 1,
[b2, b3], [b2, b3],
), d ), d
assert (d := common_prefix_depth(b3.id(), b7.id(), states)) == ( assert (d := common_prefix_depth(b3.id, b7.id, states)) == (
1, 1,
[b2, b3], [b2, b3],
2, 2,
[b2, b6, b7], [b2, b6, b7],
), d ), d
assert (d := common_prefix_depth(b7.id(), b3.id(), states)) == ( assert (d := common_prefix_depth(b7.id, b3.id, states)) == (
2, 2,
[b2, b6, b7], [b2, b6, b7],
1, 1,
[b2, b3], [b2, b3],
), d ), d
assert (d := common_prefix_depth(b5.id(), b7.id(), states)) == ( assert (d := common_prefix_depth(b5.id, b7.id, states)) == (
2, 2,
[b0, b4, b5], [b0, b4, b5],
4, 4,
[b0, b1, b2, b6, b7], [b0, b1, b2, b6, b7],
), d ), d
assert (d := common_prefix_depth(b7.id(), b5.id(), states)) == ( assert (d := common_prefix_depth(b7.id, b5.id, states)) == (
4, 4,
[b0, b1, b2, b6, b7], [b0, b1, b2, b6, b7],
2, 2,
@ -196,28 +194,28 @@ class TestForkChoice(TestCase):
k = 1 k = 1
s = 50 s = 50
states = {b.id(): LedgerState(block=b) for b in short_chain + long_chain} states = {b.id: LedgerState(block=b) for b in short_chain + long_chain}
assert ( assert (
maxvalid_bg(short_chain[-1].id(), [long_chain[-1].id()], k, s, states) maxvalid_bg(short_chain[-1].id, [long_chain[-1].id], k, s, states)
== short_chain[-1].id() == short_chain[-1].id
) )
assert ( assert (
maxvalid_mc(short_chain[-1].id(), [long_chain[-1].id()], k,states) maxvalid_mc(short_chain[-1].id, [long_chain[-1].id], k, states)
== short_chain[-1].id() == short_chain[-1].id
) )
# However, if we set k to the fork length, it will be accepted # However, if we set k to the fork length, it will be accepted
k = len(long_chain) k = len(long_chain)
assert ( assert (
maxvalid_bg(short_chain[-1].id(), [long_chain[-1].id()], k, s, states) maxvalid_bg(short_chain[-1].id, [long_chain[-1].id], k, s, states)
== long_chain[-1].id() == long_chain[-1].id
) )
assert ( assert (
maxvalid_mc(short_chain[-1].id(), [long_chain[-1].id()], k, states) maxvalid_mc(short_chain[-1].id, [long_chain[-1].id], k, states)
== long_chain[-1].id() == long_chain[-1].id
) )
def test_fork_choice_long_dense_chain(self): def test_fork_choice_long_dense_chain(self):
@ -241,18 +239,18 @@ class TestForkChoice(TestCase):
k = 1 k = 1
s = 50 s = 50
states = {b.id(): LedgerState(block=b) for b in short_chain + long_chain} states = {b.id: LedgerState(block=b) for b in short_chain + long_chain}
assert ( assert (
maxvalid_bg(short_chain[-1].id(), [long_chain[-1].id()], k, s, states) maxvalid_bg(short_chain[-1].id, [long_chain[-1].id], k, s, states)
== long_chain[-1].id() == long_chain[-1].id
) )
# praos fc rule should not accept a chain that diverged more than k blocks, # praos fc rule should not accept a chain that diverged more than k blocks,
# even if it is longer # even if it is longer
assert ( assert (
maxvalid_mc(short_chain[-1].id(), [long_chain[-1].id()], k, states) maxvalid_mc(short_chain[-1].id, [long_chain[-1].id], k, states)
== short_chain[-1].id() == short_chain[-1].id
) )
def test_fork_choice_integration(self): def test_fork_choice_integration(self):
@ -266,7 +264,7 @@ class TestForkChoice(TestCase):
follower.on_block(b1) follower.on_block(b1)
assert follower.tip_id() == b1.id() assert follower.tip_id() == b1.id
assert follower.forks == [], follower.forks assert follower.forks == [], follower.forks
# -- then we fork -- # -- then we fork --
@ -284,8 +282,8 @@ class TestForkChoice(TestCase):
follower.on_block(b2) follower.on_block(b2)
follower.on_block(b3) follower.on_block(b3)
assert follower.tip_id() == b2.id() assert follower.tip_id() == b2.id
assert len(follower.forks) == 1 and follower.forks[0] == b3.id() assert len(follower.forks) == 1 and follower.forks[0] == b3.id
# -- extend the fork causing a re-org -- # -- extend the fork causing a re-org --
# #
@ -299,8 +297,8 @@ class TestForkChoice(TestCase):
b4 = mk_block(b3, 3, n_b) b4 = mk_block(b3, 3, n_b)
follower.on_block(b4) follower.on_block(b4)
assert follower.tip_id() == b4.id() assert follower.tip_id() == b4.id
assert len(follower.forks) == 1 and follower.forks[0] == b2.id(), follower.forks assert len(follower.forks) == 1 and follower.forks[0] == b2.id, follower.forks
# -- switch to online mode -- # -- switch to online mode --
# #
@ -311,9 +309,9 @@ class TestForkChoice(TestCase):
# b3 (LIB) - b4 == tip # b3 (LIB) - b4 == tip
# #
follower.to_online() follower.to_online()
assert follower.lib == b3.id(), follower.lib assert follower.lib == b3.id, follower.lib
assert len(follower.forks) == 0, follower.forks assert len(follower.forks) == 0, follower.forks
assert b2.id() not in follower.forks assert b2.id not in follower.forks
# -- extend a fork deeper than the LIB -- # -- extend a fork deeper than the LIB --
# #
@ -338,10 +336,10 @@ class TestForkChoice(TestCase):
b8 = mk_block(b7, 5, n_b) b8 = mk_block(b7, 5, n_b)
follower.on_block(b7) follower.on_block(b7)
assert len(follower.forks) == 1 and b7.id() in follower.forks assert len(follower.forks) == 1 and b7.id in follower.forks
follower.on_block(b8) follower.on_block(b8)
assert follower.tip_id() == b8.id() assert follower.tip_id() == b8.id
# b4 was pruned as it forks deeper than the LIB # b4 was pruned as it forks deeper than the LIB
assert len(follower.forks) == 0, follower.forks assert len(follower.forks) == 0, follower.forks
@ -366,14 +364,14 @@ class TestForkChoice(TestCase):
follower.on_block(b8) follower.on_block(b8)
follower.on_block(b9) follower.on_block(b9)
assert follower.tip_id() == b9.id() assert follower.tip_id() == b9.id
follower.on_block(b10) follower.on_block(b10)
follower.on_block(b11) follower.on_block(b11)
follower.on_block(b12) follower.on_block(b12)
assert follower.tip_id() == b12.id() assert follower.tip_id() == b12.id
assert follower.lib == b7.id(), follower.lib assert follower.lib == b7.id, follower.lib
def test_lib_calc_short_chain(self): def test_lib_calc_short_chain(self):
# Test that the LIB is correctly calculated for a short chain # Test that the LIB is correctly calculated for a short chain
@ -385,15 +383,15 @@ class TestForkChoice(TestCase):
follower = Follower(genesis, config) follower = Follower(genesis, config)
follower.to_online() follower.to_online()
assert follower.lib == genesis.block.id(), follower.lib assert follower.lib == genesis.block.id, follower.lib
blocks = [genesis.block] blocks = [genesis.block]
for i in range(1, 11): for i in range(1, 11):
b = mk_block(blocks[-1], i, n_a) b = mk_block(blocks[-1], i, n_a)
blocks.append(b) blocks.append(b)
follower.on_block(b) follower.on_block(b)
assert follower.lib == genesis.block.id(), follower.lib assert follower.lib == genesis.block.id, follower.lib
b11 = mk_block(blocks[-1], 11, n_a) b11 = mk_block(blocks[-1], 11, n_a)
follower.on_block(b11) follower.on_block(b11)
assert follower.lib == blocks[1].id(), follower.lib assert follower.lib == blocks[1].id, follower.lib

View File

@ -96,7 +96,7 @@ class TestLedgerStateUpdate(TestCase):
follower.on_block(block_2) follower.on_block(block_2)
assert follower.tip() == block_1 assert follower.tip() == block_1
assert len(follower.forks) == 1, f"{len(follower.forks)}" assert len(follower.forks) == 1, f"{len(follower.forks)}"
assert follower.forks[0] == block_2.id() assert follower.forks[0] == block_2.id
# note_2 wins slot 1 and chooses to extend from block_1 # note_2 wins slot 1 and chooses to extend from block_1
# note_3 also wins slot 1 and but chooses to extend from block_2 # note_3 also wins slot 1 and but chooses to extend from block_2
@ -107,7 +107,7 @@ class TestLedgerStateUpdate(TestCase):
follower.on_block(block_4) follower.on_block(block_4)
assert follower.tip() == block_3 assert follower.tip() == block_3
assert len(follower.forks) == 1, f"{len(follower.forks)}" assert len(follower.forks) == 1, f"{len(follower.forks)}"
assert follower.forks[0] == block_4.id() assert follower.forks[0] == block_4.id
# note_4 wins slot 1 and but chooses to extend from block_2 as well # note_4 wins slot 1 and but chooses to extend from block_2 as well
# The block is accepted. A new fork is created "from the block_2". # The block is accepted. A new fork is created "from the block_2".
@ -115,8 +115,8 @@ class TestLedgerStateUpdate(TestCase):
follower.on_block(block_5) follower.on_block(block_5)
assert follower.tip() == block_3 assert follower.tip() == block_3
assert len(follower.forks) == 2, f"{len(follower.forks)}" assert len(follower.forks) == 2, f"{len(follower.forks)}"
assert follower.forks[0] == block_4.id() assert follower.forks[0] == block_4.id
assert follower.forks[1] == block_5.id() assert follower.forks[1] == block_5.id
# A block based on an unknown parent is not accepted. # A block based on an unknown parent is not accepted.
# Nothing changes from the local chain and forks. # Nothing changes from the local chain and forks.
@ -126,8 +126,8 @@ class TestLedgerStateUpdate(TestCase):
follower.on_block(block_6) follower.on_block(block_6)
assert follower.tip() == block_3 assert follower.tip() == block_3
assert len(follower.forks) == 2, f"{len(follower.forks)}" assert len(follower.forks) == 2, f"{len(follower.forks)}"
assert follower.forks[0] == block_4.id() assert follower.forks[0] == block_4.id
assert follower.forks[1] == block_5.id() assert follower.forks[1] == block_5.id
def test_epoch_transition(self): def test_epoch_transition(self):
leader_notes = [Note(sk=i, value=100) for i in range(4)] leader_notes = [Note(sk=i, value=100) for i in range(4)]
@ -170,8 +170,8 @@ class TestLedgerStateUpdate(TestCase):
follower.on_block(block_4) follower.on_block(block_4)
assert follower.tip() == block_3 assert follower.tip() == block_3
# then we add the note to "commitments" associated with slot 9 # then we add the note to "commitments" associated with slot 9
follower.ledger_state[block_2.id()].commitments.add( follower.ledger_state[block_2.id].commitments.add(
Note(sk=4, value=100).commitment() Note(sk=4, value=100).commitment
) )
follower.on_block(block_4) follower.on_block(block_4)
assert follower.tip() == block_4 assert follower.tip() == block_4
@ -196,7 +196,7 @@ class TestLedgerStateUpdate(TestCase):
# but the a new note is ineligible # but the a new note is ineligible
note_new = Note(sk=1, value=10) note_new = Note(sk=1, value=10)
follower.tip_state().commitments.add(note_new.commitment()) follower.tip_state().commitments.add(note_new.commitment)
block_3_new = mk_block(slot=2, parent=block_2, note=note_new) block_3_new = mk_block(slot=2, parent=block_2, note=note_new)
with self.assertRaises(InvalidLeaderProof): with self.assertRaises(InvalidLeaderProof):
follower.on_block(block_3_new) follower.on_block(block_3_new)
@ -220,7 +220,7 @@ class TestLedgerStateUpdate(TestCase):
# mint a new note to be used for leader elections in upcoming epochs # mint a new note to be used for leader elections in upcoming epochs
note_new = Note(sk=1, value=10) note_new = Note(sk=1, value=10)
follower.ledger_state[block_0_0.id()].commitments.add(note_new.commitment()) follower.ledger_state[block_0_0.id].commitments.add(note_new.commitment)
# the new note is not yet eligible for elections # the new note is not yet eligible for elections
block_0_1_attempt = mk_block(slot=1, parent=block_0_0, note=note_new) block_0_1_attempt = mk_block(slot=1, parent=block_0_0, note=note_new)

View File

@ -71,7 +71,7 @@ class TestStakeRelativization(TestCase):
PRINT_DEBUG = False PRINT_DEBUG = False
seed = 0 seed = 0
N = 3 N = 10
EPOCHS = 2 EPOCHS = 2
np.random.seed(seed) np.random.seed(seed)
@ -79,7 +79,7 @@ class TestStakeRelativization(TestCase):
stake = np.array((np.random.pareto(10, N) + 1) * 1000, dtype=np.int64) stake = np.array((np.random.pareto(10, N) + 1) * 1000, dtype=np.int64)
notes = [Note(sk=i, value=int(s)) for i, s in enumerate(stake)] notes = [Note(sk=i, value=int(s)) for i, s in enumerate(stake)]
config = Config.cryptarchia_v0_0_1(stake.sum() * 2).replace(k=40) config = Config.cryptarchia_v0_0_1(stake.sum() * 2).replace(k=150)
genesis = mk_genesis_state(notes) genesis = mk_genesis_state(notes)
nodes = [TestNode(config, genesis, n) for n in notes] nodes = [TestNode(config, genesis, n) for n in notes]
@ -124,11 +124,13 @@ class TestStakeRelativization(TestCase):
f"var={slot_leaders.var():.3f}", f"var={slot_leaders.var():.3f}",
) )
print("true total stake\t", stake.sum()) print("true total stake\t", stake.sum())
print("D_0\t", config.initial_total_stake) print("D_0\t", config.initial_total_active_stake)
inferred_stake_by_epoch_by_rep = [ inferred_stake_by_epoch_by_rep = [
[ [
r.epoch_state(Slot(e * config.epoch_length)).total_stake() r.epoch_state(
Slot(e * config.epoch_length)
).inferred_total_active_stake
for e in range(EPOCHS + 1) for e in range(EPOCHS + 1)
] ]
for r in reps for r in reps
@ -138,7 +140,7 @@ class TestStakeRelativization(TestCase):
"\n\t".join( "\n\t".join(
[ [
f"Rep {i}: {stakes}" f"Rep {i}: {stakes}"
for i, stakes in inferred_stake_by_epoch_by_rep for i, stakes in enumerate(inferred_stake_by_epoch_by_rep)
] ]
), ),
) )
@ -154,10 +156,10 @@ class TestStakeRelativization(TestCase):
for node in reps: for node in reps:
inferred_stake = node.epoch_state(Slot(T)).total_active_stake() inferred_stake = node.epoch_state(Slot(T)).total_active_stake()
pct_err = ( pct_err = abs(stake.sum() - inferred_stake) / stake.sum()
abs(stake.sum() - inferred_stake) / config.initial_total_active_stake # With beta=1.0, convergence is fast. Allow small error due to stochastic
) # block production not matching the expected slot occupancy rate perfectly.
eps = (1 - config.total_active_stake_learning_rate) ** EPOCHS eps = 0.01
assert pct_err < eps, f"pct_err={pct_err} < eps={eps}" assert pct_err < eps, f"pct_err={pct_err} < eps={eps}"

View File

@ -67,7 +67,7 @@ class TestSync(TestCase):
peer.on_block(b) peer.on_block(b)
self.assertEqual(peer.tip(), b5) self.assertEqual(peer.tip(), b5)
self.assertEqual(peer.forks, [b4.id()]) self.assertEqual(peer.forks, [b4.id])
# Start a sync from genesis. # Start a sync from genesis.
# Result: The same block tree as the peer's. # Result: The same block tree as the peer's.
@ -92,7 +92,7 @@ class TestSync(TestCase):
for b in [b0, b1, b2, b3, b4, b5]: for b in [b0, b1, b2, b3, b4, b5]:
peer.on_block(b) peer.on_block(b)
self.assertEqual(peer.tip(), b5) self.assertEqual(peer.tip(), b5)
self.assertEqual(peer.forks, [b4.id()]) self.assertEqual(peer.forks, [b4.id])
# Start a sync from a tree: # Start a sync from a tree:
# b0 - b1 # b0 - b1
@ -123,7 +123,7 @@ class TestSync(TestCase):
for b in [b0, b1, b2, b3, b4, b5]: for b in [b0, b1, b2, b3, b4, b5]:
peer.on_block(b) peer.on_block(b)
self.assertEqual(peer.tip(), b5) self.assertEqual(peer.tip(), b5)
self.assertEqual(peer.forks, [b4.id()]) self.assertEqual(peer.forks, [b4.id])
self.assertEqual(len(peer.ledger_state), 7) self.assertEqual(len(peer.ledger_state), 7)
# Start a sync from a tree without the fork: # Start a sync from a tree without the fork:
@ -179,7 +179,7 @@ class TestSync(TestCase):
local = Follower(genesis, config) local = Follower(genesis, config)
sync(local, [peer0, peer1, peer2]) sync(local, [peer0, peer1, peer2])
self.assertEqual(local.tip(), b5) self.assertEqual(local.tip(), b5)
self.assertEqual(local.forks, [b4.id()]) self.assertEqual(local.forks, [b4.id])
self.assertEqual(len(local.ledger_state), 7) self.assertEqual(len(local.ledger_state), 7)
def test_reject_invalid_blocks(self): def test_reject_invalid_blocks(self):
@ -236,7 +236,7 @@ class TestSync(TestCase):
for b in [b0, b1, b2, b3, b4, b5]: for b in [b0, b1, b2, b3, b4, b5]:
peer.on_block(b) peer.on_block(b)
self.assertEqual(peer.tip(), b5) self.assertEqual(peer.tip(), b5)
self.assertEqual(peer.forks, [b2.id()]) self.assertEqual(peer.forks, [b2.id])
# And deliberately, add invalid blocks (b6 ~ b7): # And deliberately, add invalid blocks (b6 ~ b7):
fake_note = Note(sk=2, value=10) fake_note = Note(sk=2, value=10)
@ -246,7 +246,7 @@ class TestSync(TestCase):
apply_invalid_block_to_ledger_state(peer, b7) apply_invalid_block_to_ledger_state(peer, b7)
# the tip shouldn't be changed. # the tip shouldn't be changed.
self.assertEqual(peer.tip(), b5) self.assertEqual(peer.tip(), b5)
self.assertEqual(peer.forks, [b2.id()]) self.assertEqual(peer.forks, [b2.id])
# Start a sync from a tree: # Start a sync from a tree:
# b0 - b1 - b3 - b4 # b0 - b1 - b3 - b4
@ -261,8 +261,8 @@ class TestSync(TestCase):
sync(local, [peer]) sync(local, [peer])
self.assertEqual(local.tip(), peer.tip()) self.assertEqual(local.tip(), peer.tip())
self.assertEqual(local.forks, peer.forks) self.assertEqual(local.forks, peer.forks)
self.assertNotIn(b6.id(), local.ledger_state) self.assertNotIn(b6.id, local.ledger_state)
self.assertNotIn(b7.id(), local.ledger_state) self.assertNotIn(b7.id, local.ledger_state)
class TestSyncFromCheckpoint(TestCase): class TestSyncFromCheckpoint(TestCase):
@ -290,7 +290,7 @@ class TestSyncFromCheckpoint(TestCase):
# #
# Result: # Result:
# b0 - b1 - b2 - b3 # b0 - b1 - b2 - b3
checkpoint = peer.ledger_state[b2.id()] checkpoint = peer.ledger_state[b2.id]
local = Follower(genesis, config) local = Follower(genesis, config)
sync(local, [peer], checkpoint) sync(local, [peer], checkpoint)
self.assertEqual(local.tip(), peer.tip()) self.assertEqual(local.tip(), peer.tip())
@ -315,7 +315,7 @@ class TestSyncFromCheckpoint(TestCase):
for b in [b0, b1, b2, b3, b4, b5]: for b in [b0, b1, b2, b3, b4, b5]:
peer.on_block(b) peer.on_block(b)
self.assertEqual(peer.tip(), b5) self.assertEqual(peer.tip(), b5)
self.assertEqual(peer.forks, [b4.id()]) self.assertEqual(peer.forks, [b4.id])
# Start a sync from the checkpoint: # Start a sync from the checkpoint:
# checkpoint # checkpoint
@ -326,7 +326,7 @@ class TestSyncFromCheckpoint(TestCase):
# b0 - b1 - b2 - b5 == tip # b0 - b1 - b2 - b5 == tip
# \ # \
# b3 - b4 # b3 - b4
checkpoint = peer.ledger_state[b2.id()] checkpoint = peer.ledger_state[b2.id]
local = Follower(genesis, config) local = Follower(genesis, config)
sync(local, [peer], checkpoint) sync(local, [peer], checkpoint)
self.assertEqual(local.tip(), peer.tip()) self.assertEqual(local.tip(), peer.tip())
@ -367,11 +367,11 @@ class TestSyncFromCheckpoint(TestCase):
# b0 - b1 - b2 - b5 == tip # b0 - b1 - b2 - b5 == tip
# \ # \
# b3 - b4 # b3 - b4
checkpoint = peer1.ledger_state[b4.id()] checkpoint = peer1.ledger_state[b4.id]
local = Follower(genesis, config) local = Follower(genesis, config)
sync(local, [peer0, peer1], checkpoint) sync(local, [peer0, peer1], checkpoint)
self.assertEqual(local.tip(), b5) self.assertEqual(local.tip(), b5)
self.assertEqual(local.forks, [b4.id()]) self.assertEqual(local.forks, [b4.id])
self.assertEqual(len(local.ledger_state.keys()), 7) self.assertEqual(len(local.ledger_state.keys()), 7)
def test_reject_invalid_blocks_from_backfilling_fork(self): def test_reject_invalid_blocks_from_backfilling_fork(self):
@ -392,7 +392,7 @@ class TestSyncFromCheckpoint(TestCase):
for b in [b0, b1, b2, b3, b4, b5]: for b in [b0, b1, b2, b3, b4, b5]:
peer.on_block(b) peer.on_block(b)
self.assertEqual(peer.tip(), b5) self.assertEqual(peer.tip(), b5)
self.assertEqual(peer.forks, [b2.id()]) self.assertEqual(peer.forks, [b2.id])
# And deliberately, add invalid blocks (b6 ~ b7): # And deliberately, add invalid blocks (b6 ~ b7):
fake_note = Note(sk=2, value=10) fake_note = Note(sk=2, value=10)
@ -401,7 +401,7 @@ class TestSyncFromCheckpoint(TestCase):
apply_invalid_block_to_ledger_state(peer, b7) apply_invalid_block_to_ledger_state(peer, b7)
# the tip shouldn't be changed. # the tip shouldn't be changed.
self.assertEqual(peer.tip(), b5) self.assertEqual(peer.tip(), b5)
self.assertEqual(peer.forks, [b2.id()]) self.assertEqual(peer.forks, [b2.id])
# Start a sync from a checkpoint where all anscestors are valid: # Start a sync from a checkpoint where all anscestors are valid:
# checkpoint # checkpoint
@ -412,13 +412,13 @@ class TestSyncFromCheckpoint(TestCase):
# b0 - b1 - b3 - b4 - b5 == tip # b0 - b1 - b3 - b4 - b5 == tip
# \ # \
# b2 # b2
checkpoint = peer.ledger_state[b4.id()] checkpoint = peer.ledger_state[b4.id]
local = Follower(genesis, config) local = Follower(genesis, config)
sync(local, [peer], checkpoint) sync(local, [peer], checkpoint)
self.assertEqual(local.tip(), peer.tip()) self.assertEqual(local.tip(), peer.tip())
self.assertEqual(local.forks, peer.forks) self.assertEqual(local.forks, peer.forks)
self.assertNotIn(b6.id(), local.ledger_state) self.assertNotIn(b6.id, local.ledger_state)
self.assertNotIn(b7.id(), local.ledger_state) self.assertNotIn(b7.id, local.ledger_state)
def test_reject_invalid_blocks_from_backfilling_checkpoint_chain(self): def test_reject_invalid_blocks_from_backfilling_checkpoint_chain(self):
# Prepare a peer with invalid blocks in a fork: # Prepare a peer with invalid blocks in a fork:
@ -438,7 +438,7 @@ class TestSyncFromCheckpoint(TestCase):
for b in [b0, b1, b2, b3, b4, b5]: for b in [b0, b1, b2, b3, b4, b5]:
peer.on_block(b) peer.on_block(b)
self.assertEqual(peer.tip(), b5) self.assertEqual(peer.tip(), b5)
self.assertEqual(peer.forks, [b2.id()]) self.assertEqual(peer.forks, [b2.id])
# And deliberately, add invalid blocks (b6 ~ b7): # And deliberately, add invalid blocks (b6 ~ b7):
fake_note = Note(sk=2, value=10) fake_note = Note(sk=2, value=10)
@ -447,7 +447,7 @@ class TestSyncFromCheckpoint(TestCase):
apply_invalid_block_to_ledger_state(peer, b7) apply_invalid_block_to_ledger_state(peer, b7)
# the tip shouldn't be changed. # the tip shouldn't be changed.
self.assertEqual(peer.tip(), b5) self.assertEqual(peer.tip(), b5)
self.assertEqual(peer.forks, [b2.id()]) self.assertEqual(peer.forks, [b2.id])
# Start a sync from a checkpoint where some anscestors are invalid: # Start a sync from a checkpoint where some anscestors are invalid:
# () checkpoint # () checkpoint
@ -455,7 +455,7 @@ class TestSyncFromCheckpoint(TestCase):
# () - () - (invalid_b7) # () - () - (invalid_b7)
# #
# Result: `InvalidBlockTree` exception # Result: `InvalidBlockTree` exception
checkpoint = peer.ledger_state[b7.id()] checkpoint = peer.ledger_state[b7.id]
local = Follower(genesis, config) local = Follower(genesis, config)
with self.assertRaises(InvalidBlockFromBackfillFork): with self.assertRaises(InvalidBlockFromBackfillFork):
sync(local, [peer], checkpoint) sync(local, [peer], checkpoint)
@ -464,4 +464,4 @@ class TestSyncFromCheckpoint(TestCase):
def apply_invalid_block_to_ledger_state(follower: Follower, block: BlockHeader): def apply_invalid_block_to_ledger_state(follower: Follower, block: BlockHeader):
state = follower.ledger_state[block.parent].copy() state = follower.ledger_state[block.parent].copy()
state.apply(block) state.apply(block)
follower.ledger_state[block.id()] = state follower.ledger_state[block.id] = state