mirror of
https://github.com/logos-blockchain/research.git
synced 2026-08-07 03:33:33 +00:00
111 lines
4.2 KiB
Python
111 lines
4.2 KiB
Python
|
|
import numpy as np
|
||
|
|
|
||
|
|
from tsi_sim.blocktree import BlockTree
|
||
|
|
from tsi_sim.config import SimConfig
|
||
|
|
from tsi_sim.uncles import annotate_uncles
|
||
|
|
|
||
|
|
|
||
|
|
def make_tree(slots, parents, heights, leaders):
|
||
|
|
n = len(slots)
|
||
|
|
return BlockTree(
|
||
|
|
slot=np.array(slots, np.int64),
|
||
|
|
parent=np.array(parents, np.int64),
|
||
|
|
height=np.array(heights, np.int64),
|
||
|
|
leader=np.array(leaders, np.int64),
|
||
|
|
uncles=[() for _ in range(n)],
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _canonical_and_orphan_tree():
|
||
|
|
# genesis(0); canonical chain 1(slot0)->3(slot3)->4(slot5); orphan 2(slot1)
|
||
|
|
tree = make_tree(
|
||
|
|
slots=[-1, 0, 1, 3, 5],
|
||
|
|
parents=[-1, 0, 0, 1, 3],
|
||
|
|
heights=[0, 1, 1, 2, 3],
|
||
|
|
leaders=[-1, 0, 1, 2, 3],
|
||
|
|
)
|
||
|
|
canonical = [4, 3, 1] # tip-first
|
||
|
|
return tree, canonical
|
||
|
|
|
||
|
|
|
||
|
|
def test_oldest_selection_and_window():
|
||
|
|
tree, canonical = _canonical_and_orphan_tree()
|
||
|
|
cfg = SimConfig(max_uncles=1, uncle_window=300, uncle_strategy="oldest")
|
||
|
|
annotate_uncles(tree, canonical, cfg, np.random.default_rng(0))
|
||
|
|
# orphan 2 (slot1) is within window of block 3 (slot3) -> referenced there
|
||
|
|
referenced = {u for b in canonical for u in tree.uncles[b]}
|
||
|
|
assert referenced == {2}
|
||
|
|
|
||
|
|
|
||
|
|
def test_no_uncles_when_u_zero():
|
||
|
|
tree, canonical = _canonical_and_orphan_tree()
|
||
|
|
cfg = SimConfig(max_uncles=0)
|
||
|
|
annotate_uncles(tree, canonical, cfg, np.random.default_rng(0))
|
||
|
|
assert all(tree.uncles[b] == () for b in canonical)
|
||
|
|
|
||
|
|
|
||
|
|
def test_window_excludes_out_of_range_orphan():
|
||
|
|
tree, canonical = _canonical_and_orphan_tree()
|
||
|
|
cfg = SimConfig(max_uncles=1, uncle_window=1, uncle_strategy="oldest")
|
||
|
|
annotate_uncles(tree, canonical, cfg, np.random.default_rng(0))
|
||
|
|
# orphan 2 at slot1; nearest canonical after it is block3 at slot3 -> gap 2 > W=1
|
||
|
|
referenced = {u for b in canonical for u in tree.uncles[b]}
|
||
|
|
assert referenced == set()
|
||
|
|
|
||
|
|
|
||
|
|
def _wide_orphan_tree():
|
||
|
|
# canonical 1(0)->6(6); orphans 2,3,4,5 at slots 1,2,3,4 (all within window of block6)
|
||
|
|
tree = make_tree(
|
||
|
|
slots=[-1, 0, 1, 2, 3, 4, 6],
|
||
|
|
parents=[-1, 0, 0, 0, 0, 0, 1],
|
||
|
|
heights=[0, 1, 1, 1, 1, 1, 2],
|
||
|
|
leaders=[-1, 0, 1, 2, 3, 4, 0],
|
||
|
|
)
|
||
|
|
return tree, [6, 1] # tip-first
|
||
|
|
|
||
|
|
|
||
|
|
def test_random_strategy_deterministic_and_capped():
|
||
|
|
import numpy as np
|
||
|
|
tree_a, canon = _wide_orphan_tree()
|
||
|
|
tree_b, _ = _wide_orphan_tree()
|
||
|
|
cfg = SimConfig(max_uncles=2, uncle_window=300, uncle_strategy="random", uncle_random_p=0.5)
|
||
|
|
annotate_uncles(tree_a, canon, cfg, np.random.default_rng(7))
|
||
|
|
annotate_uncles(tree_b, canon, cfg, np.random.default_rng(7))
|
||
|
|
assert tree_a.uncles == tree_b.uncles # same seed -> identical
|
||
|
|
total = sum(len(tree_a.uncles[b]) for b in canon)
|
||
|
|
assert total <= cfg.max_uncles # capped
|
||
|
|
|
||
|
|
|
||
|
|
def test_random_p_one_matches_oldest():
|
||
|
|
import numpy as np
|
||
|
|
tree_r, canon = _wide_orphan_tree()
|
||
|
|
tree_o, _ = _wide_orphan_tree()
|
||
|
|
annotate_uncles(tree_r, canon, SimConfig(max_uncles=2, uncle_strategy="random",
|
||
|
|
uncle_random_p=1.0), np.random.default_rng(1))
|
||
|
|
annotate_uncles(tree_o, canon, SimConfig(max_uncles=2, uncle_strategy="oldest"),
|
||
|
|
np.random.default_rng(1))
|
||
|
|
assert tree_r.uncles == tree_o.uncles # p=1 deterministically takes oldest-first
|
||
|
|
|
||
|
|
|
||
|
|
def test_random_p_zero_selects_nothing():
|
||
|
|
import numpy as np
|
||
|
|
tree, canon = _wide_orphan_tree()
|
||
|
|
annotate_uncles(tree, canon, SimConfig(max_uncles=4, uncle_strategy="random",
|
||
|
|
uncle_random_p=0.0), np.random.default_rng(1))
|
||
|
|
assert all(tree.uncles[b] == () for b in canon)
|
||
|
|
|
||
|
|
|
||
|
|
def test_dedup_across_ancestors():
|
||
|
|
# Two canonical blocks both within window of the single orphan: only one references it.
|
||
|
|
tree = make_tree(
|
||
|
|
slots=[-1, 0, 1, 2, 3],
|
||
|
|
parents=[-1, 0, 0, 1, 3],
|
||
|
|
heights=[0, 1, 1, 2, 3],
|
||
|
|
leaders=[-1, 0, 9, 2, 3],
|
||
|
|
)
|
||
|
|
canonical = [4, 3, 1]
|
||
|
|
cfg = SimConfig(max_uncles=4, uncle_window=300, uncle_strategy="oldest")
|
||
|
|
annotate_uncles(tree, canonical, cfg, np.random.default_rng(0))
|
||
|
|
counts = sum(len(tree.uncles[b]) for b in canonical)
|
||
|
|
assert counts == 1 # orphan 2 referenced exactly once despite two eligible blocks
|