diff --git a/.flake8 b/.flake8 index 6deafc261704..920e652a475c 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,3 @@ [flake8] max-line-length = 120 +exclude = ./typings/**/* diff --git a/src/blockchain.py b/src/blockchain.py index a4446f0703a2..43074923a234 100644 --- a/src/blockchain.py +++ b/src/blockchain.py @@ -1,12 +1,11 @@ from src.consensus.block_rewards import calculate_block_reward -import time import logging from enum import Enum import blspy from typing import List, Dict, Optional, Tuple from src.util.errors import BlockNotInBlockchain from src.types.sized_bytes import bytes32 -from src.util.ints import uint64 +from src.util.ints import uint64, uint32 from src.util.genesis_block import genesis_block_hardcoded from src.types.trunk_block import TrunkBlock from src.types.full_block import FullBlock @@ -26,28 +25,33 @@ class ReceiveBlockResult(Enum): - ADDED_TO_HEAD = 1 - ADDED_AS_ORPHAN = 2 - INVALID_BLOCK = 3 - ALREADY_HAVE_BLOCK = 4 - DISCONNECTED_BLOCK = 5 + """ + When Blockchain.receive_block(b) is called, one of these results is returned, + showing whether the block was added to the chain (extending a head or not), + and if not, why it was not added. + """ + ADDED_TO_HEAD = 1 # Added to one of the heads, this block is now a new head + ADDED_AS_ORPHAN = 2 # Added as an orphan/stale block (block that is not a head or ancestor of a head) + INVALID_BLOCK = 3 # Block was not added because it was invalid + ALREADY_HAVE_BLOCK = 4 # Block is already present in this blockchain + DISCONNECTED_BLOCK = 5 # Block's parent (previous pointer) is not in this blockchain class Blockchain: def __init__(self): try: - self.genesis = self.get_genesis_block() + genesis = self.get_genesis_block() except ValueError: raise ValueError("Failed to parse genesis block.") - self.heads: List[FullBlock] = [self.genesis] - self.lca_block: FullBlock = self.genesis - self.blocks: Dict[bytes32, FullBlock] = { - self.genesis.header_hash: self.genesis - } - self.height_to_hash: Dict[uint64, bytes32] = { - uint64(0): self.genesis.header_hash - } + self.heads: List[FullBlock] = [] + self.lca_block: FullBlock = None + self.blocks: Dict[bytes32, FullBlock] = {} + self.height_to_hash: Dict[uint64, bytes32] = {} + result = self.receive_block(genesis) + assert result == ReceiveBlockResult.ADDED_TO_HEAD + self.genesis = genesis + # For blocks with height % DIFFICULTY_DELAY == 1, a link to the hash of # the (DIFFICULTY_DELAY)-th parent of this block self.header_warp: Dict[bytes32, bytes32] = {} @@ -56,18 +60,18 @@ def __init__(self): def get_genesis_block() -> FullBlock: return FullBlock.from_bytes(genesis_block_hardcoded) - def get_current_heads(self) -> List[FullBlock]: + def get_current_heads(self) -> List[TrunkBlock]: """ Return the heads. """ - return self.heads + return [b.trunk_block for b in self.heads] def is_child_of_head(self, block: FullBlock): """ True iff the block is the direct ancestor of a head. """ for head in self.heads: - if (block.prev_hash == head.header_hash): + if (block.prev_header_hash == head.header_hash): return True return False @@ -78,22 +82,20 @@ def get_trunk_blocks_by_height(self, heights: List[uint64], tip_header_hash: byt """ Returns a list of trunk blocks, one for each height requested. """ - log.info("Starting get trunks by height") # TODO: optimize, don't look at all blocks sorted_heights = sorted([(height, index) for index, height in enumerate(heights)], reverse=True) if tip_header_hash not in self.blocks: raise BlockNotInBlockchain(f"Header hash {tip_header_hash} not present in chain.") - curr_block: TrunkBlock = self.blocks[tip_header_hash] + curr_block: TrunkBlock = self.blocks[tip_header_hash].trunk_block trunks: List[Tuple[int, TrunkBlock]] = [] for height, index in sorted_heights: if height > curr_block.challenge.height: raise ValueError("Height is not valid for tip {tip_header_hash}") while height < curr_block.challenge.height: - curr_block = self.blocks[curr_block.header.data.prev_header_hash] + curr_block = self.blocks[curr_block.header.data.prev_header_hash].trunk_block trunks.append((index, curr_block)) - log.info("Returning get trunks by height") return [b for index, b in sorted(trunks)] def find_fork_point(self, alternate_chain: List[TrunkBlock]): @@ -127,16 +129,17 @@ def find_fork_point(self, alternate_chain: List[TrunkBlock]): raise ValueError("Invalid genesis block") def get_difficulty(self, header_hash: bytes32) -> uint64: - trunk = self.blocks.get(header_hash, None) + trunk: TrunkBlock = self.blocks.get(header_hash, None).trunk_block if trunk is None: raise Exception("No block found for given header_hash") - elif trunk is self.genesis: + elif trunk is self.genesis.trunk_block: return uint64(DIFFICULTY_STARTING) - prev_trunk = self.blocks.get(trunk.prev_header_hash, None) - if prev_trunk is None: + prev_block = self.blocks.get(trunk.prev_header_hash, None) + if prev_block is None: raise Exception("No previous block found to compare total weight to") - return trunk.challenge.total_weight - prev_trunk.challenge.total_weight + return uint64(trunk.challenge.total_weight + - prev_block.trunk_block.challenge.total_weight) def get_next_difficulty(self, header_hash: bytes32) -> uint64: return self.get_difficulty(header_hash) @@ -165,7 +168,7 @@ def get_next_difficulty(self, header_hash: bytes32) -> uint64: for _ in range(DIFFICULTY_WARP_FACTOR - 1): warp2 = self.header_warp.get(warp2, None) # warp2: header_hash of height {i + 1 - EPOCH + DELAY} - Tp = self.get_difficulty(self.blocks[warp2].prev_hash) + Tp = self.get_difficulty(self.blocks[warp2].prev_header_hash) # X_i : timestamp of i-th block, (EPOCH divides i) # Current block @warp is i+1 @@ -191,7 +194,7 @@ def get_vdf_rate_estimate(self) -> Optional[uint64]: Looks at the last N blocks from one of the heads, and divides timestamps. Returns None if no time has elapsed, or if genesis block. """ - head: TrunkBlock = self.heads[0] + head: TrunkBlock = self.heads[0].trunk_block curr = head total_iterations_performed = 0 for _ in range(0, 200): @@ -201,7 +204,7 @@ def get_vdf_rate_estimate(self) -> Optional[uint64]: curr.proof_of_time.output.challenge_hash, self.get_difficulty(curr.header.get_hash())) total_iterations_performed += iterations_performed - curr: TrunkBlock = self.blocks[curr.header.data.prev_header_hash] + curr: TrunkBlock = self.blocks[curr.header.data.prev_header_hash].trunk_block else: break head_timestamp: int = int(head.header.data.timestamp) @@ -212,15 +215,15 @@ def get_vdf_rate_estimate(self) -> Optional[uint64]: return uint64(total_iterations_performed // time_elapsed_secs) def receive_block(self, block: FullBlock) -> ReceiveBlockResult: - if block.hash in self.blocks: + genesis: bool = block.height == 0 and len(self.heads) == 0 + + if block.header_hash in self.blocks: return ReceiveBlockResult.ALREADY_HAVE_BLOCK - start = time.time() - if not self.validate_block(block): + if not self.validate_block(block, genesis): return ReceiveBlockResult.INVALID_BLOCK - print(f"Time taken for validation: {time.time() - start}") - if block.prev_hash not in self.blocks: + if block.prev_header_hash not in self.blocks and not genesis: return ReceiveBlockResult.DISCONNECTED_BLOCK self.blocks[block.header_hash] = block @@ -229,107 +232,107 @@ def receive_block(self, block: FullBlock) -> ReceiveBlockResult: else: return ReceiveBlockResult.ADDED_AS_ORPHAN - def validate_unfinished_block(self, block: FullBlock): + def validate_unfinished_block(self, block: FullBlock, genesis: bool = False) -> bool: """ Block validation algorithm. Returns true if the candidate block is fully valid (except for proof of time). The same as validate_block, but without proof of time and challenge validation. - 1. Takes in chain: Blockchain, candidate: FullBlock - 2. Check previous pointer(s) / flyclient - 3. Check Now+2hrs > timestamp > avg timestamp of last 11 blocks - 4. Check filter hash is correct - 5. Check body hash - 6. Check extension data - 7. Compute challenge of parent - 8. Check plotter signature of header data is valid based on plotter key - 9. Check proof of space based on challenge - 10. Check coinbase height = parent coinbase height + 1 - 11. Check coinbase amount - 12. Check coinbase signature with pool pk - 13. Check transactions are valid - 14. Check aggregate BLS signature is valid - 15. Check fees amount is correct """ - if block.prev_hash not in self.blocks: - return False - last_timestamps: List[uint64] = [] - prev_block: FullBlock = self.blocks[block.prev_hash] - curr = prev_block - while len(last_timestamps) < NUMBER_OF_TIMESTAMPS: - last_timestamps.append(curr.trunk_block.header.data.timestamp) - try: - curr = self.blocks[curr.prev_hash] - except KeyError: - break - if len(last_timestamps) != NUMBER_OF_TIMESTAMPS and curr.trunk_block.challenge.height != 0: - return False - prev_time: uint64 = uint64(sum(last_timestamps) / len(last_timestamps)) - if block.trunk_block.header.data.timestamp < prev_time: - return False - if block.trunk_block.header.data.timestamp > prev_time + MAX_FUTURE_TIME: - return False - - # TODO: check filter hash - if (block.trunk_block.proof_of_time.output.get_hash() != - block.trunk_block.challenge.proof_of_time_output_hash): + # 1. Check previous pointer(s) / flyclient + if not genesis and block.prev_header_hash not in self.blocks: return False - # If any extension data is added, it should be checked here + # 2. Check Now+2hrs > timestamp > avg timestamp of last 11 blocks + if not genesis: + last_timestamps: List[uint64] = [] + prev_block: Optional[FullBlock] = self.blocks[block.prev_header_hash] + curr = prev_block + while len(last_timestamps) < NUMBER_OF_TIMESTAMPS: + last_timestamps.append(curr.trunk_block.header.data.timestamp) + try: + curr = self.blocks[curr.prev_header_hash] + except KeyError: + break + if len(last_timestamps) != NUMBER_OF_TIMESTAMPS and curr.trunk_block.challenge.height != 0: + return False + prev_time: uint64 = uint64(sum(last_timestamps) / len(last_timestamps)) + if block.trunk_block.header.data.timestamp < prev_time: + return False + if block.trunk_block.header.data.timestamp > prev_time + MAX_FUTURE_TIME: + return False + else: + prev_block: Optional[FullBlock] = None + + # 3. Check filter hash is correct TODO + # 4. Check body hash if block.body.get_hash() != block.trunk_block.header.data.body_hash: return False - if not block.trunk_block.header.plotter_signature.verify([blspy.Util.hash256(block.header_hash)], - [block.trunk_block.proof_of_space.plot_pubkey]): + # 5. Check extension data, if any is added + + # 6. Compute challenge of parent + if not genesis: + challenge_hash: bytes32 = prev_block.trunk_block.challenge.get_hash() + else: + challenge_hash: bytes32 = 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( + [blspy.Util.hash256(block.trunk_block.header.data.get_hash())], + [block.trunk_block.proof_of_space.plot_pubkey]): return False - pos_quality = block.trunk_block.proof_of_space.verify_and_get_quality( - block.trunk_block.proof_of_time.output.challenge_hash) + # 8. Check proof of space based on challenge + pos_quality = block.trunk_block.proof_of_space.verify_and_get_quality(challenge_hash) if not pos_quality: return False - if block.body.coinbase.height != prev_block.body.coinbase.height + 1: - return False + # 9. Check coinbase height = parent coinbase height + 1 + if not genesis: + if block.body.coinbase.height != prev_block.body.coinbase.height + 1: + return False + else: + if block.body.coinbase.height != 0: + return False + # 10. Check coinbase amount if calculate_block_reward(block.trunk_block.challenge.height) != block.body.coinbase.amount: return False - if not block.body.coinbase_signature.verify([blspy.Util.hash256()], + # 11. Check coinbase signature with pool pk + if not block.body.coinbase_signature.verify([blspy.Util.hash256(block.body.coinbase.serialize())], [block.trunk_block.proof_of_space.pool_pubkey]): return False - # TODO: check transactions - # TODO: check that transactions result in signature + # TODO: 12a. check transactions + # TODO: 12b. Aggregate transaction results into signature if block.body.aggregated_signature: - # TODO: check that aggregate signature is valid, based on pubkeys, and messages + # TODO: 13. check that aggregate signature is valid, based on pubkeys, and messages pass - # TODO: check fees - + # TODO: 13. check fees return True - def validate_block(self, block: FullBlock): + def validate_block(self, block: FullBlock, genesis: bool = False) -> bool: """ Block validation algorithm. Returns true iff the candidate block is fully valid, and extends something in the blockchain. - 1. Validate unfinished block (check the rest of the conditions) - 2. Check proof of space hash - 3. Check number of iterations on PoT is correct, based on prev block and PoS - 4. Check PoT - 5. and check if PoT.output.challenge_hash matches - 6. Check challenge height = parent height + 1 - 7. Check challenge total_weight = parent total_weight + difficulty - 8. Check challenge total_iters = parent total_iters + number_iters """ - if not self.validate_unfinished_block(block): + # 1. Validate unfinished block (check the rest of the conditions) + if not self.validate_unfinished_block(block, genesis): return False - prev_block: FullBlock = self.blocks[block.prev_hash] - difficulty: uint64 = self.get_next_difficulty(block.prev_hash) + if not genesis: + difficulty: uint64 = self.get_next_difficulty(block.prev_header_hash) + else: + difficulty: uint64 = uint64(DIFFICULTY_STARTING) + # 2. Check proof of space hash if block.trunk_block.proof_of_space.get_hash() != block.trunk_block.challenge.proof_of_space_hash: return False + # 3. Check number of iterations on PoT is correct, based on prev block and PoS pos_quality: bytes32 = block.trunk_block.proof_of_space.verify_and_get_quality( block.trunk_block.proof_of_time.output.challenge_hash) @@ -338,60 +341,83 @@ def validate_block(self, block: FullBlock): if number_of_iters != block.trunk_block.proof_of_time.output.number_of_iterations: return False + # 4. Check PoT if not block.trunk_block.proof_of_time.is_valid(): return False - if (block.trunk_block.proof_of_time.output.challenge_hash != - prev_block.trunk_block.challenge.get_hash()): - return False - if block.body.coinbase.height != block.trunk_block.challenge.height: return False - if block.trunk_block.challenge.height != prev_block.trunk_block.challenge.height + 1: - return False + if not genesis: + prev_block: FullBlock = self.blocks[block.prev_header_hash] - if (block.trunk_block.challenge.total_weight != - prev_block.trunk_block.challenge.total_weight + difficulty): - return False + # 5. and check if PoT.output.challenge_hash matches + if (block.trunk_block.proof_of_time.output.challenge_hash != + prev_block.trunk_block.challenge.get_hash()): + return False + + # 6a. Check challenge height = parent height + 1 + if block.trunk_block.challenge.height != prev_block.trunk_block.challenge.height + 1: + return False + + # 7a. Check challenge total_weight = parent total_weight + difficulty + if (block.trunk_block.challenge.total_weight != + prev_block.trunk_block.challenge.total_weight + difficulty): + return False + + # 8a. Check challenge total_iters = parent total_iters + number_iters + if (block.trunk_block.challenge.total_iters != + prev_block.trunk_block.challenge.total_iters + number_of_iters): + return False + else: + # 6b. Check challenge height = parent height + 1 + if block.trunk_block.challenge.height != 0: + return False + + # 7b. Check challenge total_weight = parent total_weight + difficulty + if block.trunk_block.challenge.total_weight != difficulty: + return False + + # 8b. Check challenge total_iters = parent total_iters + number_iters + if block.trunk_block.challenge.total_iters != number_of_iters: + return False - if (block.trunk_block.challenge.total_iters != - prev_block.trunk_block.challenge.total_iters + number_of_iters): - return False return True def _reconsider_heights(self, old_lca: FullBlock, new_lca: FullBlock): """ Update the mapping from height to block hash, when the lca changes. """ - curr_old: TrunkBlock = old_lca - curr_new: TrunkBlock = new_lca + curr_old: TrunkBlock = old_lca.trunk_block if old_lca else None + curr_new: TrunkBlock = new_lca.trunk_block while True: - if curr_old.height > curr_new.height: - del self.height_to_hash[uint64(curr_old.height)] - curr_old = self.blocks[curr_old.prev_hash] - elif curr_old.height < curr_new.height: + if not curr_old or curr_old.height < curr_new.height: self.height_to_hash[uint64(curr_new.height)] = curr_new.header_hash - curr_new = self.blocks[curr_new.prev_hash] + if curr_new.height == 0: + return + curr_new = self.blocks[curr_new.prev_header_hash].trunk_block + elif curr_old.height > curr_new.height: + del self.height_to_hash[uint64(curr_old.height)] + curr_old = self.blocks[curr_old.prev_header_hash].trunk_block else: if curr_new.header_hash == curr_old.header_hash: return self.height_to_hash[uint64(curr_new.height)] = curr_new.header_hash - curr_new = self.blocks[curr_new.prev_hash] - curr_old = self.blocks[curr_old.prev_hash] + curr_new = self.blocks[curr_new.prev_header_hash].trunk_block + curr_old = self.blocks[curr_old.prev_header_hash].trunk_block def _reconsider_lca(self): cur: List[FullBlock] = self.heads[:] - heights: List[uint64] = [b.height for b in cur] + heights: List[uint32] = [b.height for b in cur] while any(h != heights[0] for h in heights): i = heights.index(max(heights)) - cur[i] = self.blocks[cur[i].prev_hash] + cur[i] = self.blocks[cur[i].prev_header_hash] heights[i] = cur[i].height self._reconsider_heights(self.lca_block, cur[0]) self.lca_block = cur[0] def _reconsider_heads(self, block: FullBlock) -> bool: - if block.weight > min(b.weight for b in self.heads): + if len(self.heads) == 0 or block.weight > min([b.weight for b in self.heads]): self.heads.append(block) while len(self.heads) >= 4: self.heads.sort(key=lambda b: b.weight, reverse=True) @@ -404,7 +430,7 @@ def _reconsider_heads(self, block: FullBlock) -> bool: def _get_warpable_trunk(self, trunk: TrunkBlock) -> TrunkBlock: height = trunk.challenge.height while height % DIFFICULTY_DELAY != 1: - trunk = self.blocks[trunk.header.header_hash] + trunk = self.blocks[trunk.header.header_hash].trunk_block height = trunk.challenge.height return trunk @@ -412,8 +438,8 @@ def _consider_warping_link(self, trunk: TrunkBlock): # Assumes trunk is already connected if trunk.challenge.height % DIFFICULTY_DELAY != 1: return - warped_trunk = self.blocks[trunk.prev_header_hash] + warped_trunk: TrunkBlock = self.blocks[trunk.prev_header_hash].trunk_block while warped_trunk and warped_trunk.challenge.height % DIFFICULTY_DELAY != 1: - warped_trunk = self.blocks.get(warped_trunk.prev_header_hash, None) + warped_trunk = self.blocks.get(warped_trunk.prev_header_hash, None).trunk_block if warped_trunk is not None: self.header_warp[trunk.header.header_hash] = warped_trunk.header.header_hash diff --git a/src/config/farmer.yaml b/src/config/farmer.yaml index 7839e6391a02..ba9f29dad669 100644 --- a/src/config/farmer.yaml +++ b/src/config/farmer.yaml @@ -4,9 +4,20 @@ port: 8001 # Private key used for spending farming rewards farmer_sk: "43aead2006e8c67126adb596ce8f47aa01b956daa0214e06cd1a82ebd73aa88d" +# Address where the money will be sent # sha256(farmer_sk.get_public_key()).digest() farmer_target: "c28592540bd281f603bb479b3692139339f10464413b2d307a25d8d145ce1833" +# Private keys of pool operators +# [PrivateKey.from_seed(b'pool key 0'), PrivateKey.from_seed(b'pool key 1')] +pool_sks: + - "4c7c4ca5c7cf99e1e6dbd64a61d20698270a6e1939b2b2eb2e1e034f9568712a" + - "0c225913cb84b4601f83c89e369dd3ac696e37facce3ebfab4b87abe611db309" + +# Address of pool operator +# sha256(PrivateKey.from_seed(b'0').get_public_key().serialize()).digest() +pool_target: "9940b95222a1d19abb73c192f2c10dc65b32bcc7a703db1b40456f2dbf1e416e" + pool_share_threshold: 50 # To send to pool, must be expected to take less than these seconds propagate_threshold: 30 # To propagate to network, must be expected to take less than these seconds diff --git a/src/farmer.py b/src/farmer.py index c4f7b35e322e..06e47799a9fc 100644 --- a/src/farmer.py +++ b/src/farmer.py @@ -18,8 +18,6 @@ class Database: lock = asyncio.Lock() - pool_sks = [PrivateKey.from_seed(b'pool key 0'), PrivateKey.from_seed(b'pool key 1')] - pool_target = sha256(PrivateKey.from_seed(b'0').get_public_key().serialize()).digest() plotter_responses_header_hash: Dict[bytes32, bytes32] = {} plotter_responses_challenge: Dict[bytes32, bytes32] = {} plotter_responses_proofs: Dict[bytes32, ProofOfSpace] = {} @@ -84,7 +82,8 @@ async def respond_proof_of_space(response: plotter_protocol.RespondProofOfSpace) """ async with db.lock: - assert response.proof.pool_pubkey in [sk.get_public_key() for sk in db.pool_sks] + pool_sks: List[PrivateKey] = [PrivateKey.from_bytes(bytes.fromhex(ce)) for ce in config["pool_sks"]] + assert response.proof.pool_pubkey in [sk.get_public_key() for sk in pool_sks] challenge_hash: bytes32 = db.plotter_responses_challenge[response.quality] challenge_height: uint32 = db.challenge_to_height[challenge_hash] @@ -199,9 +198,12 @@ async def proof_of_space_finalized(proof_of_space_finalized: farmer_protocol.Pro db.current_height = proof_of_space_finalized.height # TODO: ask the pool for this information - coinbase: CoinbaseInfo = CoinbaseInfo(db.current_height + 1, calculate_block_reward(db.current_height), - db.pool_target) - coinbase_signature: PrependSignature = db.pool_sks[0].sign_prepend(coinbase.serialize()) + coinbase: CoinbaseInfo = CoinbaseInfo(uint32(db.current_height + 1), + calculate_block_reward(db.current_height), + bytes.fromhex(config["pool_target"])) + + pool_sks: List[PrivateKey] = [PrivateKey.from_bytes(bytes.fromhex(ce)) for ce in config["pool_sks"]] + coinbase_signature: PrependSignature = pool_sks[0].sign_prepend(coinbase.serialize()) db.coinbase_rewards[uint32(db.current_height + 1)] = (coinbase, coinbase_signature) log.info(f"Current height set to {db.current_height}") diff --git a/src/full_node.py b/src/full_node.py index e4e0b02690b3..86eb507749aa 100644 --- a/src/full_node.py +++ b/src/full_node.py @@ -201,6 +201,7 @@ async def sync(): async with db.lock: fork_point: TrunkBlock = db.blockchain.find_fork_point(trunks) + # TODO: optimize, send many requests at once, and for more blocks for height in range(fork_point.challenge.height + 1, tip_height + 1): # Only download from fork point (what we don't have) async with db.lock: @@ -368,7 +369,6 @@ async def request_header_hash(request: farmer_protocol.RequestHeaderHash) -> Asy # Creates the block header prev_header_hash: bytes32 = target_head.header.get_hash() - # prev_header_hash: bytes32 = bytes32([0] * 32) timestamp: uint64 = uint64(time.time()) # TODO: use a real BIP158 filter based on transactions @@ -524,18 +524,6 @@ async def unfinished_block(unfinished_block: peer_protocol.UnfinishedBlock) -> A async def block(block: peer_protocol.Block) -> AsyncGenerator[OutboundMessage, None]: """ Receive a full block from a peer full node (or ourselves). - Pseudocode: - if we have block: - return - if we don't care about block: - return - if block invalid: - return - Store block - if block actually good: - propagate to other full nodes - propagate challenge to farmers - propagate challenge to timelords """ header_hash = block.block.trunk_block.header.get_hash() @@ -595,9 +583,9 @@ async def block(block: peer_protocol.Block) -> AsyncGenerator[OutboundMessage, N block.block.trunk_block.proof_of_time.output.challenge_hash ) farmer_request = farmer_protocol.ProofOfSpaceFinalized(block.block.trunk_block.challenge.get_hash(), - block.block.trunk_block.challenge.height, - pos_quality, - difficulty) + block.block.trunk_block.challenge.height, + pos_quality, + difficulty) timelord_request = timelord_protocol.ChallengeStart(block.block.trunk_block.challenge.get_hash()) timelord_request_end = timelord_protocol.ChallengeStart(block.block.trunk_block.proof_of_time. output.challenge_hash) diff --git a/src/plotter.py b/src/plotter.py index ff9b288e69e2..4a076cc0ce05 100644 --- a/src/plotter.py +++ b/src/plotter.py @@ -1,4 +1,3 @@ -from hashlib import sha256 import logging import os import os.path @@ -75,8 +74,8 @@ async def new_challenge(new_challenge: plotter_protocol.NewChallenge): log.warning("Error using prover object. Reinitializing prover object.") db.provers[filename] = DiskProver(filename) quality_strings = prover.get_qualities_for_challenge(new_challenge.challenge_hash) - for index, quality_string in enumerate(quality_strings): - quality = sha256(new_challenge.challenge_hash + quality_string).digest() + for index, quality_str in enumerate(quality_strings): + quality = ProofOfSpace.quality_str_to_quality(new_challenge.challenge_hash, quality_str) db.challenge_hashes[quality] = (new_challenge.challenge_hash, filename, index) response: plotter_protocol.ChallengeResponse = plotter_protocol.ChallengeResponse( new_challenge.challenge_hash, diff --git a/src/server/server.py b/src/server/server.py index 159b89ad5d29..ae0fd4746a35 100644 --- a/src/server/server.py +++ b/src/server/server.py @@ -4,7 +4,11 @@ from typing import Tuple, AsyncGenerator, Callable, Optional from types import ModuleType from lib.aiter.aiter.server import start_server_aiter -from lib.aiter.aiter import parallel_map_aiter, map_aiter, join_aiters, iter_to_aiter, aiter_forker +from lib.aiter.aiter.map_aiter import map_aiter +from lib.aiter.aiter.join_aiters import join_aiters +from lib.aiter.aiter.parallel_map_aiter import parallel_map_aiter +from lib.aiter.aiter.iter_to_aiter import iter_to_aiter +from lib.aiter.aiter.aiter_forker import aiter_forker from lib.aiter.aiter.push_aiter import push_aiter from src.types.peer_info import PeerInfo from src.types.sized_bytes import bytes32 @@ -43,7 +47,7 @@ async def stream_reader_writer_to_connection(pair: Tuple[asyncio.StreamReader, a async def connection_to_outbound(connection: Connection, on_connect: Callable[[], AsyncGenerator[OutboundMessage, None]]) -> AsyncGenerator[ - OutboundMessage, None]: + Tuple[Connection, OutboundMessage], None]: """ Async generator which calls the on_connect async generator method, and yields any outbound messages. """ @@ -115,7 +119,7 @@ async def connection_to_message(connection: Connection) -> AsyncGenerator[Tuple[ connection.close() -async def handle_message(pair: Tuple[Connection, bytes], api: ModuleType) -> AsyncGenerator[ +async def handle_message(pair: Tuple[Connection, Message], api: ModuleType) -> AsyncGenerator[ Tuple[Connection, OutboundMessage], None]: """ Async generator which takes messages, parses, them, executes the right @@ -175,11 +179,11 @@ async def expand_outbound_messages(pair: Tuple[Connection, OutboundMessage]) -> yield item -async def initialize_pipeline(aiter: AsyncGenerator[Tuple[asyncio.StreamReader, asyncio.StreamWriter], None], +async def initialize_pipeline(aiter, api: ModuleType, connection_type: NodeType, on_connect: Callable[[], AsyncGenerator[OutboundMessage, None]] = None, - outbound_aiter: AsyncGenerator[OutboundMessage, None] = None, - wait_for_handshake=False) -> None: + outbound_aiter=None, + wait_for_handshake=False) -> asyncio.Task: # Maps a stream reader and writer to connection object connections_aiter = map_aiter(partial_func.partial_async(stream_reader_writer_to_connection, diff --git a/src/server/start_farmer.py b/src/server/start_farmer.py index 74826f807f67..af1203821dad 100644 --- a/src/server/start_farmer.py +++ b/src/server/start_farmer.py @@ -1,8 +1,9 @@ import asyncio import logging -from src.types.peer_info import PeerInfo - +from typing import List +from blspy import PrivateKey from src import farmer +from src.types.peer_info import PeerInfo from src.server.server import start_chia_client, start_chia_server from src.protocols.plotter_protocol import PlotterHandshake from src.server.outbound_message import OutboundMessage, Message, Delivery, NodeType @@ -18,7 +19,8 @@ async def main(): plotter_con_task, plotter_client = await start_chia_client(plotter_peer, farmer, NodeType.PLOTTER) # Sends a handshake to the plotter - msg = PlotterHandshake([sk.get_public_key() for sk in farmer.db.pool_sks]) + pool_sks: List[PrivateKey] = [PrivateKey.from_bytes(bytes.fromhex(ce)) for ce in farmer.config["pool_sks"]] + msg = PlotterHandshake([sk.get_public_key() for sk in pool_sks]) plotter_client.push(OutboundMessage(NodeType.PLOTTER, Message("plotter_handshake", msg), Delivery.BROADCAST)) # Starts the farmer server (which full nodes can connect to) diff --git a/src/types/__init__.py b/src/types/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/types/block_header.py b/src/types/block_header.py index ccf76240ab19..ddbc76848c1d 100644 --- a/src/types/block_header.py +++ b/src/types/block_header.py @@ -1,5 +1,4 @@ from blspy import PrependSignature -from hashlib import sha256 from src.util.streamable import streamable from src.util.ints import uint64 from src.types.sized_bytes import bytes32 @@ -22,4 +21,4 @@ class BlockHeader: @property def header_hash(self): - return bytes32(sha256(self.serialize()).digest()) + return self.get_hash() diff --git a/src/types/challenge.py b/src/types/challenge.py index 0e756630500b..7fb943ef69d7 100644 --- a/src/types/challenge.py +++ b/src/types/challenge.py @@ -8,5 +8,5 @@ class Challenge: proof_of_space_hash: bytes32 proof_of_time_output_hash: bytes32 height: uint32 - total_weight: uint64 - total_iters: uint64 + total_weight: uint64 # Total weight up to this point, not counting + total_iters: uint64 # Total iterations done up to this point, counting new PoT diff --git a/src/types/classgroup.py b/src/types/classgroup.py index 3bfa762adbcc..143cd52be069 100644 --- a/src/types/classgroup.py +++ b/src/types/classgroup.py @@ -1,5 +1,5 @@ -from ..util.streamable import streamable -from ..util.ints import int1024 +from src.util.streamable import streamable +from src.util.ints import int1024 @streamable diff --git a/src/types/full_block.py b/src/types/full_block.py index 3ee728948f75..8810e62f38fb 100644 --- a/src/types/full_block.py +++ b/src/types/full_block.py @@ -1,3 +1,5 @@ +from src.util.ints import uint32, uint64 +from src.types.sized_bytes import bytes32 from src.util.streamable import streamable from src.types.block_body import BlockBody from src.types.trunk_block import TrunkBlock @@ -8,25 +10,24 @@ class FullBlock: trunk_block: TrunkBlock body: BlockBody - def is_valid(self): - # TODO(alex): review, recursively. A lot of things are not verified. - body_hash = self.body.get_hash() - return (self.trunk_block.header.data.body_hash == body_hash - and self.trunk_block.is_valid() - and self.body.is_valid()) - @property - def prev_hash(self): + def prev_header_hash(self) -> bytes32: return self.trunk_block.header.data.prev_header_hash @property - def height(self): - return self.trunk_block.challenge.height + def height(self) -> uint32: + if (self.trunk_block.challenge): + return self.trunk_block.challenge.height + else: + return uint32(0) @property - def weight(self): - return self.trunk_block.challenge.total_weight + def weight(self) -> uint64: + if (self.trunk_block.challenge): + return self.trunk_block.challenge.total_weight + else: + return uint64(0) @property - def header_hash(self): + def header_hash(self) -> bytes32: return self.trunk_block.header.header_hash diff --git a/src/types/proof_of_space.py b/src/types/proof_of_space.py index 40890665854f..a0d8e4c269df 100644 --- a/src/types/proof_of_space.py +++ b/src/types/proof_of_space.py @@ -14,6 +14,8 @@ class ProofOfSpace: size: uint8 proof: List[uint8] + _cached_quality = None + def get_plot_seed(self) -> bytes32: return self.calculate_plot_seed(self.pool_pubkey, self.plot_pubkey) @@ -26,10 +28,14 @@ def verify_and_get_quality(self, challenge_hash: bytes32) -> Optional[bytes32]: bytes(self.proof)) if not quality_str: return None - self._cached_quality = sha256(challenge_hash + quality_str).digest() + self._cached_quality: bytes32 = self.quality_str_to_quality(challenge_hash, quality_str) return self._cached_quality @staticmethod def calculate_plot_seed(pool_pubkey: PublicKey, plot_pubkey: PublicKey) -> bytes32: return bytes32(sha256(pool_pubkey.serialize() + plot_pubkey.serialize()).digest()) + + @staticmethod + def quality_str_to_quality(challenge_hash: bytes32, quality_str: bytes) -> bytes32: + return bytes32(sha256(challenge_hash + quality_str).digest()) diff --git a/src/types/trunk_block.py b/src/types/trunk_block.py index 066e44890265..9294c7cf94e2 100644 --- a/src/types/trunk_block.py +++ b/src/types/trunk_block.py @@ -14,7 +14,7 @@ class TrunkBlock: header: BlockHeader @property - def prev_hash(self): + def prev_header_hash(self): return self.header.data.prev_header_hash @property diff --git a/src/util/genesis_block.py b/src/util/genesis_block.py index a99d1dea9559..fc7b1f225b01 100644 --- a/src/util/genesis_block.py +++ b/src/util/genesis_block.py @@ -4,7 +4,7 @@ from hashlib import sha256 from secrets import token_hex from chiapos import DiskPlotter, DiskProver -from blspy import PrivateKey, PrependSignature +from blspy import PublicKey, PrivateKey, PrependSignature from src.types.sized_bytes import bytes32 from src.types.full_block import FullBlock from src.types.trunk_block import TrunkBlock @@ -25,14 +25,14 @@ # Use the empty string as the seed for the private key sk: PrivateKey = PrivateKey.from_seed(b'') -pool_pk = sk.get_public_key() -plot_pk = sk.get_public_key() +pool_pk: PublicKey = sk.get_public_key() +plot_pk: PublicKey = sk.get_public_key() coinbase_target = sha256(sk.get_public_key().serialize()).digest() fee_target = sha256(sk.get_public_key().serialize()).digest() k = 19 n_wesolowski = 3 -genesis_block_hardcoded = 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\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\x13\x00\x00\x00\x98\xf9\xeb\x86\x90Kj\x01\x1cZk_\xe1\x9c\x03;Z\xb9V\xe2\xe8\xa5\xc8\n\x0c\xbbU\xa6\xc5\xc5\xbcH\xa3\xb3fd\xcd\xb8\x83\t\xa9\x97\x96\xb5\x91G \xb2\x9e\x05\\\x91\xe1<\xee\xb1\x06\xc3\x18~XuI\xc8\x8a\xb5b\xd7.7\x96Ej\xf3DThs\x18s\xa5\xd4C\x1ea\xfd\xd5\xcf\xb9o\x18\xea6n\xe22*\xb0]%\x15\xd0i\x83\xcb\x9a\xa2.+\x0f1\xcd\x03Z\xf3]\'\xbf|\x8b\xa6\xbcF\x10\xe8Q\x19\xaeZ~\xe5\x1f\xf1)\xa3\xfb\x82\x1a\xb8\x12\xce\x19\xc8\xde\xb9n\x08[\xef\xfd\xf9\x0c\xec\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\'\x8c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07~\xccf\x88\xef\xc8z;\xc9\x99\xdaSO\xa2\xdbC\x84\xe1\x9d\xc9Iv\xdbH\xb4\x9fiI\x1ew\xa78Gu\xe0\x9bg\xfdtBU\xfa\xe8\x9f\x13A\xb76iVx\xadU~\x8bj^\xeaV\xfd@\xdf,\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xedVsY\xdf\xa18.\x050\x90\x9c\xb3\xed\xaa\xb0Kv\x81{\x9a\xce=\xed\xc2\xc9m/\xce\x9b[\x04M\xbb\xe8\xdeCNg\xb6\xee\x01\x8e{\x8dEk\xecHt\x8d\xab\xbb\x19\x91\xfa\x1aT\xb4\xf871\xbc\x1b\x95~\xd5|\xc3\xb8\x84oG\xf2\xe2\x93b,\xdf\xae\x89MgzL\xcb\xfbb\xae\x85./\x00\x06i\xd3\x16\xf7\x85\xab\ru\xb7\xa6x{n\xc6\x8c\x91g\x1c\x9f\xee8E\x02\xdb\x9fd\xc6q\x15k@^\xe9\r"\x05q\xbfqa\xc6r\'\xd5\xa5\xbdyjx\xacG\xde8\x9e\xde\x9ah\xc4\x01\xbe\xdf\x94\xe1\x00\x05\xe9+\x00P\xb5w\x9d|\xcbcG\x8c\xd9\xdd3\x11\x1fh)\x95\xf2\xfe\xfeZAw\xf1\xff\xdb\xd1\xd6\x90\x8f\xf2\xbaCz]*)\xb7\xffv\xc9\xdb\xben\xb7\xfb%D\tN\x04CW+/7z\xe7\x04Q\x00r\xb7G\x9c\xb4\xa1`\x97\x8ddo\x9bv\x89+\xeaHx>le\x95\xde\xe3\xbb\x11=\x1a2\xc5\xd8\xcb\x01\x11\xaf\xac\xa9\x8b\xcbf\xa5\x8dR\t\xad5\x17\xf9\xfb4Z\xfe\xf6G\xff&4\r\xfe\x03\xa0\x88\xe3(\xff\xa1s\xf0\\\xac\xf4\x91\xe0\xc9\x8f|\x9e\x1c`+\xe5\xb8/\x18:\xad[f\x88\x94\xd9o\xcfa\xb6\x96\xcf\x0b%\x89i\x167bv\x18\x7fa\x18\nJ\xf6\x87\x97\xfb\x9dX.\x919T)lR<\xbcTf1\x00)\x99A\x95\'r\xed\xd6\xdb;\xb7\x06/\x1b\xcf\x9b\xcfD\x10!\xec\xa2\xa5@s:@C>\xc0v\xeb\xf7\xbcF\xcb\xb3\x85<5\'\xf2\xf0\xce\xcc\xf1\x82\xe0\xc5~\x88\xf8\xc2\x86ff\xc8\x13\xb4\x87\x98\xdf\xb18\x00\x0c\xc4\x97\xe52\xd7)n\xcb*\x9f\x97dP\x1c\xd8<\t\xd0\xa8V\xa6{6\xbfr?H\xa9\x8e\x99\xa2\xff\xbc\x81\x8bP7\x9e\x8b\xa7\x98]L\xbaM\xd2\x83*\xdf!Z\xaf0\xa8\xff_\x0f\xb8\xcc`l\xbaQ\x00\x12\xfd\x1aQ\xbe#t\x14\x1cF\xa0k\xdc\x08\x9b0\xe1>\x14\xf6\xc2.\xd8jp\xa6\xf4\xe7\xc9.o\xd1\x02\x1d\xd9\n\x1f\xaa\x9b\xc00_zF\x8f\xac\xbb\xe9\x9b`\x8c\xdd\xcb\xb0[=\x07\xe0\xc3\x10\xcehG\x89<\x0b\x08)\xd1\xe8\x99z9\xed\x08YJ6\x185\xd1\xbf9e&4\xb0\x18\xb7\x93\xfb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]e\xe8\xd1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00~[u\x1f\x81\x7f\x0c)\x05\xe6\xfd\xe5\xd14\\a\n\xc6I\xccJ\x0cXk\xcf,Z\x1c\xdb>\xe0\xc3z!\xc9N\xd5\x03\x8b^\xd9\xe6\xc7I\xba\xb1\x0fm\xd4\xa0=\xb6^s\x94_f\xb5\xc1\\n\xfe\xf9\xd2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00A\xd0\xe6k\xb8\x9d\x9ef\x86\x86\xe2\xb4\xe0zt\xee\x86mX+L\xc4\xd7U/\xcc\x12\x8c\x81\x1a(\x17\x05\xcdIc\x066\xe8\xe2\xe1#Z\xb6\xe1\xd4b\xd3\x9b\x17(\x08r\xb0P\x02\xae\xa7>eO\x97-.\xd3X\x00\x00\x9a\xae\x8d\xd5\xcc\xd6\xc5\x80\xcc\xc2}V\xfc\xac\xcdAl\x97\xd0\xc3\x93:\xb6\xeb7t\x17O\xfb$\x01\xea\xa2\x13\xab=bk\x84|\xc4W\xac"\x1f<\x8d\x02@\x94\xa4f~\x89}\xfbsP\xd4\xaaE[\xb1\x00nT\x91\xc2\xd7D\x99f\xb3A\xbc\xdap\xf7\x1b\xd6\x93\xb8\xe8\x81\x96\x0f\xc1;\x85~\xba\xd5w&7\x17J\xec\xf3\x02\xff\x83\x1aHS\xd9g\xd0\xc1\xcef\xa1\xb6bj\xb1\xc5oR\xf6`\xe7\xe5\x97\xc7\xee\x83\x03\x98\x14\x9f%\x9c\x93\xad\xd8^\x9bx\x00.9T\x1fVo\x98\xb6\xd9\x99x\xc7\x859K\x12\xec\xc3\xe5\xf9\xb5+\xe0\x01\xed\xab\xd6\xcd\x1d[7\xe2\xf5\x11^\xad\xefPl\x8cC7\xd4y\xba\xc1j\x00\x15\x05\xf6K\xac\xfa!\x89\xf0\x88z"t\xc3\x121\x000`s\xaeS\xd0O}\xd9k\xe9\x96jC\xb0,\xfa\x086*Q)\x8f\x1a.\r#h\xb3\xf5T\xc4\xa5l$u\xcd}\xae"\xde\xdbO\xb2{\xdc\x1eQ\x8b\xb5\x9bKp\xa3cO\xf2\xbde\xe91\x16\xdc\x9d\xff\xef\xa6{\x8c\x04\xd1\r\x1d\xc8\xd8\x97\xd0\xee2\xfe\xc0\xfa\x0c\xf5\xb4n\xe6|r\xd8\x88\xee\x8cQ\x9bX\x1f\n\xcf\xb2Yh\x01\'6j\xfd\xe7\x0c\xbc\xb1\xa2dy\t\xf1\xc1\x03 \xcf\xbcX\xfb3H\x9fM2\xa7\x00\x11\x871\xfb\xc1\xd7M]\xfb\xfeo\xb6\xceBt\x9c\xd5\xc7d\xee\xe7\xbf\x0c\x08\x96\xd7S\x056\xf90\xf8\xc4\xf6!\xc1N\xf5\x9e@;\x81\xc6\xca\xe1\xa4*=r\xd0\xd3/U\xc3\x14\x99g\xb5\x96\xa4(\x1ek\xf4\xff\xfb$;\xb7\x134\x19Nmb\xc4\x04\xec\x02\xd0\xc2%\xf5L\xb5N\xc0\n\x8e=R\xdem\xd9%\xa6\xec\x01A\x14-\xdb\xa4Iz.;J\xd4<\xf3\xab\xfd9\x92$\x05\xe9\x1b\xc0\x0b\x13\xcd\xfd[\x9c\xc1\x97\x8f\x00H\xce\x90@s\n\xeb\x90\xd2\xfdtvle\xc2\x19E&K\x8e\xbf\xea\x96\xea6i\xff\x96\x83\xe0\x93\xe3\xa0?\xb5h\xff/\x96\x9f\xdb\xce\xe76\x8df\xe0\x02\xd0\xc9\xfca\xd4\xc2\xd3\xc8\xaf\xe7\xeb\xe0\xc3\xbd;\x13\xc7Lf\x0c"\x1aC?qX\xc3\xd1jY\tZ\x01\x13\x81R\xcb\n\xd3\tv\x8a\x10\xba\xa6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00<\x00\x00\x00\x00\x00\x00/u\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]\x89\x94\xda\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00~[u\x1f\x81\x7f\x0c)\x05\xe6\xfd\xe5\xd14\\a\n\xc6I\xccJ\x0cXk\xcf,Z\x1c\xdb>\xe0\xc3z!\xc9N\xd5\x03\x8b^\xd9\xe6\xc7I\xba\xb1\x0fm\xd4\xa0=\xb6^s\x94_f\xb5\xc1\\n\xfe\xf9\xd2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00H1V\xdeN\xcc\x8a;\x1a\x8b\xe6v\x9d\x82U\xfc?\xba2K\xdfiE\xfd\x16\xe6t\x90\x86\x14;\x1aT>\xed>u\xe8P\x87\xdf7i|/\xbf\x9a3\x10\x8e\xe0\xa9p\xc3\xdcd\x86\'A\x17:6\xc2\xdc\xe1\xc7b\x9f\xe0\xbe\xd1\xfb\x8eG\xfe?\xde\xee]\xee\x1f711L\t\x0b\xbcG+\xa8b\x0e\\O\xe5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n+\x93\xa0\x02\xe4\xc2\x1d\xaa5R\xe5,\xbd\xa5\x15|%}\xa4@\xe5\x11\x00\x80\x1fG\x8aH\x0b\xe7\xe9\x10\xd3tK\xda`\xb5u\xca\x8c\xa2\xf7n\x1d\xd5\x92l\xb13k\xdb\n+\xbe/\x1e\xc0\xfe\xbf\xd9\x83\x88V\x11]~.<\x14\x0f\xce`\x8b\xbf\xb9\xa7\xce"6\x19\xa5\x19|\x81!r\x15V\xa6\x82\x07\x96w\x98F\xce\xb2(G\xcfm\x17@t\xb2\x1b\xba\xcf4I}\x0b\xc4\n\xd4\x9b\xe2E\x9e\x84\x98mY||\xa8[+\x93\xa0\x02\xe4\xc2\x1d\xaa5R\xe5,\xbd\xa5\x15|%}\xa4@\xe5\x11\x00\x80\x1fG\x8aH\x0b\xe7\xe9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # noqa: E501 def create_genesis_block(challenge_hash=bytes([0]*32)) -> FullBlock: @@ -76,6 +76,7 @@ def create_genesis_block(challenge_hash=bytes([0]*32)) -> FullBlock: coinbase: CoinbaseInfo = CoinbaseInfo(0, block_rewards.calculate_block_reward(uint32(0)), coinbase_target) coinbase_sig: PrependSignature = sk.sign_prepend(coinbase.serialize()) + fees_target: FeesTarget = FeesTarget(fee_target, 0) body: BlockBody = BlockBody(coinbase, coinbase_sig, fees_target, None, bytes([0]*32)) @@ -86,17 +87,16 @@ def create_genesis_block(challenge_hash=bytes([0]*32)) -> FullBlock: proof_of_space.get_hash(), body.get_hash(), bytes([0]*32)) - header_sig: PrependSignature = sk.sign_prepend(header_data.serialize()) - header: BlockHeader = BlockHeader(header_data, header_sig) + header_hash_sig: PrependSignature = sk.sign_prepend(header_data.get_hash()) + + header: BlockHeader = BlockHeader(header_data, header_hash_sig) challenge = Challenge(proof_of_space.get_hash(), proof_of_time.get_hash(), 0, - uint64(constants.DIFFICULTY_STARTING), 0) + uint64(constants.DIFFICULTY_STARTING), number_iters) trunk_block = TrunkBlock(proof_of_space, proof_of_time, challenge, header) full_block: FullBlock = FullBlock(trunk_block, body) return full_block - -# block = create_genesis_block() -# print(block.serialize()) +# print(create_genesis_block().serialize()) diff --git a/tests/test_blockchain.py b/tests/test_blockchain.py index e69de29bb2d1..dd4a610034a8 100644 --- a/tests/test_blockchain.py +++ b/tests/test_blockchain.py @@ -0,0 +1,22 @@ +import unittest +from src.blockchain import Blockchain +# from src.util.genesis_block import genesis_block_hardcoded +from src.util.ints import uint64 + + +class GenesisBlockTest(unittest.TestCase): + def test_basic_blockchain(self): + bc1: Blockchain = Blockchain() + assert len(bc1.get_current_heads()) == 1 + genesis_block = bc1.get_current_heads()[0] + assert genesis_block.height == 0 + assert bc1.get_trunk_blocks_by_height([uint64(0)], genesis_block.header_hash) == genesis_block + assert bc1.get_difficulty(genesis_block.header_hash) == genesis_block.trunk_block.challenge.total_weight + assert bc1.get_difficulty(genesis_block.header_hash) == bc1.get_next_difficulty(genesis_block.header_hash) + assert bc1.get_vdf_rate_estimate() is None + + +class ValidateBlock(unittest.TestCase): + # sample_block = + def test_prev_pointer(self): + pass