mirror of
https://github.com/logos-blockchain/logos-blockchain-specs.git
synced 2026-01-04 14:13:11 +00:00
Add Boostrapping/Online modes
Add Boostrapping and Online modes to cryptarchia, including relevant tests. The Boostrap mode uses the Genesis fc rule, while Online uses Praos. Swtitching between the two rules is left to the implementation and is specified in the public Notion as linked in the comment
This commit is contained in:
parent
2c5c3860f0
commit
ada1ee2d5a
@ -7,6 +7,7 @@ from dataclasses import dataclass, field, replace
|
||||
from hashlib import blake2b, sha256
|
||||
from math import floor
|
||||
from typing import Dict, Generator, List, TypeAlias
|
||||
from enum import Enum
|
||||
|
||||
import numpy as np
|
||||
|
||||
@ -308,6 +309,9 @@ class EpochState:
|
||||
def nonce(self) -> bytes:
|
||||
return self.nonce_snapshot.nonce
|
||||
|
||||
class State(Enum):
|
||||
ONLINE = 1
|
||||
BOOTSTRAPPING = 2
|
||||
|
||||
class Follower:
|
||||
def __init__(self, genesis_state: LedgerState, config: Config):
|
||||
@ -317,6 +321,17 @@ class Follower:
|
||||
self.genesis_state = genesis_state
|
||||
self.ledger_state = {genesis_state.block.id(): genesis_state.copy()}
|
||||
self.epoch_state = {}
|
||||
self.state = State.BOOTSTRAPPING
|
||||
|
||||
def to_online(self):
|
||||
"""
|
||||
Call this method when the follower has finished bootstrapping. While this is somewhat left to implementations
|
||||
https://www.notion.so/Cryptarchia-v1-Bootstrapping-Synchronization-1fd261aa09df81ac94b5fb6a4eff32a6 contains a great deal
|
||||
of information and is the reference for the Rust implementation.
|
||||
"""
|
||||
if self.state != State.BOOTSTRAPPING:
|
||||
raise RuntimeError("Follower is not in BOOTSTRAPPING state")
|
||||
self.state = State.ONLINE
|
||||
|
||||
def validate_header(self, block: BlockHeader):
|
||||
# TODO: verify blocks are not in the 'future'
|
||||
@ -368,13 +383,23 @@ class Follower:
|
||||
|
||||
# Evaluate the fork choice rule and return the chain we should be following
|
||||
def fork_choice(self) -> Hash:
|
||||
return maxvalid_bg(
|
||||
self.local_chain,
|
||||
self.forks,
|
||||
k=self.config.k,
|
||||
s=self.config.s,
|
||||
states=self.ledger_state,
|
||||
)
|
||||
if self.state == State.BOOTSTRAPPING:
|
||||
return maxvalid_bg(
|
||||
self.local_chain,
|
||||
self.forks,
|
||||
k=self.config.k,
|
||||
s=self.config.s,
|
||||
states=self.ledger_state,
|
||||
)
|
||||
elif self.state == State.ONLINE:
|
||||
return maxvalid_mc(
|
||||
self.local_chain,
|
||||
self.forks,
|
||||
k=self.config.k,
|
||||
states=self.ledger_state,
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(f"Unknown follower state: {self.state}")
|
||||
|
||||
def tip(self) -> BlockHeader:
|
||||
return self.tip_state().block
|
||||
@ -592,7 +617,7 @@ def block_children(states: Dict[Hash, LedgerState]) -> Dict[Hash, set[Hash]]:
|
||||
return children
|
||||
|
||||
|
||||
# Implementation of the Cryptarchia fork choice rule (following Ouroborous Genesis).
|
||||
# Implementation of the Ouroboros Genesis fork choice rule.
|
||||
# The fork choice has two phases:
|
||||
# 1. if the chain is not forking too deeply, we apply the longest chain fork choice rule
|
||||
# 2. otherwise we look at the chain density immidiately following the fork
|
||||
@ -633,6 +658,33 @@ def maxvalid_bg(
|
||||
return cmax
|
||||
|
||||
|
||||
# Implementation of the Ouroboros Praos fork choice rule.
|
||||
# The fork choice has two phases:
|
||||
# 1. if the chain is not forking too deeply, we apply the longest chain fork choice rule
|
||||
# 2. otherwise we discard the fork
|
||||
#
|
||||
# k defines the forking depth of a chain at which point we switch phases.
|
||||
def maxvalid_mc(
|
||||
local_chain: Hash,
|
||||
forks: List[Hash],
|
||||
k: int,
|
||||
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:
|
||||
cmax_depth, cmax_suffix, fork_depth, fork_suffix = common_prefix_depth(
|
||||
cmax, fork, states
|
||||
)
|
||||
if cmax_depth <= k:
|
||||
# Longest chain fork choice rule
|
||||
if cmax_depth < fork_depth:
|
||||
cmax = fork
|
||||
|
||||
return cmax
|
||||
|
||||
class ParentNotFound(Exception):
|
||||
def __str__(self):
|
||||
return "Parent not found"
|
||||
|
||||
@ -3,6 +3,7 @@ from unittest import TestCase
|
||||
from copy import deepcopy
|
||||
from cryptarchia.cryptarchia import (
|
||||
maxvalid_bg,
|
||||
maxvalid_mc,
|
||||
Slot,
|
||||
Note,
|
||||
Follower,
|
||||
@ -200,6 +201,11 @@ class TestForkChoice(TestCase):
|
||||
== short_chain[-1].id()
|
||||
)
|
||||
|
||||
assert (
|
||||
maxvalid_mc(short_chain[-1].id(), [long_chain[-1].id()], k,states)
|
||||
== short_chain[-1].id()
|
||||
)
|
||||
|
||||
# However, if we set k to the fork length, it will be accepted
|
||||
k = len(long_chain)
|
||||
assert (
|
||||
@ -207,6 +213,11 @@ class TestForkChoice(TestCase):
|
||||
== long_chain[-1].id()
|
||||
)
|
||||
|
||||
assert (
|
||||
maxvalid_mc(short_chain[-1].id(), [long_chain[-1].id()], k, states)
|
||||
== long_chain[-1].id()
|
||||
)
|
||||
|
||||
def test_fork_choice_long_dense_chain(self):
|
||||
# The longest chain is also the densest after the fork
|
||||
short_note, long_note = Note(sk=0, value=100), Note(sk=1, value=100)
|
||||
@ -235,6 +246,13 @@ class TestForkChoice(TestCase):
|
||||
== long_chain[-1].id()
|
||||
)
|
||||
|
||||
# praos fc rule should not accept a chain that diverged more than k blocks,
|
||||
# even if it is longer
|
||||
assert (
|
||||
maxvalid_mc(short_chain[-1].id(), [long_chain[-1].id()], k, states)
|
||||
== short_chain[-1].id()
|
||||
)
|
||||
|
||||
def test_fork_choice_integration(self):
|
||||
n_a, n_b = Note(sk=0, value=10), Note(sk=1, value=10)
|
||||
notes = [n_a, n_b]
|
||||
@ -281,3 +299,41 @@ class TestForkChoice(TestCase):
|
||||
|
||||
assert follower.tip_id() == b4.id()
|
||||
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
|
||||
# /
|
||||
# b1
|
||||
# \
|
||||
# b3 - b4 == tip
|
||||
#
|
||||
b5 = mk_block(b2, 3, n_a)
|
||||
b6 = mk_block(b5, 4, n_a)
|
||||
follower.on_block(b5)
|
||||
follower.on_block(b6)
|
||||
|
||||
assert follower.tip_id() == b4.id()
|
||||
assert len(follower.forks) == 1 and follower.forks[0] == b6.id()
|
||||
|
||||
# -- extend the main chain shallower than k --
|
||||
#
|
||||
#
|
||||
# b2 - b5 - b6
|
||||
# /
|
||||
# b1
|
||||
# \
|
||||
# b3 - b4
|
||||
# \
|
||||
# - - b7 - b8 == tip
|
||||
b7 = mk_block(b3, 4, n_b)
|
||||
b8 = mk_block(b7, 5, n_b)
|
||||
|
||||
follower.on_block(b7)
|
||||
follower.on_block(b8)
|
||||
assert follower.tip_id() == b8.id()
|
||||
assert len(follower.forks) == 2 and {b6.id(), b4.id()}.issubset(follower.forks)
|
||||
Loading…
x
Reference in New Issue
Block a user