mirror of
https://github.com/logos-blockchain/logos-blockchain-specs.git
synced 2026-01-03 21:53:07 +00:00
Add explicit LIB
This commit is contained in:
parent
ada1ee2d5a
commit
a7cf2c354a
@ -1,5 +1,5 @@
|
||||
import functools
|
||||
import itertools
|
||||
from itertools import islice
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
@ -322,6 +322,7 @@ class Follower:
|
||||
self.ledger_state = {genesis_state.block.id(): genesis_state.copy()}
|
||||
self.epoch_state = {}
|
||||
self.state = State.BOOTSTRAPPING
|
||||
self.lib = genesis_state.block.id() # Last immutable block, initially the genesis block
|
||||
|
||||
def to_online(self):
|
||||
"""
|
||||
@ -332,12 +333,17 @@ class Follower:
|
||||
if self.state != State.BOOTSTRAPPING:
|
||||
raise RuntimeError("Follower is not in BOOTSTRAPPING state")
|
||||
self.state = State.ONLINE
|
||||
self.update_lib()
|
||||
|
||||
def validate_header(self, block: BlockHeader):
|
||||
# TODO: verify blocks are not in the 'future'
|
||||
if block.parent not in self.ledger_state:
|
||||
raise ParentNotFound
|
||||
|
||||
if not is_ancestor(self.lib, block.parent, self.ledger_state):
|
||||
# If the block is not an ancestor of the last immutable block, we cannot process it.
|
||||
raise ImmutableFork
|
||||
|
||||
current_state = self.ledger_state[block.parent].copy()
|
||||
|
||||
epoch_state = self.compute_epoch_state(
|
||||
@ -381,6 +387,27 @@ class Follower:
|
||||
self.forks.remove(new_tip)
|
||||
self.local_chain = new_tip
|
||||
|
||||
if self.state == State.ONLINE:
|
||||
self.update_lib()
|
||||
|
||||
|
||||
# Update the lib, and prune forks that do not descend from it.
|
||||
def update_lib(self):
|
||||
"""
|
||||
Computes the last immutable block, which is the k-th block in the chain.
|
||||
The last immutable block is the block that is guaranteed to be part of the chain
|
||||
and will not be reverted.
|
||||
"""
|
||||
if self.state != State.ONLINE:
|
||||
return
|
||||
# prune forks that do not descend from the last immutable block, this is needed to avoid Genesis rule to roll back
|
||||
# past the LIB
|
||||
self.lib = next(islice(iter_chain(self.local_chain, self.ledger_state), self.config.k, None), self.local_chain).block.id()
|
||||
self.forks = [
|
||||
f for f in self.forks if is_ancestor(self.lib, f, self.ledger_state)
|
||||
]
|
||||
|
||||
|
||||
# Evaluate the fork choice rule and return the chain we should be following
|
||||
def fork_choice(self) -> Hash:
|
||||
if self.state == State.BOOTSTRAPPING:
|
||||
@ -555,6 +582,14 @@ def iter_chain_blocks(
|
||||
for state in iter_chain(tip, states):
|
||||
yield state.block
|
||||
|
||||
def is_ancestor(a: Hash, b: Hash, states: Dict[Hash, LedgerState]) -> bool:
|
||||
"""
|
||||
Returns True if `a` is an ancestor of `b` in the chain.
|
||||
"""
|
||||
for state in iter_chain(b, states):
|
||||
if state.block.id() == a:
|
||||
return True
|
||||
return False
|
||||
|
||||
def common_prefix_depth(
|
||||
a: Hash, b: Hash, states: Dict[Hash, LedgerState]
|
||||
@ -675,7 +710,7 @@ def maxvalid_mc(
|
||||
|
||||
cmax = local_chain
|
||||
for fork in forks:
|
||||
cmax_depth, cmax_suffix, fork_depth, fork_suffix = common_prefix_depth(
|
||||
cmax_depth, _, fork_depth, _ = common_prefix_depth(
|
||||
cmax, fork, states
|
||||
)
|
||||
if cmax_depth <= k:
|
||||
@ -694,6 +729,10 @@ class InvalidLeaderProof(Exception):
|
||||
def __str__(self):
|
||||
return "Invalid leader proof"
|
||||
|
||||
class ImmutableFork(Exception):
|
||||
def __str__(self):
|
||||
return "Block is forking from the last immutable block"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@ -6,9 +6,11 @@ from cryptarchia.cryptarchia import (
|
||||
maxvalid_mc,
|
||||
Slot,
|
||||
Note,
|
||||
State,
|
||||
Follower,
|
||||
common_prefix_depth,
|
||||
LedgerState,
|
||||
ImmutableFork,
|
||||
)
|
||||
|
||||
from .test_common import mk_chain, mk_config, mk_genesis_state, mk_block
|
||||
@ -301,39 +303,74 @@ class TestForkChoice(TestCase):
|
||||
assert len(follower.forks) == 1 and follower.forks[0] == b2.id(), follower.forks
|
||||
|
||||
# -- switch to online mode --
|
||||
follower.to_online()
|
||||
|
||||
# -- extend a fork deeper than k --
|
||||
#
|
||||
#
|
||||
# b2 - b5 - b6
|
||||
# b2 (does not descend from the LIB and is thus pruned)
|
||||
# /
|
||||
# b1
|
||||
# \
|
||||
# b3 - b4 == tip
|
||||
# b3 (LIB) - b4 == tip
|
||||
#
|
||||
b5 = mk_block(b2, 3, n_a)
|
||||
b6 = mk_block(b5, 4, n_a)
|
||||
follower.on_block(b5)
|
||||
follower.on_block(b6)
|
||||
follower.to_online()
|
||||
assert follower.lib == b3.id(), follower.lib
|
||||
assert len(follower.forks) == 0, follower.forks
|
||||
assert b2.id() not in follower.forks
|
||||
|
||||
assert follower.tip_id() == b4.id()
|
||||
assert len(follower.forks) == 1 and follower.forks[0] == b6.id()
|
||||
# -- extend a fork deeper than the LIB --
|
||||
#
|
||||
# - - - - - - b5
|
||||
# /
|
||||
# b1
|
||||
# \
|
||||
# b3 (LIB) - b4 == tip
|
||||
#
|
||||
b5 = mk_block(b1, 4, n_a)
|
||||
with self.assertRaises(ImmutableFork):
|
||||
follower.on_block(b5)
|
||||
|
||||
# -- extend the main chain shallower than k --
|
||||
#
|
||||
#
|
||||
# b2 - b5 - b6
|
||||
# /
|
||||
# b1
|
||||
# \
|
||||
# b3 - b4
|
||||
# b3 - b4 (pruned)
|
||||
# \
|
||||
# - - b7 - b8 == tip
|
||||
# - - b7 (LIB) - b8 == tip
|
||||
b7 = mk_block(b3, 4, n_b)
|
||||
b8 = mk_block(b7, 5, n_b)
|
||||
|
||||
follower.on_block(b7)
|
||||
assert len(follower.forks) == 1 and b7.id() in follower.forks
|
||||
|
||||
follower.on_block(b8)
|
||||
assert follower.tip_id() == b8.id()
|
||||
assert len(follower.forks) == 2 and {b6.id(), b4.id()}.issubset(follower.forks)
|
||||
# b4 was pruned as it forks deeper than the LIB
|
||||
assert len(follower.forks) == 0, follower.forks
|
||||
|
||||
# Even in bootstrap mode, the follower should not accept blocks that fork deeper than k
|
||||
follower.state = State.BOOTSTRAPPING
|
||||
with self.assertRaises(ImmutableFork):
|
||||
follower.on_block(b5)
|
||||
|
||||
# But it should switch a chain diverging more than k as long as it
|
||||
# descends from the LIB
|
||||
#
|
||||
# b1
|
||||
# \
|
||||
# b3 - - - - - - - b10 - b11 - b12
|
||||
# \ |
|
||||
# - - b7 (LIB) - b8 - b9 == tip
|
||||
b8 = mk_block(b7, 5, n_b)
|
||||
b9 = mk_block(b8, 6, n_b)
|
||||
b10 = mk_block(b7, 7, n_a)
|
||||
b11 = mk_block(b10, 8, n_a)
|
||||
b12 = mk_block(b11, 9, n_a)
|
||||
follower.on_block(b8)
|
||||
follower.on_block(b9)
|
||||
|
||||
assert follower.tip_id() == b9.id()
|
||||
|
||||
follower.on_block(b10)
|
||||
follower.on_block(b11)
|
||||
follower.on_block(b12)
|
||||
|
||||
assert follower.tip_id() == b12.id()
|
||||
assert follower.lib == b7.id(), follower.lib
|
||||
Loading…
x
Reference in New Issue
Block a user