Skip to content

Commit

Permalink
Changed to mypy from pyright, fix tests (full node still broken)
Browse files Browse the repository at this point in the history
  • Loading branch information
mariano54 committed Oct 22, 2019
1 parent 3916e90 commit 0fef0b9
Show file tree
Hide file tree
Showing 44 changed files with 534 additions and 374 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,11 @@ py.test tests -s -v
flake8 src
pyright
```

### Configure VS code
1. Install Python extension
2. Set the environment to ./.venv/bin/python
3. Install mypy plugin
4. Preferences > Settings > Python > Linting > flake8 enabled
5. Preferences > Settings > Python > Linting > mypy enabled
6. Preferences > Settings > mypy > Targets: set to ./src and ./tests
5 changes: 5 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[mypy]
ignore_missing_imports = True

[mypy-lib]
ignore_errors = True
15 changes: 0 additions & 15 deletions pyrightconfig.json

This file was deleted.

2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from setuptools import setup

dependencies = ['blspy', 'cbor2', 'pyyaml']
dev_dependencies = ['pytest', 'flake8', 'ipython']
dev_dependencies = ['pytest', 'flake8', 'ipython', 'mypy', 'pytest-asyncio']

setup(
name='chiablockchain',
Expand Down
77 changes: 47 additions & 30 deletions src/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,15 @@ def __init__(self, store: FullNodeStore, override_constants: Dict = {}):

self.store = store
self.heads: List[FullBlock] = []
self.lca_block: FullBlock = None
self.lca_block: FullBlock
self.height_to_hash: Dict[uint64, bytes32] = {}

async def initialize(self):
self.genesis = FullBlock.from_bytes(self.constants["GENESIS_BLOCK"])
result = self.receive_block(self.genesis)
result = await self.receive_block(self.genesis)
if result != ReceiveBlockResult.ADDED_TO_HEAD:
raise InvalidGenesisBlock()
assert self.lca_block

def get_current_heads(self) -> List[TrunkBlock]:
"""
Expand Down Expand Up @@ -89,9 +92,9 @@ async def get_trunk_blocks_by_height(self, heights: List[uint64], tip_header_has
curr_block = curr_full_block.trunk_block
trunks: List[Tuple[int, TrunkBlock]] = []
for height, index in sorted_heights:
if height > curr_block.challenge.height:
if height > curr_block.height:
raise ValueError("Height is not valid for tip {tip_header_hash}")
while height < curr_block.challenge.height:
while height < curr_block.height:
curr_full_block = (await self.store.get_block(curr_block.header.data.prev_header_hash)).trunk_block
trunks.append((index, curr_block))
return [b for index, b in sorted(trunks)]
Expand All @@ -102,9 +105,9 @@ def find_fork_point(self, alternate_chain: List[TrunkBlock]):
where both blockchains are equal.
"""
lca: TrunkBlock = self.lca_block.trunk_block
assert lca.challenge.height < alternate_chain[-1].challenge.height
assert lca.height < alternate_chain[-1].height
low = 0
high = lca.challenge.height
high = lca.height
while low + 1 < high:
mid = (low + high) // 2
if self.height_to_hash[uint64(mid)] != alternate_chain[mid].header.get_hash():
Expand Down Expand Up @@ -185,7 +188,7 @@ async def get_next_difficulty(self, header_hash: bytes32) -> uint64:
block2 = await self.store.get_block(self.height_to_hash[height2])
if not block3:
block3 = await self.store.get_block(self.height_to_hash[height3])
assert block1 is not None and block2 is not None and block3 is not None
assert block2 is not None and block3 is not None

# Current difficulty parameter (diff of block h = i - 1)
Tc = await self.get_next_difficulty(block.prev_header_hash)
Expand Down Expand Up @@ -278,7 +281,7 @@ async def get_next_ips(self, header_hash) -> uint64:
block1 = await self.store.get_block(self.height_to_hash[height1])
if not block2:
block2 = await self.store.get_block(self.height_to_hash[height2])
assert block1 is not None and block2 is not None
assert block2 is not None

if block1:
timestamp1 = block1.trunk_block.header.data.timestamp
Expand Down Expand Up @@ -314,7 +317,7 @@ async def receive_block(self, block: FullBlock) -> ReceiveBlockResult:

# Block is valid and connected, so it can be added to the blockchain.
await self.store.save_block(block)
if await self._reconsider_heads(block):
if await self._reconsider_heads(block, genesis):
return ReceiveBlockResult.ADDED_TO_HEAD
else:
return ReceiveBlockResult.ADDED_AS_ORPHAN
Expand All @@ -330,25 +333,27 @@ async def validate_unfinished_block(self, block: FullBlock, genesis: bool = Fals
return False

# 2. Check Now+2hrs > timestamp > avg timestamp of last 11 blocks
prev_block: Optional[FullBlock] = None
if not genesis:
# TODO: do something about first 11 blocks
last_timestamps: List[uint64] = []
prev_block: Optional[FullBlock] = await self.store.get_block(block.prev_header_hash)
prev_block = await self.store.get_block(block.prev_header_hash)
if not prev_block or not prev_block.trunk_block:
return False
curr = prev_block
while len(last_timestamps) < self.constants["NUMBER_OF_TIMESTAMPS"]:
last_timestamps.append(curr.trunk_block.header.data.timestamp)
try:
curr = await self.store.get_block(curr.prev_header_hash)
except KeyError:
fetched = await self.store.get_block(curr.prev_header_hash)
if not fetched:
break
curr = fetched
if len(last_timestamps) != self.constants["NUMBER_OF_TIMESTAMPS"] and curr.body.coinbase.height != 0:
return False
prev_time: uint64 = uint64(sum(last_timestamps) / len(last_timestamps))
prev_time: uint64 = uint64(int(sum(last_timestamps) / len(last_timestamps)))
if block.trunk_block.header.data.timestamp < prev_time:
return False
if block.trunk_block.header.data.timestamp > time.time() + self.constants["MAX_FUTURE_TIME"]:
return False
else:
prev_block: Optional[FullBlock] = None

# 3. Check filter hash is correct TODO

Expand All @@ -359,10 +364,14 @@ async def validate_unfinished_block(self, block: FullBlock, genesis: bool = Fals
# 5. Check extension data, if any is added

# 6. Compute challenge of parent
challenge_hash: bytes32
if not genesis:
challenge_hash: bytes32 = prev_block.trunk_block.challenge.get_hash()
assert prev_block
assert prev_block.trunk_block.challenge
challenge_hash = prev_block.trunk_block.challenge.get_hash()
else:
challenge_hash: bytes32 = block.trunk_block.proof_of_time.output.challenge_hash
assert block.trunk_block.proof_of_time
challenge_hash = block.trunk_block.proof_of_time.output.challenge_hash

# 7. Check plotter signature of header data is valid based on plotter key
if not block.trunk_block.header.plotter_signature.verify(
Expand All @@ -377,6 +386,7 @@ async def validate_unfinished_block(self, block: FullBlock, genesis: bool = Fals

# 9. Check coinbase height = parent coinbase height + 1
if not genesis:
assert prev_block
if block.body.coinbase.height != prev_block.body.coinbase.height + 1:
return False
else:
Expand Down Expand Up @@ -406,17 +416,21 @@ async def validate_block(self, block: FullBlock, genesis: bool = False) -> bool:
and extends something in the blockchain.
"""
# 1. Validate unfinished block (check the rest of the conditions)
if not self.validate_unfinished_block(block, genesis):
if not (await self.validate_unfinished_block(block, genesis)):
return False

difficulty: uint64
ips: uint64
if not genesis:
difficulty: uint64 = await self.get_next_difficulty(block.prev_header_hash)
ips: uint64 = await self.get_next_ips(block.prev_header_hash)
difficulty = await self.get_next_difficulty(block.prev_header_hash)
ips = await self.get_next_ips(block.prev_header_hash)
else:
difficulty: uint64 = uint64(self.constants["DIFFICULTY_STARTING"])
ips: uint64 = uint64(self.constants["VDF_IPS_STARTING"])
difficulty = uint64(self.constants["DIFFICULTY_STARTING"])
ips = uint64(self.constants["VDF_IPS_STARTING"])

# 2. Check proof of space hash
if not block.trunk_block.challenge or not block.trunk_block.proof_of_time:
return False
if block.trunk_block.proof_of_space.get_hash() != block.trunk_block.challenge.proof_of_space_hash:
return False

Expand All @@ -439,7 +453,7 @@ async def validate_block(self, block: FullBlock, genesis: bool = False) -> bool:

if not genesis:
prev_block: FullBlock = await self.store.get_block(block.prev_header_hash)
if not prev_block:
if not prev_block or not prev_block.trunk_block.challenge:
return False

# 5. and check if PoT.output.challenge_hash matches
Expand Down Expand Up @@ -475,11 +489,11 @@ async def validate_block(self, block: FullBlock, genesis: bool = False) -> bool:

return True

async def _reconsider_heights(self, old_lca: FullBlock, new_lca: FullBlock):
async def _reconsider_heights(self, old_lca: Optional[FullBlock], new_lca: FullBlock):
"""
Update the mapping from height to block hash, when the lca changes.
"""
curr_old: TrunkBlock = old_lca.trunk_block if old_lca else None
curr_old: Optional[TrunkBlock] = old_lca.trunk_block if old_lca else None
curr_new: TrunkBlock = new_lca.trunk_block
while True:
if not curr_old or curr_old.height < curr_new.height:
Expand All @@ -497,7 +511,7 @@ async def _reconsider_heights(self, old_lca: FullBlock, new_lca: FullBlock):
curr_new = (await self.store.get_block(curr_new.prev_header_hash)).trunk_block
curr_old = (await self.store.get_block(curr_old.prev_header_hash)).trunk_block

async def _reconsider_lca(self):
async def _reconsider_lca(self, genesis: bool):
"""
Update the least common ancestor of the heads. This is useful, since we can just assume
there is one block per height before the LCA (and use the height_to_hash dict).
Expand All @@ -508,10 +522,13 @@ async def _reconsider_lca(self):
i = heights.index(max(heights))
cur[i] = await self.store.get_block(cur[i].prev_header_hash)
heights[i] = cur[i].height
self._reconsider_heights(self.lca_block, cur[0])
if genesis:
await self._reconsider_heights(None, cur[0])
else:
await self._reconsider_heights(self.lca_block, cur[0])
self.lca_block = cur[0]

async def _reconsider_heads(self, block: FullBlock) -> bool:
async def _reconsider_heads(self, block: FullBlock, genesis: bool) -> bool:
"""
When a new block is added, this is called, to check if the new block is heavier
than one of the heads.
Expand All @@ -521,6 +538,6 @@ async def _reconsider_heads(self, block: FullBlock) -> bool:
while len(self.heads) > self.constants["NUMBER_OF_HEADS"]:
self.heads.sort(key=lambda b: b.weight, reverse=True)
self.heads.pop()
self._reconsider_lca()
await self._reconsider_lca(genesis)
return True
return False
4 changes: 3 additions & 1 deletion src/consensus/constants.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
constants = {
from typing import Dict, Any

constants: Dict[str, Any] = {
"NUMBER_OF_HEADS": 3, # The number of tips each full node keeps track of and propagates
"DIFFICULTY_STARTING": 500, # These are in units of 2^32
"DIFFICULTY_FACTOR": 3, # The next difficulty is truncated to range [prev / FACTOR, prev * FACTOR]
Expand Down
6 changes: 3 additions & 3 deletions src/consensus/pot_iterations.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def calculate_iterations_quality(quality: bytes32, size: uint8, difficulty: uint
min_iterations = min_block_time * vdf_ips
dec_iters = (Decimal(int(difficulty) << 32) *
(_quality_to_decimal(quality) / _expected_plot_size(size)))
iters_final = uint64(min_iterations + dec_iters.to_integral_exact(rounding=ROUND_UP))
iters_final = uint64(int(min_iterations + dec_iters.to_integral_exact(rounding=ROUND_UP)))
assert iters_final >= 1
return iters_final

Expand Down Expand Up @@ -74,5 +74,5 @@ def calculate_ips_from_iterations(proof_of_space: ProofOfSpace, challenge_hash:
min_iterations = uint64(iterations - iters_rounded)
ips = min_iterations / min_block_time
assert ips >= 1
assert uint64(ips) == ips
return uint64(ips)
assert uint64(int(ips)) == ips
return uint64(int(ips))
15 changes: 7 additions & 8 deletions src/farmer.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,20 +114,20 @@ async def respond_proof_of_space(response: plotter_protocol.RespondProofOfSpace)
async with state.lock:
estimate_secs: float = number_iters / state.proof_of_time_estimate_ips
if estimate_secs < config['pool_share_threshold']:
request = plotter_protocol.RequestPartialProof(response.quality,
sha256(bytes.fromhex(config['farmer_target'])).digest())
yield OutboundMessage(NodeType.PLOTTER, Message("request_partial_proof", request), Delivery.RESPOND)
request1 = plotter_protocol.RequestPartialProof(response.quality,
sha256(bytes.fromhex(config['farmer_target'])).digest())
yield OutboundMessage(NodeType.PLOTTER, Message("request_partial_proof", request1), Delivery.RESPOND)
if estimate_secs < config['propagate_threshold']:
async with state.lock:
if new_proof_height not in state.coinbase_rewards:
log.error(f"Don't have coinbase transaction for height {new_proof_height}, cannot submit PoS")
return

coinbase, signature = state.coinbase_rewards[new_proof_height]
request = farmer_protocol.RequestHeaderHash(challenge_hash, coinbase, signature,
bytes.fromhex(config['farmer_target']), response.proof)
request2 = farmer_protocol.RequestHeaderHash(challenge_hash, coinbase, signature,
bytes.fromhex(config['farmer_target']), response.proof)

yield OutboundMessage(NodeType.FULL_NODE, Message("request_header_hash", request), Delivery.BROADCAST)
yield OutboundMessage(NodeType.FULL_NODE, Message("request_header_hash", request2), Delivery.BROADCAST)


@api_request
Expand Down Expand Up @@ -235,8 +235,7 @@ async def proof_of_space_arrived(proof_of_space_arrived: farmer_protocol.ProofOf
if proof_of_space_arrived.height not in state.unfinished_challenges:
state.unfinished_challenges[proof_of_space_arrived.height] = []
else:
state.unfinished_challenges[proof_of_space_arrived.height].append(
proof_of_space_arrived.quality_string)
state.unfinished_challenges[proof_of_space_arrived.height].append(proof_of_space_arrived.quality)


@api_request
Expand Down
4 changes: 3 additions & 1 deletion src/full_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ async def send_heads_to_farmers() -> AsyncGenerator[OutboundMessage, None]:
requests: List[farmer_protocol.ProofOfSpaceFinalized] = []
async with (await store.get_lock()):
for head in blockchain.get_current_heads():
assert head.proof_of_time and head.challenge
prev_challenge_hash = head.proof_of_time.output.challenge_hash
challenge_hash = head.challenge.get_hash()
height = head.challenge.height
Expand All @@ -70,6 +71,7 @@ async def send_challenges_to_timelords() -> AsyncGenerator[OutboundMessage, None
requests: List[timelord_protocol.ChallengeStart] = []
async with (await store.get_lock()):
for head in blockchain.get_current_heads():
assert head.challenge
challenge_hash = head.challenge.get_hash()
requests.append(timelord_protocol.ChallengeStart(challenge_hash, head.challenge.height))
for request in requests:
Expand All @@ -84,7 +86,7 @@ async def on_connect() -> AsyncGenerator[OutboundMessage, None]:
async with (await store.get_lock()):
heads: List[TrunkBlock] = blockchain.get_current_heads()
for h in heads:
blocks.append(blockchain.get_block(h.header.get_hash()))
blocks.append(await blockchain.get_block(h.header.get_hash()))
for block in blocks:
request = peer_protocol.Block(block)
yield OutboundMessage(NodeType.FULL_NODE, Message("block", request), Delivery.RESPOND)
Expand Down
9 changes: 5 additions & 4 deletions src/plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

class PlotterState:
# From filename to prover
provers = {}
provers: Dict[str, DiskProver] = {}
lock: Lock = Lock()
# From quality to (challenge_hash, filename, index)
challenge_hashes: Dict[bytes32, Tuple[bytes32, str, uint8]] = {}
Expand Down Expand Up @@ -102,18 +102,19 @@ async def request_proof_of_space(request: plotter_protocol.RequestProofOfSpace):
log.warning(f"Quality {request.quality} not found")
return
if index is not None:
proof_xs: bytes
try:
proof_xs: bytes = state.provers[filename].get_full_proof(challenge_hash, index)
proof_xs = state.provers[filename].get_full_proof(challenge_hash, index)
except RuntimeError:
state.provers[filename] = DiskProver(filename)
proof_xs: bytes = state.provers[filename].get_full_proof(challenge_hash, index)
proof_xs = state.provers[filename].get_full_proof(challenge_hash, index)

pool_pubkey = PublicKey.from_bytes(bytes.fromhex(config['plots'][filename]['pool_pk']))
plot_pubkey = PrivateKey.from_bytes(bytes.fromhex(config['plots'][filename]['sk'])).get_public_key()
proof_of_space: ProofOfSpace = ProofOfSpace(pool_pubkey,
plot_pubkey,
uint8(config['plots'][filename]['k']),
list(proof_xs))
[uint8(b) for b in proof_xs])

response = plotter_protocol.RespondProofOfSpace(
request.quality,
Expand Down
Loading

0 comments on commit 0fef0b9

Please sign in to comment.