def __validate_block_headers(self, block_header_list: List[MinorBlockHeader]): for i in range(len(block_header_list) - 1): header, prev = block_header_list[i:i + 2] # type: MinorBlockHeader if header.height != prev.height + 1: return False if header.hash_prev_minor_block != prev.get_hash(): return False try: # Note that PoSW may lower diff, so checks here are necessary but not sufficient # More checks happen during block addition shard_config = self.shard.env.quark_chain_config.shards[ header.branch.get_full_shard_id()] consensus_type = shard_config.CONSENSUS_TYPE diff = header.difficulty if shard_config.POSW_CONFIG.ENABLED: diff //= shard_config.POSW_CONFIG.DIFF_DIVIDER validate_seal( header, consensus_type, adjusted_diff=diff, qkchash_with_rotation_stats=consensus_type == ConsensusType.POW_QKCHASH and self.shard.state._qkchashx_enabled(header), ) except Exception as e: Logger.warning( "[{}] got block with bad seal in sync: {}".format( header.branch.to_str(), str(e))) return False return True
async def handle_new_block(self, block): """ This is a fast path for block propagation. The block is broadcasted to peers before being added to local state. 0. if local shard is syncing, doesn't make sense to add, skip 1. if block parent is not in local state/new block pool, discard (TODO: is this necessary?) 2. if already in cache or in local state/new block pool, pass 3. validate: check time, difficulty, POW 4. add it to new minor block broadcast cache 5. broadcast to all peers (minus peer that sent it, optional) 6. add_block() to local state (then remove from cache) also, broadcast tip if tip is updated (so that peers can sync if they missed blocks, or are new) """ if self.synchronizer.running: # TODO optional: queue the block if it came from broadcast to so that once sync is over, # catch up immediately return if block.header.get_hash() in self.state.new_block_pool: return if self.state.db.contain_minor_block_by_hash(block.header.get_hash()): return if not self.state.db.contain_minor_block_by_hash( block.header.hash_prev_minor_block): if block.header.hash_prev_minor_block not in self.state.new_block_pool: return # Doing full POSW check requires prev block has been added to the state, which could # slow down block propagation. # TODO: this is a copy of the code in SyncTask.__validate_block_headers. this it a helper try: header = block.header # Note that PoSW may lower diff, so checks here are necessary but not sufficient # More checks happen during block addition shard_config = self.env.quark_chain_config.shards[ header.branch.get_full_shard_id()] consensus_type = shard_config.CONSENSUS_TYPE diff = header.difficulty if shard_config.POSW_CONFIG.ENABLED: diff //= shard_config.POSW_CONFIG.DIFF_DIVIDER validate_seal(header, consensus_type, adjusted_diff=diff) except Exception as e: Logger.warning( "[{}] got block with bad seal in handle_new_block: {}".format( header.branch.to_str(), str(e))) raise e if block.header.create_time > time_ms() // 1000 + 30: return self.state.new_block_pool[block.header.get_hash()] = block Logger.info("[{}/{}] got new block with height {}".format( block.header.branch.get_chain_id(), block.header.branch.get_shard_id(), block.header.height, )) self.broadcast_new_block(block) await self.add_block(block)
def validate_block_header(self, block_header: RootBlockHeader, block_hash=None): """ Validate the block header. """ height = block_header.height if height < 1: raise ValueError("unexpected height") if not self.db.contain_root_block_by_hash( block_header.hash_prev_block): raise ValueError("previous hash block mismatch") prev_block_header = self.db.get_root_block_header_by_hash( block_header.hash_prev_block) if prev_block_header.height + 1 != height: raise ValueError("incorrect block height") if (block_header.create_time > time_ms() // 1000 + ALLOWED_FUTURE_BLOCKS_TIME_VALIDATION): raise ValueError("block too far into future") if block_header.create_time <= prev_block_header.create_time: raise ValueError( "incorrect create time tip time {}, new block time {}".format( block_header.create_time, prev_block_header.create_time)) if (len(block_header.extra_data) > self.env.quark_chain_config.BLOCK_EXTRA_DATA_SIZE_LIMIT): raise ValueError("extra_data in block is too large") header_hash = block_header.get_hash() if block_hash is None: block_hash = header_hash # Check difficulty, potentially adjusted by guardian mechanism adjusted_diff = None # type: Optional[int] if not self.env.quark_chain_config.SKIP_ROOT_DIFFICULTY_CHECK: diff = self.diff_calc.calculate_diff_with_parent( prev_block_header, block_header.create_time) if diff != block_header.difficulty: raise ValueError("incorrect difficulty") # lower the difficulty for root block signed by guardian if block_header.verify_signature( self.env.quark_chain_config.guardian_public_key): adjusted_diff = Guardian.adjust_difficulty( diff, block_header.height) if (block_header.difficulty + prev_block_header.total_difficulty != block_header.total_difficulty): raise ValueError("incorrect total difficulty") # Check PoW if applicable consensus_type = self.root_config.CONSENSUS_TYPE validate_seal(block_header, consensus_type, adjusted_diff=adjusted_diff) return block_hash
async def add(block_to_add): h = block_to_add.header diff = h.difficulty if h.verify_signature(priv.public_key): diff = Guardian.adjust_difficulty(diff, h.height) validate_seal(block_to_add.header, doublesha, adjusted_diff=diff)
def __validate_block_headers(self, block_header_list): for i in range(len(block_header_list) - 1): header, prev = block_header_list[i:i + 2] if header.height != prev.height + 1: return False if header.hash_prev_minor_block != prev.get_hash(): return False shard_id = header.branch.get_shard_id() consensus_type = self.shard.env.quark_chain_config.SHARD_LIST[ shard_id].CONSENSUS_TYPE validate_seal(header, consensus_type) return True
def __validate_block_header(self, block_header: RootBlockHeader, adjusted_diff: int = None): """ Validate the block header. """ height = block_header.height if height < 1: raise ValueError("unexpected height") if block_header.version != 0: raise ValueError("incorrect root block version") if not self.db.contain_root_block_by_hash( block_header.hash_prev_block): raise ValueError("previous hash block mismatch") prev_block_header = self.db.get_root_block_header_by_hash( block_header.hash_prev_block) if prev_block_header.height + 1 != height: raise ValueError("incorrect block height") if (block_header.create_time > time_ms() // 1000 + ALLOWED_FUTURE_BLOCKS_TIME_VALIDATION): raise ValueError("block too far into future") if block_header.create_time <= prev_block_header.create_time: raise ValueError( "incorrect create time tip time {}, new block time {}".format( block_header.create_time, prev_block_header.create_time)) if (len(block_header.extra_data) > self.env.quark_chain_config.BLOCK_EXTRA_DATA_SIZE_LIMIT): raise ValueError("extra_data in block is too large") # Check difficulty, potentially adjusted by guardian mechanism if not self.env.quark_chain_config.SKIP_ROOT_DIFFICULTY_CHECK: diff = self.diff_calc.calculate_diff_with_parent( prev_block_header, block_header.create_time) if diff != block_header.difficulty: raise ValueError("incorrect difficulty") if (block_header.difficulty + prev_block_header.total_difficulty != block_header.total_difficulty): raise ValueError("incorrect total difficulty") # Check PoW if applicable if not self.env.quark_chain_config.DISABLE_POW_CHECK: consensus_type = self.root_config.CONSENSUS_TYPE diff = (adjusted_diff if adjusted_diff is not None else block_header.difficulty) validate_seal(block_header, consensus_type, adjusted_diff=diff) return block_header.get_hash()
def test_sha3sha3(self): miner = self.miner_gen(ConsensusType.POW_SHA3SHA3, None, None) block = RootBlock( RootBlockHeader(create_time=42, extra_data="{}".encode("utf-8"), difficulty=5)) # only process one block, which is passed in miner.input_q.put((None, {})) miner.mine_sha3sha3(block, miner.input_q, miner.output_q, {}) mined_block = miner.output_q.get() self.assertEqual(mined_block.header.nonce, 3) validate_seal(mined_block.header, ConsensusType.POW_SHA3SHA3)
def validate_block_header(self, block_header: RootBlockHeader, block_hash=None): """ Validate the block header. """ height = block_header.height if height < 1: raise ValueError("unexpected height") if not self.db.contain_root_block_by_hash( block_header.hash_prev_block): raise ValueError("previous hash block mismatch") prev_block_header = self.db.get_root_block_header_by_hash( block_header.hash_prev_block) if prev_block_header.height + 1 != height: raise ValueError("incorrect block height") if block_header.create_time <= prev_block_header.create_time: raise ValueError( "incorrect create time tip time {}, new block time {}".format( block_header.create_time, prev_block_header.create_time)) if (len(block_header.extra_data) > self.env.quark_chain_config.BLOCK_EXTRA_DATA_SIZE_LIMIT): raise ValueError("extra_data in block is too large") header_hash = block_header.get_hash() if block_hash is None: block_hash = header_hash # Check difficulty curr_diff = block_header.difficulty if not self.env.quark_chain_config.SKIP_ROOT_DIFFICULTY_CHECK: if self.env.quark_chain_config.NETWORK_ID == NetworkId.MAINNET: diff = self.diff_calc.calculate_diff_with_parent( prev_block_header, block_header.create_time) if diff != curr_diff: raise ValueError("incorrect difficulty") metric = diff * int.from_bytes(block_hash, byteorder="big") if metric >= 2**256: raise ValueError("insufficient difficulty") elif (block_header.coinbase_address.recipient != self.env.quark_chain_config.testnet_master_address.recipient ): raise ValueError("incorrect master to create the block") # Check PoW if applicable consensus_type = self.env.quark_chain_config.ROOT.CONSENSUS_TYPE validate_seal(block_header, consensus_type) return block_hash
def validate_block_header(self, block_header: RootBlockHeader, block_hash=None): """ Validate the block header. """ height = block_header.height if height < 1: raise ValueError("unexpected height") if not self.db.contain_root_block_by_hash(block_header.hash_prev_block): raise ValueError("previous hash block mismatch") prev_block_header = self.db.get_root_block_header_by_hash( block_header.hash_prev_block ) if prev_block_header.height + 1 != height: raise ValueError("incorrect block height") if block_header.create_time <= prev_block_header.create_time: raise ValueError( "incorrect create time tip time {}, new block time {}".format( block_header.create_time, prev_block_header.create_time ) ) if ( len(block_header.extra_data) > self.env.quark_chain_config.BLOCK_EXTRA_DATA_SIZE_LIMIT ): raise ValueError("extra_data in block is too large") header_hash = block_header.get_hash() if block_hash is None: block_hash = header_hash # Check difficulty if not self.env.quark_chain_config.SKIP_ROOT_DIFFICULTY_CHECK: diff = self.diff_calc.calculate_diff_with_parent( prev_block_header, block_header.create_time ) # lower the difficulty for root block signed by guardian if block_header.verify_signature( self.env.quark_chain_config.guardian_public_key ): diff = Guardian.adjust_difficulty(diff, block_header.height) if diff != block_header.difficulty: raise ValueError("incorrect difficulty") # Check PoW if applicable consensus_type = self.env.quark_chain_config.ROOT.CONSENSUS_TYPE validate_seal(block_header, consensus_type) return block_hash
def test_validate_seal_with_adjusted_diff(self): diff = 1000 block = RootBlock( RootBlockHeader(create_time=42, difficulty=diff), tracking_data="{}".encode("utf-8"), ) block.header.nonce = 0 with self.assertRaises(ValueError): validate_seal(block.header, ConsensusType.POW_DOUBLESHA256) # significantly lowering the diff should pass validate_seal(block.header, ConsensusType.POW_DOUBLESHA256, adjusted_diff=1)
def test_sha3sha3(self): miner = self.miner_gen(ConsensusType.POW_SHA3SHA3, None, None) block = RootBlock( RootBlockHeader(create_time=42, difficulty=5), tracking_data="{}".encode("utf-8"), ) work = MiningWork(block.header.get_hash_for_mining(), 42, 5) # only process one block, which is passed in. `None` means termination right after miner.input_q.put((None, {})) miner._mine_loop( ConsensusType.POW_SHA3SHA3, work, {}, miner.input_q, miner.output_q ) mined_res = miner.output_q.get() self.assertEqual(mined_res.nonce, 8) block.header.nonce = mined_res.nonce validate_seal(block.header, ConsensusType.POW_SHA3SHA3)
async def handle_new_block(self, block): """ 0. if local shard is syncing, doesn't make sense to add, skip 1. if block parent is not in local state/new block pool, discard 2. if already in cache or in local state/new block pool, pass 3. validate: check time, difficulty, POW 4. add it to new minor block broadcast cache 5. broadcast to all peers (minus peer that sent it, optional) 6. add_block() to local state (then remove from cache) also, broadcast tip if tip is updated (so that peers can sync if they missed blocks, or are new) """ if self.synchronizer.running: # TODO optinal: queue the block if it came from broadcast to so that once sync is over, catch up immediately return if block.header.get_hash() in self.state.new_block_pool: return if self.state.db.contain_minor_block_by_hash(block.header.get_hash()): return if not self.state.db.contain_minor_block_by_hash( block.header.hash_prev_minor_block): if block.header.hash_prev_minor_block not in self.state.new_block_pool: return full_shard_id = block.header.branch.get_full_shard_id() consensus_type = self.env.quark_chain_config.shards[ full_shard_id].CONSENSUS_TYPE try: validate_seal(block.header, consensus_type) except Exception as e: Logger.warning("[{}] Got block with bad seal: {}".format( full_shard_id, str(e))) return if block.header.create_time > time_ms() // 1000 + 30: return self.state.new_block_pool[block.header.get_hash()] = block Logger.info("[{}/{}] got new block with height {}".format( block.header.branch.get_chain_id(), block.header.branch.get_shard_id(), block.header.height, )) self.broadcast_new_block(block) await self.add_block(block)
def test_qkchash(self): miner = self.miner_gen(ConsensusType.POW_QKCHASH, None, None) block = RootBlock( RootBlockHeader(create_time=42, difficulty=5), tracking_data="{}".encode("utf-8"), ) work = MiningWork(block.header.get_hash_for_mining(), 42, 5) # only process one block, which is passed in. `None` means termination right after miner.input_q.put((None, {})) miner.mine_loop( work, {"consensus_type": ConsensusType.POW_QKCHASH}, miner.input_q, miner.output_q, ) mined_res = miner.output_q.get() block.header.nonce = mined_res.nonce block.header.mixhash = mined_res.mixhash validate_seal(block.header, ConsensusType.POW_QKCHASH)
async def add(block_to_add): validate_seal(block_to_add.header, doublesha) self.added_blocks.append(block_to_add)