cryptarchia/ghost: integrate GHOST with maxvalid fork choice

This commit is contained in:
David Rusu 2024-11-01 23:21:49 +04:00
parent adaeba2493
commit e4d8887fe4
4 changed files with 52 additions and 34 deletions

View File

@ -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

View File

@ -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()
)

View File

@ -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

View File

@ -5,7 +5,6 @@ import hashlib
from copy import deepcopy
from cryptarchia.cryptarchia import (
maxvalid_bg,
BlockHeader,
Slot,
Id,