def get_minor_block_by_hash(self, h, consistency_check=True ) -> Optional[MinorBlock]: if consistency_check and h not in self.m_header_pool: return None return MinorBlock.deserialize(self.db.get(b"mblock_" + h))
def create_minor_block(self, root_block: RootBlock, full_shard_id: int, evm_state: EvmState) -> MinorBlock: """ Create genesis block for shard. Genesis block's hash_prev_root_block is set to the genesis root block. Genesis state will be committed to the given evm_state. Based on ALLOC, genesis_token will be added to initial accounts. """ branch = Branch(full_shard_id) shard_config = self._qkc_config.shards[full_shard_id] genesis = shard_config.GENESIS for address_hex, alloc_amount in genesis.ALLOC.items(): address = Address.create_from(bytes.fromhex(address_hex)) check( self._qkc_config.get_full_shard_id_by_full_shard_key( address.full_shard_key) == full_shard_id) evm_state.full_shard_key = address.full_shard_key if isinstance(alloc_amount, dict): for k, v in alloc_amount.items(): evm_state.delta_token_balance(address.recipient, token_id_encode(k), v) else: evm_state.delta_token_balance(address.recipient, self._qkc_config.genesis_token, alloc_amount) evm_state.commit() meta = MinorBlockMeta( hash_merkle_root=bytes.fromhex(genesis.HASH_MERKLE_ROOT), hash_evm_state_root=evm_state.trie.root_hash, ) local_fee_rate = 1 - self._qkc_config.reward_tax_rate # type: Fraction coinbase_tokens = { self._qkc_config.genesis_token: shard_config.COINBASE_AMOUNT * local_fee_rate.numerator // local_fee_rate.denominator } coinbase_address = Address.create_empty_account(full_shard_id) header = MinorBlockHeader( version=genesis.VERSION, height=genesis.HEIGHT, branch=branch, hash_prev_minor_block=bytes.fromhex(genesis.HASH_PREV_MINOR_BLOCK), hash_prev_root_block=root_block.header.get_hash(), evm_gas_limit=genesis.GAS_LIMIT, hash_meta=sha3_256(meta.serialize()), coinbase_amount_map=TokenBalanceMap(coinbase_tokens), coinbase_address=coinbase_address, create_time=genesis.TIMESTAMP, difficulty=genesis.DIFFICULTY, extra_data=bytes.fromhex(genesis.EXTRA_DATA), ) return ( MinorBlock(header=header, meta=meta, tx_list=[]), TokenBalanceMap(coinbase_tokens), )
def recover_state(self, r_header, m_header): """ When recovering from local database, we can only guarantee the consistency of the best chain. Forking blocks can be in inconsistent state and thus should be pruned from the database so that they can be retried in the future. """ r_hash = r_header.get_hash() while (len(self.r_header_pool) < self.env.quark_chain_config.ROOT.max_root_blocks_in_memory): block = RootBlock.deserialize(self.db.get(b"rblock_" + r_hash)) self.r_minor_header_pool[ r_hash] = self.__get_last_minor_block_in_root_block(block) self.r_header_pool[r_hash] = block.header if (block.header.height <= self.env.quark_chain_config.get_genesis_root_height( self.branch.get_shard_id())): break r_hash = block.header.hash_prev_block m_hash = m_header.get_hash() shard_config = self.env.quark_chain_config.SHARD_LIST[ self.branch.get_shard_id()] while len( self.m_header_pool) < shard_config.max_minor_blocks_in_memory: block = MinorBlock.deserialize(self.db.get(b"mblock_" + m_hash)) self.m_header_pool[m_hash] = block.header self.m_meta_pool[m_hash] = block.meta if block.header.height <= 0: break m_hash = block.header.hash_prev_minor_block Logger.info("[{}] recovered {} minor blocks and {} root blocks".format( self.branch.get_shard_id(), len(self.m_header_pool), len(self.r_header_pool), ))
def get_minor_block_by_hash(self, h: bytes, consistency_check=True ) -> Optional[MinorBlock]: if consistency_check and h not in self.m_header_pool: return None data = self.db.get(b"mblock_" + h, None) return MinorBlock.deserialize(data) if data else None
def get_minor_block_by_hash(self, h: bytes) -> Optional[MinorBlock]: key = b"mblock_" + h if key in self.mblock_cache: return self.mblock_cache[key] raw_block = self.db.get(key, None) block = raw_block and MinorBlock.deserialize(raw_block) if block is not None: self.mblock_cache[key] = block return block
def test_getNextBlockToMine_with_shard_mask(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) with ClusterContext(1, acc1) as clusters, jrpc_server_context( clusters[0].master): response = send_request("getNextBlockToMine", "0x" + acc1.serialize().hex(), "0x2") self.assertFalse(response["isRootBlock"]) block1 = MinorBlock.deserialize( bytes.fromhex(response["blockData"][2:])) self.assertEqual(block1.header.branch.value, 0b10) response = send_request("getNextBlockToMine", "0x" + acc1.serialize().hex(), "0x3") self.assertFalse(response["isRootBlock"]) block1 = MinorBlock.deserialize( bytes.fromhex(response["blockData"][2:])) self.assertEqual(block1.header.branch.value, 0b11)
async def handle_get_transaction_request(self, req): minor_block, i = self.slave_server.get_transaction_by_hash( req.tx_hash, req.branch ) if not minor_block: empty_block = MinorBlock(MinorBlockHeader(), MinorBlockMeta()) return GetTransactionResponse( error_code=1, minor_block=empty_block, index=0 ) return GetTransactionResponse(error_code=0, minor_block=minor_block, index=i)
def test_get_minor_block_by_hash(self): db = ShardDbOperator(InMemoryDb(), DEFAULT_ENV, Branch(2)) block = MinorBlock(MinorBlockHeader(), MinorBlockMeta()) block_hash = block.header.get_hash() db.put_minor_block(block, []) self.assertEqual(db.get_minor_block_by_hash(block_hash), block) self.assertIsNone(db.get_minor_block_by_hash(b"")) self.assertEqual(db.get_minor_block_header_by_hash(block_hash), block.header) self.assertIsNone(db.get_minor_block_header_by_hash(b""))
async def handle_get_transaction_receipt_request(self, req): resp = self.slave_server.get_transaction_receipt(req.tx_hash, req.branch) if not resp: empty_block = MinorBlock(MinorBlockHeader(), MinorBlockMeta()) empty_receipt = TransactionReceipt.create_empty_receipt() return GetTransactionReceiptResponse( error_code=1, minor_block=empty_block, index=0, receipt=empty_receipt ) minor_block, i, receipt = resp return GetTransactionReceiptResponse( error_code=0, minor_block=minor_block, index=i, receipt=receipt )
async def handle_get_minor_block_request(self, req): if req.minor_block_hash != bytes(32): block = self.slave_server.get_minor_block_by_hash( req.minor_block_hash, req.branch) else: block = self.slave_server.get_minor_block_by_height( req.height, req.branch) if not block: empty_block = MinorBlock(MinorBlockHeader(), MinorBlockMeta()) return GetMinorBlockResponse(error_code=1, minor_block=empty_block) return GetMinorBlockResponse(error_code=0, minor_block=block)
def create_minor_block(self, root_block: RootBlock, shard_id: int, evm_state: EvmState) -> MinorBlock: """ Create genesis block for shard. Genesis block's hash_prev_root_block is set to the genesis root block. Genesis state will be committed to the given evm_state. """ branch = Branch.create(self._qkc_config.SHARD_SIZE, shard_id) shard_config = self._qkc_config.SHARD_LIST[shard_id] genesis = shard_config.GENESIS for address_hex, amount_in_wei in genesis.ALLOC.items(): address = Address.create_from(bytes.fromhex(address_hex)) check( address.get_shard_id(self._qkc_config.SHARD_SIZE) == shard_id) evm_state.full_shard_id = address.full_shard_id evm_state.delta_balance(address.recipient, amount_in_wei) evm_state.commit() meta = MinorBlockMeta( hash_merkle_root=bytes.fromhex(genesis.HASH_MERKLE_ROOT), hash_evm_state_root=evm_state.trie.root_hash, ) local_fee_rate = 1 - self._qkc_config.reward_tax_rate # type: Fraction coinbase_amount = (shard_config.COINBASE_AMOUNT * local_fee_rate.numerator // local_fee_rate.denominator) coinbase_address = Address.create_empty_account(shard_id) header = MinorBlockHeader( version=genesis.VERSION, height=genesis.HEIGHT, branch=branch, hash_prev_minor_block=bytes.fromhex(genesis.HASH_PREV_MINOR_BLOCK), hash_prev_root_block=root_block.header.get_hash(), evm_gas_limit=genesis.GAS_LIMIT, hash_meta=sha3_256(meta.serialize()), coinbase_amount=coinbase_amount, coinbase_address=coinbase_address, create_time=genesis.TIMESTAMP, difficulty=genesis.DIFFICULTY, extra_data=bytes.fromhex(genesis.EXTRA_DATA), ) return MinorBlock(header=header, meta=meta, tx_list=[])
async def handle_add_minor_block_request(self, req): """ For local miner to submit mined blocks through master """ try: block = MinorBlock.deserialize(req.minor_block_data) except Exception: return AddMinorBlockResponse(error_code=errno.EBADMSG) shard = self.shards.get(block.header.branch, None) if not shard: return AddMinorBlockResponse(error_code=errno.EBADMSG) if block.header.hash_prev_minor_block != shard.state.header_tip.get_hash( ): # Tip changed, don't bother creating a fork Logger.info("[{}] dropped stale block {} mined locally".format( block.header.branch.get_shard_id(), block.header.height)) return AddMinorBlockResponse(error_code=0) success = await shard.add_block(block) return AddMinorBlockResponse(error_code=0 if success else errno.EFAULT)
def test_getNextBlockToMine_and_addBlock(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) acc3 = Address.create_random_account(full_shard_id=1) with ClusterContext(1, acc1) as clusters, jrpc_server_context( clusters[0].master): slaves = clusters[0].slave_list # Expect to mine root that confirms the genesis minor blocks response = send_request("getNextBlockToMine", "0x" + acc1.serialize().hex(), "0x0") self.assertTrue(response["isRootBlock"]) block = RootBlock.deserialize( bytes.fromhex(response["blockData"][2:])) self.assertEqual(block.header.height, 1) self.assertEqual(len(block.minor_block_header_list), 2) self.assertEqual(block.minor_block_header_list[0].height, 0) self.assertEqual(block.minor_block_header_list[1].height, 0) send_request("addBlock", "0x0", response["blockData"]) tx = create_transfer_transaction( shard_state=clusters[0].get_shard_state(0), key=id1.get_key(), from_address=acc1, to_address=acc3, value=14, gas=opcodes.GTXXSHARDCOST + opcodes.GTXCOST, ) self.assertTrue(slaves[0].add_tx(tx)) # Expect to mine shard 0 since it has one tx response = send_request("getNextBlockToMine", "0x" + acc1.serialize().hex(), "0x0") self.assertFalse(response["isRootBlock"]) block1 = MinorBlock.deserialize( bytes.fromhex(response["blockData"][2:])) self.assertEqual(block1.header.branch.value, 0b10) self.assertTrue( send_request("addBlock", "0x2", response["blockData"])) self.assertEqual( clusters[0].get_shard_state(1).get_balance(acc3.recipient, 0), 0) # Expect to mine shard 1 due to proof-of-progress response = send_request("getNextBlockToMine", "0x" + acc1.serialize().hex(), "0x0") self.assertFalse(response["isRootBlock"]) block2 = MinorBlock.deserialize( bytes.fromhex(response["blockData"][2:])) self.assertEqual(block2.header.branch.value, 0b11) self.assertTrue( send_request("addBlock", "0x3", response["blockData"])) # Expect to mine root response = send_request("getNextBlockToMine", "0x" + acc1.serialize().hex(), "0x0") self.assertTrue(response["isRootBlock"]) block = RootBlock.deserialize( bytes.fromhex(response["blockData"][2:])) self.assertEqual(block.header.height, 2) self.assertEqual(len(block.minor_block_header_list), 2) self.assertEqual(block.minor_block_header_list[0], block1.header) self.assertEqual(block.minor_block_header_list[1], block2.header) send_request("addBlock", "0x0", response["blockData"]) self.assertEqual( clusters[0].get_shard_state(1).get_balance(acc3.recipient, 0), 0) # Expect to mine shard 1 for the gas on xshard tx to acc3 response = send_request("getNextBlockToMine", "0x" + acc1.serialize().hex(), "0x0") self.assertFalse(response["isRootBlock"]) block3 = MinorBlock.deserialize( bytes.fromhex(response["blockData"][2:])) self.assertEqual(block3.header.branch.value, 0b11) self.assertTrue( send_request("addBlock", "0x3", response["blockData"])) # Expect withdrawTo is included in acc3's balance resp = send_request("getBalance", "0x" + acc3.serialize().hex()) self.assertEqual(resp["branch"], "0x3") self.assertEqual(resp["balance"], "0xe")
def get_genesis_block(self, root_block_hash): data = self.db.get(b"genesis_" + root_block_hash, None) if not data: return None else: return MinorBlock.deserialize(data)
def get_minor_block_by_hash(self, h: bytes) -> Optional[MinorBlock]: data = self.db.get(b"mblock_" + h, None) return MinorBlock.deserialize(data) if data else None
def create_minor_block( self, root_block: RootBlock, full_shard_id: int, evm_state: EvmState) -> Tuple[MinorBlock, TokenBalanceMap]: """ Create genesis block for shard. Genesis block's hash_prev_root_block is set to the genesis root block. Genesis state will be committed to the given evm_state. Based on ALLOC, genesis_token will be added to initial accounts. """ branch = Branch(full_shard_id) shard_config = self._qkc_config.shards[full_shard_id] genesis = shard_config.GENESIS for address_hex, alloc_data in genesis.ALLOC.items(): address = Address.create_from(bytes.fromhex(address_hex)) check( self._qkc_config.get_full_shard_id_by_full_shard_key( address.full_shard_key) == full_shard_id) evm_state.full_shard_key = address.full_shard_key recipient = address.recipient if "code" in alloc_data: code = decode_hex(remove_0x_head(alloc_data["code"])) evm_state.set_code(recipient, code) evm_state.set_nonce(recipient, 1) if "storage" in alloc_data: for k, v in alloc_data["storage"].items(): evm_state.set_storage_data( recipient, big_endian_to_int(decode_hex(k[2:])), big_endian_to_int(decode_hex(v[2:])), ) # backward compatible: # v1: {addr: {QKC: 1234}} # v2: {addr: {balances: {QKC: 1234}, code: 0x, storage: {0x12: 0x34}}} balances = alloc_data if "balances" in alloc_data: balances = alloc_data["balances"] for k, v in balances.items(): if k in ("code", "storage"): continue evm_state.delta_token_balance(recipient, token_id_encode(k), v) evm_state.commit() meta = MinorBlockMeta( hash_merkle_root=bytes.fromhex(genesis.HASH_MERKLE_ROOT), hash_evm_state_root=evm_state.trie.root_hash, xshard_tx_cursor_info=XshardTxCursorInfo(root_block.header.height, 0, 0), ) local_fee_rate = 1 - self._qkc_config.reward_tax_rate # type: Fraction coinbase_tokens = { self._qkc_config.genesis_token: shard_config.COINBASE_AMOUNT * local_fee_rate.numerator // local_fee_rate.denominator } coinbase_address = Address.create_empty_account(full_shard_id) header = MinorBlockHeader( version=genesis.VERSION, height=genesis.HEIGHT, branch=branch, hash_prev_minor_block=bytes.fromhex(genesis.HASH_PREV_MINOR_BLOCK), hash_prev_root_block=root_block.header.get_hash(), evm_gas_limit=genesis.GAS_LIMIT, hash_meta=sha3_256(meta.serialize()), coinbase_amount_map=TokenBalanceMap(coinbase_tokens), coinbase_address=coinbase_address, create_time=genesis.TIMESTAMP, difficulty=genesis.DIFFICULTY, extra_data=bytes.fromhex(genesis.EXTRA_DATA), ) return ( MinorBlock(header=header, meta=meta, tx_list=[]), TokenBalanceMap(coinbase_tokens), )