Пример #1
0
    def test_broadcast_cross_shard_transactions(self):
        """ Test the cross shard transactions are broadcasted to the destination shards """
        id1 = Identity.create_random_identity()
        acc1 = Address.create_from_identity(id1, full_shard_key=0)
        acc3 = Address.create_random_account(full_shard_key=1)

        with ClusterContext(1, acc1) as clusters:
            master = clusters[0].master
            slaves = clusters[0].slave_list

            # Add a root block first so that later minor blocks referring to this root
            # can be broadcasted to other shards
            is_root, root_block = call_async(
                master.get_next_block_to_mine(
                    Address.create_empty_account(), prefer_root=True
                )
            )
            self.assertTrue(is_root)
            call_async(master.add_root_block(root_block))

            tx1 = create_transfer_transaction(
                shard_state=clusters[0].get_shard_state(2 | 0),
                key=id1.get_key(),
                from_address=acc1,
                to_address=acc3,
                value=54321,
                gas=opcodes.GTXXSHARDCOST + opcodes.GTXCOST,
            )
            self.assertTrue(slaves[0].add_tx(tx1))

            b1 = clusters[0].get_shard_state(2 | 0).create_block_to_mine(address=acc1)
            b2 = clusters[0].get_shard_state(2 | 0).create_block_to_mine(address=acc1)
            b2.header.create_time += 1
            self.assertNotEqual(b1.header.get_hash(), b2.header.get_hash())

            call_async(clusters[0].get_shard(2 | 0).add_block(b1))

            # expect shard 1 got the CrossShardTransactionList of b1
            xshard_tx_list = (
                clusters[0]
                .get_shard_state(2 | 1)
                .db.get_minor_block_xshard_tx_list(b1.header.get_hash())
            )
            self.assertEqual(len(xshard_tx_list.tx_list), 1)
            self.assertEqual(xshard_tx_list.tx_list[0].tx_hash, tx1.get_hash())
            self.assertEqual(xshard_tx_list.tx_list[0].from_address, acc1)
            self.assertEqual(xshard_tx_list.tx_list[0].to_address, acc3)
            self.assertEqual(xshard_tx_list.tx_list[0].value, 54321)

            call_async(clusters[0].get_shard(2 | 0).add_block(b2))
            # b2 doesn't update tip
            self.assertEqual(clusters[0].get_shard_state(2 | 0).header_tip, b1.header)

            # expect shard 1 got the CrossShardTransactionList of b2
            xshard_tx_list = (
                clusters[0]
                .get_shard_state(2 | 1)
                .db.get_minor_block_xshard_tx_list(b2.header.get_hash())
            )
            self.assertEqual(len(xshard_tx_list.tx_list), 1)
            self.assertEqual(xshard_tx_list.tx_list[0].tx_hash, tx1.get_hash())
            self.assertEqual(xshard_tx_list.tx_list[0].from_address, acc1)
            self.assertEqual(xshard_tx_list.tx_list[0].to_address, acc3)
            self.assertEqual(xshard_tx_list.tx_list[0].value, 54321)

            b3 = (
                clusters[0]
                .get_shard_state(2 | 1)
                .create_block_to_mine(address=acc1.address_in_shard(1))
            )
            call_async(master.add_raw_minor_block(b3.header.branch, b3.serialize()))

            is_root, root_block = call_async(
                master.get_next_block_to_mine(address=acc1)
            )
            self.assertTrue(is_root)
            call_async(master.add_root_block(root_block))

            # b4 should include the withdraw of tx1
            b4 = (
                clusters[0]
                .get_shard_state(2 | 1)
                .create_block_to_mine(address=acc1.address_in_shard(1))
            )

            # adding b1, b2, b3 again shouldn't affect b4 to be added later
            self.assertTrue(
                call_async(master.add_raw_minor_block(b1.header.branch, b1.serialize()))
            )
            self.assertTrue(
                call_async(master.add_raw_minor_block(b2.header.branch, b2.serialize()))
            )
            self.assertTrue(
                call_async(master.add_raw_minor_block(b3.header.branch, b3.serialize()))
            )
            self.assertTrue(
                call_async(master.add_raw_minor_block(b4.header.branch, b4.serialize()))
            )
            self.assertEqual(
                call_async(master.get_primary_account_data(acc3)).balance, 54321
            )
Пример #2
0
    def test_root_state_difficulty_and_coinbase(self):
        env = get_test_env()
        env.quark_chain_config.SKIP_ROOT_DIFFICULTY_CHECK = False
        env.quark_chain_config.ROOT.GENESIS.DIFFICULTY = 1000
        diff_calc = EthDifficultyCalculator(cutoff=9,
                                            diff_factor=2048,
                                            minimum_diff=1)
        env.quark_chain_config.NETWORK_ID = (
            1)  # other network ids will skip difficulty check
        env.quark_chain_config.REWARD_TAX_RATE = 0.8
        env.quark_chain_config.ROOT.COINBASE_AMOUNT = 5
        for c in env.quark_chain_config.shards.values():
            c.COINBASE_AMOUNT = 5

        r_state, s_states = create_default_state(env, diff_calc=diff_calc)
        s_state0 = s_states[2 | 0]
        s_state1 = s_states[2 | 1]
        g0 = s_state0.header_tip
        b0 = s_state0.get_tip().create_block_to_append()
        add_minor_block_to_cluster(s_states, b0)
        g1 = s_state1.header_tip
        b1 = s_state1.get_tip().create_block_to_append()
        add_minor_block_to_cluster(s_states, b1)
        self.assertEqual(
            b0.header.coinbase_amount_map.balance_map,
            {env.quark_chain_config.genesis_token: 1},
        )
        self.assertEqual(
            b1.header.coinbase_amount_map.balance_map,
            {env.quark_chain_config.genesis_token: 1},
        )

        r_state.add_validated_minor_block_hash(
            b0.header.get_hash(), b0.header.coinbase_amount_map.balance_map)
        r_state.add_validated_minor_block_hash(
            b1.header.get_hash(), b1.header.coinbase_amount_map.balance_map)

        # Test coinbase
        original_reward_tax_rate = env.quark_chain_config.REWARD_TAX_RATE
        for tax_rate in [0.8, 0.6, 0.9]:
            env.quark_chain_config.REWARD_TAX_RATE = tax_rate
            root_block_tmp = r_state.create_block_to_mine(
                m_header_list=[b0.header, b1.header],
                address=Address.create_empty_account(),
                create_time=r_state.tip.create_time + 9,
            )
            self.assertEqual(root_block_tmp.header.signature,
                             bytes(65))  # empty sig
            # still use minor block's coinbase amount, 1
            self.assertEqual(
                root_block_tmp.header.coinbase_amount_map.balance_map[
                    env.quark_chain_config.genesis_token],
                round((1 + 1) / (1 - tax_rate) * tax_rate + 5),
            )
        env.quark_chain_config.REWARD_TAX_RATE = original_reward_tax_rate

        # Check new difficulty
        root_block0 = r_state.create_block_to_mine(
            m_header_list=[b0.header, b1.header],
            address=Address.create_empty_account(),
            create_time=r_state.tip.create_time + 9,
        )
        self.assertEqual(r_state.tip.difficulty, root_block0.header.difficulty)
        root_block0 = r_state.create_block_to_mine(
            m_header_list=[b0.header, b1.header],
            address=Address.create_empty_account(),
            create_time=r_state.tip.create_time + 3,
        )
        self.assertEqual(
            r_state.tip.difficulty + r_state.tip.difficulty // 2048,
            root_block0.header.difficulty,
        )

        root_block0 = r_state.create_block_to_mine(
            m_header_list=[g0, b0.header, g1, b1.header],
            address=Address.create_empty_account(),
            create_time=r_state.tip.create_time + 26,
        )
        self.assertEqual(
            r_state.tip.difficulty - r_state.tip.difficulty // 2048,
            root_block0.header.difficulty,
        )
Пример #3
0
    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),
        )
Пример #4
0
    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),
        )
Пример #5
0
    def test_shard_state_add_root_block(self):
        id1 = Identity.create_random_identity()
        acc1 = Address.create_from_identity(id1, full_shard_id=0)

        env0 = get_test_env(genesis_account=acc1,
                            genesis_minor_quarkash=10000000)
        env1 = get_test_env(genesis_account=acc1,
                            genesis_minor_quarkash=10000000)
        state0 = create_default_shard_state(env=env0, shard_id=0)
        state1 = create_default_shard_state(env=env1, shard_id=1)

        # Add one block and prepare a fork
        b0 = state0.get_tip().create_block_to_append(address=acc1)
        b2 = state0.get_tip().create_block_to_append(
            address=Address.create_empty_account())

        state0.finalize_and_add_block(b0)
        state0.finalize_and_add_block(b2)

        b1 = state1.get_tip().create_block_to_append()
        b1.finalize(evm_state=state1.run_block(b1))

        # Create a root block containing the block with the x-shard tx
        state0.add_cross_shard_tx_list_by_minor_block_hash(
            h=b1.header.get_hash(),
            tx_list=CrossShardTransactionList(tx_list=[]))
        root_block = (
            state0.root_tip.create_block_to_append().add_minor_block_header(
                b0.header).add_minor_block_header(b1.header).finalize())
        root_block1 = (
            state0.root_tip.create_block_to_append().add_minor_block_header(
                b2.header).add_minor_block_header(b1.header).finalize())

        state0.add_root_block(root_block)

        b00 = b0.create_block_to_append()
        state0.finalize_and_add_block(b00)
        self.assertEqual(state0.header_tip, b00.header)

        # Create another fork that is much longer (however not confirmed by root_block)
        b3 = b2.create_block_to_append()
        state0.finalize_and_add_block(b3)
        b4 = b3.create_block_to_append()
        state0.finalize_and_add_block(b4)
        self.assertEqual(state0.header_tip, b00.header)
        self.assertEqual(state0.db.get_minor_block_by_height(2), b00)
        self.assertIsNone(state0.db.get_minor_block_by_height(3))

        b5 = b1.create_block_to_append()
        state0.add_cross_shard_tx_list_by_minor_block_hash(
            h=b5.header.get_hash(),
            tx_list=CrossShardTransactionList(tx_list=[]))
        root_block2 = (
            root_block1.create_block_to_append().add_minor_block_header(
                b3.header).add_minor_block_header(
                    b4.header).add_minor_block_header(b5.header).finalize())

        self.assertFalse(state0.add_root_block(root_block1))
        self.assertTrue(state0.add_root_block(root_block2))
        self.assertEqual(state0.header_tip, b4.header)
        self.assertEqual(state0.meta_tip, b4.meta)
        self.assertEqual(state0.root_tip, root_block2.header)

        self.assertEqual(state0.db.get_minor_block_by_height(2), b3)
        self.assertEqual(state0.db.get_minor_block_by_height(3), b4)
Пример #6
0
def get_test_env(
    genesis_account=Address.create_empty_account(),
    genesis_minor_quarkash=0,
    chain_size=2,
    shard_size=2,
    genesis_root_heights=None,  # dict(full_shard_id, genesis_root_height)
    remote_mining=False,
    genesis_minor_token_balances=None,
):
    check(is_p2(shard_size))
    env = DEFAULT_ENV.copy()

    env.db = InMemoryDb()
    env.set_network_id(1234567890)

    env.cluster_config = ClusterConfig()
    env.quark_chain_config.update(chain_size, shard_size, 10, 1,
                                  env.quark_chain_config.GENESIS_TOKEN)
    env.quark_chain_config.MIN_TX_POOL_GAS_PRICE = 0
    env.quark_chain_config.MIN_MINING_GAS_PRICE = 0

    if remote_mining:
        env.quark_chain_config.ROOT.CONSENSUS_CONFIG.REMOTE_MINE = True
        env.quark_chain_config.ROOT.CONSENSUS_TYPE = ConsensusType.POW_DOUBLESHA256
        env.quark_chain_config.ROOT.GENESIS.DIFFICULTY = 10

    env.quark_chain_config.ROOT.DIFFICULTY_ADJUSTMENT_CUTOFF_TIME = 40
    env.quark_chain_config.ROOT.DIFFICULTY_ADJUSTMENT_FACTOR = 1024

    if genesis_root_heights:
        check(len(genesis_root_heights) == shard_size * chain_size)
        for chain_id in range(chain_size):
            for shard_id in range(shard_size):
                full_shard_id = chain_id << 16 | shard_size | shard_id
                shard = env.quark_chain_config.shards[full_shard_id]
                shard.GENESIS.ROOT_HEIGHT = genesis_root_heights[full_shard_id]

    # fund genesis account in all shards
    for full_shard_id, shard in env.quark_chain_config.shards.items():
        addr = genesis_account.address_in_shard(
            full_shard_id).serialize().hex()
        if genesis_minor_token_balances is not None:
            shard.GENESIS.ALLOC[addr] = genesis_minor_token_balances
        else:
            shard.GENESIS.ALLOC[addr] = {
                env.quark_chain_config.GENESIS_TOKEN: genesis_minor_quarkash
            }
        shard.CONSENSUS_CONFIG.REMOTE_MINE = remote_mining
        shard.DIFFICULTY_ADJUSTMENT_CUTOFF_TIME = 7
        shard.DIFFICULTY_ADJUSTMENT_FACTOR = 512
        if remote_mining:
            shard.CONSENSUS_TYPE = ConsensusType.POW_DOUBLESHA256
            shard.GENESIS.DIFFICULTY = 10
        shard.POSW_CONFIG.WINDOW_SIZE = 2

    env.quark_chain_config.SKIP_MINOR_DIFFICULTY_CHECK = True
    env.quark_chain_config.SKIP_ROOT_DIFFICULTY_CHECK = True
    env.cluster_config.ENABLE_TRANSACTION_HISTORY = True
    env.cluster_config.DB_PATH_ROOT = ""

    check(env.cluster_config.use_mem_db())

    return env