cryptarchia/ghost: prep for move to weight based fork choice

This commit is contained in:
David Rusu 2024-10-31 01:52:42 +04:00
parent 5434fcb315
commit bf5ef98174
7 changed files with 248 additions and 111 deletions

View File

@ -1,4 +1,4 @@
from typing import TypeAlias, List, Optional
from typing import TypeAlias, List, Optional, Dict
from hashlib import sha256, blake2b
from math import floor
from copy import deepcopy
@ -174,11 +174,11 @@ class Coin:
@dataclass
class MockLeaderProof:
commitment: Id
nullifier: Id
evolved_commitment: Id
slot: Slot
parent: Id
commitment: Id = bytes(32)
nullifier: Id = bytes(32)
evolved_commitment: Id = bytes(32)
slot: Slot = field(default_factory=lambda: Slot(0))
parent: Id = bytes(32)
@staticmethod
def new(coin: Coin, slot: Slot, parent: Id):
@ -194,18 +194,31 @@ class MockLeaderProof:
def verify(self, slot: Slot, parent: Id):
# TODO: verification not implemented
return slot == self.slot and parent == self.parent
if slot != self.slot:
logger.warning("PoL: wrong slot")
return False
if parent != self.parent:
logger.warning("PoL: wrong parent")
return False
return True
@dataclass
class BlockHeader:
slot: Slot
parent: Id
content_size: int
content_id: Id
leader_proof: MockLeaderProof
parent: Id = bytes(32)
content_size: int = 0
content_id: Id = bytes(32)
leader_proof: MockLeaderProof = field(default_factory=MockLeaderProof)
orphaned_proofs: List["BlockHeader"] = field(default_factory=list)
def __post_init__(self):
assert type(self.slot) == Slot
assert type(self.parent) == Id
assert self.slot == self.leader_proof.slot
assert self.parent == self.leader_proof.parent
def update_header_hash(self, h):
# version byte
h.update(b"\x01")
@ -277,7 +290,7 @@ class LedgerState:
A snapshot of the ledger state up to some block
"""
block: Id = None
block: BlockHeader
# This nonce is used to derive the seed for the slot leader lottery.
# It's updated at every block by hashing the previous nonce with the
@ -324,7 +337,7 @@ class LedgerState:
return nullifier not in self.nullifiers
def apply(self, block: BlockHeader):
assert block.parent == self.block
assert block.parent == self.block.id()
h = blake2b(digest_size=32)
h.update("epoch-nonce".encode(encoding="utf-8"))
@ -333,7 +346,7 @@ class LedgerState:
h.update(block.slot.encode())
self.nonce = h.digest()
self.block = block.id()
self.block = block
for proof in chain(block.orphaned_proofs, [block]):
self.apply_leader_proof(proof.leader_proof)
@ -385,9 +398,9 @@ class Follower:
def __init__(self, genesis_state: LedgerState, config: Config):
self.config = config
self.forks = []
self.local_chain = Chain([], genesis=genesis_state.block)
self.local_chain = Chain([], genesis=genesis_state.block.id())
self.genesis_state = genesis_state
self.ledger_state = {genesis_state.block: genesis_state.copy()}
self.ledger_state = {genesis_state.block.id(): genesis_state.copy()}
self.epoch_state = {}
def validate_header(self, block: BlockHeader, chain: Chain) -> bool:
@ -453,14 +466,21 @@ class Follower:
# This will change once we start putting merkle roots in headers
current_state: LedgerState,
) -> bool:
return (
proof.verify(slot, parent) # verify slot leader proof
and (
current_state.verify_eligible_to_lead(proof.commitment)
or epoch_state.verify_eligible_to_lead_due_to_age(proof.commitment)
)
and current_state.verify_unspent(proof.nullifier)
)
if not proof.verify(slot, parent):
logger.warning("invalid PoL")
return False
if not (
current_state.verify_eligible_to_lead(proof.commitment)
or epoch_state.verify_eligible_to_lead_due_to_age(proof.commitment)
):
logger.warning("invalid commitment")
return False
if not current_state.verify_unspent(proof.nullifier):
logger.warning("PoL coin already spent")
return False
return True
# Try appending this block to an existing chain and return whether
# the operation was successful
@ -475,9 +495,9 @@ class Follower:
return None
def try_create_fork(self, block: BlockHeader) -> Optional[Chain]:
if self.genesis_state.block == block.parent:
if self.genesis_state.block.id() == block.parent:
# this block is forking off the genesis state
return Chain(blocks=[], genesis=self.genesis_state.block)
return Chain(blocks=[], genesis=self.genesis_state.block.id())
chains = self.forks + [self.local_chain]
for chain in chains:
@ -485,7 +505,7 @@ class Follower:
if block_position is not None:
return Chain(
blocks=chain.blocks[: block_position + 1],
genesis=self.genesis_state.block,
genesis=self.genesis_state.block.id(),
)
return None
@ -509,6 +529,10 @@ class Follower:
logger.warning("invalid header")
return
new_state = self.ledger_state[block.parent].copy()
new_state.apply(block)
self.ledger_state[block.id()] = new_state
new_chain.blocks.append(block)
# We may need to switch forks, lets run the fork choice rule to check.
@ -518,10 +542,6 @@ class Follower:
self.forks.append(self.local_chain)
self.local_chain = new_chain
new_state = self.ledger_state[block.parent].copy()
new_state.apply(block)
self.ledger_state[block.id()] = new_state
def unimported_orphans(self, tip: Id) -> list[BlockHeader]:
"""
Returns all unimported orphans w.r.t. the given tip's state.
@ -547,7 +567,11 @@ class Follower:
# Evaluate the fork choice rule and return the chain we should be following
def fork_choice(self) -> Chain:
return maxvalid_bg(
self.local_chain, self.forks, k=self.config.k, s=self.config.s
self.local_chain,
self.forks,
self.ledger_state,
k=self.config.k,
s=self.config.s,
)
def tip(self) -> BlockHeader:
@ -596,7 +620,7 @@ class Follower:
nonce_snapshot = self.nonce_snapshot(epoch, chain)
# we memoize epoch states to avoid recursion killing our performance
memo_block_id = nonce_snapshot.block
memo_block_id = nonce_snapshot.block.id()
if state := self.epoch_state.get((epoch, memo_block_id)):
return state
@ -696,7 +720,53 @@ class Leader:
)
def common_prefix_len(a: Chain, b: Chain) -> int:
def common_prefix_depth(
local_chain: Id, fork: Id, states: Dict[Id, LedgerState]
) -> int:
local_block = local_chain
fork_block = fork
seen = {}
depth = 0
while True:
if local_block not in states and fork_block not in states:
# conflicting genesis blocks
print("")
print("local\t", local_chain[:2])
print("fork\t", fork[:2])
print(
"states\n\t",
"\n\t".join(
[f"{b[:2]} -> {s.block.parent[:2]}" for b, s in states.items()]
),
)
print("seen\t", {s[:2] for s in seen})
break
if local_block in seen:
# we had seen this block from the fork chain
return depth
if local_block in states:
seen[local_block] = depth
local_block = states[local_block].block.parent
if fork_block in seen:
# we had seen the fork in the local chain
# return the depth w.r.t to the local chain
return seen[fork_block]
if fork_block in states:
seen[fork_block] = depth
fork_block = states[fork_block].block.parent
depth += 1
assert False
def common_prefix_len(a, b) -> int:
for i, (x, y) in enumerate(zip(a.blocks, b.blocks)):
if x.id() != y.id():
return i
@ -716,11 +786,19 @@ def chain_density(chain: Chain, slot: Slot) -> int:
# 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(local_chain: Chain, forks: List[Chain], k: int, s: int) -> Chain:
def maxvalid_bg(
local_chain: Chain,
forks: List[Chain],
states: Dict[Id, LedgerState],
k: int,
s: int,
) -> Chain:
cmax = local_chain
for chain in forks:
lowest_common_ancestor = common_prefix_len(cmax, chain)
m = cmax.length() - lowest_common_ancestor
m2 = common_prefix_depth(cmax.tip_id(), chain.tip_id(), states)
assert m == m2, f"{m} != {m2}"
if m <= k:
# Classic longest chain rule with parameter k
if cmax.length() < chain.length():

View File

@ -53,7 +53,7 @@ def mk_config(initial_stake_distribution: list[Coin]) -> Config:
def mk_genesis_state(initial_stake_distribution: list[Coin]) -> LedgerState:
return LedgerState(
block=bytes(32),
block=BlockHeader(slot=Slot(0), parent=bytes(32)),
nonce=bytes(32),
commitments_spend={c.commitment() for c in initial_stake_distribution},
commitments_lead={c.commitment() for c in initial_stake_distribution},
@ -62,26 +62,30 @@ def mk_genesis_state(initial_stake_distribution: list[Coin]) -> LedgerState:
def mk_block(
parent: Id, slot: int, coin: Coin, content=bytes(32), orphaned_proofs=[]
parent: BlockHeader, slot: int, coin: Coin, content=bytes(32), orphaned_proofs=[]
) -> BlockHeader:
assert len(parent) == 32
assert type(parent) == BlockHeader, type(parent)
assert type(slot) == int, type(slot)
from hashlib import sha256
return BlockHeader(
slot=Slot(slot),
parent=parent,
parent=parent.id(),
content_size=len(content),
content_id=sha256(content).digest(),
leader_proof=MockLeaderProof.new(coin, Slot(slot), parent=parent),
leader_proof=MockLeaderProof.new(coin, Slot(slot), parent=parent.id()),
orphaned_proofs=orphaned_proofs,
)
def mk_chain(parent, coin: Coin, slots: list[int]) -> tuple[list[BlockHeader], Coin]:
def mk_chain(
parent: BlockHeader, coin: Coin, slots: list[int]
) -> tuple[list[BlockHeader], Coin]:
assert type(parent) == BlockHeader
chain = []
for s in slots:
block = mk_block(parent=parent, slot=s, coin=coin)
chain.append(block)
parent = block.id()
parent = block
coin = coin.evolve()
return chain, coin

View File

@ -13,28 +13,77 @@ from cryptarchia.cryptarchia import (
MockLeaderProof,
Coin,
Follower,
common_prefix_depth,
LedgerState,
)
from .test_common import mk_chain, mk_config, mk_genesis_state, mk_block
class TestForkChoice(TestCase):
def test_common_prefix_depth(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]
}
assert (d := common_prefix_depth(b0.id(), b0.id(), states)) == 0, d
assert (d := common_prefix_depth(b1.id(), b0.id(), states)) == 1, d
assert (d := common_prefix_depth(b0.id(), b1.id(), states)) == 0, d
assert (d := common_prefix_depth(b1.id(), b1.id(), states)) == 0, d
assert (d := common_prefix_depth(b2.id(), b0.id(), states)) == 2, d
assert (d := common_prefix_depth(b0.id(), b2.id(), states)) == 0, d
assert (d := common_prefix_depth(b3.id(), b0.id(), states)) == 3, d
assert (d := common_prefix_depth(b0.id(), b3.id(), states)) == 0, d
assert (d := common_prefix_depth(b1.id(), b4.id(), states)) == 1, d
assert (d := common_prefix_depth(b4.id(), b1.id(), states)) == 1, d
assert (d := common_prefix_depth(b1.id(), b5.id(), states)) == 1, d
assert (d := common_prefix_depth(b5.id(), b1.id(), states)) == 2, d
assert (d := common_prefix_depth(b2.id(), b5.id(), states)) == 2, d
assert (d := common_prefix_depth(b5.id(), b2.id(), states)) == 2, d
assert (d := common_prefix_depth(b3.id(), b5.id(), states)) == 3, d
assert (d := common_prefix_depth(b5.id(), b3.id(), states)) == 2, d
assert (d := common_prefix_depth(b3.id(), b6.id(), states)) == 1, d
assert (d := common_prefix_depth(b6.id(), b3.id(), states)) == 1, d
assert (d := common_prefix_depth(b3.id(), b7.id(), states)) == 1, d
assert (d := common_prefix_depth(b7.id(), b3.id(), states)) == 2, d
assert (d := common_prefix_depth(b5.id(), b7.id(), states)) == 2, d
assert (d := common_prefix_depth(b7.id(), b5.id(), states)) == 4, d
def test_fork_choice_long_sparse_chain(self):
# The longest chain is not dense after the fork
genesis = BlockHeader(slot=Slot(0), parent=bytes(32))
short_coin, long_coin = Coin(sk=0, value=100), Coin(sk=1, value=100)
common, long_coin = mk_chain(parent=bytes(32), coin=long_coin, slots=range(50))
common, long_coin = mk_chain(parent=genesis, coin=long_coin, slots=range(50))
long_chain_sparse_ext, long_coin = mk_chain(
parent=common[-1].id(), coin=long_coin, slots=range(50, 100, 2)
parent=common[-1], coin=long_coin, slots=range(50, 100, 2)
)
short_chain_dense_ext, _ = mk_chain(
parent=common[-1].id(), coin=short_coin, slots=range(50, 100)
parent=common[-1], coin=short_coin, slots=range(50, 100)
)
# add more blocks to the long chain to ensure the long chain is indeed longer
long_chain_further_ext, _ = mk_chain(
parent=long_chain_sparse_ext[-1].id(), coin=long_coin, slots=range(100, 126)
parent=long_chain_sparse_ext[-1], coin=long_coin, slots=range(100, 126)
)
long_chain = deepcopy(common) + long_chain_sparse_ext + long_chain_further_ext
@ -47,24 +96,30 @@ class TestForkChoice(TestCase):
short_chain = Chain(short_chain, genesis=bytes(32))
long_chain = Chain(long_chain, genesis=bytes(32))
assert maxvalid_bg(short_chain, [long_chain], k, s) == short_chain
states = {
b.id(): LedgerState(block=b) for b in short_chain.blocks + long_chain.blocks
}
assert maxvalid_bg(short_chain, [long_chain], states, k, s) == short_chain
# However, if we set k to the fork length, it will be accepted
k = long_chain.length()
assert maxvalid_bg(short_chain, [long_chain], k, s) == long_chain
assert maxvalid_bg(short_chain, [long_chain], states, k, s) == long_chain
def test_fork_choice_long_dense_chain(self):
# The longest chain is also the densest after the fork
short_coin, long_coin = Coin(sk=0, value=100), Coin(sk=1, value=100)
common, long_coin = mk_chain(
parent=bytes(32), coin=long_coin, slots=range(1, 50)
parent=BlockHeader(slot=Slot(0), parent=bytes(32)),
coin=long_coin,
slots=range(1, 50),
)
long_chain_dense_ext, _ = mk_chain(
parent=common[-1].id(), coin=long_coin, slots=range(50, 100)
parent=common[-1], coin=long_coin, slots=range(50, 100)
)
short_chain_sparse_ext, _ = mk_chain(
parent=common[-1].id(), coin=short_coin, slots=range(50, 100, 2)
parent=common[-1], coin=short_coin, slots=range(50, 100, 2)
)
long_chain = deepcopy(common) + long_chain_dense_ext
@ -74,7 +129,11 @@ class TestForkChoice(TestCase):
s = 50
short_chain = Chain(short_chain, genesis=bytes(32))
long_chain = Chain(long_chain, genesis=bytes(32))
assert maxvalid_bg(short_chain, [long_chain], k, s) == long_chain
states = {
b.id(): LedgerState(block=b) for b in short_chain.blocks + long_chain.blocks
}
assert maxvalid_bg(short_chain, [long_chain], states, k, s) == long_chain
def test_fork_choice_integration(self):
c_a, c_b = Coin(sk=0, value=10), Coin(sk=1, value=10)
@ -99,8 +158,8 @@ class TestForkChoice(TestCase):
# b3
#
b2, c_a = mk_block(b1.id(), 2, c_a), c_a.evolve()
b3, c_b = mk_block(b1.id(), 2, c_b), c_b.evolve()
b2, c_a = mk_block(b1, 2, c_a), c_a.evolve()
b3, c_b = mk_block(b1, 2, c_b), c_b.evolve()
follower.on_block(b2)
follower.on_block(b3)
@ -117,7 +176,7 @@ class TestForkChoice(TestCase):
# b3 - b4 == tip
#
b4, c_b = mk_block(b3.id(), 3, c_b), c_a.evolve()
b4, c_b = mk_block(b3, 3, c_b), c_a.evolve()
follower.on_block(b4)
assert follower.tip_id() == b4.id()

View File

@ -18,8 +18,8 @@ from .test_common import mk_config
class TestLeader(TestCase):
def test_slot_leader_statistics(self):
epoch = EpochState(
stake_distribution_snapshot=LedgerState(),
nonce_snapshot=LedgerState(nonce=b"1010101010"),
stake_distribution_snapshot=LedgerState(block=None),
nonce_snapshot=LedgerState(block=None, nonce=b"1010101010"),
inferred_total_active_stake=1000,
)

View File

@ -34,7 +34,7 @@ class TestLedgerStateUpdate(TestCase):
# Follower should have updated their ledger state to mark the leader coin as spent
assert follower.tip_state().verify_unspent(leader_coin.nullifier()) == False
reuse_coin_block = mk_block(slot=1, parent=block.id(), coin=leader_coin)
reuse_coin_block = mk_block(slot=1, parent=block, coin=leader_coin)
follower.on_block(block)
# Follower should *not* have accepted the block
@ -67,7 +67,7 @@ class TestLedgerStateUpdate(TestCase):
# 4) then coin[2] wins slot 1 and chooses to extend from block_2
block_3 = mk_block(parent=block_2.id(), slot=1, coin=coin[2])
block_3 = mk_block(parent=block_2, slot=1, coin=coin[2])
follower.on_block(block_3)
# the follower should have switched over to the block_2 fork
assert follower.tip() == block_3
@ -94,8 +94,8 @@ class TestLedgerStateUpdate(TestCase):
# coin_2 wins slot 1 and chooses to extend from block_1
# coin_3 also wins slot 1 and but chooses to extend from block_2
# Both blocks are accepted. Both the local chain and the fork grow. No fork is newly created.
block_3 = mk_block(parent=block_1.id(), slot=1, coin=coins[2])
block_4 = mk_block(parent=block_2.id(), slot=1, coin=coins[3])
block_3 = mk_block(parent=block_1, slot=1, coin=coins[2])
block_4 = mk_block(parent=block_2, slot=1, coin=coins[3])
follower.on_block(block_3)
follower.on_block(block_4)
assert follower.tip() == block_3
@ -104,7 +104,7 @@ class TestLedgerStateUpdate(TestCase):
# coin_4 wins slot 1 and but chooses to extend from block_2 as well
# The block is accepted. A new fork is created "from the block_2".
block_5 = mk_block(parent=block_2.id(), slot=1, coin=coins[4])
block_5 = mk_block(parent=block_2, slot=1, coin=coins[4])
follower.on_block(block_5)
assert follower.tip() == block_3
assert len(follower.forks) == 2, f"{len(follower.forks)}"
@ -113,8 +113,8 @@ class TestLedgerStateUpdate(TestCase):
# A block based on an unknown parent is not accepted.
# Nothing changes from the local chain and forks.
unknown_block = mk_block(parent=block_5.id(), slot=2, coin=coins[5])
block_6 = mk_block(parent=unknown_block.id(), slot=2, coin=coins[6])
unknown_block = mk_block(parent=block_5, slot=2, coin=coins[5])
block_6 = mk_block(parent=unknown_block, slot=2, coin=coins[6])
follower.on_block(block_6)
assert follower.tip() == block_3
assert len(follower.forks) == 2, f"{len(follower.forks)}"
@ -138,14 +138,14 @@ class TestLedgerStateUpdate(TestCase):
assert follower.tip() == block_1
assert follower.tip().slot.epoch(config).epoch == 0
block_2 = mk_block(slot=19, parent=block_1.id(), coin=leader_coins[1])
block_2 = mk_block(slot=19, parent=block_1, coin=leader_coins[1])
follower.on_block(block_2)
assert follower.tip() == block_2
assert follower.tip().slot.epoch(config).epoch == 0
# ---- EPOCH 1 ----
block_3 = mk_block(slot=20, parent=block_2.id(), coin=leader_coins[2])
block_3 = mk_block(slot=20, parent=block_2, coin=leader_coins[2])
follower.on_block(block_3)
assert follower.tip() == block_3
assert follower.tip().slot.epoch(config).epoch == 1
@ -157,7 +157,7 @@ class TestLedgerStateUpdate(TestCase):
# To ensure this is the case, we add a new coin just to the state associated with that slot,
# so that the new block can be accepted only if that is the snapshot used
# first, verify that if we don't change the state, the block is not accepted
block_4 = mk_block(slot=40, parent=block_3.id(), coin=Coin(sk=4, value=100))
block_4 = mk_block(slot=40, parent=block_3, coin=Coin(sk=4, value=100))
follower.on_block(block_4)
assert follower.tip() == block_3
# then we add the coin to "spendable commitments" associated with slot 9
@ -181,12 +181,12 @@ class TestLedgerStateUpdate(TestCase):
assert follower.tip() == block_1
# coin can't be reused to win following slots:
block_2_reuse = mk_block(slot=1, parent=block_1.id(), coin=coin)
block_2_reuse = mk_block(slot=1, parent=block_1, coin=coin)
follower.on_block(block_2_reuse)
assert follower.tip() == block_1
# but the evolved coin is eligible
block_2_evolve = mk_block(slot=1, parent=block_1.id(), coin=coin.evolve())
block_2_evolve = mk_block(slot=1, parent=block_1, coin=coin.evolve())
follower.on_block(block_2_evolve)
assert follower.tip() == block_2_evolve
@ -212,12 +212,12 @@ class TestLedgerStateUpdate(TestCase):
)
# the new coin is not yet eligible for elections
block_0_1_attempt = mk_block(slot=1, parent=block_0_0.id(), coin=coin_new)
block_0_1_attempt = mk_block(slot=1, parent=block_0_0, coin=coin_new)
follower.on_block(block_0_1_attempt)
assert follower.tip() == block_0_0
# whereas the evolved coin from genesis can be spent immediately
block_0_1 = mk_block(slot=1, parent=block_0_0.id(), coin=coin.evolve())
block_0_1 = mk_block(slot=1, parent=block_0_0, coin=coin.evolve())
follower.on_block(block_0_1)
assert follower.tip() == block_0_1
@ -226,7 +226,7 @@ class TestLedgerStateUpdate(TestCase):
# The newly minted coin is still not eligible in the following epoch since the
# stake distribution snapshot is taken at the beginning of the previous epoch
block_1_0 = mk_block(slot=20, parent=block_0_1.id(), coin=coin_new)
block_1_0 = mk_block(slot=20, parent=block_0_1, coin=coin_new)
follower.on_block(block_1_0)
assert follower.tip() == block_0_1
@ -234,16 +234,12 @@ class TestLedgerStateUpdate(TestCase):
# The coin is finally eligible 2 epochs after it was first minted
block_2_0 = mk_block(
slot=40,
parent=block_0_1.id(),
coin=coin_new,
)
block_2_0 = mk_block(slot=40, parent=block_0_1, coin=coin_new)
follower.on_block(block_2_0)
assert follower.tip() == block_2_0
# And now the minted coin can freely use the evolved coin for subsequent blocks
block_2_1 = mk_block(slot=40, parent=block_2_0.id(), coin=coin_new.evolve())
block_2_1 = mk_block(slot=40, parent=block_2_0, coin=coin_new.evolve())
follower.on_block(block_2_1)
assert follower.tip() == block_2_1
@ -259,7 +255,7 @@ class TestLedgerStateUpdate(TestCase):
coin_new = coin.evolve()
coin_new_new = coin_new.evolve()
block_0_1 = mk_block(slot=1, parent=block_0_0.id(), coin=coin_new_new)
block_0_1 = mk_block(slot=1, parent=block_0_0, coin=coin_new_new)
follower.on_block(block_0_1)
# the coin evolved twice should not be accepted as it is not in the lead commitments
assert follower.tip() == block_0_0
@ -272,7 +268,7 @@ class TestLedgerStateUpdate(TestCase):
orphan = mk_block(parent=genesis.block, slot=0, coin=coin_orphan)
block_0_1 = mk_block(
slot=1,
parent=block_0_0.id(),
parent=block_0_0,
coin=coin_orphan.evolve(),
orphaned_proofs=[orphan],
)

View File

@ -36,8 +36,8 @@ class TestOrphanedProofs(TestCase):
#
b1, c_a = mk_block(genesis.block, 1, c_a), c_a.evolve()
b2, c_a = mk_block(b1.id(), 2, c_a), c_a.evolve()
b3, c_b = mk_block(b1.id(), 2, c_b), c_b.evolve()
b2, c_a = mk_block(b1, 2, c_a), c_a.evolve()
b3, c_b = mk_block(b1, 2, c_b), c_b.evolve()
for b in [b1, b2, b3]:
follower.on_block(b)
@ -54,7 +54,7 @@ class TestOrphanedProofs(TestCase):
# \ /
# b3
#
b4, c_a = mk_block(b2.id(), 3, c_a, orphaned_proofs=[b3]), c_a.evolve()
b4, c_a = mk_block(b2, 3, c_a, orphaned_proofs=[b3]), c_a.evolve()
follower.on_block(b4)
assert follower.tip() == b4
@ -79,11 +79,11 @@ class TestOrphanedProofs(TestCase):
b1, c_a = mk_block(genesis.block, 1, c_a), c_a.evolve()
b2, c_a = mk_block(b1.id(), 2, c_a), c_a.evolve()
b3, c_a = mk_block(b2.id(), 3, c_a), c_a.evolve()
b2, c_a = mk_block(b1, 2, c_a), c_a.evolve()
b3, c_a = mk_block(b2, 3, c_a), c_a.evolve()
b4, c_b = mk_block(b1.id(), 2, c_b), c_b.evolve()
b5, c_b = mk_block(b4.id(), 3, c_b), c_b.evolve()
b4, c_b = mk_block(b1, 2, c_b), c_b.evolve()
b5, c_b = mk_block(b4, 3, c_b), c_b.evolve()
for b in [b1, b2, b3, b4, b5]:
follower.on_block(b)
@ -100,7 +100,7 @@ class TestOrphanedProofs(TestCase):
# \ / /
# b4 - b5
b6, c_a = mk_block(b3.id(), 4, c_a, orphaned_proofs=[b4, b5]), c_a.evolve()
b6, c_a = mk_block(b3, 4, c_a, orphaned_proofs=[b4, b5]), c_a.evolve()
follower.on_block(b6)
assert follower.tip() == b6
@ -123,13 +123,13 @@ class TestOrphanedProofs(TestCase):
b1, c_a = mk_block(genesis.block, 1, c_a), c_a.evolve()
b2, c_a = mk_block(b1.id(), 2, c_a), c_a.evolve()
b3, c_a = mk_block(b2.id(), 3, c_a), c_a.evolve()
b4, c_a = mk_block(b3.id(), 4, c_a), c_a.evolve()
b2, c_a = mk_block(b1, 2, c_a), c_a.evolve()
b3, c_a = mk_block(b2, 3, c_a), c_a.evolve()
b4, c_a = mk_block(b3, 4, c_a), c_a.evolve()
b5, c_b = mk_block(b1.id(), 2, c_b), c_b.evolve()
b6, c_b = mk_block(b5.id(), 3, c_b), c_b.evolve()
b7, c_b = mk_block(b6.id(), 4, c_b), c_b.evolve()
b5, c_b = mk_block(b1, 2, c_b), c_b.evolve()
b6, c_b = mk_block(b5, 3, c_b), c_b.evolve()
b7, c_b = mk_block(b6, 4, c_b), c_b.evolve()
for b in [b1, b2, b3, b4, b5, b6, b7]:
follower.on_block(b)
@ -149,7 +149,7 @@ class TestOrphanedProofs(TestCase):
# Earlier implementations of orphan proof validation failed to
# validate b7 as an orphan here.
b8, c_a = mk_block(b4.id(), 5, c_a, orphaned_proofs=[b5, b6, b7]), c_a.evolve()
b8, c_a = mk_block(b4, 5, c_a, orphaned_proofs=[b5, b6, b7]), c_a.evolve()
follower.on_block(b8)
assert follower.tip() == b8
@ -187,13 +187,13 @@ class TestOrphanedProofs(TestCase):
b1, c_a = mk_block(genesis.block, 1, c_a), c_a.evolve()
b2, c_a = mk_block(b1.id(), 2, c_a), c_a.evolve()
b3, c_a = mk_block(b2.id(), 3, c_a), c_a.evolve()
b2, c_a = mk_block(b1, 2, c_a), c_a.evolve()
b3, c_a = mk_block(b2, 3, c_a), c_a.evolve()
b4, c_b = mk_block(b1.id(), 2, c_b), c_b.evolve()
b5, c_b = mk_block(b4.id(), 3, c_b), c_b.evolve()
b4, c_b = mk_block(b1, 2, c_b), c_b.evolve()
b5, c_b = mk_block(b4, 3, c_b), c_b.evolve()
b6, c_c = mk_block(b4.id(), 3, c_c), c_c.evolve()
b6, c_c = mk_block(b4, 3, c_c), c_c.evolve()
for b in [b1, b2, b3, b4, b5, b6]:
follower.on_block(b)
@ -202,7 +202,7 @@ class TestOrphanedProofs(TestCase):
assert [f.tip() for f in follower.forks] == [b5, b6]
assert follower.unimported_orphans(follower.tip_id()) == [b4, b5, b6]
b7, c_a = mk_block(b3.id(), 4, c_a, orphaned_proofs=[b4, b5, b6]), c_a.evolve()
b7, c_a = mk_block(b3, 4, c_a, orphaned_proofs=[b4, b5, b6]), c_a.evolve()
follower.on_block(b7)
assert follower.tip() == b7
@ -235,16 +235,16 @@ class TestOrphanedProofs(TestCase):
follower = Follower(genesis, config)
b1, c_a = mk_block(genesis.block, 1, c_a), c_a.evolve()
b2, c_a = mk_block(b1.id(), 2, c_a), c_a.evolve()
b3, c_a = mk_block(b2.id(), 3, c_a), c_a.evolve()
b2, c_a = mk_block(b1, 2, c_a), c_a.evolve()
b3, c_a = mk_block(b2, 3, c_a), c_a.evolve()
b4, c_b = mk_block(b3.id(), 4, c_b), c_b.evolve()
b5, c_a = mk_block(b3.id(), 4, c_a), c_a.evolve()
b4, c_b = mk_block(b3, 4, c_b), c_b.evolve()
b5, c_a = mk_block(b3, 4, c_a), c_a.evolve()
b6, c_b = mk_block(b4.id(), 5, c_b, orphaned_proofs=[b5]), c_b.evolve()
b7, c_a = mk_block(b4.id(), 5, c_a, orphaned_proofs=[b5]), c_a.evolve()
b6, c_b = mk_block(b4, 5, c_b, orphaned_proofs=[b5]), c_b.evolve()
b7, c_a = mk_block(b4, 5, c_a, orphaned_proofs=[b5]), c_a.evolve()
b8, c_b = mk_block(b6.id(), 6, c_b, orphaned_proofs=[b7]), c_b.evolve()
b8, c_b = mk_block(b6, 6, c_b, orphaned_proofs=[b7]), c_b.evolve()
for b in [b1, b2, b3, b4, b5]:
follower.on_block(b)

View File

@ -29,13 +29,13 @@ class TestStakeRelativization(TestCase):
# on fork, tip state is not updated
orphan = mk_block(genesis.block, slot=1, coin=c_b)
follower.on_block(orphan)
assert follower.tip_state().block == b1.id()
assert follower.tip_state().block == b1
assert follower.tip_state().leader_count == 1
# after orphan is adopted, leader count should jumpy by 2 (each orphan counts as a leader)
b2 = mk_block(b1.id(), slot=2, coin=c_a.evolve(), orphaned_proofs=[orphan])
b2 = mk_block(b1, slot=2, coin=c_a.evolve(), orphaned_proofs=[orphan])
follower.on_block(b2)
assert follower.tip_state().block == b2.id()
assert follower.tip_state().block == b2
assert follower.tip_state().leader_count == 3
def test_inference_on_empty_genesis_epoch(self):