Exemplo n.º 1
0
    def __init__(self):
        configure_logger(log)

        if 'monitor' in config:
            node_url = config['monitor']['url']
        else:
            log.warning("monitor using config['ethereum'] node")
            node_url = config['ethereum']['url']

        self.eth = JsonRPCClient(node_url,
                                 connect_timeout=5.0,
                                 request_timeout=10.0)
        # filter health processes depend on some of the calls failing on the first time
        # so we have a separate client to handle those
        self.filter_eth = JsonRPCClient(node_url,
                                        force_instance=True,
                                        connect_timeout=10.0,
                                        request_timeout=60.0)

        self._check_schedule = None
        self._poll_schedule = None
        self._sanity_check_schedule = None
        self._block_checking_process = None
        self._filter_poll_process = None
        self._sanity_check_process = None
        self._process_unconfirmed_transactions_process = None

        self._new_pending_transaction_filter_id = None
        self._last_saw_new_block = asyncio.get_event_loop().time()
        self._shutdown = False

        self._lastlog = 0
        self._blocktimes = []
Exemplo n.º 2
0
    def process_config(self):
        config = super().process_config()

        if 'ETHEREUM_NODE_URL' in os.environ:
            config['ethereum'] = {'url': os.environ['ETHEREUM_NODE_URL']}

        if 'DEFAULT_GASPRICE' in os.environ:
            if 'ethereum' not in config:
                config['ethereum'] = {}
            config['ethereum']['default_gasprice'] = os.environ[
                'DEFAULT_GASPRICE']

        if 'ethereum' in config:
            if 'ETHEREUM_NETWORK_ID' in os.environ:
                config['ethereum']['network_id'] = os.environ[
                    'ETHEREUM_NETWORK_ID']
            else:
                config['ethereum'][
                    'network_id'] = self.asyncio_loop.run_until_complete(
                        to_asyncio_future(
                            JsonRPCClient(
                                config['ethereum']['url']).net_version()))

        configure_logger(services_log)

        return config
Exemplo n.º 3
0
    def __init__(self, *args, listener_id="block_monitor", **kwargs):

        # so DatabaseMixin works
        self.application = self

        super().__init__([], *args, listener_id=listener_id, **kwargs)

        configure_logger(log)

        if 'monitor' in self.config:
            node_url = self.config['monitor']['url']
        else:
            log.warning("monitor using config['ethereum'] node")
            node_url = self.config['ethereum']['url']

        self.eth = JsonRPCClient(node_url)

        self._check_schedule = None
        self._poll_schedule = None
        self._block_checking_process = None
        self._filter_poll_process = None

        self._lastlog = 0

        self.tasks = TaskDispatcher(self.task_listener)
Exemplo n.º 4
0
 async def test_getLogs_unknown_block_number_handling(self, *, parity):
     """Ensures that the block number check is working correctly"""
     client = JsonRPCClient(parity.dsn()['url'])
     block_number = await client.eth_blockNumber()
     logs = await client.eth_getLogs(fromBlock=block_number + 2)
     self.assertEqual(logs, [])
     self.assertGreaterEqual(
         await client.eth_blockNumber(), block_number + 2,
         "eth_getLogs did not wait until the node's block number caught up to the requested block"
     )
     logs = await client.eth_getLogs(fromBlock=block_number,
                                     toBlock=block_number + 6)
     self.assertEqual(logs, [])
     self.assertGreaterEqual(
         await client.eth_blockNumber(), block_number + 6,
         "eth_getLogs did not wait until the node's block number caught up to the requested block"
     )
     logs = await client.eth_getLogs(toBlock=block_number + 10)
     self.assertEqual(logs, [])
     self.assertGreaterEqual(
         await client.eth_blockNumber(), block_number + 10,
         "eth_getLogs did not wait until the node's block number caught up to the requested block"
     )
     logs = await client.eth_getLogs(toBlock=block_number + 20,
                                     validate_block_number=False)
     self.assertLess(
         await client.eth_blockNumber(), block_number + 20,
         "eth_getLogs unexpectidly waited until the block number caught up to the requested block"
     )
     self.assertEqual(
         logs, [],
         "parity started returning something other than [] for eth_getLogs when block params are too high"
     )
     await client.close()
Exemplo n.º 5
0
 async def test_aiohttp_jsonrpc_client(self, *, parity):
     client = JsonRPCClient(parity.dsn()['url'], client_cls=AIOHTTPClient)
     block_number = await client.eth_blockNumber()
     balance = await client.eth_getBalance(FAUCET_ADDRESS)
     self.assertEqual(
         balance,
         1606938044258990275541962092341162602522202993782792835301376)
     await client.close()
Exemplo n.º 6
0
 def __init__(self, *args, **kwargs):
     super().__init__([(CollectiblesProcessingHandler, )],
                      *args,
                      listener_id=self.__class__.__name__,
                      **kwargs)
     self.eth = JsonRPCClient(self.config['ethereum']['url'],
                              should_retry=False)
     self.ioloop.add_callback(self.process_block)
Exemplo n.º 7
0
 async def test_unknown_block_number_handling(self, *, parity):
     client = JsonRPCClient(parity.dsn()['url'])
     block_number = await client.eth_blockNumber()
     balance = await client.eth_getBalance(FAUCET_ADDRESS,
                                           block=block_number + 2)
     self.assertEqual(
         balance,
         1606938044258990275541962092341162602522202993782792835301376)
     await client.close()
Exemplo n.º 8
0
    async def faucet(self,
                     to,
                     value,
                     *,
                     from_private_key=FAUCET_PRIVATE_KEY,
                     startgas=None,
                     gasprice=DEFAULT_GASPRICE,
                     nonce=None,
                     data=b"",
                     wait_on_confirmation=True):

        if isinstance(from_private_key, str):
            from_private_key = data_decoder(from_private_key)
        from_address = private_key_to_address(from_private_key)

        ethclient = JsonRPCClient(config['ethereum']['url'])

        to = data_decoder(to)
        if len(to) not in (20, 0):
            raise Exception(
                'Addresses must be 20 or 0 bytes long (len was {})'.format(
                    len(to)))

        if nonce is None:
            nonce = await ethclient.eth_getTransactionCount(from_address)
        balance = await ethclient.eth_getBalance(from_address)

        if startgas is None:
            startgas = await ethclient.eth_estimateGas(from_address,
                                                       to,
                                                       data=data,
                                                       nonce=nonce,
                                                       value=value,
                                                       gasprice=gasprice)

        tx = Transaction(nonce, gasprice, startgas, to, value, data, 0, 0, 0)

        if balance < (tx.value + (tx.startgas * tx.gasprice)):
            raise Exception("Faucet doesn't have enough funds")

        tx.sign(from_private_key)

        tx_encoded = data_encoder(rlp.encode(tx, Transaction))

        tx_hash = await ethclient.eth_sendRawTransaction(tx_encoded)

        while wait_on_confirmation:
            resp = await ethclient.eth_getTransactionByHash(tx_hash)
            if resp is None or resp['blockNumber'] is None:
                await asyncio.sleep(0.1)
            else:
                break

        if to == b'':
            print("contract address: {}".format(data_encoder(tx.creates)))

        return tx_hash
Exemplo n.º 9
0
    def __init__(self):
        configure_logger(log)

        if 'monitor' in config:
            node_url = config['monitor']['url']
        else:
            log.warning("monitor using config['ethereum'] node")
            node_url = config['ethereum']['url']

        self.eth = JsonRPCClient(node_url, should_retry=True)
Exemplo n.º 10
0
    async def deploy_contract(self,
                              bytecode,
                              *,
                              from_private_key=FAUCET_PRIVATE_KEY,
                              startgas=None,
                              gasprice=DEFAULT_GASPRICE,
                              wait_on_confirmation=True):

        if isinstance(from_private_key, str):
            from_private_key = data_decoder(from_private_key)
        from_address = private_key_to_address(from_private_key)

        ethclient = JsonRPCClient(config['ethereum']['url'])

        nonce = await ethclient.eth_getTransactionCount(from_address)
        balance = await ethclient.eth_getBalance(from_address)

        gasestimate = await ethclient.eth_estimateGas(from_address,
                                                      '',
                                                      data=bytecode,
                                                      nonce=nonce,
                                                      value=0,
                                                      gasprice=gasprice)

        if startgas is None:
            startgas = gasestimate
        elif gasestimate > startgas:
            raise Exception(
                "Estimated gas usage is larger than the provided gas")

        tx = Transaction(nonce, gasprice, startgas, '', 0, bytecode, 0, 0, 0)

        if balance < (tx.value + (tx.startgas * tx.gasprice)):
            raise Exception("Faucet doesn't have enough funds")

        tx.sign(from_private_key)

        tx_encoded = data_encoder(rlp.encode(tx, Transaction))

        tx_hash = await ethclient.eth_sendRawTransaction(tx_encoded)

        contract_address = data_encoder(tx.creates)

        while wait_on_confirmation:
            resp = await ethclient.eth_getTransactionByHash(tx_hash)
            if resp is None or resp['blockNumber'] is None:
                await asyncio.sleep(0.1)
            else:
                code = await ethclient.eth_getCode(contract_address)
                if code == '0x':
                    raise Exception("Failed to deploy contract")
                break

        return tx_hash, contract_address
Exemplo n.º 11
0
    async def test_deploy_contract(self, *, node):

        client = JsonRPCClient(node.dsn()['url'])

        sourcecode = b"contract greeter{string greeting;function greeter(string _greeting) public{greeting=_greeting;}function greet() constant returns (string){return greeting;}}"
        #source_fn = os.path.join(node.get_data_directory(), 'greeting.sol')
        #with open(source_fn, 'wb') as wf:
        #    wf.write(sourcecode)
        source_fn = '<stdin>'

        contract_name = 'greeter'
        constructor_args = [b'hello world!']

        args = ['solc', '--combined-json', 'bin,abi',
                '--add-std']  # , source_fn]
        #output = subprocess.check_output(args, stderr=subprocess.PIPE)
        process = subprocess.Popen(args,
                                   stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
        output, stderrdata = process.communicate(input=sourcecode)
        output = json_decode(output)

        contract = output['contracts']['{}:{}'.format(source_fn,
                                                      contract_name)]
        bytecode = data_decoder(contract['bin'])
        contract_interface = json_decode(contract['abi'])

        translator = ContractTranslator(contract_interface)
        constructor_call = translator.encode_constructor_arguments(
            constructor_args)

        bytecode += constructor_call

        tx_hash, contract_address = await self.deploy_contract(bytecode)

        tx_receipt = await client.eth_getTransactionReceipt(tx_hash)
        self.assertIsNotNone(tx_receipt)

        code = await client.eth_getCode(contract_address)
        self.assertIsNotNone(code)
        self.assertNotEqual(data_decoder(code), b'')

        # call the contract and check the result
        res = await client.eth_call(
            from_address='0x39bf9e501e61440b4b268d7b2e9aa2458dd201bb',
            to_address=contract_address,
            data=sha3('greet()'))
        result = translator.decode_function_result('greet', data_decoder(res))
        self.assertEqual(result[0], constructor_args[0])
    async def test_transaction_overwrite_spam(self, *, ethminer, parity,
                                              monitor, push_client):

        no_to_spam = 10

        # make sure no blocks are confirmed
        ethminer.pause()

        # set up pn registrations
        async with self.pool.acquire() as con:
            await con.fetch(
                "INSERT INTO notification_registrations (service, registration_id, toshi_id, eth_address) VALUES ($1, $2, $3, $4)",
                'gcm', TEST_GCM_ID, TEST_ID_ADDRESS, TEST_WALLET_ADDRESS)

        # send initial tx
        tx1 = await self.get_tx_skel(FAUCET_PRIVATE_KEY, TEST_WALLET_ADDRESS,
                                     10**18)

        txs = []
        for i in range(no_to_spam):
            tx = await self.get_tx_skel(FAUCET_PRIVATE_KEY,
                                        TEST_WALLET_ADDRESS, i)
            txs.append(sign_transaction(tx, FAUCET_PRIVATE_KEY))

        tx1_hash = await self.sign_and_send_tx(FAUCET_PRIVATE_KEY, tx1)
        # wait for tx PN
        await push_client.get()

        # spam send txs manually
        rpcclient = JsonRPCClient(parity.dsn()['url'])
        for ntx in txs:
            await rpcclient.eth_sendRawTransaction(ntx)
            # force the pending transaction filter polling to
            # run after each new transaction is posted
            await monitor.filter_poll()
            # we expect two pns for each overwrite
            await push_client.get()
            await push_client.get()

        async with self.pool.acquire() as con:
            tx1_row = await con.fetchrow(
                "SELECT * FROM transactions WHERE hash = $1", tx1_hash)
            tx_rows = await con.fetchrow("SELECT COUNT(*) FROM transactions")
            tx_rows_error = await con.fetchrow(
                "SELECT COUNT(*) FROM transactions WHERE status = 'error'")

        self.assertEqual(tx1_row['status'], 'error')

        self.assertEqual(tx_rows['count'], no_to_spam + 1)
        self.assertEqual(tx_rows_error['count'], no_to_spam)
    async def test_raw_deploy_contract(self, *, parity):
        """Tests that sending a raw transaction with a contract deployment works"""

        # contract data
        data = "0x6060604052341561000c57fe5b6040516102b83803806102b8833981016040528080518201919050505b806000908051906020019061003f929190610047565b505b506100ec565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061008857805160ff19168380011785556100b6565b828001600101855582156100b6579182015b828111156100b557825182559160200191906001019061009a565b5b5090506100c391906100c7565b5090565b6100e991905b808211156100e55760008160009055506001016100cd565b5090565b90565b6101bd806100fb6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063cfae32171461003b575bfe5b341561004357fe5b61004b6100d4565b604051808060200182810382528381815181526020019150805190602001908083836000831461009a575b80518252602083111561009a57602082019150602081019050602083039250610076565b505050905090810190601f1680156100c65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100dc61017d565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101725780601f1061014757610100808354040283529160200191610172565b820191906000526020600020905b81548152906001019060200180831161015557829003601f168201915b505050505090505b90565b6020604051908101604052806000815250905600a165627a7a72305820493059270656b40625319934bd6e91b0e68cf32c54c099dfc6cf540e40c91b9500290000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c68656c6c6f20776f726c64210000000000000000000000000000000000000000"

        resp = await self.fetch("/tx/skel",
                                method="POST",
                                body={
                                    "from": FAUCET_ADDRESS,
                                    "data": data
                                })
        self.assertEqual(resp.code, 200)
        body = json_decode(resp.body)
        tx = sign_transaction(body['tx'], FAUCET_PRIVATE_KEY)
        resp = await self.fetch("/tx", method="POST", body={"tx": tx})
        self.assertEqual(resp.code, 200, resp.body)

        await self.wait_on_tx_confirmation(json_decode(resp.body)['tx_hash'])

        # test that contract txs from outside are handled correctly
        resp = await self.fetch_signed(
            "/apn/register",
            signing_key=FAUCET_PRIVATE_KEY,
            method="POST",
            body={"registration_id": "blahblahblah"})

        resp = await self.fetch("/tx/skel",
                                method="POST",
                                body={
                                    "from": FAUCET_ADDRESS,
                                    "data": data
                                })
        self.assertEqual(resp.code, 200)
        body = json_decode(resp.body)
        tx = sign_transaction(body['tx'], FAUCET_PRIVATE_KEY)

        # deploy manually
        rpcclient = JsonRPCClient(parity.dsn()['url'])
        tx_hash = await rpcclient.eth_sendRawTransaction(tx)

        await self.wait_on_tx_confirmation(tx_hash)
        await asyncio.sleep(
            5)  # make sure the monitor has had a chance to process this

        async with self.pool.acquire() as con:
            rows = await con.fetch(
                "SELECT * FROM transactions WHERE hash = $1", tx_hash)
        self.assertEqual(len(rows), 1)
    async def test_resend_old_after_overwrite(self, *, ethminer, parity,
                                              monitor, push_client):
        # make sure no blocks are confirmed for the meantime
        ethminer.pause()

        # set up pn registrations
        async with self.pool.acquire() as con:
            await con.fetch(
                "INSERT INTO notification_registrations (service, registration_id, toshi_id, eth_address) VALUES ($1, $2, $3, $4)",
                'gcm', TEST_GCM_ID, TEST_ID_ADDRESS, TEST_WALLET_ADDRESS)

        # get tx skeleton
        tx1 = await self.get_tx_skel(FAUCET_PRIVATE_KEY, TEST_WALLET_ADDRESS,
                                     10**18)
        tx2 = await self.get_tx_skel(FAUCET_PRIVATE_KEY, TEST_WALLET_ADDRESS,
                                     0)
        self.assertEqual(
            decode_transaction(tx1).nonce,
            decode_transaction(tx2).nonce)
        # sign and send
        tx1_hash = await self.sign_and_send_tx(FAUCET_PRIVATE_KEY, tx1)
        # wait for tx PN
        await push_client.get()

        # send tx2 manually
        rpcclient = JsonRPCClient(parity.dsn()['url'])
        tx2_hash = await rpcclient.eth_sendRawTransaction(
            sign_transaction(tx2, FAUCET_PRIVATE_KEY))
        await monitor.filter_poll()
        _, pn = await push_client.get()
        _, pn = await push_client.get()

        # resend tx1 manually
        tx1_hash = await rpcclient.eth_sendRawTransaction(
            sign_transaction(tx1, FAUCET_PRIVATE_KEY))
        await monitor.filter_poll()
        _, pn = await push_client.get()
        _, pn = await push_client.get()

        async with self.pool.acquire() as con:
            tx1_row = await con.fetchrow(
                "SELECT * FROM transactions WHERE hash = $1", tx1_hash)
            tx2_row = await con.fetchrow(
                "SELECT * FROM transactions WHERE hash = $1", tx2_hash)

        self.assertEqual(tx1_row['status'], 'unconfirmed')
        self.assertEqual(tx2_row['status'], 'error')
Exemplo n.º 15
0
def extra_service_config():
    config.set_from_os_environ('ethereum', 'url', 'ETHEREUM_NODE_URL')
    config.set_from_os_environ('monitor', 'url', 'MONITOR_ETHEREUM_NODE_URL')
    if 'ethereum' in config:
        if 'ETHEREUM_NETWORK_ID' in os.environ:
            config['ethereum']['network_id'] = os.environ[
                'ETHEREUM_NETWORK_ID']
        else:
            config['ethereum']['network_id'] = asyncio.get_event_loop(
            ).run_until_complete(
                JsonRPCClient(config['ethereum']['url']).net_version())

    # push service config
    config.set_from_os_environ('pushserver', 'url', 'PUSH_URL')
    config.set_from_os_environ('pushserver', 'username', 'PUSH_USERNAME')
    config.set_from_os_environ('pushserver', 'password', 'PUSH_PASSWORD')
    config.set_from_os_environ('gcm', 'server_key', 'GCM_SERVER_KEY')
Exemplo n.º 16
0
def prepare_ethereum_jsonrpc_client(config):
    if 'url' in config:
        url = config['url']
    elif 'host' in config:
        ssl = config.get('ssl', 'false')
        if ssl is True or (isinstance(ssl, str) and ssl.lower() == 'true'):
            protocol = 'https://'
        else:
            protocol = 'http://'
        port = config.get('port', '8545')
        host = config.get('host', 'localhost')
        path = config.get('path', '/')
        if not path.startswith('/'):
            path = "/{}".format(path)

        url = "{}{}:{}{}".format(protocol, host, port, path)
    return JsonRPCClient(url)
Exemplo n.º 17
0
    def __init__(self):
        configure_logger(log)

        if 'monitor' in config:
            node_url = config['monitor']['url']
        else:
            log.warning("monitor using config['ethereum'] node")
            node_url = config['ethereum']['url']

        self.eth = JsonRPCClient(node_url, should_retry=False)

        self._check_schedule = None
        self._poll_schedule = None
        self._sanity_check_schedule = None
        self._block_checking_process = None
        self._filter_poll_process = None
        self._sanity_check_process = None
        self._process_unconfirmed_transactions_process = None

        self._lastlog = 0
Exemplo n.º 18
0
    def process_config(self):
        config = super().process_config()
        if 'ETHEREUM_NODE_URL' in os.environ:
            config['ethereum'] = {'url': os.environ['ETHEREUM_NODE_URL']}

        if 'MONITOR_ETHEREUM_NODE_URL' in os.environ:
            config['monitor'] = {
                'url': os.environ['MONITOR_ETHEREUM_NODE_URL']
            }

        if 'ethereum' in config:
            if 'ETHEREUM_NETWORK_ID' in os.environ:
                config['ethereum']['network_id'] = os.environ[
                    'ETHEREUM_NETWORK_ID']
            else:
                config['ethereum'][
                    'network_id'] = self.asyncio_loop.run_until_complete(
                        to_asyncio_future(
                            JsonRPCClient(
                                config['ethereum']['url']).net_version()))
        return config
Exemplo n.º 19
0
    async def test_bulk(self, *, parity):
        client = JsonRPCClient(parity.dsn()['url'])

        bulk = client.bulk()
        f1 = bulk.eth_blockNumber()
        f2 = bulk.eth_getBalance(FAUCET_ADDRESS)
        f3 = bulk.eth_gasPrice()
        f4 = bulk.eth_getBalance("0x0000000000000000000000000000000000000000")
        f5 = bulk.eth_getBalance(FAUCET_ADDRESS, block=100000000)
        results = await bulk.execute()
        self.assertEqual(f1.result(), results[0])
        self.assertEqual(f2.result(), results[1])
        self.assertEqual(f3.result(), results[2])
        self.assertEqual(f4.result(), results[3])

        try:
            f5.result()
            self.fail("expected exception")
        except JsonRPCError as e:
            self.assertEqual(e.message, "Unknown block number")
        except Exception as e:
            self.fail("unexpected exception: {}".format(e))
        await client.close()
Exemplo n.º 20
0
    async def update_default_gas_price(self, blocknumber):

        client = AsyncHTTPClient()
        fast_wei = None
        standard_wei = None
        safelow_wei = None
        eth_gasprice = None

        # only needed on mainnet
        if config['ethereum']['network_id'] == '1':

            try:
                resp = await client.fetch(
                    "https://ethgasstation.info/json/ethgasAPI.json")
                rval = json_decode(resp.body)

                if 'fast' not in rval:
                    log.error(
                        "Unexpected results from EthGasStation: {}".format(
                            resp.body))
                elif not isinstance(rval['fast'], (int, float)):
                    log.error(
                        "Unexpected 'average' gas price returned by EthGasStation: {}"
                        .format(rval['fast']))
                else:
                    gwei_x1000 = int(rval['fast'] * 100)
                    fast_wei = gwei_x1000 * 1000000

                if 'average' not in rval:
                    log.error(
                        "Unexpected results from EthGasStation: {}".format(
                            resp.body))
                elif not isinstance(rval['average'], (int, float)):
                    log.error(
                        "Unexpected 'average' gas price returned by EthGasStation: {}"
                        .format(rval['average']))
                else:
                    gwei_x1000 = int(rval['average'] * 100)
                    standard_wei = gwei_x1000 * 1000000

                if 'safeLow' not in rval:
                    log.error(
                        "Unexpected results from EthGasStation: {}".format(
                            resp.body))
                elif not isinstance(rval['safeLow'], (int, float)):
                    log.error(
                        "Unexpected 'safeLow' gas price returned by EthGasStation: {}"
                        .format(rval['safeLow']))
                else:
                    gwei_x1000 = int(rval['safeLow'] * 100)
                    safelow_wei = gwei_x1000 * 1000000

                # sanity check the values, if safelow is greater than standard
                # then use the safe low as standard + an extra gwei of padding
                if safelow_wei > standard_wei:
                    standard_wei = safelow_wei + 1000000000

                safelow_wei = hex(safelow_wei)
                standard_wei = hex(standard_wei)
                fast_wei = hex(fast_wei)

                await self.redis.mset('gas_station_safelow_gas_price',
                                      safelow_wei,
                                      'gas_station_standard_gas_price',
                                      standard_wei,
                                      'gas_station_fast_gas_price', fast_wei)

            except:
                log.exception(
                    "Error updating default gas price from EthGasStation")

        try:
            # use the monitor url if available
            if 'monitor' in config:
                node_url = config['monitor']['url']
            else:
                log.warning("monitor using config['ethereum'] node")
                node_url = config['ethereum']['url']
            eth = JsonRPCClient(node_url,
                                connect_timeout=5.0,
                                request_timeout=10.0)
            eth_gasprice = await eth.eth_gasPrice()
            eth_gasprice = hex(eth_gasprice)
        except:
            log.exception("Error updating default gas price from eth node")

        # in case the eth gas station check failed, fall back on node gas price
        # if the node gas price is higher than the previous eth gas station fast gas price
        if fast_wei is None and eth_gasprice is not None:
            old_fast_wei = parse_int(
                await self.redis.get('gas_station_fast_gas_price'))
            if old_fast_wei is None or parse_int(eth_gasprice) > old_fast_wei:
                await self.redis.set('gas_station_fast_gas_price',
                                     eth_gasprice)

        async with self.db:
            await self.db.execute(
                "INSERT INTO gas_price_history "
                "(timestamp, blocknumber, gas_station_fast, gas_station_standard, gas_station_safelow, eth_gasprice) "
                "VALUES ($1, $2, $3, $4, $5, $6) "
                "ON CONFLICT (timestamp) DO NOTHING", int(time.time()),
                blocknumber, fast_wei, standard_wei, safelow_wei, eth_gasprice)
            await self.db.commit()
Exemplo n.º 21
0
 def eth(self):
     if not hasattr(self, '_eth_jsonrpc_client'):
         self._eth_jsonrpc_client = JsonRPCClient(
             config['ethereum']['url'],
             connect_timeout=5.0, request_timeout=5.0)
     return self._eth_jsonrpc_client
Exemplo n.º 22
0
    async def test_tx_queue_error_propagation(self, *, ethminer, parity, push_client):
        """Tests that a long chain of txs depending on a single transaction propagate errors correctly"""

        # start 2nd parity server
        p2 = ParityServer(bootnodes=parity.dsn()['node'])
        e2 = EthMiner(jsonrpc_url=p2.dsn()['url'],
                      debug=False)
        rpcclient = JsonRPCClient(p2.dsn()['url'])

        default_fees = DEFAULT_STARTGAS * DEFAULT_GASPRICE

        val = 100 * 10 ** 18
        txs = []
        # send funds to address1
        f_tx_hash = await self.send_tx(FAUCET_PRIVATE_KEY, TEST_ADDRESS_1, val)

        await self.ensure_confirmed(f_tx_hash)

        # make sure the nodes are synchronized
        while True:
            bal = await rpcclient.eth_getBalance(TEST_ADDRESS_1)
            if bal == 0:
                await asyncio.sleep(1)
            else:
                break

        # make sure no blocks are mined
        ethminer.pause()
        e2.pause()

        addresses = [(TEST_ADDRESS_1, TEST_PRIVATE_KEY_1),
                     (TEST_ADDRESS_2, TEST_PRIVATE_KEY_2),
                     (TEST_ADDRESS_3, TEST_PRIVATE_KEY_3),
                     (TEST_ADDRESS_4, TEST_PRIVATE_KEY_4),
                     (TEST_ADDRESS_5, TEST_PRIVATE_KEY_5),
                     (TEST_ADDRESS_6, TEST_PRIVATE_KEY_6)]

        async with self.pool.acquire() as con:
            for addr, pk in addresses:
                await con.fetch("INSERT INTO notification_registrations (service, registration_id, toshi_id, eth_address) VALUES ($1, $2, $3, $4)",
                                'gcm', "abc", addr, addr)

        # send a tx from outside the system first which wont be seen by
        # the system until after the transactions generated in the next block
        tx = await self.get_tx_skel(TEST_PRIVATE_KEY_1, FAUCET_ADDRESS, val - default_fees)
        tx = sign_transaction(tx, TEST_PRIVATE_KEY_1)
        await rpcclient.eth_sendRawTransaction(tx)

        # generate internal transactions
        for i in range(len(addresses) * 2):
            val = val - default_fees
            addr1, pk1 = addresses[0]
            addr2, pk2 = addresses[1]
            # send funds
            tx_hash = await self.send_tx(pk1, addr2, val)
            txs.append(tx_hash)
            # swap all the variables
            addresses = addresses[1:] + [addresses[0]]

        # make sure we got pns for all
        for i in range(len(addresses) * 2):
            await push_client.get()
            await push_client.get()

        # start mining again
        e2.start()

        await self.ensure_errors(*txs)

        # make sure we got error pns for all
        for i in range(len(addresses) * 2):
            await push_client.get()
            await push_client.get()
        # and the pn for the overwritten tx
        await push_client.get()
Exemplo n.º 23
0
 def __init__(self):
     extra_service_config()
     self.eth = JsonRPCClient(config['ethereum']['url'], should_retry=False)
     asyncio.get_event_loop().create_task(self._initialize())
    async def test_jsonrpc_errors(self, *, parity, push_client, monitor, fung):

        creator_contract = await self.deploy_contract(ARTTOKEN_CONTRACT,
                                                      "ArtTokenCreator", [])
        async with self.pool.acquire() as con:
            await con.execute(
                "INSERT INTO collectibles (contract_address, name, type, image_url_format_string) VALUES ($1, $2, $3, $4)",
                creator_contract.address, "Art Tokens", 2,
                "https://ipfs.node/{token_uri}")

        await self.faucet(TEST_ADDRESS, 10**18)

        # "mint" some tokens
        txhash = await creator_contract.createAsset.set_sender(
            TEST_PRIVATE_KEY)("ART1", 10, "dasdasdasdasdasdasdasd",
                              TEST_ADDRESS)
        receipt = await self.eth.eth_getTransactionReceipt(txhash)
        arttokenaddr = "0x" + receipt['logs'][0]['topics'][1][-40:]

        arttoken1 = await Contract.from_source_code(ARTTOKEN_CONTRACT,
                                                    "ArtToken",
                                                    address=arttokenaddr,
                                                    deploy=False)

        # force block check to clear out txs pre registration
        await asyncio.sleep(0.1)
        await monitor.block_check()
        await asyncio.sleep(0.1)

        async with self.pool.acquire() as con:
            self.assertEqual(
                await
                con.fetchval("SELECT count(*) FROM fungible_collectibles"), 1)

        # send an art token!
        await arttoken1.transfer.set_sender(TEST_PRIVATE_KEY)(TEST_ADDRESS_2,
                                                              1)

        await asyncio.sleep(0.1)
        await monitor.block_check()
        await asyncio.sleep(0.1)

        async with self.pool.acquire() as con:
            owner_balance = await con.fetchval(
                "SELECT balance FROM fungible_collectible_balances WHERE owner_address = $1 AND contract_address = $2",
                TEST_ADDRESS, arttoken1.address)
            receiver_balance = await con.fetchval(
                "SELECT balance FROM fungible_collectible_balances WHERE owner_address = $1 AND contract_address = $2",
                TEST_ADDRESS_2, arttoken1.address)
        self.assertEqual(owner_balance, hex(9))
        self.assertEqual(receiver_balance, hex(1))

        # break the fungible monitor's eth instance
        old_fung_eth = fung.eth
        fung.eth = JsonRPCClient(self.get_url('/fake_jsonrpc'),
                                 should_retry=False)

        await arttoken1.transfer.set_sender(TEST_PRIVATE_KEY)(TEST_ADDRESS_3,
                                                              1)

        for i in range(10):
            async with self.pool.acquire() as con:
                val = await con.fetchval(
                    "SELECT blocknumber FROM last_blocknumber")
                blk = await con.fetchval(
                    "SELECT last_block FROM fungible_collectibles")
                count = await con.fetchval(
                    "SELECT count(*) FROM fungible_collectible_balances")
            self.assertEqual(count, 2)
            if i > 0:
                self.assertNotEqual(val, blk)
            await asyncio.sleep(1)

        fung.eth = old_fung_eth
        await asyncio.sleep(5)
        async with self.pool.acquire() as con:
            val = await con.fetchval("SELECT blocknumber FROM last_blocknumber"
                                     )
            blk = await con.fetchval(
                "SELECT last_block FROM fungible_collectibles")
            count = await con.fetchval(
                "SELECT count(*) FROM fungible_collectible_balances")
        self.assertEqual(count, 3)
        if val - blk > 1:
            self.fail(
                "fungible last_block is not caught up with the current block after failures"
            )
Exemplo n.º 25
0
    async def __call__(self,
                       *args,
                       startgas=None,
                       gasprice=20000000000,
                       value=0,
                       wait_for_confirmation=True):

        # TODO: figure out if we can validate args
        validated_args = []
        for (type, name), arg in zip(
                self.contract.translator.function_data[self.name]['signature'],
                args):
            if type == 'address' and isinstance(arg, str):
                validated_args.append(data_decoder(arg))
            elif (type.startswith("uint")
                  or type.startswith("int")) and isinstance(arg, str):
                validated_args.append(int(arg, 16))
            else:
                validated_args.append(arg)

        ethurl = get_url()

        ethclient = JsonRPCClient(ethurl)

        data = self.contract.translator.encode_function_call(
            self.name, validated_args)

        # TODO: figure out if there's a better way to tell if the function needs to be called via sendTransaction
        if self.is_constant:
            result = await ethclient.eth_call(from_address=self.from_address
                                              or '',
                                              to_address=self.contract.address,
                                              data=data)
            result = data_decoder(result)
            if result:
                decoded = self.contract.translator.decode_function_result(
                    self.name, result)
                # decode string results
                decoded = [
                    val.decode('utf-8')
                    if isinstance(val, bytes) and type == 'string' else val
                    for val, type in zip(
                        decoded, self.contract.translator.function_data[
                            self.name]['decode_types'])
                ]
                # return the single value if there is only a single return value
                if len(decoded) == 1:
                    return decoded[0]
                return decoded
            return None

        else:
            if self.from_address is None:
                raise Exception(
                    "Cannot call non-constant function without a sender")

            nonce = await ethclient.eth_getTransactionCount(self.from_address)
            balance = await ethclient.eth_getBalance(self.from_address)

            if startgas is None:
                startgas = await ethclient.eth_estimateGas(
                    self.from_address,
                    self.contract.address,
                    data=data,
                    nonce=nonce,
                    value=value,
                    gasprice=gasprice)
            if startgas == 50000000 or startgas is None:
                raise Exception(
                    "Unable to estimate gas cost, possibly something wrong with the transaction arguments"
                )

            if balance < (startgas * gasprice):
                raise Exception("Given account doesn't have enough funds")

            tx = Transaction(nonce, gasprice, startgas, self.contract.address,
                             value, data, 0, 0, 0)
            tx.sign(self.from_key)

            tx_encoded = data_encoder(rlp.encode(tx, Transaction))

            if self.return_raw_tx:
                return tx_encoded

            try:
                tx_hash = await ethclient.eth_sendRawTransaction(tx_encoded)
            except:
                print(balance, startgas * gasprice, startgas)
                raise

            # wait for the contract to be deployed
            if wait_for_confirmation:
                print("waiting on transaction: {}".format(tx_hash))
                starttime = time.time()
                warnlevel = 0
            while wait_for_confirmation:
                resp = await ethclient.eth_getTransactionByHash(tx_hash)
                if resp is None or resp['blockNumber'] is None:
                    await asyncio.sleep(0.1)
                    if resp is None and warnlevel == 0 and time.time(
                    ) - starttime < 10:
                        print(
                            "WARNING: 10 seconds have passed and transaction is not showing as a pending transaction"
                        )
                        warnlevel = 1
                    elif resp is None and warnlevel == 1 and time.time(
                    ) - starttime < 60:
                        print(
                            "WARNING: 60 seconds have passed and transaction is not showing as a pending transaction"
                        )
                        raise Exception(
                            "Unexpected error waiting for transaction to complete"
                        )
                else:
                    receipt = await ethclient.eth_getTransactionReceipt(tx_hash
                                                                        )
                    if 'status' in receipt and receipt['status'] != "0x1":
                        raise Exception(
                            "Transaction status returned {}".format(
                                receipt['status']))
                    break

            # TODO: is it possible for non-const functions to have return types?
            return tx_hash
Exemplo n.º 26
0
    async def from_source_code(cls,
                               sourcecode,
                               contract_name,
                               constructor_data=None,
                               *,
                               address=None,
                               deployer_private_key=None,
                               import_mappings=None,
                               libraries=None,
                               optimize=False,
                               deploy=True,
                               cwd=None,
                               wait_for_confirmation=True):

        if deploy:
            ethurl = get_url()

            if address is None and deployer_private_key is None:
                raise TypeError(
                    "requires either address or deployer_private_key")
            if address is None and not isinstance(constructor_data,
                                                  (list, type(None))):
                raise TypeError(
                    "must supply constructor_data as a list (hint: use [] if args should be empty)"
                )

        args = ['solc', '--combined-json', 'bin,abi']
        if libraries:
            args.extend([
                '--libraries',
                ','.join(['{}:{}'.format(*library) for library in libraries])
            ])
        if optimize:
            args.append('--optimize')
        if import_mappings:
            args.extend([
                "{}={}".format(path, mapping)
                for path, mapping in import_mappings
            ])
        # check if sourcecode is actually a filename
        if cwd:
            filename = os.path.join(cwd, sourcecode)
        else:
            filename = sourcecode
        if os.path.exists(filename):
            args.append(filename)
            sourcecode = None
        else:
            filename = '<stdin>'
        process = subprocess.Popen(args,
                                   stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=cwd)
        output, stderrdata = process.communicate(input=sourcecode)
        try:
            output = json_decode(output)
        except json.JSONDecodeError:
            if output and stderrdata:
                output += b'\n' + stderrdata
            elif stderrdata:
                output = stderrdata
            raise Exception("Failed to compile source: {}\n{}\n{}".format(
                filename, ' '.join(args), output.decode('utf-8')))

        try:
            contract = output['contracts']['{}:{}'.format(
                filename, contract_name)]
        except KeyError:
            print(output)
            raise
        abi = json_decode(contract['abi'])

        # deploy contract
        translator = ContractTranslator(abi)
        # fix things that don't have a constructor

        if not deploy:
            return Contract(abi=abi, address=address, translator=translator)

        ethclient = JsonRPCClient(ethurl)

        if address is not None:
            # verify there is code at the given address
            for i in range(10):
                code = await ethclient.eth_getCode(address)
                if code == "0x":
                    await asyncio.sleep(1)
                    continue
                break
            else:
                raise Exception("No code found at given address")
            return Contract(abi=abi, address=address, translator=translator)

        try:
            bytecode = data_decoder(contract['bin'])
        except binascii.Error:
            print(contract['bin'])
            raise

        if constructor_data is not None:
            constructor_call = translator.encode_constructor_arguments(
                constructor_data)
            bytecode += constructor_call

        if isinstance(deployer_private_key, str):
            deployer_private_key = data_decoder(deployer_private_key)
        deployer_address = private_key_to_address(deployer_private_key)
        nonce = await ethclient.eth_getTransactionCount(deployer_address)
        balance = await ethclient.eth_getBalance(deployer_address)

        gasprice = 20000000000
        value = 0

        startgas = await ethclient.eth_estimateGas(deployer_address,
                                                   '',
                                                   data=bytecode,
                                                   nonce=nonce,
                                                   value=0,
                                                   gasprice=gasprice)

        if balance < (startgas * gasprice):
            raise Exception("Given account doesn't have enough funds")

        tx = Transaction(nonce, gasprice, startgas, '', value, bytecode, 0, 0,
                         0)
        tx.sign(deployer_private_key)

        tx_encoded = data_encoder(rlp.encode(tx, Transaction))

        contract_address = data_encoder(tx.creates)

        tx_hash = await ethclient.eth_sendRawTransaction(tx_encoded)

        # wait for the contract to be deployed
        while wait_for_confirmation:
            resp = await ethclient.eth_getTransactionByHash(tx_hash)
            if resp is None or resp['blockNumber'] is None:
                await asyncio.sleep(0.1)
            else:
                code = await ethclient.eth_getCode(contract_address)
                if code == '0x':
                    raise Exception(
                        "Failed to deploy contract: resulting address '{}' has no code"
                        .format(contract_address))
                break

        return Contract(abi=abi,
                        address=contract_address,
                        translator=translator,
                        creation_tx_hash=tx_hash)
    async def test_tx_overwrite(self, *, ethminer, parity, push_client):
        """Tests that if a transaction with the same nonce and one the system knows about
        is sent from outside of the system and included in the block, that the error
        handling picks this up correctly"""

        # start 2nd parity server
        p2 = ParityServer(bootnodes=parity.dsn()['node'])
        e2 = EthMiner(jsonrpc_url=p2.dsn()['url'], debug=False)
        rpcclient2 = JsonRPCClient(p2.dsn()['url'])

        addr1, pk1 = TEST_ADDRESSES[0]
        addr2, pk2 = TEST_ADDRESSES[1]
        addr3, pk3 = TEST_ADDRESSES[2]

        val = 1000 * 10**18

        # send funds to address1
        f_tx_hash = await self.send_tx(FAUCET_PRIVATE_KEY, TEST_ADDRESS_1, val)

        # make sure sync is done
        while True:
            data2 = await rpcclient2.eth_getTransactionByHash(f_tx_hash)
            if data2 and data2['blockNumber'] is not None:
                break
            await asyncio.sleep(1)

        # make sure no blocks are mined
        ethminer.pause()
        e2.pause()

        # make sure transactions are "interesting" to the monitory
        async with self.pool.acquire() as con:
            for addr, pk in TEST_ADDRESSES[:3]:
                await con.fetch(
                    "INSERT INTO notification_registrations (service, registration_id, toshi_id, eth_address) VALUES ($1, $2, $3, $4)",
                    'gcm', "abc", addr, addr)

        # create two transactions with the same nonce and submit them both
        tx1 = await self.get_tx_skel(pk1, addr2, int(val / 3))
        tx2 = await self.get_tx_skel(pk1,
                                     addr3,
                                     int(val / 3),
                                     nonce=decode_transaction(tx1).nonce)
        tx2 = sign_transaction(tx2, pk1)
        tx_hash_2 = await rpcclient2.eth_sendRawTransaction(tx2)
        tx_hash_1 = await self.sign_and_send_tx(pk1, tx1)

        # start mining again
        e2.start()

        # wait for one of the two transactions to complete
        try:
            while True:
                async with self.pool.acquire() as con:
                    tx1_row = await con.fetchrow(
                        "SELECT * FROM transactions WHERE hash = $1",
                        tx_hash_1)
                    tx2_row = await con.fetchrow(
                        "SELECT * FROM transactions WHERE hash = $1",
                        tx_hash_2)
                if tx2_row is not None and tx2_row['status'] == 'confirmed':
                    # good!
                    break
                if tx1_row is not None and tx1_row['status'] == 'confirmed':
                    self.assertFail(
                        "tx1 confirmed, expected tx1 overwrite and tx2 confirmed"
                    )
                await asyncio.sleep(1)
        finally:
            e2.stop()
            p2.stop()