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") 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 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)
def setUp(self): super().setUp() dummy_tip = lambda: RootBlockHeader() def miner_gen(consensus, create_func, add_func, tip_func=dummy_tip, **kwargs): m = Miner(consensus, create_func, add_func, self.get_mining_params, tip_func, **kwargs) m.enabled = True return m self.miner_gen = miner_gen self.added_blocks = []
def test_submit_work_with_remote_guardian(self): now = 42 doublesha = ConsensusType.POW_DOUBLESHA256 block = RootBlock( RootBlockHeader(create_time=42, extra_data=b"{}", difficulty=1000)) priv = ecies.generate_privkey() async def create(retry=True): return block 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) # just with the guardian public key miner = self.miner_gen(doublesha, create, add, remote=True) async def go(): for i in range(42, 100): work, _ = await miner.get_work(now=now) self.assertEqual(work.height, 0) # remote guardian: diff 1000 -> 1, any number should work # mimic the sign process of the remote guardian server block.header.nonce = i block.header.mixhash = sha3_256(b"") block.header.sign_with_private_key(priv) signature = block.header.signature # reset the signature to the default value block.header.signature = bytes(65) # submit the signature through the submit work res = await miner.submit_work(work.hash, i, sha3_256(b""), signature) self.assertTrue(res) res = await miner.submit_work(work.hash, i, sha3_256(b""), bytes(65)) self.assertFalse(res) loop = asyncio.get_event_loop() loop.run_until_complete(go())
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)
def test_submit_work(self): now = 42 block = RootBlock( RootBlockHeader(create_time=42, extra_data=b"{}", difficulty=2)) async def create(): return block async def add(block_to_add): self.added_blocks.append(block_to_add) miner = self.miner_gen(ConsensusType.POW_SHA3SHA3, create, add, remote=True) async def go(): work = await miner.get_work(now=now) self.assertEqual(work.height, 0) self.assertEqual(work.difficulty, 2) # submitted block doesn't exist res = await miner.submit_work(b"lolwut", 0, sha3_256(b"")) self.assertFalse(res) solver = DoubleSHA256(block) mined = solver.mine(100, 200) self.assertTrue(mined) sol = int.from_bytes(solver.nonce_found, byteorder="big") self.assertGreater(sol, 100) # ensure non-solution is tried non_sol = sol - 1 # invalid pow proof res = await miner.submit_work(work.hash, non_sol, sha3_256(b"")) self.assertFalse(res) # valid submission, also check internal state afterwards res = await miner.submit_work(work.hash, sol, sha3_256(b"")) self.assertTrue(res) self.assertEqual(miner.work_map, {}) self.assertEqual(len(self.added_blocks), 1) self.assertIsNone(miner.current_work) loop = asyncio.get_event_loop() loop.run_until_complete(go())
def test_submit_work(self): now = 42 doublesha = ConsensusType.POW_DOUBLESHA256 block = RootBlock( RootBlockHeader(create_time=42, extra_data=b"{}", difficulty=5)) async def create(retry=True): return block async def add(block_to_add): validate_seal(block_to_add.header, doublesha) self.added_blocks.append(block_to_add) miner = self.miner_gen(doublesha, create, add, remote=True) async def go(): work, _ = await miner.get_work(now=now) self.assertEqual(work.height, 0) self.assertEqual(work.difficulty, 5) # submitted block doesn't exist res = await miner.submit_work(b"lolwut", 0, sha3_256(b"")) self.assertFalse(res) solver = DoubleSHA256(work) sol = solver.mine(200, 300).nonce self.assertGreater(sol, 200) # ensure non-solution is tried non_sol = sol - 1 # invalid pow proof res = await miner.submit_work(work.hash, non_sol, sha3_256(b"")) self.assertFalse(res) # valid submission, also check internal state afterwards res = await miner.submit_work(work.hash, sol, sha3_256(b"")) self.assertTrue(res) self.assertEqual(miner.work_map, {}) self.assertEqual(len(self.added_blocks), 1) self.assertIsNone(miner.current_work) loop = asyncio.get_event_loop() loop.run_until_complete(go())
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()
async def create(retry=True): nonlocal now return RootBlock(RootBlockHeader(create_time=now, extra_data=b"{}"))
def test_get_work(self): now, height = 42, 42 mock_tip = RootBlockHeader(height=height) async def create(coinbase_addr, **kwargs): nonlocal now, mock_tip return RootBlock( RootBlockHeader( coinbase_address=coinbase_addr, create_time=now, extra_data=b"{}", hash_prev_block=mock_tip.get_hash(), height=mock_tip.height + 1, )) def tip_getter(): nonlocal mock_tip return mock_tip miner = self.miner_gen(ConsensusType.POW_DOUBLESHA256, create, None, tip_getter, remote=True) async def go(): nonlocal now, mock_tip # no current work, will generate a new one work, block = await miner.get_work(EMPTY_ADDR, now=now) self.assertEqual(len(work), 3) self.assertEqual(block.header.coinbase_address, Address.create_empty_account()) self.assertEqual(len(miner.work_map), 1) h = list(miner.work_map.keys())[0] self.assertEqual(work.hash, h) # cache hit and new block is linked to tip (by default) now += 1 work, _ = await miner.get_work(EMPTY_ADDR, now=now) self.assertEqual(work.hash, h) self.assertEqual(work.height, 43) self.assertEqual(len(miner.work_map), 1) # cache hit, but current work is outdated because tip has updated mock_tip.height += 1 work, _ = await miner.get_work(EMPTY_ADDR, now=now) h = work.hash self.assertEqual(len(miner.work_map), 2) self.assertEqual(work.height, 44) # new work if interval passed now += 11 work, _ = await miner.get_work(EMPTY_ADDR, now=now) self.assertEqual(len(miner.work_map), 3) # height didn't change, but hash should self.assertNotEqual(work.hash, h) self.assertEqual(work.height, 44) # get work with specified coinbase address addr = Address.create_random_account(0) work, block = await miner.get_work(addr, now=now) self.assertEqual(block.header.coinbase_address, addr) self.assertEqual(len(miner.work_map), 4) self.assertEqual(len(miner.current_works), 2) loop = asyncio.get_event_loop() loop.run_until_complete(go())
async def create(): nonlocal i if i >= 5: return None return RootBlock(RootBlockHeader(create_time=int(time.time()), extra_data="{}".encode("utf-8")))
async def dummy_create_block_async() -> Optional[RootBlock]: if len(TestMiner.added_blocks) >= 5: return None # stop the game return RootBlock(RootBlockHeader(create_time=int(time.time()), extra_data="{}".encode("utf-8")))
async def create(): if len(self.added_blocks) >= 5: return None # stop the game return RootBlock( RootBlockHeader(create_time=int(time.time()), extra_data="{}".encode("utf-8")))