async def test_timestamp(self, initial_blockchain): blocks, b = initial_blockchain # Time too far in the past block_bad = FullBlock( HeaderBlock( blocks[9].header_block.proof_of_space, blocks[9].header_block.proof_of_time, blocks[9].header_block.challenge, Header( HeaderData( blocks[9].header_block.header.data.prev_header_hash, blocks[9].header_block.header.data.timestamp - 1000, blocks[9].header_block.header.data.filter_hash, blocks[9].header_block.header.data.proof_of_space_hash, blocks[9].header_block.header.data.body_hash, blocks[9].header_block.header.data.extension_data, ), blocks[9].header_block.header.harvester_signature, ), ), blocks[9].body, ) assert (await b.receive_block( block_bad, blocks[8].header_block)) == ReceiveBlockResult.INVALID_BLOCK # Time too far in the future block_bad = FullBlock( HeaderBlock( blocks[9].header_block.proof_of_space, blocks[9].header_block.proof_of_time, blocks[9].header_block.challenge, Header( HeaderData( blocks[9].header_block.header.data.prev_header_hash, uint64(int(time.time() + 3600 * 3)), blocks[9].header_block.header.data.filter_hash, blocks[9].header_block.header.data.proof_of_space_hash, blocks[9].header_block.header.data.body_hash, blocks[9].header_block.header.data.extension_data, ), blocks[9].header_block.header.harvester_signature, ), ), blocks[9].body, ) assert (await b.receive_block( block_bad, blocks[8].header_block)) == ReceiveBlockResult.INVALID_BLOCK
async def header_signature( self, header_signature: farmer_protocol.HeaderSignature ) -> OutboundMessageGenerator: """ Signature of header hash, by the harvester. This is enough to create an unfinished block, which only needs a Proof of Time to be finished. If the signature is valid, we call the unfinished_block routine. """ async with self.store.lock: candidate: Optional[ Tuple[Body, HeaderData, ProofOfSpace]] = await self.store.get_candidate_block( header_signature.pos_hash) if candidate is None: log.warning( f"PoS hash {header_signature.pos_hash} not found in database" ) return # Verifies that we have the correct header and body self.stored block_body, block_header_data, pos = candidate assert block_header_data.get_hash() == header_signature.header_hash block_header: Header = Header(block_header_data, header_signature.header_signature) header: HeaderBlock = HeaderBlock(pos, None, None, block_header) unfinished_block_obj: FullBlock = FullBlock(header, block_body) # Propagate to ourselves (which validates and does further propagations) request = peer_protocol.UnfinishedBlock(unfinished_block_obj) async for m in self.unfinished_block(request): # Yield all new messages (propagation to peers) yield m
def get_header_block(self, block: FullBlock) -> Optional[HeaderBlock]: challenge: Optional[Challenge] = self.get_challenge(block) if challenge is None or block.proof_of_time is None: return None return HeaderBlock( block.proof_of_space, block.proof_of_time, challenge, block.header )
async def test_body_hash(self, initial_blockchain): blocks, b = initial_blockchain block_bad = FullBlock( HeaderBlock( blocks[9].header_block.proof_of_space, blocks[9].header_block.proof_of_time, blocks[9].header_block.challenge, Header( HeaderData( blocks[9].header_block.header.data.prev_header_hash, blocks[9].header_block.header.data.timestamp, blocks[9].header_block.header.data.filter_hash, blocks[9].header_block.header.data.proof_of_space_hash, bytes([1] * 32), blocks[9].header_block.header.data.extension_data, ), blocks[9].header_block.header.harvester_signature, ), ), blocks[9].body, ) assert (await b.receive_block( block_bad, blocks[8].header_block)) == ReceiveBlockResult.INVALID_BLOCK
def get_block_header(self) -> HeaderBlock: # Create filter if self.is_block(): byte_array_tx: List[bytes32] = [] removals_names, addition_coins = self.tx_removals_and_additions() for coin in addition_coins: byte_array_tx.append(bytearray(coin.puzzle_hash)) for name in removals_names: byte_array_tx.append(bytearray(name)) for coin in self.get_included_reward_coins(): byte_array_tx.append(bytearray(coin.puzzle_hash)) bip158: PyBIP158 = PyBIP158(byte_array_tx) encoded_filter: bytes = bytes(bip158.GetEncoded()) else: encoded_filter = b"" return HeaderBlock( self.finished_sub_slots, self.reward_chain_sub_block, self.challenge_chain_sp_proof, self.challenge_chain_ip_proof, self.reward_chain_sp_proof, self.reward_chain_ip_proof, self.infused_challenge_chain_ip_proof, self.foliage_sub_block, self.foliage_block, encoded_filter, self.transactions_info, )
def get_block_header(self, addition_coins=None, removals_names=None) -> HeaderBlock: # Create filter byte_array_tx: List[bytes32] = [] if self.is_transaction_block(): if addition_coins is None or removals_names is None: removals_names, addition_coins = self.tx_removals_and_additions() assert removals_names is not None assert addition_coins is not None for coin in addition_coins: byte_array_tx.append(bytearray(coin.puzzle_hash)) for name in removals_names: byte_array_tx.append(bytearray(name)) for coin in self.get_included_reward_coins(): byte_array_tx.append(bytearray(coin.puzzle_hash)) bip158: PyBIP158 = PyBIP158(byte_array_tx) encoded_filter: bytes = bytes(bip158.GetEncoded()) return HeaderBlock( self.finished_sub_slots, self.reward_chain_block, self.challenge_chain_sp_proof, self.challenge_chain_ip_proof, self.reward_chain_sp_proof, self.reward_chain_ip_proof, self.infused_challenge_chain_ip_proof, self.foliage, self.foliage_transaction_block, encoded_filter, self.transactions_info, )
async def proof_of_time_finished( self, request: timelord_protocol.ProofOfTimeFinished ) -> OutboundMessageGenerator: """ A proof of time, received by a peer timelord. We can use this to complete a block, and call the block routine (which handles propagation and verification of blocks). """ async with self.store.lock: dict_key = ( request.proof.challenge_hash, request.proof.number_of_iterations, ) unfinished_block_obj: Optional[ FullBlock] = await self.store.get_unfinished_block(dict_key) if not unfinished_block_obj: log.warning( f"Received a proof of time that we cannot use to complete a block {dict_key}" ) return prev_full_block = await self.store.get_block( unfinished_block_obj.prev_header_hash) assert prev_full_block prev_block: HeaderBlock = prev_full_block.header_block difficulty: uint64 = self.blockchain.get_next_difficulty( unfinished_block_obj.prev_header_hash) assert prev_block.challenge challenge: Challenge = Challenge( request.proof.challenge_hash, unfinished_block_obj.header_block.proof_of_space.get_hash(), request.proof.output.get_hash(), uint32(prev_block.challenge.height + 1), uint64(prev_block.challenge.total_weight + difficulty), uint64(prev_block.challenge.total_iters + request.proof.number_of_iterations), ) new_header_block = HeaderBlock( unfinished_block_obj.header_block.proof_of_space, request.proof, challenge, unfinished_block_obj.header_block.header, ) new_full_block: FullBlock = FullBlock(new_header_block, unfinished_block_obj.body) async with self.store.lock: sync_mode = await self.store.get_sync_mode() if sync_mode: async with self.store.lock: await self.store.add_potential_future_block(new_full_block) else: async for msg in self.block(peer_protocol.Block(new_full_block)): yield msg
async def get_header_block_at(self, heights: List[uint32]) -> List[HeaderBlock]: if len(heights) == 0: return [] heights_db = tuple(heights) formatted_str = f'SELECT block from header_blocks WHERE height in ({"?," * (len(heights_db) - 1)}?)' cursor = await self.db.execute(formatted_str, heights_db) rows = await cursor.fetchall() await cursor.close() return [HeaderBlock.from_bytes(row[0]) for row in rows]
def batch_pre_validate_blocks( constants_dict: Dict, blocks_pickled: Dict[bytes, bytes], header_blocks_pickled: List[bytes], transaction_generators: List[Optional[bytes]], check_filter: bool, expected_difficulty: List[uint64], expected_sub_slot_iters: List[uint64], validate_transactions: bool, ) -> List[bytes]: assert len(header_blocks_pickled) == len(transaction_generators) blocks = {} for k, v in blocks_pickled.items(): blocks[k] = BlockRecord.from_bytes(v) results: List[PreValidationResult] = [] constants: ConsensusConstants = dataclass_from_dict( ConsensusConstants, constants_dict) for i in range(len(header_blocks_pickled)): try: header_block: HeaderBlock = HeaderBlock.from_bytes( header_blocks_pickled[i]) generator: Optional[bytes] = transaction_generators[i] required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) cost_result: Optional[CostResult] = None error_int: Optional[uint16] = None if error is not None: error_int = uint16(error.code.value) if constants_dict["NETWORK_TYPE"] == NetworkType.MAINNET.value: cost_result = None else: if not error and generator is not None and validate_transactions: cost_result = calculate_cost_of_program( SerializedProgram.from_bytes(generator), constants.CLVM_COST_RATIO_CONSTANT) results.append( PreValidationResult(error_int, required_iters, cost_result)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None)) return [bytes(r) for r in results]
async def test_harvester_signature(self, initial_blockchain): blocks, b = initial_blockchain # Time too far in the past block_bad = FullBlock( HeaderBlock( blocks[9].header_block.proof_of_space, blocks[9].header_block.proof_of_time, blocks[9].header_block.challenge, Header( blocks[9].header_block.header.data, PrivateKey.from_seed(b"0").sign_prepend(b"random junk"), ), ), blocks[9].body, ) assert (await b.receive_block(block_bad)) == ReceiveBlockResult.INVALID_BLOCK
async def get_header_blocks_in_range( self, start: int, stop: int, ) -> Dict[bytes32, HeaderBlock]: formatted_str = f"SELECT header_hash, block from header_blocks WHERE height >= {start} and height <= {stop}" cursor = await self.db.execute(formatted_str) rows = await cursor.fetchall() await cursor.close() ret: Dict[bytes32, HeaderBlock] = {} for row in rows: header_hash_bytes, sub_block_bytes = row header_hash = bytes.fromhex(header_hash_bytes) ret[header_hash] = HeaderBlock.from_bytes(sub_block_bytes) return ret
async def test_invalid_pos(self, initial_blockchain): blocks, b = initial_blockchain bad_pos = [i for i in blocks[9].header_block.proof_of_space.proof] bad_pos[0] = uint8((bad_pos[0] + 1) % 256) # Proof of space invalid block_bad = FullBlock( HeaderBlock( ProofOfSpace( blocks[9].header_block.proof_of_space.challenge_hash, blocks[9].header_block.proof_of_space.pool_pubkey, blocks[9].header_block.proof_of_space.plot_pubkey, blocks[9].header_block.proof_of_space.size, bad_pos, ), blocks[9].header_block.proof_of_time, blocks[9].header_block.challenge, blocks[9].header_block.header, ), blocks[9].body, ) assert (await b.receive_block(block_bad)) == ReceiveBlockResult.INVALID_BLOCK
def _create_block( self, test_constants: Dict, challenge_hash: bytes32, height: uint32, prev_header_hash: bytes32, prev_iters: uint64, prev_weight: uint64, timestamp: uint64, difficulty: uint64, ips: uint64, seed: bytes, ) -> FullBlock: """ Creates a block with the specified details. Uses the stored plots to create a proof of space, and also evaluates the VDF for the proof of time. """ prover = None plot_pk = None plot_sk = None qualities: List[bytes] = [] for pn in range(num_plots): # Allow passing in seed, to create reorgs and different chains seeded_pn = (pn + 17 * int.from_bytes(seed, "big")) % num_plots filename = self.filenames[seeded_pn] plot_pk = plot_pks[seeded_pn] plot_sk = plot_sks[seeded_pn] prover = DiskProver(os.path.join(self.plot_dir, filename)) qualities = prover.get_qualities_for_challenge(challenge_hash) if len(qualities) > 0: break assert prover assert plot_pk assert plot_sk if len(qualities) == 0: raise NoProofsOfSpaceFound("No proofs for this challenge") proof_xs: bytes = prover.get_full_proof(challenge_hash, 0) proof_of_space: ProofOfSpace = ProofOfSpace( challenge_hash, pool_pk, plot_pk, k, [uint8(b) for b in proof_xs]) number_iters: uint64 = pot_iterations.calculate_iterations( proof_of_space, difficulty, ips, test_constants["MIN_BLOCK_TIME"]) disc: int = create_discriminant( challenge_hash, test_constants["DISCRIMINANT_SIZE_BITS"]) start_x: ClassGroup = ClassGroup.from_ab_discriminant(2, 1, disc) y_cl, proof_bytes = create_proof_of_time_nwesolowski( disc, start_x, number_iters, disc, n_wesolowski) output = ClassgroupElement(y_cl[0], y_cl[1]) proof_of_time = ProofOfTime( challenge_hash, number_iters, output, n_wesolowski, [uint8(b) for b in proof_bytes], ) coinbase: CoinbaseInfo = CoinbaseInfo( height, block_rewards.calculate_block_reward(uint32(height)), coinbase_target, ) coinbase_sig: PrependSignature = pool_sk.sign_prepend(bytes(coinbase)) fees_target: FeesTarget = FeesTarget(fee_target, uint64(0)) solutions_generator: bytes32 = sha256(seed).digest() cost = uint64(0) body: Body = Body(coinbase, coinbase_sig, fees_target, None, solutions_generator, cost) header_data: HeaderData = HeaderData( prev_header_hash, timestamp, bytes([0] * 32), proof_of_space.get_hash(), body.get_hash(), bytes([0] * 32), ) header_hash_sig: PrependSignature = plot_sk.sign_prepend( header_data.get_hash()) header: Header = Header(header_data, header_hash_sig) challenge = Challenge( challenge_hash, proof_of_space.get_hash(), proof_of_time.get_hash(), height, uint64(prev_weight + difficulty), uint64(prev_iters + number_iters), ) header_block = HeaderBlock(proof_of_space, proof_of_time, challenge, header) full_block: FullBlock = FullBlock(header_block, body) return full_block
async def create( key_config: Dict, config: Dict, db_path: Path, constants: Dict, name: str = None, ): self = WalletStateManager() self.config = config self.constants = constants if name: self.log = logging.getLogger(name) else: self.log = logging.getLogger(__name__) self.lock = asyncio.Lock() self.db_connection = await aiosqlite.connect(db_path) self.wallet_store = await WalletStore.create(self.db_connection) self.tx_store = await WalletTransactionStore.create(self.db_connection) self.puzzle_store = await WalletPuzzleStore.create(self.db_connection) self.user_store = await WalletUserStore.create(self.db_connection) self.lca = None self.sync_mode = False self.height_to_hash = {} self.block_records = await self.wallet_store.get_lca_path() genesis = FullBlock.from_bytes(self.constants["GENESIS_BLOCK"]) self.genesis = genesis self.state_changed_callback = None self.pending_tx_callback = None self.difficulty_resets_prev = {} self.db_path = db_path main_wallet_info = await self.user_store.get_wallet_by_id(1) assert main_wallet_info is not None self.main_wallet = await Wallet.create(config, key_config, self, main_wallet_info) self.wallets = {} main_wallet = await Wallet.create(config, key_config, self, main_wallet_info) self.wallets[main_wallet_info.id] = main_wallet for wallet_info in await self.get_all_wallets(): self.log.info(f"wallet_info {wallet_info}") if wallet_info.type == WalletType.STANDARD_WALLET: if wallet_info.id == 1: continue wallet = await Wallet.create(config, key_config, self, main_wallet_info) self.wallets[wallet_info.id] = wallet elif wallet_info.type == WalletType.RATE_LIMITED: wallet = await RLWallet.create( config, key_config, self, wallet_info, self.main_wallet, ) self.wallets[wallet_info.id] = wallet async with self.puzzle_store.lock: await self.create_more_puzzle_hashes(from_zero=True) if len(self.block_records) > 0: # Initializes the state based on the DB block records # Header hash with the highest weight self.lca = max((item[1].weight, item[0]) for item in self.block_records.items())[1] for key, value in self.block_records.items(): self.height_to_hash[value.height] = value.header_hash # Checks genesis block is the same in config, as in DB assert self.block_records[genesis.header_hash].height == 0 assert self.block_records[ genesis.header_hash].weight == genesis.weight else: # Loads the genesis block if there are no blocks genesis_challenge = Challenge( genesis.proof_of_space.challenge_hash, std_hash(genesis.proof_of_space.get_hash() + genesis.proof_of_time.output.get_hash()), None, ) genesis_hb = HeaderBlock( genesis.proof_of_space, genesis.proof_of_time, genesis_challenge, genesis.header, ) await self.receive_block( BlockRecord( genesis.header_hash, genesis.prev_header_hash, uint32(0), genesis.weight, [], [], genesis_hb.header.data.total_iters, genesis_challenge.get_hash(), ), genesis_hb, ) return self