mirror of
https://github.com/logos-blockchain/logos-blockchain-specs.git
synced 2026-01-10 00:53:08 +00:00
cryptarchia/ghost: impl GHOST fork choice rule
This commit is contained in:
parent
5050f4636e
commit
adaeba2493
@ -6,6 +6,7 @@ import itertools
|
||||
import functools
|
||||
from dataclasses import dataclass, field, replace
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
|
||||
import numpy as np
|
||||
|
||||
@ -720,6 +721,51 @@ def chain_density(
|
||||
return density
|
||||
|
||||
|
||||
def block_children(states: Dict[Id, LedgerState]) -> Dict[Id, set[Id]]:
|
||||
children = defaultdict(set)
|
||||
for c, state in states.items():
|
||||
children[state.block.parent].add(c)
|
||||
|
||||
return children
|
||||
|
||||
|
||||
def block_weight(states: Dict[Id, LedgerState]) -> Dict[Id, int]:
|
||||
children = block_children(states)
|
||||
|
||||
block_weight = {}
|
||||
|
||||
pending = {b for b in states if len(children[b]) == 0}
|
||||
ready = set()
|
||||
|
||||
while len(pending) > 0:
|
||||
new_ready = set()
|
||||
for b in pending:
|
||||
if children[b] <= ready:
|
||||
block_weight[b] = 1 + sum(block_weight[c] for c in children[b])
|
||||
new_ready.add(b)
|
||||
|
||||
for b in new_ready:
|
||||
pending.remove(b)
|
||||
|
||||
if states[b].block.parent in states:
|
||||
pending.add(states[b].block.parent)
|
||||
|
||||
ready.add(b)
|
||||
|
||||
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
|
||||
|
||||
@ -5,6 +5,8 @@ import hashlib
|
||||
|
||||
from copy import deepcopy
|
||||
from cryptarchia.cryptarchia import (
|
||||
ghost_fork_choice,
|
||||
block_weight,
|
||||
maxvalid_bg,
|
||||
BlockHeader,
|
||||
Slot,
|
||||
@ -20,8 +22,202 @@ from .test_common import mk_chain, mk_config, mk_genesis_state, mk_block
|
||||
|
||||
|
||||
class TestForkChoice(TestCase):
|
||||
def test_common_prefix_depth(self):
|
||||
def test_ghost_fork_choice(self):
|
||||
# Example from the GHOST paper
|
||||
#
|
||||
# 2D - 3F - 4C - 5B
|
||||
# /
|
||||
# / 3E
|
||||
# / /
|
||||
# 1B - 2C - 3D - 4B
|
||||
# / \ \
|
||||
# 0 \ 3C
|
||||
# \ \
|
||||
# \ 2B - 3B
|
||||
# \
|
||||
# 1A - 2A - 3A - 4A - 5A - 6A
|
||||
|
||||
coin = Coin(sk=1, value=100)
|
||||
|
||||
b0 = BlockHeader(slot=Slot(0), parent=bytes(32))
|
||||
|
||||
b1A = mk_block(b0, 1, coin, content=b"b1A")
|
||||
b2A = mk_block(b1A, 2, coin, content=b"b2A")
|
||||
b3A = mk_block(b2A, 3, coin, content=b"b3A")
|
||||
b4A = mk_block(b3A, 4, coin, content=b"b4A")
|
||||
b5A = mk_block(b4A, 5, coin, content=b"b5A")
|
||||
b6A = mk_block(b5A, 6, coin, content=b"b6A")
|
||||
b1B = mk_block(b0, 1, coin, content=b"b1B")
|
||||
b2B = mk_block(b1B, 2, coin, content=b"b2B")
|
||||
b3B = mk_block(b2B, 3, coin, content=b"b3B")
|
||||
b2C = mk_block(b1B, 2, coin, content=b"b2C")
|
||||
b3C = mk_block(b2C, 3, coin, content=b"b3C")
|
||||
b2D = mk_block(b1B, 2, coin, content=b"b2D")
|
||||
b3D = mk_block(b2C, 3, coin, content=b"b3D")
|
||||
b4B = mk_block(b3D, 4, coin, content=b"b4B")
|
||||
b3E = mk_block(b2C, 3, coin, content=b"b3E")
|
||||
b3F = mk_block(b2D, 3, coin, content=b"b3F")
|
||||
b4C = mk_block(b3F, 4, coin, content=b"b4C")
|
||||
b5B = mk_block(b4C, 5, coin, content=b"b5B")
|
||||
|
||||
states = {
|
||||
b.id(): LedgerState(block=b)
|
||||
for b in [
|
||||
b0,
|
||||
b1A,
|
||||
b2A,
|
||||
b3A,
|
||||
b4A,
|
||||
b5A,
|
||||
b6A,
|
||||
b1B,
|
||||
b2B,
|
||||
b3B,
|
||||
b4B,
|
||||
b5B,
|
||||
b2C,
|
||||
b3C,
|
||||
b4C,
|
||||
b2D,
|
||||
b3D,
|
||||
b3E,
|
||||
b3F,
|
||||
]
|
||||
}
|
||||
|
||||
tip = ghost_fork_choice(b0.id(), states)
|
||||
assert tip == b4B.id()
|
||||
|
||||
tip = ghost_fork_choice(b1A.id(), states)
|
||||
assert tip == b6A.id()
|
||||
|
||||
def test_block_weight_paper(self):
|
||||
# Example from the GHOST paper
|
||||
#
|
||||
# 2D - 3F - 4C - 5B
|
||||
# /
|
||||
# / 3E
|
||||
# / /
|
||||
# 1B - 2C - 3D - 4B
|
||||
# / \ \
|
||||
# 0 \ 3C
|
||||
# \ \
|
||||
# \ 2B - 3B
|
||||
# \
|
||||
# 1A - 2A - 3A - 4A - 5A - 6A
|
||||
|
||||
coin = Coin(sk=1, value=100)
|
||||
|
||||
b0 = BlockHeader(slot=Slot(0), parent=bytes(32))
|
||||
|
||||
b1A = mk_block(b0, 1, coin, content=b"b1A")
|
||||
b2A = mk_block(b1A, 2, coin, content=b"b2A")
|
||||
b3A = mk_block(b2A, 3, coin, content=b"b3A")
|
||||
b4A = mk_block(b3A, 4, coin, content=b"b4A")
|
||||
b5A = mk_block(b4A, 5, coin, content=b"b5A")
|
||||
b6A = mk_block(b5A, 6, coin, content=b"b6A")
|
||||
b1B = mk_block(b0, 1, coin, content=b"b1B")
|
||||
b2B = mk_block(b1B, 2, coin, content=b"b2B")
|
||||
b3B = mk_block(b2B, 3, coin, content=b"b3B")
|
||||
b2C = mk_block(b1B, 2, coin, content=b"b2C")
|
||||
b3C = mk_block(b2C, 3, coin, content=b"b3C")
|
||||
b2D = mk_block(b1B, 2, coin, content=b"b2D")
|
||||
b3D = mk_block(b2C, 3, coin, content=b"b3D")
|
||||
b4B = mk_block(b3D, 4, coin, content=b"b4B")
|
||||
b3E = mk_block(b2C, 3, coin, content=b"b3E")
|
||||
b3F = mk_block(b2D, 3, coin, content=b"b3F")
|
||||
b4C = mk_block(b3F, 4, coin, content=b"b4C")
|
||||
b5B = mk_block(b4C, 5, coin, content=b"b5B")
|
||||
|
||||
states = {
|
||||
b.id(): LedgerState(block=b)
|
||||
for b in [
|
||||
b0,
|
||||
b1A,
|
||||
b2A,
|
||||
b3A,
|
||||
b4A,
|
||||
b5A,
|
||||
b6A,
|
||||
b1B,
|
||||
b2B,
|
||||
b3B,
|
||||
b4B,
|
||||
b5B,
|
||||
b2C,
|
||||
b3C,
|
||||
b4C,
|
||||
b2D,
|
||||
b3D,
|
||||
b3E,
|
||||
b3F,
|
||||
]
|
||||
}
|
||||
|
||||
weight = block_weight(states)
|
||||
|
||||
expected_weight = {
|
||||
b0.id(): 19,
|
||||
b1A.id(): 6,
|
||||
b2A.id(): 5,
|
||||
b3A.id(): 4,
|
||||
b4A.id(): 3,
|
||||
b5A.id(): 2,
|
||||
b6A.id(): 1,
|
||||
b1B.id(): 12,
|
||||
b2B.id(): 2,
|
||||
b3B.id(): 1,
|
||||
b4B.id(): 1,
|
||||
b5B.id(): 1,
|
||||
b2C.id(): 5,
|
||||
b3C.id(): 1,
|
||||
b4C.id(): 2,
|
||||
b2D.id(): 4,
|
||||
b3D.id(): 2,
|
||||
b3E.id(): 1,
|
||||
b3F.id(): 3,
|
||||
}
|
||||
|
||||
assert weight == expected_weight
|
||||
|
||||
def test_block_weight(self):
|
||||
# 6 - 7
|
||||
# /
|
||||
# 0 - 1 - 2 - 3
|
||||
# \
|
||||
# 4 - 5
|
||||
|
||||
coin = Coin(sk=1, value=100)
|
||||
|
||||
b0 = BlockHeader(slot=Slot(0), parent=bytes(32))
|
||||
b1 = mk_block(b0, 1, coin)
|
||||
b2 = mk_block(b1, 2, coin)
|
||||
b3 = mk_block(b2, 3, coin)
|
||||
b4 = mk_block(b0, 1, coin, content=b"b4")
|
||||
b5 = mk_block(b4, 2, coin)
|
||||
b6 = mk_block(b2, 3, coin, content=b"b6")
|
||||
b7 = mk_block(b6, 4, coin)
|
||||
|
||||
states = {
|
||||
b.id(): LedgerState(block=b) for b in [b0, b1, b2, b3, b4, b5, b6, b7]
|
||||
}
|
||||
|
||||
weights = block_weight(states)
|
||||
|
||||
expected_weights = {
|
||||
b0.id(): 8,
|
||||
b1.id(): 5,
|
||||
b2.id(): 4,
|
||||
b3.id(): 1,
|
||||
b4.id(): 2,
|
||||
b5.id(): 1,
|
||||
b6.id(): 2,
|
||||
b7.id(): 1,
|
||||
}
|
||||
|
||||
assert weights == expected_weights, weights
|
||||
|
||||
def test_common_prefix_depth(self):
|
||||
# 6 - 7
|
||||
# /
|
||||
# 0 - 1 - 2 - 3
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user