persist should work

This commit is contained in:
Alex Wice 2019-11-13 16:25:42 +09:00
parent 1aab0de425
commit 3c3df3776e
13 changed files with 225 additions and 173 deletions

View file

@ -17,6 +17,10 @@ pip install -r requirements.txt
cd lib/chiavdf/fast_vdf cd lib/chiavdf/fast_vdf
# Install libgmp, libboost, and libflint, and then run the following # Install libgmp, libboost, and libflint, and then run the following
sh install.sh sh install.sh
# Install mongoDB from https://docs.mongodb.com/manual/administration/install-community/
# Then, ensure that the mongod service is running.
mongod --dbpath ./db/
``` ```
### Generate keys ### Generate keys

View file

@ -1,7 +1,9 @@
asyncssh==2.0.0 asyncssh==2.0.0
atomicwrites==1.3.0 atomicwrites==1.3.0
attrs==19.3.0 attrs==19.3.0
autoflake==1.3.1
bitstring==3.1.6 bitstring==3.1.6
black==19.10b0
blspy==0.1.12 blspy==0.1.12
cbor2==4.1.2 cbor2==4.1.2
cffi==1.13.1 cffi==1.13.1
@ -10,6 +12,7 @@ cryptography==2.8
entrypoints==0.3 entrypoints==0.3
flake8==3.7.9 flake8==3.7.9
importlib-metadata==0.23 importlib-metadata==0.23
isort==4.3.21
Mako==1.1.0 Mako==1.1.0
MarkupSafe==1.1.1 MarkupSafe==1.1.1
mccabe==0.6.1 mccabe==0.6.1

2
scripts/stop_all_servers.sh Executable file
View file

@ -0,0 +1,2 @@
ps -e | grep python | awk '{print $1}' | xargs -L1 kill
ps -e | grep "vdf_server" | awk '{print $1}' | xargs -L1 kill

View file

@ -1,8 +1,8 @@
#!/usr/bin/python3 #!/usr/bin/python3
from setuptools import setup from setuptools import setup
dependencies = ['blspy', 'cbor2', 'pyyaml', 'asyncssh'] dependencies = ["blspy", "cbor2", "pyyaml", "asyncssh"]
dev_dependencies = ['pytest', 'flake8', 'mypy', 'pytest-asyncio'] dev_dependencies = ["pytest", "flake8", "mypy", "isort", "autoflake", "black", "pytest-asyncio"]
setup( setup(
name='chiablockchain', name='chiablockchain',
@ -14,6 +14,6 @@ setup(
python_requires='>=3.7, <4', python_requires='>=3.7, <4',
keywords='chia blockchain node', keywords='chia blockchain node',
install_requires=dependencies + dev_dependencies, install_requires=dependencies + dev_dependencies,
long_description=open('README.md').read(), long_description=open("README.md").read(),
zip_safe=False, zip_safe=False,
) )

View file

@ -1,26 +1,39 @@
from abc import ABC
from typing import Optional, Tuple, AsyncGenerator
import asyncio import asyncio
import motor.motor_asyncio as maio from abc import ABC
from src.types.proof_of_space import ProofOfSpace from motor import motor_asyncio
from src.types.header import HeaderData from src.types.header import HeaderData
from src.types.header_block import HeaderBlock from src.types.header_block import HeaderBlock
from src.types.body import Body from src.types.body import Body
from typing import AsyncGenerator, Dict, List, Optional, Tuple
from bson.binary import Binary
from bson.codec_options import CodecOptions, TypeRegistry
from src.types.full_block import FullBlock from src.types.full_block import FullBlock
from src.types.proof_of_space import ProofOfSpace
from src.types.sized_bytes import bytes32 from src.types.sized_bytes import bytes32
from src.types.trunk_block import TrunkBlock
from src.util.ints import uint32, uint64 from src.util.ints import uint32, uint64
from src.db.codecs import codec_options from src.util.streamable import Streamable
import subprocess
class Database(ABC): class Database(ABC):
# All databases must subclass this so that there's one client # All databases must subclass this so that there's one client
# Ensure mongod service is running
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
subprocess.run("mongod", stdout=subprocess.PIPE) # Ensure mongod service is running client = motor_asyncio.AsyncIOMotorClient(
client = maio.AsyncIOMotorClient("mongodb://localhost:27017/", io_loop=loop) "mongodb://localhost:27017/", io_loop=loop
)
def __init__(self, db_name): def __init__(self, db_name):
# self.lock = asyncio.Lock() self.db = Database.client.get_database(
self.db = Database.client.get_database(db_name, codec_options=codec_options) db_name,
codec_options=CodecOptions(
type_registry=TypeRegistry(
fallback_encoder=lambda obj: Binary(bytes(obj))
if isinstance(obj, Streamable)
else obj
)
),
)
class FullNodeStore(Database): class FullNodeStore(Database):
@ -28,24 +41,22 @@ class FullNodeStore(Database):
super().__init__(db_name) super().__init__(db_name)
# Stored on database # Stored on database
getc = self.db.get_collection self.full_blocks = self.db.get_collection("full_blocks")
self.full_blocks = getc("full_blocks") self.potential_heads = self.db.get_collection("potential_heads")
self.potential_heads = getc("potential_heads") self.potential_trunks = self.db.get_collection("potential_trunks")
self.potential_trunks = getc("potential_trunks") self.potential_blocks = self.db.get_collection("potential_blocks")
self.potential_blocks = getc("potential_blocks") self.candidate_blocks = self.db.get_collection("candidate_blocks")
self.candidate_blocks = getc("candidate_blocks") self.unfinished_blocks = self.db.get_collection("unfinished_blocks")
self.unfinished_blocks = getc("unfinished_blocks") self.sync_mode = self.db.get_collection("sync_mode")
self.unfinished_blocks_leader = getc("unfinished_blocks_leader")
self.sync_mode = getc("sync_mode")
# Stored on memory # Stored in memory
self.potential_blocks_received: Dict[uint32, Event] = {} self.unfinished_blocks_leader = None
self.potential_blocks_received: Dict[uint32, asyncio.Event] = {}
self.proof_of_time_estimate_ips: uint64 = uint64(3000) self.proof_of_time_estimate_ips: uint64 = uint64(3000)
# Lock # Lock
self.lock = asyncio.Lock() # external self.lock = asyncio.Lock() # external
async def _clear_database(self): async def _clear_database(self):
await self.full_blocks.drop() await self.full_blocks.drop()
await self.potential_heads.drop() await self.potential_heads.drop()
@ -53,7 +64,6 @@ class FullNodeStore(Database):
await self.potential_blocks.drop() await self.potential_blocks.drop()
await self.candidate_blocks.drop() await self.candidate_blocks.drop()
await self.unfinished_blocks.drop() await self.unfinished_blocks.drop()
await self.unfinished_blocks_leader.drop()
await self.sync_mode.drop() await self.sync_mode.drop()
async def save_block(self, block: FullBlock) -> None: async def save_block(self, block: FullBlock) -> None:
@ -68,6 +78,7 @@ class FullNodeStore(Database):
query = await self.full_blocks.find_one({"_id": header_hash}) query = await self.full_blocks.find_one({"_id": header_hash})
if query is not None: if query is not None:
return FullBlock.from_bytes(query["block"]) return FullBlock.from_bytes(query["block"])
return None
async def get_blocks(self) -> AsyncGenerator[FullBlock, None]: async def get_blocks(self) -> AsyncGenerator[FullBlock, None]:
async for query in self.full_blocks.find({}): async for query in self.full_blocks.find({}):
@ -95,13 +106,15 @@ class FullNodeStore(Database):
async def get_potential_heads_number(self) -> int: async def get_potential_heads_number(self) -> int:
return await self.potential_heads.count_documents({}) return await self.potential_heads.count_documents({})
async def get_potential_heads_tuples(self) -> AsyncGenerator[Tuple[bytes32, FullBlock], None]: async def get_potential_heads_tuples(self) -> List[Tuple[bytes32, FullBlock]]:
ans = []
async for query in self.potential_heads.find({}): async for query in self.potential_heads.find({}):
if query and "block" in query: if query and "block" in query:
block = FullBlock.from_bytes(query["block"]) block = FullBlock.from_bytes(query["block"])
else: else:
block = None block = None
yield bytes32(query["_id"]), block ans.append((bytes32(query["_id"]), block))
return ans
async def add_potential_head( async def add_potential_head(
self, header_hash: bytes32, block: Optional[FullBlock] = None self, header_hash: bytes32, block: Optional[FullBlock] = None
@ -184,86 +197,14 @@ class FullNodeStore(Database):
query = await self.unfinished_blocks.find_one({"_id": code}) query = await self.unfinished_blocks.find_one({"_id": code})
return FullBlock.from_bytes(query["block"]) if query else None return FullBlock.from_bytes(query["block"]) if query else None
async def set_unfinished_block_leader(self, key: Tuple[bytes32, uint64]) -> None: def set_unfinished_block_leader(self, key: Tuple[bytes32, uint64]) -> None:
await self.unfinished_blocks_leader.find_one_and_update( self.unfinished_blocks_leader = key
{"_id": 0},
{"$set": {"_id": 0, "header": key[0], "iters": key[1]}},
upsert=True,
)
async def get_unfinished_block_leader(self) -> Optional[Tuple[bytes32, uint64]]: def get_unfinished_block_leader(self) -> Optional[Tuple[bytes32, uint64]]:
query = await self.unfinished_blocks_leader.find_one({"_id": 0}) return self.unfinished_blocks_leader
return (query["header"], query["iters"]) if query else None
async def set_proof_of_time_estimate_ips(self, estimate: uint64): async def set_proof_of_time_estimate_ips(self, estimate: uint64):
self.proof_of_time_estimate_ips = estimate self.proof_of_time_estimate_ips = estimate
async def get_proof_of_time_estimate_ips(self) -> uint64: async def get_proof_of_time_estimate_ips(self) -> uint64:
return self.proof_of_time_estimate_ips return self.proof_of_time_estimate_ips
# TODO: remove below when tested better
if __name__ == "__main__":
async def tests():
print("started testing")
db = FullNodeStore("test3")
await db._clear_database()
from src.consensus.constants import constants
genesis = FullBlock.from_bytes(constants["GENESIS_BLOCK"])
# Save/get block
await db.save_block(genesis)
assert genesis == await db.get_block(genesis.header_hash)
# Save/get sync
for sync_mode in (False, True):
await db.set_sync_mode(sync_mode)
assert sync_mode == await db.get_sync_mode()
# clear sync info
await db.clear_sync_info()
assert await db.get_potential_heads_number() == 0
# add/get potential head, get potential heads num
await db.add_potential_head(genesis.header_hash)
assert await db.get_potential_heads_number() == 1
await db.add_potential_head(genesis.header_hash, genesis)
assert await db.get_potential_heads_number() == 1
assert genesis == await db.get_potential_head(genesis.header_hash)
# add/get potential header
header = genesis.header_block
await db.add_potential_header(header)
assert await db.get_potential_header(genesis.height) == header
# Add potential block
await db.add_potential_block(genesis)
assert genesis == await db.get_potential_block(uint32(0))
# Add/get candidate block
assert await db.get_candidate_block(0) is None
partial = (
genesis.body,
genesis.header_block.header.data,
genesis.header_block.proof_of_space,
)
await db.add_candidate_block(genesis.header_hash, *partial)
assert await db.get_candidate_block(genesis.header_hash) == partial
# Add/get unfinished block
key = (genesis.header_hash, uint64(1000))
assert await db.get_unfinished_block(key) is None
await db.add_unfinished_block(key, genesis)
assert await db.get_unfinished_block(key) == genesis
# Set/get unf block leader
assert await db.get_unfinished_block_leader() is None
await db.set_unfinished_block_leader(key)
assert await db.get_unfinished_block_leader() == key
print("done testing")
#Database.loop.run_until_complete(tests())

View file

View file

@ -1,15 +0,0 @@
from bson.binary import Binary
from src.util.streamable import Streamable
from bson.codec_options import TypeRegistry
from bson.codec_options import CodecOptions
def fallback_encoder(obj):
if isinstance(obj, Streamable):
return Binary(bytes(obj))
return obj
codec_options = CodecOptions(
type_registry=TypeRegistry(fallback_encoder=fallback_encoder)
)

View file

@ -1,13 +1,9 @@
from typing import Callable, List, Optional
import asyncio import asyncio
import logging import logging
from typing import Callable, List, Optional
import asyncssh import asyncssh
from prompt_toolkit.layout.dimension import D
from prompt_toolkit.contrib.ssh import PromptToolkitSSHServer
from prompt_toolkit.key_binding.bindings.focus import (
focus_next,
focus_previous,
)
from prompt_toolkit import Application from prompt_toolkit import Application
from prompt_toolkit.styles import Style from prompt_toolkit.styles import Style
from prompt_toolkit.layout.containers import VSplit, HSplit, Window from prompt_toolkit.layout.containers import VSplit, HSplit, Window
@ -122,14 +118,14 @@ class FullNodeUI:
def setup_keybindings(self) -> KeyBindings: def setup_keybindings(self) -> KeyBindings:
kb = KeyBindings() kb = KeyBindings()
kb.add('tab')(focus_next) kb.add("tab")(focus_next)
kb.add('s-tab')(focus_previous) kb.add("s-tab")(focus_previous)
kb.add('down')(focus_next) kb.add("down")(focus_next)
kb.add('up')(focus_previous) kb.add("up")(focus_previous)
kb.add('right')(focus_next) kb.add("right")(focus_next)
kb.add('left')(focus_previous) kb.add("left")(focus_previous)
@kb.add('c-c') @kb.add("c-c")
def exit_(event): def exit_(event):
self.close() self.close()
return kb return kb
@ -148,7 +144,7 @@ class FullNodeUI:
self.ips_label = TextArea(focusable=False, height=1) self.ips_label = TextArea(focusable=False, height=1)
self.total_iters_label = TextArea(focusable=False, height=2) self.total_iters_label = TextArea(focusable=False, height=2)
self.con_rows = [] self.con_rows = []
self.connections_msg = Label(text=f'Connections') self.connections_msg = Label(text=f"Connections")
self.connection_rows_vsplit = Window() self.connection_rows_vsplit = Window()
self.add_connection_msg = Label(text=f'Add a connection ip:port') self.add_connection_msg = Label(text=f'Add a connection ip:port')
self.add_connection_field = TextArea(height=1, prompt='>>> ', style='class:input-field', self.add_connection_field = TextArea(height=1, prompt='>>> ', style='class:input-field',
@ -173,11 +169,11 @@ class FullNodeUI:
self.challenge_msg = Label(text=f'Block Header') self.challenge_msg = Label(text=f'Block Header')
self.challenge = TextArea(focusable=False) self.challenge = TextArea(focusable=False)
body = HSplit([self.loading_msg, self.server_msg], body = HSplit([self.loading_msg, self.server_msg], height=D(), width=D())
height=D(), width=D())
self.content = Frame(title="Chia Full Node", body=body) self.content = Frame(title="Chia Full Node", body=body)
self.layout = Layout(VSplit([self.content], height=D(), width=D())) self.layout = Layout(VSplit([self.content], height=D(), width=D()))
def change_route_handler(self, route): def change_route_handler(self, route):
def change_route(): def change_route():
self.prev_route = self.route self.prev_route = self.route
@ -223,7 +219,9 @@ class FullNodeUI:
added_blocks.append(max_block) added_blocks.append(max_block)
heads.remove(max_block) heads.remove(max_block)
async with self.store.lock: async with self.store.lock:
prev: Optional[FullBlock] = await self.store.get_block(max_block.prev_header_hash) prev: Optional[FullBlock] = await self.store.get_block(
max_block.prev_header_hash
)
if prev is not None: if prev is not None:
heads.append(prev.header_block) heads.append(prev.header_block)
return added_blocks return added_blocks
@ -236,7 +234,6 @@ class FullNodeUI:
labels = [row.children[0].content.text() for row in self.con_rows] labels = [row.children[0].content.text() for row in self.con_rows]
if con_str not in labels: if con_str not in labels:
con_label = Label(text=con_str) con_label = Label(text=con_str)
def disconnect(): def disconnect():
con.close() con.close()
self.layout.focus(self.quit_button) self.layout.focus(self.quit_button)
@ -245,7 +242,9 @@ class FullNodeUI:
row = VSplit([con_label, disconnect_button]) row = VSplit([con_label, disconnect_button])
self.con_rows.append(row) self.con_rows.append(row)
new_con_rows = [row for row in self.con_rows if row.children[0].content.text() in con_strs] new_con_rows = [
row for row in self.con_rows if row.children[0].content.text() in con_strs
]
if new_con_rows != self.con_rows: if new_con_rows != self.con_rows:
self.con_rows = new_con_rows self.con_rows = new_con_rows
if len(self.con_rows) > 0: if len(self.con_rows) > 0:
@ -258,8 +257,8 @@ class FullNodeUI:
else: else:
new_con_rows = Window(width=D(), height=0) new_con_rows = Window(width=D(), height=0)
async with self.store.lock(): async with self.store.lock:
if (await self.store.get_sync_mode()): if await self.store.get_sync_mode():
max_height = -1 max_height = -1
for _, block in await self.store.get_potential_heads_tuples(): for _, block in await self.store.get_potential_heads_tuples():
if block.height > max_height: if block.height > max_height:
@ -274,10 +273,14 @@ class FullNodeUI:
heads: List[HeaderBlock] = self.blockchain.get_current_tips() heads: List[HeaderBlock] = self.blockchain.get_current_tips()
lca_block: FullBlock = self.blockchain.lca_block lca_block: FullBlock = self.blockchain.lca_block
if lca_block.height > 0: if lca_block.height > 0:
difficulty = await self.blockchain.get_next_difficulty(lca_block.prev_header_hash) difficulty = await self.blockchain.get_next_difficulty(
lca_block.prev_header_hash
)
ips = await self.blockchain.get_next_ips(lca_block.prev_header_hash) ips = await self.blockchain.get_next_ips(lca_block.prev_header_hash)
else: else:
difficulty = await self.blockchain.get_next_difficulty(lca_block.header_hash) difficulty = await self.blockchain.get_next_difficulty(
lca_block.header_hash
)
ips = await self.blockchain.get_next_ips(lca_block.header_hash) ips = await self.blockchain.get_next_ips(lca_block.header_hash)
total_iters = lca_block.header_block.challenge.total_iters total_iters = lca_block.header_block.challenge.total_iters
latest_blocks: List[HeaderBlock] = await self.get_latest_blocks(heads) latest_blocks: List[HeaderBlock] = await self.get_latest_blocks(heads)
@ -287,12 +290,17 @@ class FullNodeUI:
self.latest_blocks_labels[i].text = ( self.latest_blocks_labels[i].text = (
f"{b.height}:{b.header_hash}" f"{b.height}:{b.header_hash}"
f" {'LCA' if b.header_hash == lca_block.header_hash else ''}" f" {'LCA' if b.header_hash == lca_block.header_hash else ''}"
f" {'HEAD' if b.header_hash in [h.header_hash for h in heads] else ''}") f" {'HEAD' if b.header_hash in [h.header_hash for h in heads] else ''}"
self.latest_blocks_labels[i].handler = self.change_route_handler(f"block/{b.header_hash}") )
self.latest_blocks_labels[i].handler = self.change_route_handler(
f"block/{b.header_hash}"
)
new_labels.append(self.latest_blocks_labels[i]) new_labels.append(self.latest_blocks_labels[i])
self.lca_label.text = f"Current least common ancestor {lca_block.header_hash} height {lca_block.height}" self.lca_label.text = f"Current least common ancestor {lca_block.header_hash} height {lca_block.height}"
self.current_heads_label.text = "Heights of heads: " + str([h.height for h in heads]) self.current_heads_label.text = "Heights of heads: " + str(
[h.height for h in heads]
)
self.difficulty_label.text = f"Current difficuty: {difficulty}" self.difficulty_label.text = f"Current difficuty: {difficulty}"
self.ips_label.text = f"Current VDF iterations per second: {ips}" self.ips_label.text = f"Current VDF iterations per second: {ips}"
self.total_iters_label.text = f"Total iterations since genesis: {total_iters}" self.total_iters_label.text = f"Total iterations since genesis: {total_iters}"
@ -320,7 +328,9 @@ class FullNodeUI:
async def draw_block(self): async def draw_block(self):
block_hash: str = self.route.split("block/")[1] block_hash: str = self.route.split("block/")[1]
async with self.store.lock: async with self.store.lock:
block: Optional[FullBlock] = await self.store.get_block(bytes32(bytes.fromhex(block_hash))) block: Optional[FullBlock] = await self.store.get_block(
bytes32(bytes.fromhex(block_hash))
)
if block is not None: if block is not None:
self.block_msg.text = f"Block {str(block.header_hash)}" self.block_msg.text = f"Block {str(block.header_hash)}"
if self.block_label.text != str(block): if self.block_label.text != str(block):

View file

@ -1,9 +1,13 @@
import time
import pytest
import asyncio import asyncio
from typing import Dict, Any import time
from typing import Any, Dict
import pytest
from blspy import PrivateKey from blspy import PrivateKey
from src.blockchain import Blockchain, ReceiveBlockResult
from src.consensus.constants import constants from src.consensus.constants import constants
from src.types.coinbase import CoinbaseInfo from src.types.coinbase import CoinbaseInfo
from src.types.body import Body from src.types.body import Body
from src.types.proof_of_space import ProofOfSpace from src.types.proof_of_space import ProofOfSpace
@ -14,8 +18,8 @@ from src.types.header import HeaderData
from src.blockchain import Blockchain, ReceiveBlockResult from src.blockchain import Blockchain, ReceiveBlockResult
from src.db.database import FullNodeStore from src.db.database import FullNodeStore
from src.util.ints import uint64, uint32 from src.util.ints import uint64, uint32
from tests.block_tools import BlockTools
from tests.block_tools import BlockTools
bt = BlockTools() bt = BlockTools()
@ -27,10 +31,12 @@ test_constants: Dict[str, Any] = {
"DIFFICULTY_FACTOR": 3, "DIFFICULTY_FACTOR": 3,
"DIFFICULTY_EPOCH": 12, # The number of blocks per epoch "DIFFICULTY_EPOCH": 12, # The number of blocks per epoch
"DIFFICULTY_WARP_FACTOR": 4, # DELAY divides EPOCH in order to warp efficiently. "DIFFICULTY_WARP_FACTOR": 4, # DELAY divides EPOCH in order to warp efficiently.
"DIFFICULTY_DELAY": 3 # EPOCH / WARP_FACTOR "DIFFICULTY_DELAY": 3, # EPOCH / WARP_FACTOR
} }
test_constants["GENESIS_BLOCK"] = bytes(bt.create_genesis_block(test_constants, bytes([0]*32), b'0')) test_constants["GENESIS_BLOCK"] = bytes(
#test_constants["GENESIS_BLOCK"] = b'\x15N3\xd3\xf9H\xc2K\x96\xfe\xf2f\xa2\xbf\x87\x0e\x0f,\xd0\xd4\x0f6s\xb1".\\\xf5\x8a\xb4\x03\x84\x8e\xf9\xbb\xa1\xca\xdef3:\xe4?\x0c\xe5\xc6\x12\x80\x17\xd2\xcc\xd7\xb4m\x94\xb7V\x959\xed4\x89\x04b\x08\x07^\xca`\x8f#%\xe9\x9c\x9d\x86y\x10\x96W\x9d\xce\xc1\x15r\x97\x91U\n\x11<\xdf\xb2\xfc\xfb<\x13\x00\x00\x00\x98\xf4\x88\xcb\xb2MYo]\xaf \xd8a>\x06\xfe\xc8F\x8d\x15\x90\x15\xbb\x04\xd48\x10\xc6\xd8b\x82\x88\x7fx<\xe5\xe6\x8b\x8f\x84\xdd\x1cU"\x83\xfb7\x9d`\xb0I\xb3\xbe;bvE\xc6\x92\xdd\xbe\x988\xe9y;\xc6.\xa1\xce\x94\xdc\xd8\xab\xaf\xba\x8f\xd8r\x8br\xc8\xa0\xac\xc0\xe9T\x87\x08\x08\x8b#-\xb6o\xf0\x1f\x0bzv\xb3\x81\x1a\xd4\xf7\x01\xdf\xc5A\x11\xe0\x0c\xc0\x87\xa6\xc2v\xbbR\xc4{"\xa5\xe5\xe0bx7\xfa\n\xae\xea\xfe\x02\xac\xef\xec\xd1\xc2\xc55\x06{\xe1\x0c\xb2\x99q\xd7\xd8\xcb\x97\x86\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xeb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Y\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x007\x03\x00\x00\x004\x00\x8c\xff\xc3\x00\x00\x00\x00\x00\x00\x01T\x00>\xff\xe3\x00\x80\x00[\x00\x00\x00\x00\x00\x00\x05R\x00\x08\x00\x05\x00j\xff\xfd\x00\x00\x00\x00\x00\x00\x17\xf0\x00j\xff\x99\x00j\x00\x03\x01\x03\xa1\xde8\x0f\xb75VB\xf6"`\x94\xc7\x0b\xaa\x1f\xa2Nv\x8a\xf9\xc9\x9a>\x13\xa3a\xc8\x0c\xcb?\x968\xc7\xeb\xc3\x10a\x1a\xa7\xfb\x85\xa7iu\x14`\x8f\x90\x16o\x97\xd5\t\xa4,\xe5\xed\xe1\x15\x86<\x9d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x1f\xeb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]\xbf\xd7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xa1\xde8\x0f\xb75VB\xf6"`\x94\xc7\x0b\xaa\x1f\xa2Nv\x8a\xf9\xc9\x9a>\x13\xa3a\xc8\x0c\xcb?\x13\x16J\xe5\xfc\xa9\x06\xe8A\xe9\xc0Ql\xfb\xaeF\xcd\xd6\xa7\x8ei\xc4\xfa\xd4i\x84\xee\xc9\xe2\xaa\xa4f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00OB!\x81)\xf0l\xbcg\xa3^\xef\x0e\xfc\xb7\x02\x80\xe4\xa9NO\x89\xa0\t\xc3C\xd9\xda\xff\xd7\t\xeebfC&8\x9c+n$\x00\xa4\xe85\x19\xb0\xf6\x18\xa1\xeeR\xae\xec \x82k\xe0v@;\x1c\xc14PMh\xfb\xe3\x1c\xbf\x84O\xcd\xbc\xc4\xb8\xeabz`\xf7\x06;\xf6q\x8b,\x18\tf~\xd1\x11l#\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x8b)\xaa\x96x8\xd76J\xa6\x8b[\x98\t\xe0\\\xe3^7qD\x8c\xf5q\x08\xf2\xa2\xc9\xb03mvU\x1a\xe2\x181\x88\xfe\t\x03?\x12\xadj\x9d\xe8K\xb8!\xee\xe7e8\x82\xfb$\xf0Y\xfaJ\x10\x1f\x1a\xe5\xe9\xa8\xbb\xea\x87\xfc\xb12y\x94\x8d,\x16\xe4C\x02\xba\xe6\xac\x94{\xc4c\x07(\xb8\xeb\xab\xe3\xcfy{6\x98\t\xf4\x8fm\xd62\x85\x87\xb0\x03f\x01B]\xe3\xc6\x13l6\x8d\x0e\x18\xc64%\x97\x1a\xa6\xf4\x8b)\xaa\x96x8\xd76J\xa6\x8b[\x98\t\xe0\\\xe3^7qD\x8c\xf5q\x08\xf2\xa2\xc9\xb03mv\x00\x00\x00\x00\x00\x00\x00\x00\x00_\xec\xebf\xff\xc8o8\xd9Rxlmily\xc2\xdb\xc29\xddN\x91\xb4g)\xd7:\'\xfbW\xe9' bt.create_genesis_block(test_constants, bytes([0] * 32), b"0")
)
# test_constants["GENESIS_BLOCK"] = b'\x15N3\xd3\xf9H\xc2K\x96\xfe\xf2f\xa2\xbf\x87\x0e\x0f,\xd0\xd4\x0f6s\xb1".\\\xf5\x8a\xb4\x03\x84\x8e\xf9\xbb\xa1\xca\xdef3:\xe4?\x0c\xe5\xc6\x12\x80\x17\xd2\xcc\xd7\xb4m\x94\xb7V\x959\xed4\x89\x04b\x08\x07^\xca`\x8f#%\xe9\x9c\x9d\x86y\x10\x96W\x9d\xce\xc1\x15r\x97\x91U\n\x11<\xdf\xb2\xfc\xfb<\x13\x00\x00\x00\x98\xf4\x88\xcb\xb2MYo]\xaf \xd8a>\x06\xfe\xc8F\x8d\x15\x90\x15\xbb\x04\xd48\x10\xc6\xd8b\x82\x88\x7fx<\xe5\xe6\x8b\x8f\x84\xdd\x1cU"\x83\xfb7\x9d`\xb0I\xb3\xbe;bvE\xc6\x92\xdd\xbe\x988\xe9y;\xc6.\xa1\xce\x94\xdc\xd8\xab\xaf\xba\x8f\xd8r\x8br\xc8\xa0\xac\xc0\xe9T\x87\x08\x08\x8b#-\xb6o\xf0\x1f\x0bzv\xb3\x81\x1a\xd4\xf7\x01\xdf\xc5A\x11\xe0\x0c\xc0\x87\xa6\xc2v\xbbR\xc4{"\xa5\xe5\xe0bx7\xfa\n\xae\xea\xfe\x02\xac\xef\xec\xd1\xc2\xc55\x06{\xe1\x0c\xb2\x99q\xd7\xd8\xcb\x97\x86\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xeb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Y\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x007\x03\x00\x00\x004\x00\x8c\xff\xc3\x00\x00\x00\x00\x00\x00\x01T\x00>\xff\xe3\x00\x80\x00[\x00\x00\x00\x00\x00\x00\x05R\x00\x08\x00\x05\x00j\xff\xfd\x00\x00\x00\x00\x00\x00\x17\xf0\x00j\xff\x99\x00j\x00\x03\x01\x03\xa1\xde8\x0f\xb75VB\xf6"`\x94\xc7\x0b\xaa\x1f\xa2Nv\x8a\xf9\xc9\x9a>\x13\xa3a\xc8\x0c\xcb?\x968\xc7\xeb\xc3\x10a\x1a\xa7\xfb\x85\xa7iu\x14`\x8f\x90\x16o\x97\xd5\t\xa4,\xe5\xed\xe1\x15\x86<\x9d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x1f\xeb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]\xbf\xd7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xa1\xde8\x0f\xb75VB\xf6"`\x94\xc7\x0b\xaa\x1f\xa2Nv\x8a\xf9\xc9\x9a>\x13\xa3a\xc8\x0c\xcb?\x13\x16J\xe5\xfc\xa9\x06\xe8A\xe9\xc0Ql\xfb\xaeF\xcd\xd6\xa7\x8ei\xc4\xfa\xd4i\x84\xee\xc9\xe2\xaa\xa4f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00OB!\x81)\xf0l\xbcg\xa3^\xef\x0e\xfc\xb7\x02\x80\xe4\xa9NO\x89\xa0\t\xc3C\xd9\xda\xff\xd7\t\xeebfC&8\x9c+n$\x00\xa4\xe85\x19\xb0\xf6\x18\xa1\xeeR\xae\xec \x82k\xe0v@;\x1c\xc14PMh\xfb\xe3\x1c\xbf\x84O\xcd\xbc\xc4\xb8\xeabz`\xf7\x06;\xf6q\x8b,\x18\tf~\xd1\x11l#\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x8b)\xaa\x96x8\xd76J\xa6\x8b[\x98\t\xe0\\\xe3^7qD\x8c\xf5q\x08\xf2\xa2\xc9\xb03mvU\x1a\xe2\x181\x88\xfe\t\x03?\x12\xadj\x9d\xe8K\xb8!\xee\xe7e8\x82\xfb$\xf0Y\xfaJ\x10\x1f\x1a\xe5\xe9\xa8\xbb\xea\x87\xfc\xb12y\x94\x8d,\x16\xe4C\x02\xba\xe6\xac\x94{\xc4c\x07(\xb8\xeb\xab\xe3\xcfy{6\x98\t\xf4\x8fm\xd62\x85\x87\xb0\x03f\x01B]\xe3\xc6\x13l6\x8d\x0e\x18\xc64%\x97\x1a\xa6\xf4\x8b)\xaa\x96x8\xd76J\xa6\x8b[\x98\t\xe0\\\xe3^7qD\x8c\xf5q\x08\xf2\xa2\xc9\xb03mv\x00\x00\x00\x00\x00\x00\x00\x00\x00_\xec\xebf\xff\xc8o8\xd9Rxlmily\xc2\xdb\xc29\xddN\x91\xb4g)\xd7:\'\xfbW\xe9'
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
@ -40,7 +46,7 @@ def event_loop():
loop.close() loop.close()
class TestGenesisBlock(): class TestGenesisBlock:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_basic_blockchain(self): async def test_basic_blockchain(self):
store = FullNodeStore("fndb_test") store = FullNodeStore("fndb_test")
@ -56,7 +62,7 @@ class TestGenesisBlock():
assert await bc1.get_next_ips(genesis_block.header_hash) > 0 assert await bc1.get_next_ips(genesis_block.header_hash) > 0
class TestBlockValidation(): class TestBlockValidation:
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
async def initial_blockchain(self): async def initial_blockchain(self):
""" """
@ -68,7 +74,9 @@ class TestBlockValidation():
b: Blockchain = Blockchain(store, test_constants) b: Blockchain = Blockchain(store, test_constants)
await b.initialize() await b.initialize()
for i in range(1, 9): for i in range(1, 9):
assert (await b.receive_block(blocks[i])) == ReceiveBlockResult.ADDED_TO_HEAD assert (
await b.receive_block(blocks[i])
) == ReceiveBlockResult.ADDED_TO_HEAD
return (blocks, b) return (blocks, b)
@pytest.mark.asyncio @pytest.mark.asyncio
@ -89,6 +97,7 @@ class TestBlockValidation():
), blocks[9].body) ), blocks[9].body)
assert (await b.receive_block(block_bad)) == ReceiveBlockResult.DISCONNECTED_BLOCK assert (await b.receive_block(block_bad)) == ReceiveBlockResult.DISCONNECTED_BLOCK
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_timestamp(self, initial_blockchain): async def test_timestamp(self, initial_blockchain):
blocks, b = initial_blockchain blocks, b = initial_blockchain
@ -141,6 +150,7 @@ class TestBlockValidation():
blocks[9].header_block.header.data.extension_data blocks[9].header_block.header.data.extension_data
), blocks[9].header_block.header.harvester_signature) ), blocks[9].header_block.header.harvester_signature)
), blocks[9].body) ), blocks[9].body)
assert (await b.receive_block(block_bad)) == ReceiveBlockResult.INVALID_BLOCK assert (await b.receive_block(block_bad)) == ReceiveBlockResult.INVALID_BLOCK
@pytest.mark.asyncio @pytest.mark.asyncio
@ -185,9 +195,9 @@ class TestBlockValidation():
# Coinbase height invalid # Coinbase height invalid
block_bad = FullBlock(blocks[9].header_block, Body( block_bad = FullBlock(blocks[9].header_block, Body(
CoinbaseInfo( CoinbaseInfo(
uint32(3), uint32(3),
blocks[9].body.coinbase.amount, blocks[9].body.coinbase.amount,
blocks[9].body.coinbase.puzzle_hash blocks[9].body.coinbase.puzzle_hash,
), ),
blocks[9].body.coinbase_signature, blocks[9].body.coinbase_signature,
blocks[9].body.fees_target_info, blocks[9].body.fees_target_info,
@ -208,7 +218,9 @@ class TestBlockValidation():
b: Blockchain = Blockchain(store, test_constants) b: Blockchain = Blockchain(store, test_constants)
await b.initialize() await b.initialize()
for i in range(1, num_blocks): for i in range(1, num_blocks):
assert (await b.receive_block(blocks[i])) == ReceiveBlockResult.ADDED_TO_HEAD assert (
await b.receive_block(blocks[i])
) == ReceiveBlockResult.ADDED_TO_HEAD
diff_25 = await b.get_next_difficulty(blocks[24].header_hash) diff_25 = await b.get_next_difficulty(blocks[24].header_hash)
diff_26 = await b.get_next_difficulty(blocks[25].header_hash) diff_26 = await b.get_next_difficulty(blocks[25].header_hash)
@ -238,7 +250,9 @@ class TestReorgs():
await b.receive_block(block) await b.receive_block(block)
assert b.get_current_tips()[0].height == 100 assert b.get_current_tips()[0].height == 100
blocks_reorg_chain = bt.get_consecutive_blocks(test_constants, 30, blocks[:90], 9, b'1') blocks_reorg_chain = bt.get_consecutive_blocks(
test_constants, 30, blocks[:90], 9, b"1"
)
for reorg_block in blocks_reorg_chain: for reorg_block in blocks_reorg_chain:
result = await b.receive_block(reorg_block) result = await b.receive_block(reorg_block)
if reorg_block.height < 90: if reorg_block.height < 90:
@ -251,7 +265,7 @@ class TestReorgs():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_reorg_from_genesis(self): async def test_reorg_from_genesis(self):
blocks = bt.get_consecutive_blocks(test_constants, 20, [], 9, b'0') blocks = bt.get_consecutive_blocks(test_constants, 20, [], 9, b"0")
store = FullNodeStore("fndb_test") store = FullNodeStore("fndb_test")
await store._clear_database() await store._clear_database()
b: Blockchain = Blockchain(store, test_constants) b: Blockchain = Blockchain(store, test_constants)
@ -261,7 +275,9 @@ class TestReorgs():
assert b.get_current_tips()[0].height == 20 assert b.get_current_tips()[0].height == 20
# Reorg from genesis # Reorg from genesis
blocks_reorg_chain = bt.get_consecutive_blocks(test_constants, 21, [blocks[0]], 9, b'1') blocks_reorg_chain = bt.get_consecutive_blocks(
test_constants, 21, [blocks[0]], 9, b"1"
)
for reorg_block in blocks_reorg_chain: for reorg_block in blocks_reorg_chain:
result = await b.receive_block(reorg_block) result = await b.receive_block(reorg_block)
if reorg_block.height == 0: if reorg_block.height == 0:
@ -273,14 +289,22 @@ class TestReorgs():
assert b.get_current_tips()[0].height == 21 assert b.get_current_tips()[0].height == 21
# Reorg back to original branch # Reorg back to original branch
blocks_reorg_chain_2 = bt.get_consecutive_blocks(test_constants, 3, blocks, 9, b'3') blocks_reorg_chain_2 = bt.get_consecutive_blocks(
await b.receive_block(blocks_reorg_chain_2[20]) == ReceiveBlockResult.ADDED_AS_ORPHAN test_constants, 3, blocks, 9, b"3"
assert (await b.receive_block(blocks_reorg_chain_2[21])) == ReceiveBlockResult.ADDED_TO_HEAD )
assert (await b.receive_block(blocks_reorg_chain_2[22])) == ReceiveBlockResult.ADDED_TO_HEAD await b.receive_block(
blocks_reorg_chain_2[20]
) == ReceiveBlockResult.ADDED_AS_ORPHAN
assert (
await b.receive_block(blocks_reorg_chain_2[21])
) == ReceiveBlockResult.ADDED_TO_HEAD
assert (
await b.receive_block(blocks_reorg_chain_2[22])
) == ReceiveBlockResult.ADDED_TO_HEAD
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_lca(self): async def test_lca(self):
blocks = bt.get_consecutive_blocks(test_constants, 5, [], 9, b'0') blocks = bt.get_consecutive_blocks(test_constants, 5, [], 9, b"0")
store = FullNodeStore("fndb_test") store = FullNodeStore("fndb_test")
await store._clear_database() await store._clear_database()
b: Blockchain = Blockchain(store, test_constants) b: Blockchain = Blockchain(store, test_constants)
@ -289,15 +313,15 @@ class TestReorgs():
await b.receive_block(block) await b.receive_block(block)
assert b.lca_block == blocks[3] assert b.lca_block == blocks[3]
block_5_2 = bt.get_consecutive_blocks(test_constants, 1, blocks[:5], 9, b'1')[5] block_5_2 = bt.get_consecutive_blocks(test_constants, 1, blocks[:5], 9, b"1")[5]
block_5_3 = bt.get_consecutive_blocks(test_constants, 1, blocks[:5], 9, b'2')[5] block_5_3 = bt.get_consecutive_blocks(test_constants, 1, blocks[:5], 9, b"2")[5]
await b.receive_block(block_5_2) await b.receive_block(block_5_2)
assert b.lca_block == blocks[4] assert b.lca_block == blocks[4]
await b.receive_block(block_5_3) await b.receive_block(block_5_3)
assert b.lca_block == blocks[4] assert b.lca_block == blocks[4]
reorg = bt.get_consecutive_blocks(test_constants, 6, [], 9, b'3') reorg = bt.get_consecutive_blocks(test_constants, 6, [], 9, b"3")
for block in reorg: for block in reorg:
await b.receive_block(block) await b.receive_block(block)
assert b.lca_block == blocks[0] assert b.lca_block == blocks[0]

83
tests/test_database.py Normal file
View file

@ -0,0 +1,83 @@
import asyncio
from typing import Any, Dict
import pytest
from bson.binary import Binary
from bson.codec_options import CodecOptions, TypeRegistry
from motor import motor_asyncio
from src.consensus.constants import constants
from src.database import FullNodeStore
from src.types.block_body import BlockBody
from src.types.block_header import BlockHeaderData
from src.types.full_block import FullBlock
from src.types.proof_of_space import ProofOfSpace
from src.types.sized_bytes import bytes32
from src.types.trunk_block import TrunkBlock
from src.util.ints import uint32, uint64
from src.util.streamable import Streamable
@pytest.fixture(scope="module")
def event_loop():
loop = asyncio.get_event_loop()
yield loop
loop.close()
class TestDatabase:
@pytest.mark.asyncio
async def test_basic_database(self):
db = FullNodeStore("fndb_test")
await db._clear_database()
genesis = FullBlock.from_bytes(constants["GENESIS_BLOCK"])
# Save/get block
await db.save_block(genesis)
assert genesis == await db.get_block(genesis.header_hash)
# Save/get sync
for sync_mode in (False, True):
await db.set_sync_mode(sync_mode)
assert sync_mode == await db.get_sync_mode()
# clear sync info
await db.clear_sync_info()
assert await db.get_potential_heads_number() == 0
# add/get potential head, get potential heads num
await db.add_potential_head(genesis.header_hash)
assert await db.get_potential_heads_number() == 1
await db.add_potential_head(genesis.header_hash, genesis)
assert await db.get_potential_heads_number() == 1
assert genesis == await db.get_potential_head(genesis.header_hash)
# add/get potential trunk
trunk = genesis.trunk_block
await db.add_potential_trunk(trunk)
assert await db.get_potential_trunk(genesis.height) == trunk
# Add potential block
await db.add_potential_block(genesis)
assert genesis == await db.get_potential_block(0)
# Add/get candidate block
assert await db.get_candidate_block(0) is None
partial = (
genesis.body,
genesis.trunk_block.header.data,
genesis.trunk_block.proof_of_space,
)
await db.add_candidate_block(genesis.header_hash, *partial)
assert await db.get_candidate_block(genesis.header_hash) == partial
# Add/get unfinished block
key = (genesis.header_hash, 1000)
assert await db.get_unfinished_block(key) is None
await db.add_unfinished_block(key, genesis)
assert await db.get_unfinished_block(key) == genesis
# Set/get unf block leader
assert db.get_unfinished_block_leader() is None
db.set_unfinished_block_leader(key)
assert db.get_unfinished_block_leader() == key