Start work on full node

This commit is contained in:
Mariano Sorgente 2020-10-28 19:59:17 +09:00 committed by Yostra
parent d893cc6a72
commit 26ed01c4ab
4 changed files with 80 additions and 99 deletions

View File

@ -151,7 +151,7 @@ class Blockchain:
assert block is not None
return block
def is_child_of_peak(self, block: FullBlock) -> bool:
def is_child_of_peak(self, block: UnfinishedBlock) -> bool:
"""
True iff the block is the direct ancestor of the peak
"""

View File

@ -139,10 +139,6 @@ class FullNode:
# uncompact_interval = self.config["send_uncompact_interval"]
# if uncompact_interval > 0:
# self.broadcast_uncompact_task = asyncio.create_task(self.broadcast_uncompact_blocks(uncompact_interval))
#
# for ((_, _), block) in (await self.full_node_store.get_unfinished_blocks()).items():
# if block.height > self.full_node_store.get_unfinished_block_leader()[0]:
# self.full_node_store.set_unfinished_block_leader((block.height, 999999999999))
def _set_global_connections(self, global_connections: PeerConnections):
self.global_connections = global_connections
@ -183,6 +179,7 @@ class FullNode:
pos_info_requests.append(timelord_protocol.ProofOfSpaceInfo(chall, iters))
# Sends our best unfinished block (proof of space) to peer
# TODO(mariano) send all unf blocks
for ((_, iters), block) in sorted(tip_infos, key=lambda t: t[0][1]):
if block.height < self.full_node_store.get_unfinished_block_leader()[0]:
continue
@ -864,10 +861,10 @@ class FullNode:
await asyncio.sleep(uncompact_interval)
@api_request
async def new_unfinished_block(
self, new_unfinished_block: full_node_protocol.NewUnfinishedBlock
async def new_unfinished_sub_block(
self, new_unfinished_sub_block: full_node_protocol.NewUnfinishedSubBlock
) -> OutboundMessageGenerator:
if self.blockchain.contains_block(new_unfinished_block.new_header_hash):
if self.blockchain.contains_block(new_unfinished_sub_block.unfinished_reward_hash):
return
if not self.blockchain.contains_block(new_unfinished_block.previous_header_hash):
return
@ -931,21 +928,21 @@ class FullNode:
yield OutboundMessage(NodeType.FULL_NODE, reject, Delivery.RESPOND)
@api_request
async def respond_unfinished_block(
self, respond_unfinished_block: full_node_protocol.RespondUnfinishedBlock
async def respond_unfinished_sub_block(
self, respond_unfinished_sub_block: full_node_protocol.RespondUnfinishedSubBlock
) -> OutboundMessageGenerator:
"""
We have received an unfinished block, either created by us, or from another peer.
We can validate it and if it's a good block, propagate it to other peers and
timelords.
"""
block = respond_unfinished_block.block
block = respond_unfinished_sub_block.unfinished_sub_block
# Adds the unfinished block to seen, and check if it's seen before, to prevent
# processing it twice
if self.full_node_store.seen_unfinished_block(block.header_hash):
return
if not self.blockchain.is_child_of_head(block):
if not self.blockchain.is_child_of_peak(block):
return
prev_full_block: Optional[FullBlock] = await self.block_store.get_block(block.prev_header_hash)

View File

@ -1,60 +1,52 @@
import logging
import aiosqlite
from typing import Dict, List, Optional, Tuple
from src.types.program import Program
from src.consensus.constants import ConsensusConstants
from src.types.challenge_slot import ChallengeSlot
from src.types.full_block import FullBlock
from src.types.header import HeaderData
from src.types.proof_of_space import ProofOfSpace
from src.types.reward_chain_end_of_slot import RewardChainEndOfSlot, EndOfSlotProofs
from src.types.sized_bytes import bytes32
from src.types.unfinished_block import UnfinishedBlock
from src.util.ints import uint32, uint64
from src.types.vdf import VDFProof, VDFInfo
from src.util.ints import uint32, uint64, uint8
log = logging.getLogger(__name__)
class FullNodeStore:
db: aiosqlite.Connection
# Current estimate of the speed of the network timelords
proof_of_time_estimate_ips: int
# TODO(mariano): replace
# Proof of time heights
proof_of_time_heights: Dict[Tuple[bytes32, uint64], uint32]
# proof_of_time_heights: Dict[Tuple[bytes32, uint64], uint32]
constants: ConsensusConstants
# Our best unfinished block
unfinished_blocks_leader: Tuple[uint32, uint64]
# Blocks which we have created, but don't have proof of space yet, old ones are cleared
# Blocks which we have created, but don't have plot signatures yet
candidate_blocks: Dict[bytes32, UnfinishedBlock]
# Header hashes of unfinished blocks that we have seen recently
seen_unfinished_blocks: set
# Blocks which we have received but our blockchain does not reach, old ones are cleared
disconnected_blocks: Dict[bytes32, FullBlock]
# Unfinished blocks, keyed from reward hash
unfinished_blocks: Dict[bytes32, UnfinishedBlock]
# Finished slots from the peak onwards
finished_slots: List[Tuple[ChallengeSlot, RewardChainEndOfSlot, EndOfSlotProofs]]
# ICPs from the last finished slot onwards
latest_icps: List[Optional[Tuple[VDFInfo, VDFProof]]]
@classmethod
async def create(cls, connection):
async def create(cls, constants: ConsensusConstants):
self = cls()
self.db = connection
await self.db.commit()
self.proof_of_time_estimate_ips = 100000
# TODO(mariano): replace
self.proof_of_time_heights = {}
self.unfinished_blocks_leader = (
uint32(0),
uint64((1 << 64) - 1),
)
self.constants = constants
self.clear_slots_and_icps()
self.unfinished_blocks = {}
self.candidate_blocks = {}
self.seen_unfinished_blocks = set()
self.disconnected_blocks = {}
await self.db.execute(
"CREATE TABLE IF NOT EXISTS unfinished_blocks("
"challenge_hash text,"
"iterations bigint,"
"block blob,"
"height int,"
"PRIMARY KEY (challenge_hash, iterations))"
)
await self.db.commit()
return self
def add_disconnected_block(self, block: FullBlock) -> None:
@ -95,24 +87,11 @@ class FullNodeStore:
except KeyError:
pass
async def add_unfinished_block(self, key: Tuple[bytes32, uint64], block: FullBlock) -> None:
cursor_1 = await self.db.execute(
"INSERT OR REPLACE INTO unfinished_blocks VALUES(?, ?, ?, ?)",
(key[0].hex(), key[1], bytes(block), block.height),
)
await cursor_1.close()
await self.db.commit()
def add_unfinished_block(self, unfinished_block: UnfinishedBlock) -> None:
self.unfinished_blocks[unfinished_block.reward_chain_sub_block.get_hash()] = unfinished_block
async def get_unfinished_block(self, key: Tuple[bytes32, uint64]) -> Optional[FullBlock]:
cursor = await self.db.execute(
"SELECT block from unfinished_blocks WHERE challenge_hash=? AND iterations=?",
(key[0].hex(), key[1]),
)
row = await cursor.fetchone()
await cursor.close()
if row is not None:
return FullBlock.from_bytes(row[0])
return None
def get_unfinished_block(self, partial_reward_hash: bytes32) -> Optional[FullBlock]:
return self.unfinished_blocks.get(partial_reward_hash, None)
def seen_unfinished_block(self, header_hash: bytes32) -> bool:
if header_hash in self.seen_unfinished_blocks:
@ -120,45 +99,56 @@ class FullNodeStore:
self.seen_unfinished_blocks.add(header_hash)
return False
async def clear_seen_unfinished_blocks(self) -> None:
def clear_seen_unfinished_blocks(self) -> None:
self.seen_unfinished_blocks.clear()
async def get_unfinished_blocks(self) -> Dict[Tuple[bytes32, uint64], FullBlock]:
cursor = await self.db.execute("SELECT challenge_hash, iterations, block from unfinished_blocks")
rows = await cursor.fetchall()
await cursor.close()
return {(bytes.fromhex(a), b): FullBlock.from_bytes(c) for a, b, c in rows}
def get_unfinished_blocks(self) -> Dict[bytes32, UnfinishedBlock]:
return self.unfinished_blocks
async def clear_unfinished_blocks_below(self, height: uint32) -> None:
cursor = await self.db.execute("DELETE from unfinished_blocks WHERE height<? ", (height,))
await cursor.close()
await self.db.commit()
def clear_unfinished_blocks_below(self, height: uint32) -> None:
for partial_reward_hash, unfinished_block in self.unfinished_blocks.items():
if unfinished_block.height < height:
del self.unfinished_blocks[partial_reward_hash]
def set_unfinished_block_leader(self, key: Tuple[uint32, uint64]) -> None:
self.unfinished_blocks_leader = key
def remove_unfinished_block(self, partial_reward_hash: bytes32):
if partial_reward_hash in self.unfinished_blocks:
del self.unfinished_blocks[partial_reward_hash]
def get_unfinished_block_leader(self) -> Tuple[uint32, uint64]:
return self.unfinished_blocks_leader
def clear_slots_and_icps(self):
self.finished_slots.clear()
self.clear_icps()
def set_proof_of_time_estimate_ips(self, estimate: int):
self.proof_of_time_estimate_ips = estimate
def new_finished_slot(self, finished_slot: Tuple[ChallengeSlot, RewardChainEndOfSlot, EndOfSlotProofs]):
if len(self.finished_slots) == 0:
self.finished_slots.append(finished_slot)
return
if finished_slot[0].proof_of_space.challenge_hash != self.finished_slots[-1][0].get_hash():
return
self.finished_slots.append(finished_slot)
def get_proof_of_time_estimate_ips(self) -> int:
return self.proof_of_time_estimate_ips
def new_icp(self, challenge_hash: bytes32, index: uint8, vdf_info: VDFInfo, proof: VDFProof):
if len(self.finished_slots) != 0:
assert challenge_hash == self.finished_slots[-1][0].get_hash()
assert index
self.latest_icps[index] = (vdf_info, proof)
def add_proof_of_time_heights(self, challenge_iters: Tuple[bytes32, uint64], height: uint32) -> None:
self.proof_of_time_heights[challenge_iters] = height
def clear_icps(self):
self.latest_icps = [None] * self.constants.NUM_CHECKPOINTS_PER_SLOT
def get_proof_of_time_heights(self, challenge_iters: Tuple[bytes32, uint64]) -> Optional[uint32]:
return self.proof_of_time_heights.get(challenge_iters, None)
def clear_proof_of_time_heights_below(self, height: uint32) -> None:
del_keys: List = []
for key, value in self.proof_of_time_heights.items():
if value < height:
del_keys.append(key)
for key in del_keys:
try:
del self.proof_of_time_heights[key]
except KeyError:
pass
# TODO(mariano)
# def add_proof_of_time_heights(self, challenge_iters: Tuple[bytes32, uint64], height: uint32) -> None:
# self.proof_of_time_heights[challenge_iters] = height
#
# def get_proof_of_time_heights(self, challenge_iters: Tuple[bytes32, uint64]) -> Optional[uint32]:
# return self.proof_of_time_heights.get(challenge_iters, None)
#
# def clear_proof_of_time_heights_below(self, height: uint32) -> None:
# del_keys: List = []
# for key, value in self.proof_of_time_heights.items():
# if value < height:
# del_keys.append(key)
# for key in del_keys:
# try:
# del self.proof_of_time_heights[key]
# except KeyError:
# pass

View File

@ -81,11 +81,6 @@ class TestFullNodeStore:
await db.clear_unfinished_blocks_below(uint32(5))
assert len(await db.get_unfinished_blocks()) == 5
# Set/get unf block leader
assert db.get_unfinished_block_leader() == (0, (1 << 64) - 1)
db.set_unfinished_block_leader(key)
assert db.get_unfinished_block_leader() == key
assert db.get_disconnected_block(blocks[0].prev_header_hash) is None
# Disconnected blocks
for block in blocks:
@ -111,7 +106,6 @@ class TestFullNodeStore:
# Different database should have different data
db_3 = await FullNodeStore.create(connection_3)
assert db_3.get_unfinished_block_leader() == (0, (1 << 64) - 1)
await connection.close()
await connection_2.close()