Esempio n. 1
0
    def test_peer_table_updated_on_join_command(self):
        # Network params issue
        w1 = Wallet()
        p1 = Network(wallet=w1, socket_base='tcp://127.0.0.1', ctx=self.ctx)

        w2 = Wallet()
        d = DiscoveryServer(wallet=w2,
                            socket_id=_socket('tcp://127.0.0.1:19000'),
                            pepper=PEPPER.encode(),
                            ctx=self.ctx,
                            linger=200)

        # 1. start network
        # 2. start discovery of other side
        # 3. send join request
        # 4. check to see if the data has been added

        join_message = ['join', (w2.verifying_key().hex(), 'tcp://127.0.0.1')]
        join_message = json.dumps(join_message).encode()

        tasks = asyncio.gather(
            p1.peer_service.serve(), d.serve(),
            services.get(_socket('tcp://127.0.0.1:10002'),
                         msg=join_message,
                         ctx=self.ctx,
                         timeout=1000), stop_server(p1.peer_service, 0.3),
            stop_server(d, 0.3))

        loop = asyncio.get_event_loop()
        loop.run_until_complete(tasks)

        self.assertEqual(p1.peer_service.table[w2.verifying_key().hex()],
                         'tcp://127.0.0.1')
    def test_signature_with_wrong_message_returns_false(self):
        w = Wallet()

        message = b'howdy'
        signature = w.sign(message)

        self.assertFalse(w.verify(b'hello', signature))
    def test_upg_trigger_1_1(self):
        mns, dls = make_network(1, 1, self.ctx)
        start_up = make_start_awaitable(mns, dls)

        candidate = Wallet()
        stu = Wallet()

        tx1 = make_tx_packed(
            processor=mns[1].wallet.verifying_key(),
            contract_name='upgrade',
            function_name='init_upgrade',
            kwargs={
                'pepper': 'peppertest',
                'initiator_vk': stu,
            },
            sender=candidate,
            drivers=[node.driver for node in mns + dls],
            nonce=0,
            stamps=1_000_000,
        )

        async def test():
            await start_up
            await asyncio.sleep(3)
            await send_tx_batch(mns[1], [tx1])
            await asyncio.sleep(5)

        loop = asyncio.get_event_loop()
        loop.run_until_complete(test())

        for node in mns + dls:
            v = node.driver.get_var(contract='upgrade',
                                    variable='upg_lock',
                                    arguments=[])
            self.assertEqual(v, True)
Esempio n. 4
0
    def test_start_and_stopping_destroys_servers_ipc(self):
        # Create Network service
        w1 = Wallet()
        n1 = NetworkParameters(peer_ipc='peers1',
                               event_ipc='events1',
                               discovery_ipc='discovery1')
        p1 = Network(wallet=w1,
                     ctx=self.ctx,
                     socket_base='ipc:///tmp',
                     params=n1)

        # Create Network service
        w2 = Wallet()
        n2 = NetworkParameters(peer_ipc='peers2', event_port='events2')
        p2 = Network(wallet=w2,
                     ctx=self.ctx,
                     socket_base='ipc:///tmp',
                     params=n2)

        async def stop(n: Network, s):
            await asyncio.sleep(s)
            n.peer_service.stop()

        tasks = asyncio.gather(p1.peer_service.start(),
                               p2.peer_service.start(), stop(p1, 0.3),
                               stop(p2, 0.3))

        loop = asyncio.get_event_loop()
        loop.run_until_complete(tasks)
Esempio n. 5
0
    def test_processor_and_nonce_correct_but_not_enough_stamps_returns_false(self):
        w = Wallet()
        expected_processor = secrets.token_bytes(32)

        tx = TransactionBuilder(w.verifying_key(),
                                contract='currency',
                                function='transfer',
                                kwargs={'amount': 10, 'to': 'jeff'},
                                stamps=500000,
                                processor=expected_processor,
                                nonce=0)

        tx.sign(w.signing_key())
        tx_bytes = tx.serialize()
        tx_struct = transaction_capnp.NewTransaction.from_bytes_packed(tx_bytes)

        with self.assertRaises(transaction.TransactionSenderTooFewStamps):
            transaction_is_valid(tx=tx_struct, expected_processor=expected_processor, driver=self.nonce_manager)

        balances_key = '{}{}{}{}{}'.format('currency',
                                           config.INDEX_SEPARATOR,
                                           'balances',
                                           config.DELIMITER,
                                           tx.payload.sender.hex())

        balance = self.nonce_manager.get(balances_key) or 0

        self.assertEqual(balance, 0)
Esempio n. 6
0
    def test_all_valid_with_stamps_when_balance_is_set(self):
        w = Wallet()
        expected_processor = secrets.token_bytes(32)

        tx = TransactionBuilder(w.verifying_key(),
                                contract='currency',
                                function='transfer',
                                kwargs={'amount': 10, 'to': 'jeff'},
                                stamps=500000,
                                processor=expected_processor,
                                nonce=0)

        tx.sign(w.signing_key())
        tx_bytes = tx.serialize()
        tx_struct = transaction_capnp.NewTransaction.from_bytes_packed(tx_bytes)

        balances_key = '{}{}{}{}{}'.format('currency',
                                           config.INDEX_SEPARATOR,
                                           'balances',
                                           config.DELIMITER,
                                           tx.payload.sender.hex())

        self.nonce_manager.set(balances_key, 500000)

        transaction_is_valid(tx=tx_struct, expected_processor=expected_processor, driver=self.nonce_manager)
        balance = self.nonce_manager.get(balances_key) or 0

        self.assertEqual(balance, 500000)
    def test_discover_works_with_ipc_sockets(self):
        wallet = Wallet()

        d = DiscoveryServer(_socket('ipc:///tmp/discovery'), wallet, b'CORRECT_PEPPER', ctx=self.ctx)

        success_task = ping(_socket('ipc:///tmp/discovery'),
                            pepper=b'CORRECT_PEPPER',
                            ctx=self.ctx,
                            timeout=300)

        failure_task = ping(_socket('tcp://127.0.0.1:20999'),
                            pepper=b'CORRECT_PEPPER',
                            ctx=self.ctx,
                            timeout=300)

        async def stop_server(timeout):
            await asyncio.sleep(timeout)
            d.stop()

        tasks = asyncio.gather(success_task, failure_task, d.serve(), stop_server(0.3))

        loop = asyncio.get_event_loop()
        results = loop.run_until_complete(tasks)

        vk_ip1, vk_ip2, _, _ = results

        _, vk1 = vk_ip1
        _, vk2 = vk_ip2

        self.assertEqual(vk1.hex(), wallet.verifying_key().hex())
        self.assertIsNone(vk2)
Esempio n. 8
0
    def test_submission_prepended_with_con_succeeds(self):
        w = Wallet()
        expected_processor = secrets.token_bytes(32)

        balances_key = '{}{}{}{}{}'.format('currency',
                                           config.INDEX_SEPARATOR,
                                           'balances',
                                           config.DELIMITER,
                                           w.verifying_key().hex())

        self.nonce_manager.set(balances_key, 500000)

        tx = TransactionBuilder(w.verifying_key(),
                                contract='submission',
                                function='submit_contract',
                                kwargs={'name': 'con_bad_name', 'code': 'blah'},
                                stamps=3000,
                                processor=expected_processor,
                                nonce=0)

        tx.sign(w.signing_key())
        tx_bytes = tx.serialize()
        tx_struct = transaction_capnp.NewTransaction.from_bytes_packed(tx_bytes)

        transaction_is_valid(tx=tx_struct, expected_processor=expected_processor, driver=self.nonce_manager)
    def test_discover_nodes_none_found(self):
        addresses = [_socket('tcp://127.0.0.1:10999'), _socket('tcp://127.0.0.1:11999'), _socket('tcp://127.0.0.1:12999')]
        addresses_wrong = [_socket('tcp://127.0.0.1:15999'), _socket('tcp://127.0.0.1:14999'), _socket('tcp://127.0.0.1:13999')]
        wallets = [Wallet(), Wallet(), Wallet()]
        pepper = b'CORRECT_PEPPER'
        server_timeout = 1

        servers = [DiscoveryServer(addresses[0], wallets[0], pepper, ctx=self.ctx),
                   DiscoveryServer(addresses[1], wallets[1], pepper, ctx=self.ctx),
                   DiscoveryServer(addresses[2], wallets[2], pepper, ctx=self.ctx)]

        async def stop_server(s, timeout):
            await asyncio.sleep(timeout)
            s.stop()

        tasks = asyncio.gather(
            servers[0].serve(),
            servers[1].serve(),
            servers[2].serve(),
            stop_server(servers[0], server_timeout),
            stop_server(servers[1], server_timeout),
            stop_server(servers[2], server_timeout),
            discover_nodes(ip_list=addresses_wrong, pepper=pepper, ctx=self.ctx, timeout=500, retries=3)
        )

        loop = asyncio.get_event_loop()
        results = loop.run_until_complete(tasks)

        r = results[-1]

        self.assertIsNone(r.get(str(addresses[0])))
        self.assertIsNone(r.get(str(addresses[1])))
        self.assertIsNone(r.get(str(addresses[2])))
    def setUp(self):
        self.wallet = Wallet()

        # Wallets for VKs
        self.test_wallet_1 = Wallet()
        self.test_wallet_2 = Wallet()

        self.peer_table = {
            self.test_wallet_1.verifying_key().hex(): 'ipc:///tmp/n1',
            self.test_wallet_2.verifying_key().hex(): 'ipc:///tmp/n2',
        }

        self.ctx = zmq.asyncio.Context()
        self.loop = asyncio.new_event_loop()

        self.contacts = MockContacts(
            masters=[self.test_wallet_1.verifying_key().hex()],
            delegates=[self.test_wallet_2.verifying_key().hex()])

        self.paramaters = Parameters(socket_base='tcp://127.0.0.1',
                                     wallet=self.wallet,
                                     ctx=self.ctx,
                                     contacts=self.contacts)

        self.authenticator = SocketAuthenticator(wallet=self.wallet,
                                                 contacts=self.contacts,
                                                 ctx=self.ctx)

        asyncio.set_event_loop(self.loop)
Esempio n. 11
0
    def test_sending_transfer_of_most_money_doesnt_fail_if_enough_stamps(self):
        self.nonce_manager.set_var('stamp_cost', 'S', ['value'], value=3000)

        w = Wallet()
        expected_processor = secrets.token_bytes(32)

        balances_key = '{}{}{}{}{}'.format('currency',
                                           config.INDEX_SEPARATOR,
                                           'balances',
                                           config.DELIMITER,
                                           w.verifying_key().hex())

        self.nonce_manager.set(balances_key, 500000)
        tx = TransactionBuilder(w.verifying_key(),
                                contract='currency',
                                function='transfer',
                                kwargs={'amount': 499990, 'to': 'jeff'},
                                stamps=3000,
                                processor=expected_processor,
                                nonce=0)

        tx.sign(w.signing_key())
        tx_bytes = tx.serialize()
        tx_struct = transaction_capnp.NewTransaction.from_bytes_packed(tx_bytes)

        transaction_is_valid(tx=tx_struct, expected_processor=expected_processor, driver=self.nonce_manager)
    def test_init_wallet_with_seed_returns_deterministic_wallet(self):
        w = Wallet()

        a = Wallet(seed=w.signing_key())

        self.assertEqual(w.vk, a.vk)
        self.assertEqual(w.sk, a.sk)
def make_tx_packed(sender,
                   server,
                   contract_name,
                   function_name,
                   kwargs={},
                   stamps=10_000):
    wallet = Wallet(seed=sender)

    nonce_req = requests.get('{}/nonce/{}'.format(
        server,
        wallet.verifying_key().hex()))
    nonce = nonce_req.json()['nonce']
    processor = bytes.fromhex(nonce_req.json()['processor'])

    batch = TransactionBuilder(sender=wallet.verifying_key(),
                               contract=contract_name,
                               function=function_name,
                               kwargs=kwargs,
                               stamps=stamps,
                               processor=processor,
                               nonce=nonce)

    batch.sign(sender)
    b = batch.serialize()

    return b
    def test_signature_with_correct_message_returns_true(self):
        w = Wallet()

        message = b'howdy'
        signature = w.sign(message)

        self.assertTrue(w.verify(message, signature))
Esempio n. 15
0
def random_packed_tx(nonce=0, processor=None, give_stamps=False):
    w = Wallet()

    processor = secrets.token_bytes(32) if processor is None else processor
    stamps = random.randint(100_000, 1_000_000)

    if give_stamps:
        balances_key = '{}{}{}{}{}'.format('currency', config.INDEX_SEPARATOR,
                                           'balances', config.DELIMITER,
                                           w.verifying_key().hex())

        N.set(balances_key, stamps + 1000)

    tx = TransactionBuilder(
        w.verifying_key(),
        contract=secrets.token_hex(8),
        function=secrets.token_hex(8),
        kwargs={secrets.token_hex(8): secrets.token_hex(8)},
        stamps=stamps,
        processor=processor,
        nonce=nonce)

    tx.sign(w.signing_key())

    #tx.proof = b'\x00' * 32
    #tx.proof_generated = True

    packed_tx = transaction_capnp.Transaction.from_bytes_packed(tx.serialize())
    return packed_tx
    def test_sign_bytes_returns_hex_signature(self):
        w = Wallet()

        signature = w.sign(b'hello', as_hex=True)

        self.assertTrue(isinstance(signature, str))
        self.assertEqual(len(signature), 128)
    def test_sign_bytes_returns_signature(self):
        w = Wallet()

        signature = w.sign(b'hello')

        self.assertTrue(isinstance(signature, bytes))
        self.assertEqual(len(signature), 64)
Esempio n. 18
0
def make_tx(processor, contract_name, function_name, kwargs={}):
    w = Wallet()
    batch = TransactionBuilder(sender=w.verifying_key(),
                               contract=contract_name,
                               function=function_name,
                               kwargs=kwargs,
                               stamps=10000,
                               processor=processor,
                               nonce=0)

    batch.sign(w.signing_key())
    b = batch.serialize()

    tx = transaction_capnp.Transaction.from_bytes_packed(b)

    currency_contract = 'currency'
    balances_hash = 'balances'

    balances_key = '{}{}{}{}{}'.format(currency_contract,
                                       config.INDEX_SEPARATOR, balances_hash,
                                       config.DELIMITER,
                                       w.verifying_key().hex())

    driver = ContractDriver()
    driver.set(balances_key, 1_000_000)
    driver.commit()

    return tx
    def test_discover_works_with_blend_of_tcp_and_ipc(self):
        addresses = [_socket('ipc:///tmp/discover1'), _socket('tcp://127.0.0.1:11999'),
                     _socket('ipc:///tmp/woohoo')]
        wallets = [Wallet(), Wallet(), Wallet()]
        pepper = b'CORRECT_PEPPER'
        server_timeout = 0.3

        servers = [DiscoveryServer(addresses[0], wallets[0], pepper, ctx=self.ctx),
                   DiscoveryServer(addresses[1], wallets[1], pepper, ctx=self.ctx),
                   DiscoveryServer(addresses[2], wallets[2], pepper, ctx=self.ctx)]

        async def stop_server(s, timeout):
            await asyncio.sleep(timeout)
            s.stop()

        tasks = asyncio.gather(
            servers[0].serve(),
            servers[1].serve(),
            servers[2].serve(),
            stop_server(servers[0], server_timeout),
            stop_server(servers[1], server_timeout),
            stop_server(servers[2], server_timeout),
            discover_nodes(ip_list=addresses, pepper=pepper, ctx=self.ctx)
        )

        loop = asyncio.get_event_loop()
        results = loop.run_until_complete(tasks)

        r = results[-1]

        self.assertEqual(r[str(addresses[0])], wallets[0].verifying_key().hex())
        self.assertEqual(r[str(addresses[1])], wallets[1].verifying_key().hex())
        self.assertEqual(r[str(addresses[2])], wallets[2].verifying_key().hex())
Esempio n. 20
0
    def test_start_and_stopping_destroys_servers(self):
        # Create Network service
        w1 = Wallet()
        n1 = NetworkParameters(peer_port=10001, event_port=10002)
        p1 = Network(wallet=w1,
                     ctx=self.ctx,
                     socket_base='tcp://127.0.0.1',
                     params=n1)

        # Create Network service
        w2 = Wallet()
        n2 = NetworkParameters(peer_port=10003, event_port=10004)
        p2 = Network(wallet=w2,
                     ctx=self.ctx,
                     socket_base='tcp://127.0.0.1',
                     params=n2)

        async def stop(n: Network, s):
            await asyncio.sleep(s)
            n.peer_service.stop()

        tasks = asyncio.gather(p1.peer_service.start(),
                               p2.peer_service.start(), stop(p1, 0.3),
                               stop(p2, 0.3))

        loop = asyncio.get_event_loop()
        loop.run_until_complete(tasks)
Esempio n. 21
0
    def test_current_contacts_joins_mn_seed_adds_table_to_joiner(self):
        n1 = '/tmp/n1'
        make_ipc(n1)

        mnw1 = Wallet()
        mn1 = Network(wallet=mnw1, ctx=self.ctx, socket_base=f'ipc://{n1}')

        mn1.peer_service.table = {'a': 'b', 'c': 'd', 'e': 'f'}

        mnw2 = Wallet()
        n2 = '/tmp/n2'
        make_ipc(n2)
        mn2 = Network(wallet=mnw2,
                      ctx=self.ctx,
                      socket_base=f'ipc://{n2}',
                      mn_seed='ipc:///tmp/n1')

        tasks = asyncio.gather(mn1.peer_service.start(),
                               mn2.discovery_server.serve(),
                               stop_server(mn2.discovery_server, 0.3),
                               stop_server(mn1.peer_service, 0.3),
                               mn2.get_current_contacts())

        loop = asyncio.get_event_loop()
        loop.run_until_complete(tasks)

        self.assertDictEqual(mn1.peer_service.table, mn2.peer_service.table)
    def test_verify_vk_pepper_wrong_vk_pepper_message(self):
        wallet = Wallet()
        vk = wallet.verifying_key()
        pepper = b'TESTING_PEPPER'

        pepper_msg = vk + wallet.sign(pepper)

        self.assertFalse(verify_vk_pepper(pepper_msg, b'WRONG_PEPPER'))
    def test_verify_vk_pepper_correct_vk_pepper_message(self):
        wallet = Wallet()
        vk = wallet.verifying_key()
        pepper = b'TESTING_PEPPER'

        pepper_msg = vk + wallet.sign(pepper)

        self.assertTrue(verify_vk_pepper(pepper_msg, pepper))
Esempio n. 24
0
    def test_event_service_triggered_when_new_node_added_ipc(self):
        # Create Network service
        w1 = Wallet()
        p1 = Network(wallet=w1, ctx=self.ctx, socket_base='ipc:///tmp')

        n1 = '/tmp/n1'
        try:
            os.mkdir('/tmp/n1')
        except:
            pass

        # Create Discovery Server
        w2 = Wallet()
        d = DiscoveryServer(wallet=w2,
                            socket_id=_socket('ipc:///tmp/n1/discovery'),
                            pepper=PEPPER.encode(),
                            ctx=self.ctx,
                            poll_timeout=2000,
                            linger=200)

        # Create raw subscriber
        subscriber = self.ctx.socket(zmq.SUB)
        subscriber.setsockopt(zmq.SUBSCRIBE, b'')
        subscriber.connect('ipc:///tmp/events')

        # TCP takes a bit longer to bind and is prone to dropping messages...
        sleep(0.3)

        # Construct the join RPC message
        join_message = ['join', (w2.verifying_key().hex(), 'ipc:///tmp/n1')]
        join_message = json.dumps(join_message).encode()

        # Wrap recv() in an async
        async def recv():
            msg = await subscriber.recv()
            return msg

        tasks = asyncio.gather(
            p1.peer_service.start(
            ),  # Start the PeerService which will process RPC and emit events
            d.serve(
            ),  # Start Discovery so PeerService can verify they are online
            services.get(_socket('ipc:///tmp/peers'),
                         msg=join_message,
                         ctx=self.ctx,
                         timeout=3000),  # Push out a join request
            stop_server(p1.peer_service, 1),
            stop_server(d, 1),
            recv()  # Collect the subscription result
        )

        loop = asyncio.get_event_loop()
        res = loop.run_until_complete(tasks)

        expected_list = ['join', [w2.verifying_key().hex(), 'ipc:///tmp/n1']]
        got_list = json.loads(res[-1].decode())

        self.assertListEqual(expected_list, got_list)
 def test_init(self):
     w = Wallet()
     TransactionBuilder(sender=w.verifying_key().hex(),
                        stamps=1000000,
                        contract='currency',
                        function='transfer',
                        kwargs={'amount': 'b'},
                        processor=b'\x00' * 32,
                        nonce=0)
    def test_resync_adds_new_contacts(self):
        p1 = Network(wallet=self.wallet,
                     ctx=self.ctx,
                     socket_base='tcp://127.0.0.1')

        p1.peer_service.table = self.peer_table

        p = Peers(wallet=self.wallet,
                  ctx=self.ctx,
                  parameters=self.paramaters,
                  node_type=MN,
                  service_type=ServiceType.INCOMING_WORK)

        self.authenticator.sync_certs()

        async def late_refresh():
            await asyncio.sleep(0.3)
            await self.paramaters.refresh()
            p.sync_sockets()

        async def stop():
            await asyncio.sleep(0.5)
            p1.stop()

        tasks = asyncio.gather(p1.start(discover=False), late_refresh(),
                               stop())

        self.loop.run_until_complete(tasks)

        socket_1 = p.sockets.get(self.test_wallet_1.verifying_key().hex())

        self.assertIsNotNone(socket_1)

        new_wallet = Wallet()

        p1.peer_service.table[
            new_wallet.verifying_key().hex()] = 'ipc:///tmp/n3'
        self.contacts.masternodes.append(new_wallet.verifying_key().hex())
        self.authenticator.sync_certs()

        async def late_refresh():
            await asyncio.sleep(0.3)
            await self.paramaters.refresh()
            p.sync_sockets()

        async def stop():
            await asyncio.sleep(0.5)
            p1.stop()

        tasks = asyncio.gather(p1.start(discover=False), late_refresh(),
                               stop())

        self.loop.run_until_complete(tasks)

        socket_1 = p.sockets.get(new_wallet.verifying_key().hex())
        self.assertIsNotNone(socket_1)
def make_network(masternodes,
                 delegates,
                 ctx,
                 mn_min_quorum=2,
                 del_min_quorum=2):
    mn_wallets = [Wallet() for _ in range(masternodes)]
    dl_wallets = [Wallet() for _ in range(delegates)]

    constitution = {
        'masternodes': [mn.verifying_key().hex() for mn in mn_wallets],
        'delegates': [dl.verifying_key().hex() for dl in dl_wallets],
        'masternode_min_quorum': mn_min_quorum,
        'delegate_min_quorum': del_min_quorum,
    }

    mns = []
    dls = []
    bootnodes = None
    node_count = 0
    for wallet in mn_wallets:
        driver = BlockchainDriver(driver=InMemDriver())
        # driver = IsolatedDriver()
        ipc = f'/tmp/n{node_count}'
        make_ipc(ipc)

        if bootnodes is None:
            bootnodes = [f'ipc://{ipc}']

        mn = Masternode(wallet=wallet,
                        ctx=ctx,
                        socket_base=f'ipc://{ipc}',
                        bootnodes=bootnodes,
                        constitution=deepcopy(constitution),
                        webserver_port=18080 + node_count,
                        driver=driver)

        mns.append(mn)
        node_count += 1

    for wallet in dl_wallets:
        driver = BlockchainDriver(driver=InMemDriver())
        # driver = IsolatedDriver()
        ipc = f'/tmp/n{node_count}'
        make_ipc(ipc)

        dl = Delegate(wallet=wallet,
                      ctx=ctx,
                      socket_base=f'ipc://{ipc}',
                      constitution=deepcopy(constitution),
                      bootnodes=bootnodes,
                      driver=driver)

        dls.append(dl)
        node_count += 1

    return mns, dls
Esempio n. 28
0
def build_nodes(num_nodes=1) -> list:
    nodes = []
    for i in range(num_nodes):
        i = Wallet()
        nodes.append({
            'sk': i.signing_key(as_hex=True),
            'vk': i.verifying_key(as_hex=True)
        })

    print(nodes)
 def test_passing_float_in_contract_kwargs_raises_assertion(self):
     w = Wallet()
     with self.assertRaises(AssertionError):
         TransactionBuilder(sender=w.verifying_key().hex(),
                            stamps=1000000,
                            contract='currency',
                            function='transfer',
                            kwargs={'amount': 123.00},
                            processor=b'\x00' * 32,
                            nonce=0)
    def test_verifying_key_as_bytes(self):
        w = Wallet()

        _w = w.verifying_key()

        self.assertTrue(isinstance(_w, bytes))

        _h = w.verifying_key(as_hex=True)

        self.assertTrue(isinstance(_h, str))