add storage

This commit is contained in:
Youngjoon Lee 2025-02-28 14:45:23 +09:00
parent 72b93bb8e9
commit 1d38073f6a
No known key found for this signature in database
GPG Key ID: 303963A54A81DD4D
5 changed files with 50 additions and 34 deletions

View File

@ -1,15 +1,15 @@
from typing import TypeAlias, List, Dict
from hashlib import sha256, blake2b
from math import floor
from copy import deepcopy
import itertools
import functools
from dataclasses import dataclass, field, replace
import itertools
import logging
from collections import defaultdict
from copy import deepcopy
from dataclasses import dataclass, field, replace
from hashlib import blake2b, sha256
from math import floor
from typing import Dict, Generator, List, TypeAlias
import numpy as np
from sortedcontainers import SortedDict
logger = logging.getLogger(__name__)
@ -124,6 +124,9 @@ class Slot:
def __lt__(self, other):
return self.absolute_slot < other.absolute_slot
def __hash__(self):
return hash(self.absolute_slot)
@dataclass
class Coin:
@ -367,6 +370,7 @@ class Follower:
self.genesis_state = genesis_state
self.ledger_state = {genesis_state.block.id(): genesis_state.copy()}
self.epoch_state = {}
self.block_storage = BlockStorage()
def validate_header(self, block: BlockHeader) -> bool:
# TODO: verify blocks are not in the 'future'
@ -471,6 +475,8 @@ class Follower:
self.forks.remove(new_tip)
self.local_chain = new_tip
self.block_storage.add_block(block)
def unimported_orphans(self) -> list[BlockHeader]:
"""
Returns all unimported orphans w.r.t. the given tip's state.
@ -486,6 +492,8 @@ class Follower:
for block_state in chain_suffix(fork, fork_depth, self.ledger_state):
b = block_state.block
if b.leader_proof.nullifier not in tip_state.nullifiers:
# YJ: Why do this? This function is used only in tests.
# Let's try to remove this line and run tests.
tip_state.nullifiers.add(b.leader_proof.nullifier)
orphans += [b]
@ -593,6 +601,26 @@ class Follower:
return int(prev_epoch.inferred_total_active_stake - h * blocks_per_slot_err)
class BlockStorage:
def __init__(self):
self.blocks: dict[Id, BlockHeader] = dict()
self.ids_by_slot: SortedDict[Slot, set[Id]] = SortedDict()
def add_block(self, block: BlockHeader):
id = block.id()
self.blocks[id] = block
if block.slot not in self.ids_by_slot:
self.ids_by_slot[block.slot] = set()
self.ids_by_slot[block.slot].add(id)
def blocks_by_range(
self, from_slot: Slot, to_slot: Slot
) -> Generator[BlockHeader, None, None]:
for slot in self.ids_by_slot.irange(from_slot, to_slot, inclusive=(True, True)):
for id in self.ids_by_slot[slot]:
yield self.blocks[id]
def phi(f: float, alpha: float) -> float:
"""
params:

View File

@ -1,7 +1,6 @@
from collections import defaultdict
from typing import Generator
from cryptarchia.cryptarchia import BlockHeader, Follower, Id, Slot
from cryptarchia.cryptarchia import Follower, Id, Slot
SLOT_TOLERANCE = 1
@ -15,7 +14,7 @@ def full_sync(local: Follower, remotes: list[Follower], start_slot: Slot):
def range_sync(local: Follower, remote: Follower, from_slot: Slot, to_slot: Slot):
for block in request_blocks_by_range(remote, from_slot, to_slot):
for block in remote.block_storage.blocks_by_range(from_slot, to_slot):
local.on_block(block)
@ -27,18 +26,3 @@ def group_targets(
if target.tip().slot.absolute_slot - start_slot.absolute_slot > SLOT_TOLERANCE:
groups[target.tip_id()].append(target)
return groups
def request_blocks_by_range(
remote: Follower, from_slot: Slot, to_slot: Slot
) -> Generator[BlockHeader, None, None]:
# TODO: Optimize this by keeping blocks by slot in the Follower
blocks_by_slot: dict[int, list[BlockHeader]] = defaultdict(list)
for ledger_state in remote.ledger_state.values():
if from_slot <= ledger_state.block.slot <= to_slot:
blocks_by_slot[ledger_state.block.slot.absolute_slot].append(
ledger_state.block
)
for slot in range(from_slot.absolute_slot, to_slot.absolute_slot + 1):
for block in blocks_by_slot[slot]:
yield block

View File

@ -75,8 +75,12 @@ class TestFullSync(TestCase):
new_follower = Follower(genesis, config)
full_sync(new_follower, [follower], genesis.block.slot)
assert new_follower.tip() == follower.tip()
assert new_follower.forks == follower.forks
# Since the length of two forks is the same, the tip is chosen
# depending on the order of block delivery.
assert new_follower.tip() in [b2, b4]
expected_forks = [b2.id(), b4.id()]
expected_forks.remove(new_follower.tip_id())
assert new_follower.forks == expected_forks
def test_continue_syncing_forks(self):
# b0 - b1 - b2 == tip
@ -137,7 +141,7 @@ class TestFullSync(TestCase):
b0, c_b = mk_block(genesis.block, 1, c_b), c_b.evolve()
b3, c_b = mk_block(b0, 2, c_b), c_b.evolve()
b4, c_b = mk_block(b3, 3, c_b), c_b.evolve()
b5, c_b = mk_block(b4, 3, c_b), c_b.evolve()
b5, c_b = mk_block(b4, 4, c_b), c_b.evolve()
for b in [b0, b3, b4, b5]:
peer_1.on_block(b)
assert peer_1.tip() == b5

View File

@ -2,9 +2,8 @@ from unittest import TestCase
import numpy as np
from .cryptarchia import Follower, Coin, iter_chain
from .test_common import mk_config, mk_block, mk_genesis_state
from .cryptarchia import Coin, Follower, iter_chain
from .test_common import mk_block, mk_config, mk_genesis_state
class TestLedgerStateUpdate(TestCase):
@ -139,7 +138,7 @@ class TestLedgerStateUpdate(TestCase):
follower = Follower(genesis, config)
# We assume an epoch length of 10 slots in this test.
# We assume an epoch length of 20 slots in this test.
assert config.epoch_length == 20, f"epoch len: {config.epoch_length}"
# ---- EPOCH 0 ----
@ -164,14 +163,14 @@ class TestLedgerStateUpdate(TestCase):
# ---- EPOCH 2 ----
# when trying to propose a block for epoch 2, the stake distribution snapshot should be taken
# at the end of epoch 0, i.e. slot 9
# at the end of epoch 0, i.e. slot 19
# 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, 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
# then we add the coin to "spendable commitments" associated with slot 19
follower.ledger_state[block_2.id()].commitments_spend.add(
Coin(sk=4, value=100).commitment()
)

View File

@ -13,3 +13,4 @@ portalocker==2.8.2 # portable file locking
keum==0.2.0 # for CL's use of more obscure curves
poseidon-hash==0.1.4 # used as the algebraic hash in CL
hypothesis==6.103.0
sortedcontainers==2.4.0