mirror of
https://github.com/logos-blockchain/logos-blockchain-specs.git
synced 2026-01-11 17:43:12 +00:00
cryptarchia/ghost: prep for move to weight based fork choice
This commit is contained in:
parent
5434fcb315
commit
bf5ef98174
@ -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():
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
|
||||
@ -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],
|
||||
)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user