mirror of
https://github.com/logos-blockchain/logos-blockchain-specs.git
synced 2026-01-08 16:13:10 +00:00
cryptarchia/ghost: integrate GHOST with maxvalid fork choice
This commit is contained in:
parent
adaeba2493
commit
e4d8887fe4
@ -464,6 +464,10 @@ class Follower:
|
||||
return True
|
||||
|
||||
def on_block(self, block: BlockHeader):
|
||||
if block.id() in self.ledger_state:
|
||||
logger.warning("dropping already processed block")
|
||||
return
|
||||
|
||||
if not self.validate_header(block):
|
||||
logger.warning("invalid header")
|
||||
return
|
||||
@ -513,12 +517,12 @@ class Follower:
|
||||
|
||||
# Evaluate the fork choice rule and return the chain we should be following
|
||||
def fork_choice(self) -> Id:
|
||||
return maxvalid_bg(
|
||||
return ghost_maxvalid_bg(
|
||||
self.local_chain,
|
||||
self.forks,
|
||||
self.ledger_state,
|
||||
k=self.config.k,
|
||||
s=self.config.s,
|
||||
states=self.ledger_state,
|
||||
)
|
||||
|
||||
def tip(self) -> BlockHeader:
|
||||
@ -673,7 +677,7 @@ def iter_chain(tip: Id, states: Dict[Id, LedgerState]):
|
||||
|
||||
|
||||
def chain_suffix(tip: Id, n: int, states: Dict[Id, LedgerState]) -> list[LedgerState]:
|
||||
return reversed(list(itertools.islice(iter_chain(tip, states), n)))
|
||||
return list(reversed(list(itertools.islice(iter_chain(tip, states), n))))
|
||||
|
||||
|
||||
def common_prefix_depth(a: Id, b: Id, states: Dict[Id, LedgerState]) -> (int, int):
|
||||
@ -755,49 +759,42 @@ def block_weight(states: Dict[Id, LedgerState]) -> Dict[Id, int]:
|
||||
return block_weight
|
||||
|
||||
|
||||
def ghost_fork_choice(finalized: Id, states: Dict[Id, LedgerState]) -> Id:
|
||||
weights = block_weight(states)
|
||||
children = block_children(states)
|
||||
|
||||
tip = finalized
|
||||
while len(children[tip]) > 0:
|
||||
tip = max(children[tip], key=lambda c: weights[c])
|
||||
|
||||
return tip
|
||||
|
||||
|
||||
# Implementation of the fork choice rule as defined in the Ouroboros Genesis paper
|
||||
# k defines the forking depth of chain we accept without more analysis
|
||||
# s defines the length of time (unit of slots) after the fork happened we will inspect for chain density
|
||||
def maxvalid_bg(
|
||||
def ghost_maxvalid_bg(
|
||||
local_chain: Id,
|
||||
forks: List[Id],
|
||||
states: Dict[Id, LedgerState],
|
||||
k: int,
|
||||
s: int,
|
||||
states: Dict[Id, LedgerState],
|
||||
) -> Id:
|
||||
assert type(local_chain) == Id
|
||||
assert all(type(f) == Id for f in forks)
|
||||
|
||||
weights = block_weight(states)
|
||||
|
||||
cmax = local_chain
|
||||
for fork in forks:
|
||||
local_depth, fork_depth = common_prefix_depth(cmax, fork, states)
|
||||
if local_depth <= k:
|
||||
cmax_depth, fork_depth = common_prefix_depth(cmax, fork, states)
|
||||
if cmax_depth <= k:
|
||||
cmax_divergent_block = chain_suffix(cmax, cmax_depth, states)[0].block.id()
|
||||
fork_divergent_block = chain_suffix(fork, fork_depth, states)[0].block.id()
|
||||
# Classic longest chain rule with parameter k
|
||||
if local_depth < fork_depth:
|
||||
if weights[cmax_divergent_block] < weights[fork_divergent_block]:
|
||||
cmax = fork
|
||||
else:
|
||||
# The chain is forking too much, we need to pay a bit more attention
|
||||
# In particular, select the chain that is the densest after the fork
|
||||
forking_block = local_chain
|
||||
for _ in range(local_depth):
|
||||
for _ in range(cmax_depth):
|
||||
forking_block = states[forking_block].block.parent
|
||||
|
||||
forking_slot = Slot(states[forking_block].block.slot.absolute_slot + s)
|
||||
cmax_density = chain_density(cmax, forking_slot, local_depth, states)
|
||||
candidate_density = chain_density(fork, forking_slot, fork_depth, states)
|
||||
cmax_density = chain_density(cmax, forking_slot, cmax_depth, states)
|
||||
fork_density = chain_density(fork, forking_slot, fork_depth, states)
|
||||
|
||||
if cmax_density < candidate_density:
|
||||
if cmax_density < fork_density:
|
||||
cmax = fork
|
||||
|
||||
return cmax
|
||||
|
||||
@ -5,9 +5,8 @@ import hashlib
|
||||
|
||||
from copy import deepcopy
|
||||
from cryptarchia.cryptarchia import (
|
||||
ghost_fork_choice,
|
||||
block_weight,
|
||||
maxvalid_bg,
|
||||
ghost_maxvalid_bg,
|
||||
BlockHeader,
|
||||
Slot,
|
||||
Id,
|
||||
@ -85,11 +84,14 @@ class TestForkChoice(TestCase):
|
||||
]
|
||||
}
|
||||
|
||||
tip = ghost_fork_choice(b0.id(), states)
|
||||
assert tip == b4B.id()
|
||||
assert (d := common_prefix_depth(b5B.id(), b3E.id(), states)) == (4, 2), d
|
||||
|
||||
tip = ghost_fork_choice(b1A.id(), states)
|
||||
assert tip == b6A.id()
|
||||
k = 100
|
||||
s = int(3 * k / 0.05)
|
||||
tip = ghost_maxvalid_bg(
|
||||
b5B.id(), [b3E.id(), b4B.id(), b3C.id(), b3B.id(), b6A.id()], k, s, states
|
||||
)
|
||||
assert tip == b4B.id()
|
||||
|
||||
def test_block_weight_paper(self):
|
||||
# Example from the GHOST paper
|
||||
@ -292,14 +294,14 @@ class TestForkChoice(TestCase):
|
||||
states = {b.id(): LedgerState(block=b) for b in short_chain + long_chain}
|
||||
|
||||
assert (
|
||||
maxvalid_bg(short_chain[-1].id(), [long_chain[-1].id()], states, k, s)
|
||||
ghost_maxvalid_bg(short_chain[-1].id(), [long_chain[-1].id()], k, s, states)
|
||||
== short_chain[-1].id()
|
||||
)
|
||||
|
||||
# However, if we set k to the fork length, it will be accepted
|
||||
k = len(long_chain)
|
||||
assert (
|
||||
maxvalid_bg(short_chain[-1].id(), [long_chain[-1].id()], states, k, s)
|
||||
ghost_maxvalid_bg(short_chain[-1].id(), [long_chain[-1].id()], k, s, states)
|
||||
== long_chain[-1].id()
|
||||
)
|
||||
|
||||
@ -327,7 +329,7 @@ class TestForkChoice(TestCase):
|
||||
states = {b.id(): LedgerState(block=b) for b in short_chain + long_chain}
|
||||
|
||||
assert (
|
||||
maxvalid_bg(short_chain[-1].id(), [long_chain[-1].id()], states, k, s)
|
||||
ghost_maxvalid_bg(short_chain[-1].id(), [long_chain[-1].id()], k, s, states)
|
||||
== long_chain[-1].id()
|
||||
)
|
||||
|
||||
|
||||
@ -18,6 +18,26 @@ from .test_common import mk_config, mk_block, mk_genesis_state
|
||||
|
||||
|
||||
class TestLedgerStateUpdate(TestCase):
|
||||
def test_on_block_idempotent(self):
|
||||
leader_coin = Coin(sk=0, value=100)
|
||||
genesis = mk_genesis_state([leader_coin])
|
||||
|
||||
follower = Follower(genesis, mk_config([leader_coin]))
|
||||
|
||||
block = mk_block(slot=0, parent=genesis.block, coin=leader_coin)
|
||||
follower.on_block(block)
|
||||
|
||||
# Follower should have accepted the block
|
||||
assert follower.tip_state().height == 1
|
||||
assert follower.tip() == block
|
||||
|
||||
follower.on_block(block)
|
||||
|
||||
assert follower.tip_state().height == 1
|
||||
assert follower.tip() == block
|
||||
assert len(follower.ledger_state) == 2
|
||||
assert len(follower.forks) == 0
|
||||
|
||||
def test_ledger_state_prevents_coin_reuse(self):
|
||||
leader_coin = Coin(sk=0, value=100)
|
||||
genesis = mk_genesis_state([leader_coin])
|
||||
@ -35,7 +55,7 @@ class TestLedgerStateUpdate(TestCase):
|
||||
assert follower.tip_state().verify_unspent(leader_coin.nullifier()) == False
|
||||
|
||||
reuse_coin_block = mk_block(slot=1, parent=block, coin=leader_coin)
|
||||
follower.on_block(block)
|
||||
follower.on_block(reuse_coin_block)
|
||||
|
||||
# Follower should *not* have accepted the block
|
||||
assert follower.tip_state().height == 1
|
||||
|
||||
@ -5,7 +5,6 @@ import hashlib
|
||||
|
||||
from copy import deepcopy
|
||||
from cryptarchia.cryptarchia import (
|
||||
maxvalid_bg,
|
||||
BlockHeader,
|
||||
Slot,
|
||||
Id,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user