Ejemplo n.º 1
0
    def __init__(
            self,
            host: str,
            port: int,
            privkey: bytes,
            nonce_update_interval: float = 5.0,
            nonce_offset: int = 0):

        endpoint = 'http://{}:{}'.format(host, port)
        session = requests.Session()
        adapter = requests.adapters.HTTPAdapter(pool_maxsize=50)
        session.mount(endpoint, adapter)

        self.transport = HttpPostClientTransport(
            endpoint,
            post_method=session.post,
            headers={'content-type': 'application/json'},
        )

        self.port = port
        self.privkey = privkey
        self.protocol = JSONRPCProtocol()
        self.sender = privatekey_to_address(privkey)
        # Needs to be initialized to None in the beginning since JSONRPCClient
        # gets constructed before the RaidenService Object.
        self.stop_event = None

        self.nonce_last_update = 0
        self.nonce_current_value = None
        self.nonce_lock = Semaphore()
        self.nonce_update_interval = nonce_update_interval
        self.nonce_offset = nonce_offset
Ejemplo n.º 2
0
class JSONRPCClient(object):
    protocol = JSONRPCProtocol()

    def __init__(self, port=4000, print_communication=True):
        self.transport = HttpPostClientTransport('http://127.0.0.1:{}'.format(port))
        self.print_communication = print_communication

    def call(self, method, *args, **kwargs):
        request = self.protocol.create_request(method, args, kwargs)
        reply = self.transport.send_message(request.serialize())
        if self.print_communication:
            print "Request:"
            print json.dumps(json.loads(request.serialize()), indent=2)
            print "Reply:"
            print reply
        return self.protocol.parse_reply(reply).result

    __call__ = call

    def find_block(self, condition):
        """Query all blocks one by one and return the first one for which
        `condition(block)` evaluates to `True`.
        """
        i = 0
        while True:
            block = self.call('eth_getBlockByNumber', quantity_encoder(i), True, print_comm=False)
            if condition(block):
                return block
            i += 1

    def eth_sendTransaction(self, nonce=None, sender='', to='', value=0, data='',
                            gasprice=default_gasprice, startgas=default_startgas,
                            v=None, r=None, s=None):
        encoders = dict(nonce=quantity_encoder, sender=address_encoder, to=data_encoder,
                        value=quantity_encoder, gasprice=quantity_encoder,
                        startgas=quantity_encoder, data=data_encoder,
                        v=quantity_encoder, r=quantity_encoder, s=quantity_encoder)
        data = {k: encoders[k](v) for k, v in locals().items()
                if k not in ('self', 'encoders') and v is not None}
        data['from'] = data.pop('sender')
        assert data.get('from') or (v and r and s)

        res = self.call('eth_sendTransaction', data)
        return data_decoder(res)
Ejemplo n.º 3
0
    def __init__(self, zmq_context, source, destination, n_bytes):
        threading.Thread.__init__(self)

        self.key = "SENDER%i" % Sender.count
        Sender.count += 1

        self.subscriber = zmq_context.socket(zmq.SUB)
        self.subscriber.connect("tcp://%s:5500" % HOST)
        self.subscriber.setsockopt(zmq.SUBSCRIBE, self.key)

        rpc_client = RPCClient(
            JSONRPCProtocol(),
            HttpPostClientTransport("http://%s:5501" % HOST))
        self.remote_server = rpc_client.get_proxy()

        self.source = source
        self.destination = destination
        self.n_bytes = n_bytes

        self.result = ""
Ejemplo n.º 4
0
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc.transports.http import HttpPostClientTransport
from tinyrpc import RPCClient

rpc_client = RPCClient(JSONRPCProtocol(),
                       HttpPostClientTransport('http://127.0.0.1:10090/'))

remote_server = rpc_client.get_proxy()

# call a method called 'reverse_string' with a single string argument
msgs = [{
    "topic": "tA",
    "params": {}
}, {
    "topic": "huh",
    "params": {}
}, {
    "topic": "/tf",
    "params": {
        "frame_id": "World",
        "child_frame_id": "Robot",
        "x": "10.0",
        "y": "2",
        "z": "3",
        "qx": "0.0",
        "qy": "0.7",
        "qz": "-0.7",
        "qw": "0.01"
    }
}, {
    "topic": "/tf",
Ejemplo n.º 5
0
 def __init__(self, obj, wrap_exceptions=True, one_way=False):
     url = obj.env['device_manager.settings']._get_param(
         'mqtt_rpc_bridge_url')
     logger.debug('HTTP bridge url {}'.format(url))
     rpc_client = RPCClient(JSONRPCProtocol(), HttpPostClientTransport(url))
     self.proxy = rpc_client.get_proxy(one_way=one_way)
Ejemplo n.º 6
0
class JSONRPCClient:
    """ Ethereum JSON RPC client.

    Args:
        host: Ethereum node host address.
        port: Ethereum node port number.
        privkey: Local user private key, used to sign transactions.
        nonce_update_interval: Update the account nonce every
            `nonce_update_interval` seconds.
        nonce_offset: Network's default base nonce number.
    """
    def __init__(self,
                 host: str,
                 port: int,
                 privkey: bytes,
                 gasprice: int = None,
                 nonce_update_interval: float = 5.0,
                 nonce_offset: int = 0):

        if privkey is None or len(privkey) != 32:
            raise ValueError('Invalid private key')

        endpoint = 'http://{}:{}'.format(host, port)
        self.session = requests.Session()
        adapter = requests.adapters.HTTPAdapter(pool_maxsize=50)
        self.session.mount(endpoint, adapter)

        self.transport = HttpPostClientTransport(
            endpoint,
            post_method=self.session.post,
            headers={'content-type': 'application/json'},
        )

        self.port = port
        self.privkey = privkey
        self.protocol = JSONRPCProtocol()
        self.sender = privatekey_to_address(privkey)
        # Needs to be initialized to None in the beginning since JSONRPCClient
        # gets constructed before the RaidenService Object.
        self.stop_event = None

        self.nonce_last_update = 0
        self.nonce_available_value = None
        self.nonce_lock = Semaphore()
        self.nonce_update_interval = nonce_update_interval
        self.nonce_offset = nonce_offset
        self.given_gas_price = gasprice

        cache = cachetools.TTLCache(
            maxsize=1,
            ttl=RPC_CACHE_TTL,
        )
        cache_wrapper = cachetools.cached(cache=cache)
        self.gaslimit = cache_wrapper(self._gaslimit)
        cache = cachetools.TTLCache(
            maxsize=1,
            ttl=RPC_CACHE_TTL,
        )
        cache_wrapper = cachetools.cached(cache=cache)
        self.gasprice = cache_wrapper(self._gasprice)

    def __del__(self):
        self.session.close()

    def __repr__(self):
        return '<JSONRPCClient @%d>' % self.port

    def block_number(self):
        """ Return the most recent block. """
        return quantity_decoder(self.rpccall_with_retry('eth_blockNumber'))

    def nonce_needs_update(self):
        if self.nonce_available_value is None:
            return True

        now = time.time()

        # Python's 2.7 time is not monotonic and it's affected by clock resets,
        # force an update.
        if self.nonce_last_update > now:
            return True

        return now - self.nonce_last_update > self.nonce_update_interval

    def nonce_update_from_node(self):
        nonce = -2
        nonce_available_value = self.nonce_available_value or -1

        # Wait until all tx are registered as pending
        while nonce < nonce_available_value:
            pending_transactions_hex = self.rpccall_with_retry(
                'eth_getTransactionCount',
                address_encoder(self.sender),
                'pending',
            )
            pending_transactions = quantity_decoder(pending_transactions_hex)
            nonce = pending_transactions + self.nonce_offset

            log.debug(
                'updated nonce from server',
                server=nonce,
                local=nonce_available_value,
            )

        self.nonce_last_update = time.time()
        self.nonce_available_value = nonce

    def nonce(self):
        with self.nonce_lock:
            if self.nonce_needs_update():
                self.nonce_update_from_node()

            self.nonce_available_value += 1
            return self.nonce_available_value - 1

    def inject_stop_event(self, event):
        self.stop_event = event

    def balance(self, account: Address):
        """ Return the balance of the account of given address. """
        res = self.rpccall_with_retry('eth_getBalance',
                                      address_encoder(account), 'pending')
        return quantity_decoder(res)

    def _gaslimit(self, location='pending') -> int:
        last_block = self.rpccall_with_retry('eth_getBlockByNumber', location,
                                             True)
        gas_limit = quantity_decoder(last_block['gasLimit'])
        return gas_limit * 8 // 10

    def _gasprice(self) -> int:
        if self.given_gas_price:
            return self.given_gas_price

        gas_price = self.rpccall_with_retry('eth_gasPrice')
        return quantity_decoder(gas_price)

    def check_startgas(self, startgas):
        if not startgas:
            return self.gaslimit()
        return startgas

    def new_contract_proxy(self, contract_interface,
                           contract_address: Address):
        """ Return a proxy for interacting with a smart contract.

        Args:
            contract_interface: The contract interface as defined by the json.
            address: The contract's address.
        """
        return ContractProxy(
            self.sender,
            contract_interface,
            contract_address,
            self.eth_call,
            self.send_transaction,
            self.eth_estimateGas,
        )

    def deploy_solidity_contract(
            self,  # pylint: disable=too-many-locals
            contract_name,
            all_contracts,
            libraries,
            constructor_parameters,
            contract_path=None,
            timeout=None):
        """
        Deploy a solidity contract.
        Args:
            sender (address): the sender address
            contract_name (str): the name of the contract to compile
            all_contracts (dict): the json dictionary containing the result of compiling a file
            libraries (list): A list of libraries to use in deployment
            constructor_parameters (tuple): A tuple of arguments to pass to the constructor
            contract_path (str): If we are dealing with solc >= v0.4.9 then the path
                                 to the contract is a required argument to extract
                                 the contract data from the `all_contracts` dict.
            timeout (int): Amount of time to poll the chain to confirm deployment
        """

        if contract_name in all_contracts:
            contract_key = contract_name

        elif contract_path is not None:
            _, filename = os.path.split(contract_path)
            contract_key = filename + ':' + contract_name

            if contract_key not in all_contracts:
                raise ValueError('Unknown contract {}'.format(contract_name))
        else:
            raise ValueError(
                'Unknown contract {} and no contract_path given'.format(
                    contract_name))

        libraries = dict(libraries)
        contract = all_contracts[contract_key]
        contract_interface = contract['abi']
        symbols = solidity_unresolved_symbols(contract['bin_hex'])

        if symbols:
            available_symbols = list(
                map(solidity_library_symbol, all_contracts.keys()))

            unknown_symbols = set(symbols) - set(available_symbols)
            if unknown_symbols:
                msg = 'Cannot deploy contract, known symbols {}, unresolved symbols {}.'.format(
                    available_symbols,
                    unknown_symbols,
                )
                raise Exception(msg)

            dependencies = deploy_dependencies_symbols(all_contracts)
            deployment_order = dependencies_order_of_build(
                contract_key, dependencies)

            deployment_order.pop()  # remove `contract_name` from the list

            log.debug('Deploying dependencies: {}'.format(
                str(deployment_order)))

            for deploy_contract in deployment_order:
                dependency_contract = all_contracts[deploy_contract]

                hex_bytecode = solidity_resolve_symbols(
                    dependency_contract['bin_hex'], libraries)
                bytecode = unhexlify(hex_bytecode)

                dependency_contract['bin_hex'] = hex_bytecode
                dependency_contract['bin'] = bytecode

                transaction_hash_hex = self.send_transaction(
                    to=b'',
                    data=bytecode,
                )
                transaction_hash = unhexlify(transaction_hash_hex)

                self.poll(transaction_hash, timeout=timeout)
                receipt = self.eth_getTransactionReceipt(transaction_hash)

                contract_address = receipt['contractAddress']
                # remove the hexadecimal prefix 0x from the address
                contract_address = contract_address[2:]

                libraries[deploy_contract] = contract_address

                deployed_code = self.eth_getCode(
                    address_decoder(contract_address))

                if not deployed_code:
                    raise RuntimeError(
                        'Contract address has no code, check gas usage.')

            hex_bytecode = solidity_resolve_symbols(contract['bin_hex'],
                                                    libraries)
            bytecode = unhexlify(hex_bytecode)

            contract['bin_hex'] = hex_bytecode
            contract['bin'] = bytecode

        if constructor_parameters:
            translator = ContractTranslator(contract_interface)
            parameters = translator.encode_constructor_arguments(
                constructor_parameters)
            bytecode = contract['bin'] + parameters
        else:
            bytecode = contract['bin']

        transaction_hash_hex = self.send_transaction(
            to=b'',
            data=bytecode,
        )
        transaction_hash = unhexlify(transaction_hash_hex)

        self.poll(transaction_hash, timeout=timeout)
        receipt = self.eth_getTransactionReceipt(transaction_hash)
        contract_address = receipt['contractAddress']

        deployed_code = self.eth_getCode(address_decoder(contract_address))

        if not deployed_code:
            raise RuntimeError(
                'Deployment of {} failed. Contract address has no code, check gas usage.'
                .format(contract_name, ))

        return self.new_contract_proxy(
            contract_interface,
            contract_address,
        )

    def new_filter(self,
                   fromBlock=None,
                   toBlock=None,
                   address=None,
                   topics=None):
        """ Creates a filter object, based on filter options, to notify when
        the state changes (logs). To check if the state has changed, call
        eth_getFilterChanges.
        """

        json_data = {
            'fromBlock': block_tag_encoder(fromBlock or ''),
            'toBlock': block_tag_encoder(toBlock or ''),
        }

        if address is not None:
            json_data['address'] = address_encoder(address)

        if topics is not None:
            if not isinstance(topics, list):
                raise ValueError('topics must be a list')

            json_data['topics'] = [topic_encoder(topic) for topic in topics]

        filter_id = self.rpccall_with_retry('eth_newFilter', json_data)
        return quantity_decoder(filter_id)

    def filter_changes(self, fid: int) -> List:
        changes = self.rpccall_with_retry('eth_getFilterChanges',
                                          quantity_encoder(fid))

        if not changes:
            return list()

        assert isinstance(changes, list)
        decoders = {
            'blockHash': data_decoder,
            'transactionHash': data_decoder,
            'data': data_decoder,
            'address': address_decoder,
            'topics': lambda x: [topic_decoder(t) for t in x],
            'blockNumber': quantity_decoder,
            'logIndex': quantity_decoder,
            'transactionIndex': quantity_decoder
        }
        return [{k: decoders[k](v)
                 for k, v in c.items() if v is not None} for c in changes]

    def rpccall_with_retry(self, method: str, *args):
        """ Do the request and return the result.

        Args:
            method: The JSON-RPC method.
            args: The encoded arguments expected by the method.
                - Object arguments must be supplied as a dictionary.
                - Quantity arguments must be hex encoded starting with '0x' and
                without left zeros.
                - Data arguments must be hex encoded starting with '0x'
        """
        request = self.protocol.create_request(method, args)
        request_serialized = request.serialize().encode()

        for i, timeout in enumerate(timeout_two_stage(10, 3, 10)):
            if self.stop_event and self.stop_event.is_set():
                raise RaidenShuttingDown()

            try:
                reply = self.transport.send_message(request_serialized)
            except (requests.exceptions.ConnectionError, InvalidReplyError):
                log.info(
                    'Timeout in eth client connection to {}. Is the client offline? Trying '
                    'again in {}s.'.format(self.transport.endpoint, timeout))
            else:
                if self.stop_event and self.stop_event.is_set():
                    raise RaidenShuttingDown()

                if i > 0:
                    log.info('Client reconnected')

                jsonrpc_reply = self.protocol.parse_reply(reply)

                if isinstance(jsonrpc_reply, JSONRPCSuccessResponse):
                    return jsonrpc_reply.result
                elif isinstance(jsonrpc_reply, JSONRPCErrorResponse):
                    raise EthNodeCommunicationError(
                        jsonrpc_reply.error,
                        jsonrpc_reply._jsonrpc_error_code,  # pylint: disable=protected-access
                    )
                else:
                    raise EthNodeCommunicationError(
                        'Unknown type of JSONRPC reply')

            gevent.sleep(timeout)

    def send_transaction(
        self,
        to: Address,
        value: int = 0,
        data: bytes = b'',
        startgas: int = None,
    ):
        """ Helper to send signed messages.

        This method will use the `privkey` provided in the constructor to
        locally sign the transaction. This requires an extended server
        implementation that accepts the variables v, r, and s.
        """

        if to == b'' and data.isalnum():
            warnings.warn(
                'Verify that the data parameter is _not_ hex encoded, if this is the case '
                'the data will be double encoded and result in unexpected '
                'behavior.')

        if to == b'0' * 40:
            warnings.warn(
                'For contract creation the empty string must be used.')

        nonce = self.nonce()
        startgas = self.check_startgas(startgas)

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

        tx.sign(self.privkey)
        result = self.rpccall_with_retry(
            'eth_sendRawTransaction',
            data_encoder(rlp.encode(tx)),
        )
        return result[2 if result.startswith('0x') else 0:]

    def eth_call(self,
                 sender: Address = b'',
                 to: Address = b'',
                 value: int = 0,
                 data: bytes = b'',
                 startgas: int = None,
                 block_number: Union[str, int] = 'latest') -> bytes:
        """ Executes a new message call immediately without creating a
        transaction on the blockchain.

        Args:
            sender: The address the transaction is sent from.
            to: The address the transaction is directed to.
            gas: Gas provided for the transaction execution. eth_call
                consumes zero gas, but this parameter may be needed by some
                executions.
            gasPrice: gasPrice used for unit of gas paid.
            value: Integer of the value sent with this transaction.
            data: Hash of the method signature and encoded parameters.
                For details see Ethereum Contract ABI.
            block_number: Determines the state of ethereum used in the
                call.
        """
        startgas = self.check_startgas(startgas)
        json_data = format_data_for_rpccall(
            sender,
            to,
            value,
            data,
            startgas,
            self.gasprice(),
        )
        res = self.rpccall_with_retry('eth_call', json_data, block_number)

        return data_decoder(res)

    def eth_estimateGas(self,
                        sender: Address = b'',
                        to: Address = b'',
                        value: int = 0,
                        data: bytes = b'',
                        startgas: int = None) -> Optional[int]:
        """ Makes a call or transaction, which won't be added to the blockchain
        and returns the used gas, which can be used for estimating the used
        gas.

        Args:
            sender: The address the transaction is sent from.
            to: The address the transaction is directed to.
            gas: Gas provided for the transaction execution. eth_call
                consumes zero gas, but this parameter may be needed by some
                executions.
            value: Integer of the value sent with this transaction.
            data: Hash of the method signature and encoded parameters.
                For details see Ethereum Contract ABI.
            block_number: Determines the state of ethereum used in the
                call.
        """
        startgas = self.check_startgas(startgas)
        json_data = format_data_for_rpccall(sender, to, value, data, startgas)
        try:
            res = self.rpccall_with_retry('eth_estimateGas', json_data)
        except EthNodeCommunicationError as e:
            tx_would_fail = e.error_code and e.error_code in (-32015, -32000)
            if tx_would_fail:  # -32015 is parity and -32000 is geth
                return None
            else:
                raise e

        return quantity_decoder(res)

    def eth_getTransactionReceipt(self, transaction_hash: bytes) -> Dict:
        """ Returns the receipt of a transaction by transaction hash.

        Args:
            transaction_hash: Hash of a transaction.

        Returns:
            A dict representing the transaction receipt object, or null when no
            receipt was found.
        """
        if transaction_hash.startswith(b'0x'):
            warnings.warn(
                'transaction_hash seems to be already encoded, this will'
                ' result in unexpected behavior')

        if len(transaction_hash) != 32:
            raise ValueError(
                'transaction_hash length must be 32 (it might be hex encoded)')

        transaction_hash = data_encoder(transaction_hash)
        return self.rpccall_with_retry('eth_getTransactionReceipt',
                                       transaction_hash)

    def eth_getCode(self,
                    code_address: Address,
                    block: Union[int, str] = 'latest') -> bytes:
        """ Returns code at a given address.

        Args:
            code_address: An address.
            block: Integer block number, or the string 'latest',
                'earliest' or 'pending'. Default is 'latest'.
        """
        if code_address.startswith(b'0x'):
            warnings.warn(
                'address seems to be already encoded, this will result '
                'in unexpected behavior')

        if len(code_address) != 20:
            raise ValueError(
                'address length must be 20 (it might be hex encoded)')

        result = self.rpccall_with_retry('eth_getCode',
                                         address_encoder(code_address), block)
        return data_decoder(result)

    def eth_getTransactionByHash(self, transaction_hash: bytes):
        """ Returns the information about a transaction requested by
        transaction hash.
        """

        if transaction_hash.startswith(b'0x'):
            warnings.warn(
                'transaction_hash seems to be already encoded, this will'
                ' result in unexpected behavior')

        if len(transaction_hash) != 32:
            raise ValueError(
                'transaction_hash length must be 32 (it might be hex encoded)')

        transaction_hash = data_encoder(transaction_hash)
        return self.rpccall_with_retry('eth_getTransactionByHash',
                                       transaction_hash)

    def poll(self,
             transaction_hash: bytes,
             confirmations: int = None,
             timeout: float = None):
        """ Wait until the `transaction_hash` is applied or rejected.
        If timeout is None, this could wait indefinitely!

        Args:
            transaction_hash: Transaction hash that we are waiting for.
            confirmations: Number of block confirmations that we will
                wait for.
            timeout: Timeout in seconds, raise an Excpetion on timeout.
        """
        if transaction_hash.startswith(b'0x'):
            warnings.warn(
                'transaction_hash seems to be already encoded, this will'
                ' result in unexpected behavior')

        if len(transaction_hash) != 32:
            raise ValueError(
                'transaction_hash length must be 32 (it might be hex encoded)')

        transaction_hash = data_encoder(transaction_hash)

        deadline = None
        if timeout:
            deadline = gevent.Timeout(timeout)
            deadline.start()

        try:
            # used to check if the transaction was removed, this could happen
            # if gas price is too low:
            #
            # > Transaction (acbca3d6) below gas price (tx=1 Wei ask=18
            # > Shannon). All sequential txs from this address(7d0eae79)
            # > will be ignored
            #
            last_result = None

            while True:
                # Could return None for a short period of time, until the
                # transaction is added to the pool
                transaction = self.rpccall_with_retry(
                    'eth_getTransactionByHash',
                    transaction_hash,
                )

                # if the transaction was added to the pool and then removed
                if transaction is None and last_result is not None:
                    raise Exception('invalid transaction, check gas price')

                # the transaction was added to the pool and mined
                if transaction and transaction['blockNumber'] is not None:
                    break

                last_result = transaction

                gevent.sleep(.5)

            if confirmations:
                # this will wait for both APPLIED and REVERTED transactions
                transaction_block = quantity_decoder(
                    transaction['blockNumber'])
                confirmation_block = transaction_block + confirmations

                block_number = self.block_number()

                while block_number < confirmation_block:
                    gevent.sleep(.5)
                    block_number = self.block_number()

        except gevent.Timeout:
            raise Exception('timeout when polling for transaction')

        finally:
            if deadline:
                deadline.cancel()
Ejemplo n.º 7
0
def non_sessioned_client():
    client = HttpPostClientTransport('http://%s:%d' % TEST_SERVER_ADDR)
    return client
Ejemplo n.º 8
0
class JSONRPCClient(object):
    protocol = JSONRPCProtocol()

    def __init__(self,
                 port=4000,
                 print_communication=True,
                 privkey=None,
                 sender=None):
        "specify privkey for local signing"
        self.transport = HttpPostClientTransport(
            'http://127.0.0.1:{}'.format(port))
        self.print_communication = print_communication
        self.privkey = privkey
        self._sender = sender

    @property
    def sender(self):
        if self.privkey:
            return privtoaddr(self.privkey)
        if self._sender is None:
            self._sender = self.coinbase
        return self._sender

    def call(self, method, *args):
        request = self.protocol.create_request(method, args)
        reply = self.transport.send_message(request.serialize())
        if self.print_communication:
            print json.dumps(json.loads(request.serialize()), indent=2)
            print reply
        return self.protocol.parse_reply(reply).result

    __call__ = call

    def find_block(self, condition):
        """Query all blocks one by one and return the first one for which
        `condition(block)` evaluates to `True`.
        """
        i = 0
        while True:
            block = self.call('eth_getBlockByNumber', quantity_encoder(i),
                              True)
            if condition(block):
                return block
            i += 1

            return None

    def new_filter(self, fromBlock="", toBlock="", address=None, topics=[]):
        encoders = dict(fromBlock=block_tag_encoder,
                        toBlock=block_tag_encoder,
                        address=address_encoder,
                        topics=lambda x: [topic_encoder(t) for t in x])
        data = {
            k: encoders[k](v)
            for k, v in locals().items()
            if k not in ('self', 'encoders') and v is not None
        }
        fid = self.call('eth_newFilter', data)
        return quantity_decoder(fid)

    def filter_changes(self, fid):
        changes = self.call('eth_getFilterChanges', quantity_encoder(fid))
        if not changes:
            return None
        elif isinstance(changes, bytes):
            return data_decoder(changes)
        else:
            decoders = dict(blockHash=data_decoder,
                            transactionHash=data_decoder,
                            data=data_decoder,
                            address=address_decoder,
                            topics=lambda x: [topic_decoder(t) for t in x],
                            blockNumber=quantity_decoder,
                            logIndex=quantity_decoder,
                            transactionIndex=quantity_decoder)
            return [{k: decoders[k](v)
                     for k, v in c.items() if v is not None} for c in changes]

    def eth_sendTransaction(self,
                            nonce=None,
                            sender='',
                            to='',
                            value=0,
                            data='',
                            gasPrice=default_gasprice,
                            gas=default_startgas,
                            v=None,
                            r=None,
                            s=None,
                            secret=None):
        to = address20(to)
        encoders = dict(nonce=quantity_encoder,
                        sender=address_encoder,
                        to=data_encoder,
                        value=quantity_encoder,
                        gasPrice=quantity_encoder,
                        gas=quantity_encoder,
                        data=data_encoder,
                        v=quantity_encoder,
                        r=quantity_encoder,
                        s=quantity_encoder,
                        secret=secret_encoder)
        data = {
            k: encoders[k](v)
            for k, v in locals().items()
            if k not in ('self', 'encoders') and v is not None
        }
        data['from'] = data.pop('sender')
        assert data.get('from') or (v and r and s)
        res = self.call('eth_sendTransaction', data)
        return data_decoder(res)

    def eth_call(self,
                 sender='',
                 to='',
                 value=0,
                 data='',
                 startgas=default_startgas,
                 gasprice=default_gasprice):
        "call on pending block"
        encoders = dict(sender=address_encoder,
                        to=data_encoder,
                        value=quantity_encoder,
                        gasprice=quantity_encoder,
                        startgas=quantity_encoder,
                        data=data_encoder)
        data = {
            k: encoders[k](v)
            for k, v in locals().items()
            if k not in ('self', 'encoders') and v is not None
        }
        for k, v in dict(gasprice='gasPrice', startgas='gas',
                         sender='from').items():
            data[v] = data.pop(k)
        res = self.call('eth_call', data)
        return data_decoder(res)

    def blocknumber(self):
        return quantity_decoder(self.call('eth_blockNumber'))

    def nonce(self, address):
        if len(address) == 40:
            address = address.decode('hex')
        return quantity_decoder(
            self.call('eth_getTransactionCount', address_encoder(address),
                      'pending'))

    @property
    def coinbase(self):
        return address_decoder(self.call('eth_coinbase'))

    def balance(self, account):
        b = quantity_decoder(
            self.call('eth_getBalance', address_encoder(account), 'pending'))
        return b

    def gaslimit(self):
        return quantity_decoder(self.call('eth_gasLimit'))

    def lastgasprice(self):
        return quantity_decoder(self.call('eth_lastGasPrice'))

    def send_transaction(self,
                         sender,
                         to,
                         value=0,
                         data='',
                         startgas=0,
                         gasprice=10 * denoms.szabo):
        "can send a locally signed transaction if privkey is given"
        assert self.privkey or sender
        if self.privkey:
            _sender = sender
            sender = privtoaddr(self.privkey)
            assert sender == _sender
        assert sender
        # fetch nonce
        nonce = self.nonce(sender)
        if not startgas:
            startgas = quantity_decoder(self.call('eth_gasLimit')) - 1

        # create transaction
        tx = Transaction(nonce,
                         gasprice,
                         startgas,
                         to=to,
                         value=value,
                         data=data)
        if self.privkey:
            tx.sign(self.privkey)
        tx_dict = tx.to_dict()
        tx_dict.pop('hash')
        for k, v in dict(gasprice='gasPrice', startgas='gas').items():
            tx_dict[v] = tx_dict.pop(k)
        tx_dict['sender'] = sender
        res = self.eth_sendTransaction(**tx_dict)
        assert len(res) in (20, 32)
        return res.encode('hex')

    def new_abi_contract(self, _abi, address):
        sender = self.sender or privtoaddr(self.privkey)
        return ABIContract(sender, _abi, address, self.eth_call,
                           self.send_transaction)
Ejemplo n.º 9
0
#!/usr/bin/python

#
# pip install tinyrpc
#

from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc.transports.http import HttpPostClientTransport
from tinyrpc import RPCClient

rpc_client = RPCClient(JSONRPCProtocol(),
                       HttpPostClientTransport('http://localhost:8080/'))

local = rpc_client.get_proxy()

s = '   This is a message  '
stripped = local.strip(str=s)

assert stripped == s.strip()

print 'rpc ok'

import sys
sys.exit(0)
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc.transports.http import HttpPostClientTransport
from tinyrpc import RPCClient
import time
import sys

rpc_client = RPCClient(JSONRPCProtocol(),
                       HttpPostClientTransport('http://192.168.1.100:8080/'))

remote_server = rpc_client.get_proxy()

try:
    vhf_data_rate = None
    uhf_data_rate = None
    sat_comm_data_rate = None
    while True:
        time.sleep(2)
        vhf_data_rate_json = remote_server.get_vhf_data_rate()
        vhf_data_rate = vhf_data_rate_json['vhf_rate']

        uhf_data_rate_json = remote_server.get_uhf_data_rate()
        uhf_data_rate = uhf_data_rate_json['uhf_rate']

        sat_comm_data_rate_json = remote_server.get_sat_comm_data_rate()
        sat_comm_data_rate = sat_comm_data_rate_json['satcomm_rate']

        print("\n\n\nCurrent VHF data rate is: ", vhf_data_rate)
        print("\n\n\nCurrent UHF data rate is: ", uhf_data_rate)
        print("\n\n\nCurrent SatComm data rate is: ", sat_comm_data_rate)

except KeyboardInterrupt:
Ejemplo n.º 11
0
class JSONRPCClient:
    """ Ethereum JSON RPC client.

    Args:
        host: Ethereum node host address.
        port: Ethereum node port number.
        privkey: Local user private key, used to sign transactions.
        nonce_update_interval: Update the account nonce every
            `nonce_update_interval` seconds.
        nonce_offset: Network's default base nonce number.
    """

    def __init__(
            self,
            host: str,
            port: int,
            privkey: bytes,
            nonce_update_interval: float = 5.0,
            nonce_offset: int = 0):

        endpoint = 'http://{}:{}'.format(host, port)
        session = requests.Session()
        adapter = requests.adapters.HTTPAdapter(pool_maxsize=50)
        session.mount(endpoint, adapter)

        self.transport = HttpPostClientTransport(
            endpoint,
            post_method=session.post,
            headers={'content-type': 'application/json'},
        )

        self.port = port
        self.privkey = privkey
        self.protocol = JSONRPCProtocol()
        self.sender = privatekey_to_address(privkey)
        # Needs to be initialized to None in the beginning since JSONRPCClient
        # gets constructed before the RaidenService Object.
        self.stop_event = None

        self.nonce_last_update = 0
        self.nonce_current_value = None
        self.nonce_lock = Semaphore()
        self.nonce_update_interval = nonce_update_interval
        self.nonce_offset = nonce_offset

    def __repr__(self):
        return '<JSONRPCClient @%d>' % self.port

    def block_number(self):
        """ Return the most recent block. """
        return quantity_decoder(self.call('eth_blockNumber'))

    def nonce(self, address):
        if len(address) == 40:
            address = unhexlify(address)

        with self.nonce_lock:
            initialized = self.nonce_current_value is not None
            query_time = now()

            if self.nonce_last_update > query_time:
                # Python's 2.7 time is not monotonic and it's affected by clock
                # resets, force an update.
                self.nonce_update_interval = query_time - self.nonce_update_interval
                needs_update = True

            else:
                last_update_interval = query_time - self.nonce_last_update
                needs_update = last_update_interval > self.nonce_update_interval

            if initialized and not needs_update:
                self.nonce_current_value += 1
                return self.nonce_current_value

            pending_transactions_hex = self.call(
                'eth_getTransactionCount',
                address_encoder(address),
                'pending',
            )
            pending_transactions = quantity_decoder(pending_transactions_hex)
            nonce = pending_transactions + self.nonce_offset

            # we may have hammered the server and not all tx are
            # registered as `pending` yet
            if initialized:
                while nonce < self.nonce_current_value:
                    log.debug(
                        'nonce on server too low; retrying',
                        server=nonce,
                        local=self.nonce_current_value,
                    )

                    query_time = now()
                    pending_transactions_hex = self.call(
                        'eth_getTransactionCount',
                        address_encoder(address),
                        'pending',
                    )
                    pending_transactions = quantity_decoder(pending_transactions_hex)
                    nonce = pending_transactions + self.nonce_offset

            self.nonce_current_value = nonce
            self.nonce_last_update = query_time

            return self.nonce_current_value

    def inject_stop_event(self, event):
        self.stop_event = event

    def balance(self, account: address):
        """ Return the balance of the account of given address. """
        res = self.call('eth_getBalance', address_encoder(account), 'pending')
        return quantity_decoder(res)

    def gaslimit(self) -> int:
        last_block = self.call('eth_getBlockByNumber', 'latest', True)
        gas_limit = quantity_decoder(last_block['gasLimit'])
        return gas_limit

    def new_contract_proxy(self, contract_interface, contract_address: address):
        """ Return a proxy for interacting with a smart contract.

        Args:
            contract_interface: The contract interface as defined by the json.
            address: The contract's address.
        """
        return ContractProxy(
            self.sender,
            contract_interface,
            contract_address,
            self.eth_call,
            self.send_transaction,
            self.eth_estimateGas,
        )

    def deploy_solidity_contract(
            self,  # pylint: disable=too-many-locals
            sender,
            contract_name,
            all_contracts,
            libraries,
            constructor_parameters,
            contract_path=None,
            timeout=None,
            gasprice=GAS_PRICE):
        """
        Deploy a solidity contract.
        Args:
            sender (address): the sender address
            contract_name (str): the name of the contract to compile
            all_contracts (dict): the json dictionary containing the result of compiling a file
            libraries (list): A list of libraries to use in deployment
            constructor_parameters (tuple): A tuple of arguments to pass to the constructor
            contract_path (str): If we are dealing with solc >= v0.4.9 then the path
                                 to the contract is a required argument to extract
                                 the contract data from the `all_contracts` dict.
            timeout (int): Amount of time to poll the chain to confirm deployment
            gasprice: The gasprice to provide for the transaction
        """

        if contract_name in all_contracts:
            contract_key = contract_name

        elif contract_path is not None:
            _, filename = os.path.split(contract_path)
            contract_key = filename + ':' + contract_name

            if contract_key not in all_contracts:
                raise ValueError('Unknown contract {}'.format(contract_name))
        else:
            raise ValueError(
                'Unknown contract {} and no contract_path given'.format(contract_name)
            )

        libraries = dict(libraries)
        contract = all_contracts[contract_key]
        contract_interface = contract['abi']
        symbols = solidity_unresolved_symbols(contract['bin_hex'])

        if symbols:
            available_symbols = list(map(solidity_library_symbol, all_contracts.keys()))

            unknown_symbols = set(symbols) - set(available_symbols)
            if unknown_symbols:
                msg = 'Cannot deploy contract, known symbols {}, unresolved symbols {}.'.format(
                    available_symbols,
                    unknown_symbols,
                )
                raise Exception(msg)

            dependencies = deploy_dependencies_symbols(all_contracts)
            deployment_order = dependencies_order_of_build(contract_key, dependencies)

            deployment_order.pop()  # remove `contract_name` from the list

            log.debug('Deploying dependencies: {}'.format(str(deployment_order)))

            for deploy_contract in deployment_order:
                dependency_contract = all_contracts[deploy_contract]

                hex_bytecode = solidity_resolve_symbols(dependency_contract['bin_hex'], libraries)
                bytecode = unhexlify(hex_bytecode)

                dependency_contract['bin_hex'] = hex_bytecode
                dependency_contract['bin'] = bytecode

                transaction_hash_hex = self.send_transaction(
                    sender,
                    to=b'',
                    data=bytecode,
                    gasprice=gasprice,
                )
                transaction_hash = unhexlify(transaction_hash_hex)

                self.poll(transaction_hash, timeout=timeout)
                receipt = self.eth_getTransactionReceipt(transaction_hash)

                contract_address = receipt['contractAddress']
                # remove the hexadecimal prefix 0x from the address
                contract_address = contract_address[2:]

                libraries[deploy_contract] = contract_address

                deployed_code = self.eth_getCode(address_decoder(contract_address))

                if len(deployed_code) == 0:
                    raise RuntimeError('Contract address has no code, check gas usage.')

            hex_bytecode = solidity_resolve_symbols(contract['bin_hex'], libraries)
            bytecode = unhexlify(hex_bytecode)

            contract['bin_hex'] = hex_bytecode
            contract['bin'] = bytecode

        if constructor_parameters:
            translator = ContractTranslator(contract_interface)
            parameters = translator.encode_constructor_arguments(constructor_parameters)
            bytecode = contract['bin'] + parameters
        else:
            bytecode = contract['bin']

        transaction_hash_hex = self.send_transaction(
            sender,
            to=b'',
            data=bytecode,
            gasprice=gasprice,
        )
        transaction_hash = unhexlify(transaction_hash_hex)

        self.poll(transaction_hash, timeout=timeout)
        receipt = self.eth_getTransactionReceipt(transaction_hash)
        contract_address = receipt['contractAddress']

        deployed_code = self.eth_getCode(address_decoder(contract_address))

        if len(deployed_code) == 0:
            raise RuntimeError(
                'Deployment of {} failed. Contract address has no code, check gas usage.'.format(
                    contract_name,
                )
            )

        return self.new_contract_proxy(
            contract_interface,
            contract_address,
        )

    def new_filter(self, fromBlock=None, toBlock=None, address=None, topics=None):
        """ Creates a filter object, based on filter options, to notify when
        the state changes (logs). To check if the state has changed, call
        eth_getFilterChanges.
        """

        json_data = {
            'fromBlock': block_tag_encoder(fromBlock or ''),
            'toBlock': block_tag_encoder(toBlock or ''),
        }

        if address is not None:
            json_data['address'] = address_encoder(address)

        if topics is not None:
            if not isinstance(topics, list):
                raise ValueError('topics must be a list')

            json_data['topics'] = [topic_encoder(topic) for topic in topics]

        filter_id = self.call('eth_newFilter', json_data)
        return quantity_decoder(filter_id)

    def filter_changes(self, fid: int) -> List:
        changes = self.call('eth_getFilterChanges', quantity_encoder(fid))

        if not changes:
            return list()

        assert isinstance(changes, list)
        decoders = {
            'blockHash': data_decoder,
            'transactionHash': data_decoder,
            'data': data_decoder,
            'address': address_decoder,
            'topics': lambda x: [topic_decoder(t) for t in x],
            'blockNumber': quantity_decoder,
            'logIndex': quantity_decoder,
            'transactionIndex': quantity_decoder
        }
        return [
            {k: decoders[k](v) for k, v in c.items() if v is not None}
            for c in changes
        ]

    @check_node_connection
    def call(self, method: str, *args):
        """ Do the request and return the result.

        Args:
            method: The RPC method.
            args: The encoded arguments expected by the method.
                - Object arguments must be supplied as a dictionary.
                - Quantity arguments must be hex encoded starting with '0x' and
                without left zeros.
                - Data arguments must be hex encoded starting with '0x'
        """
        request = self.protocol.create_request(method, args)
        reply = self.transport.send_message(request.serialize().encode())

        jsonrpc_reply = self.protocol.parse_reply(reply)
        if isinstance(jsonrpc_reply, JSONRPCSuccessResponse):
            return jsonrpc_reply.result
        elif isinstance(jsonrpc_reply, JSONRPCErrorResponse):
            raise EthNodeCommunicationError(jsonrpc_reply.error, jsonrpc_reply._jsonrpc_error_code)
        else:
            raise EthNodeCommunicationError('Unknown type of JSONRPC reply')

    def send_transaction(
            self,
            sender: address,
            to: address,
            value: int = 0,
            data: bytes = b'',
            startgas: int = 0,
            gasprice: int = GAS_PRICE,
            nonce: Optional[int] = None):
        """ Helper to send signed messages.

        This method will use the `privkey` provided in the constructor to
        locally sign the transaction. This requires an extended server
        implementation that accepts the variables v, r, and s.
        """

        if not self.privkey and not sender:
            raise ValueError('Either privkey or sender needs to be supplied.')

        if self.privkey:
            privkey_address = privatekey_to_address(self.privkey)
            sender = sender or privkey_address

            if sender != privkey_address:
                raise ValueError('sender for a different privkey.')

            if nonce is None:
                nonce = self.nonce(sender)
        else:
            if nonce is None:
                nonce = 0

        if not startgas:
            startgas = self.gaslimit() - 1

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

        if self.privkey:
            tx.sign(self.privkey)
            result = self.call(
                'eth_sendRawTransaction',
                data_encoder(rlp.encode(tx)),
            )
            return result[2 if result.startswith('0x') else 0:]

        else:

            # rename the fields to match the eth_sendTransaction signature
            tx_dict = tx.to_dict()
            tx_dict.pop('hash')
            tx_dict['sender'] = sender
            tx_dict['gasPrice'] = tx_dict.pop('gasprice')
            tx_dict['gas'] = tx_dict.pop('startgas')

            res = self.eth_sendTransaction(**tx_dict)

        assert len(res) in (20, 32)
        return hexlify(res)

    def eth_sendTransaction(
            self,
            sender: address = b'',
            to: address = b'',
            value: int = 0,
            data: bytes = b'',
            gasPrice: int = GAS_PRICE,
            gas: int = GAS_PRICE,
            nonce: Optional[int] = None):
        """ Creates new message call transaction or a contract creation, if the
        data field contains code.

        Args:
            sender: The address the transaction is sent from.
            to: The address the transaction is directed to.
                (optional when creating new contract)
            gas: Gas provided for the transaction execution. It will
                return unused gas.
            gasPrice: gasPrice used for each unit of gas paid.
            value: Value sent with this transaction.
            data: The compiled code of a contract OR the hash of the
                invoked method signature and encoded parameters.
            nonce: This allows to overwrite your own pending transactions
                that use the same nonce.
        """

        if to == b'' and data.isalnum():
            warnings.warn(
                'Verify that the data parameter is _not_ hex encoded, if this is the case '
                'the data will be double encoded and result in unexpected '
                'behavior.'
            )

        if to == b'0' * 40:
            warnings.warn('For contract creation the empty string must be used.')

        if sender is None:
            raise ValueError('sender needs to be provided.')

        json_data = format_data_for_call(
            sender,
            to,
            value,
            data,
            gas,
            gasPrice
        )

        if nonce is not None:
            json_data['nonce'] = quantity_encoder(nonce)

        res = self.call('eth_sendTransaction', json_data)

        return data_decoder(res)

    def eth_call(
            self,
            sender: address = b'',
            to: address = b'',
            value: int = 0,
            data: bytes = b'',
            startgas: int = GAS_PRICE,
            gasprice: int = GAS_PRICE,
            block_number: Union[str, int] = 'latest'):
        """ Executes a new message call immediately without creating a
        transaction on the blockchain.

        Args:
            sender: The address the transaction is sent from.
            to: The address the transaction is directed to.
            gas: Gas provided for the transaction execution. eth_call
                consumes zero gas, but this parameter may be needed by some
                executions.
            gasPrice: gasPrice used for unit of gas paid.
            value: Integer of the value sent with this transaction.
            data: Hash of the method signature and encoded parameters.
                For details see Ethereum Contract ABI.
            block_number: Determines the state of ethereum used in the
                call.
        """

        json_data = format_data_for_call(
            sender,
            to,
            value,
            data,
            startgas,
            gasprice,
        )
        res = self.call('eth_call', json_data, block_number)

        return data_decoder(res)

    def eth_estimateGas(
            self,
            sender: address = b'',
            to: address = b'',
            value: int = 0,
            data: bytes = b'',
            startgas: int = GAS_PRICE,
            gasprice: int = GAS_PRICE) -> int:
        """ Makes a call or transaction, which won't be added to the blockchain
        and returns the used gas, which can be used for estimating the used
        gas.

        Args:
            sender: The address the transaction is sent from.
            to: The address the transaction is directed to.
            gas: Gas provided for the transaction execution. eth_call
                consumes zero gas, but this parameter may be needed by some
                executions.
            gasPrice: gasPrice used for unit of gas paid.
            value: Integer of the value sent with this transaction.
            data: Hash of the method signature and encoded parameters.
                For details see Ethereum Contract ABI.
            block_number: Determines the state of ethereum used in the
                call.
        """

        json_data = format_data_for_call(
            sender,
            to,
            value,
            data,
            startgas,
            gasprice,
        )
        try:
            res = self.call('eth_estimateGas', json_data)
        except EthNodeCommunicationError as e:
            tx_would_fail = e.error_code and e.error_code in (-32015, -32000)
            if tx_would_fail:  # -32015 is parity and -32000 is geth
                return None
            else:
                raise e

        return quantity_decoder(res)

    def eth_getTransactionReceipt(self, transaction_hash: bytes) -> Dict:
        """ Returns the receipt of a transaction by transaction hash.

        Args:
            transaction_hash: Hash of a transaction.

        Returns:
            A dict representing the transaction receipt object, or null when no
            receipt was found.
        """
        if transaction_hash.startswith(b'0x'):
            warnings.warn(
                'transaction_hash seems to be already encoded, this will'
                ' result in unexpected behavior'
            )

        if len(transaction_hash) != 32:
            raise ValueError(
                'transaction_hash length must be 32 (it might be hex encoded)'
            )

        transaction_hash = data_encoder(transaction_hash)
        return self.call('eth_getTransactionReceipt', transaction_hash)

    def eth_getCode(self, code_address: address, block: Union[int, str] = 'latest') -> bytes:
        """ Returns code at a given address.

        Args:
            code_address: An address.
            block: Integer block number, or the string 'latest',
                'earliest' or 'pending'. Default is 'latest'.
        """
        if code_address.startswith(b'0x'):
            warnings.warn(
                'address seems to be already encoded, this will result '
                'in unexpected behavior'
            )

        if len(code_address) != 20:
            raise ValueError(
                'address length must be 20 (it might be hex encoded)'
            )

        result = self.call('eth_getCode', address_encoder(code_address), block)
        return data_decoder(result)

    def eth_getTransactionByHash(self, transaction_hash: bytes):
        """ Returns the information about a transaction requested by
        transaction hash.
        """

        if transaction_hash.startswith(b'0x'):
            warnings.warn(
                'transaction_hash seems to be already encoded, this will'
                ' result in unexpected behavior'
            )

        if len(transaction_hash) != 32:
            raise ValueError(
                'transaction_hash length must be 32 (it might be hex encoded)'
            )

        transaction_hash = data_encoder(transaction_hash)
        return self.call('eth_getTransactionByHash', transaction_hash)

    def poll(
            self,
            transaction_hash: bytes,
            confirmations: Optional[int] = None,
            timeout: Optional[float] = None):
        """ Wait until the `transaction_hash` is applied or rejected.
        If timeout is None, this could wait indefinitely!

        Args:
            transaction_hash: Transaction hash that we are waiting for.
            confirmations: Number of block confirmations that we will
                wait for.
            timeout: Timeout in seconds, raise an Excpetion on timeout.
        """
        if transaction_hash.startswith(b'0x'):
            warnings.warn(
                'transaction_hash seems to be already encoded, this will'
                ' result in unexpected behavior'
            )

        if len(transaction_hash) != 32:
            raise ValueError(
                'transaction_hash length must be 32 (it might be hex encoded)'
            )

        transaction_hash = data_encoder(transaction_hash)

        deadline = None
        if timeout:
            deadline = gevent.Timeout(timeout)
            deadline.start()

        try:
            # used to check if the transaction was removed, this could happen
            # if gas price is too low:
            #
            # > Transaction (acbca3d6) below gas price (tx=1 Wei ask=18
            # > Shannon). All sequential txs from this address(7d0eae79)
            # > will be ignored
            #
            last_result = None

            while True:
                # Could return None for a short period of time, until the
                # transaction is added to the pool
                transaction = self.call('eth_getTransactionByHash', transaction_hash)

                # if the transaction was added to the pool and then removed
                if transaction is None and last_result is not None:
                    raise Exception('invalid transaction, check gas price')

                # the transaction was added to the pool and mined
                if transaction and transaction['blockNumber'] is not None:
                    break

                last_result = transaction

                gevent.sleep(.5)

            if confirmations:
                # this will wait for both APPLIED and REVERTED transactions
                transaction_block = quantity_decoder(transaction['blockNumber'])
                confirmation_block = transaction_block + confirmations

                block_number = self.block_number()

                while block_number < confirmation_block:
                    gevent.sleep(.5)
                    block_number = self.block_number()

        except gevent.Timeout:
            raise Exception('timeout when polling for transaction')

        finally:
            if deadline:
                deadline.cancel()
import time
from threading import Thread

from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc.transports.http import HttpPostClientTransport
from tinyrpc import RPCClient

rpc_client = RPCClient(
    JSONRPCProtocol(),
    HttpPostClientTransport('http://192.168.100.174:10090/'))

remote_server = rpc_client.get_proxy()

topicName = 'test_topic'
topicName = 'display_command'

k = 0


def queryTopics():
    while True:
        time.sleep(0.05)
        result = remote_server.ROSPublishTopics([])
        print "Server answered:"
        print result


prgThread = Thread(target=queryTopics)
prgThread.setDaemon(True)
prgThread.start()
Ejemplo n.º 13
0
 def __init__(self, port=4000, print_communication=True):
     self.transport = HttpPostClientTransport(
         'http://127.0.0.1:{}'.format(port))
     self.print_communication = print_communication
Ejemplo n.º 14
0
class JSONRPCClient(object):
    protocol = JSONRPCProtocol()

    def __init__(self, port=4000, print_communication=True):
        self.transport = HttpPostClientTransport(
            'http://127.0.0.1:{}'.format(port))
        self.print_communication = print_communication

    def call(self, method, *args, **kwargs):
        request = self.protocol.create_request(method, args, kwargs)
        reply = self.transport.send_message(request.serialize())
        if self.print_communication:
            print "Request:"
            print json.dumps(json.loads(request.serialize()), indent=2)
            print "Reply:"
            print reply
        return self.protocol.parse_reply(reply).result

    __call__ = call

    def find_block(self, condition):
        """Query all blocks one by one and return the first one for which
        `condition(block)` evaluates to `True`.
        """
        i = 0
        while True:
            block = self.call('eth_getBlockByNumber',
                              quantity_encoder(i),
                              True,
                              print_comm=False)
            if condition(block):
                return block
            i += 1

    def eth_sendTransaction(self,
                            nonce=None,
                            sender='',
                            to='',
                            value=0,
                            data='',
                            gasprice=default_gasprice,
                            startgas=default_startgas,
                            v=None,
                            r=None,
                            s=None):
        encoders = dict(nonce=quantity_encoder,
                        sender=address_encoder,
                        to=data_encoder,
                        value=quantity_encoder,
                        gasprice=quantity_encoder,
                        startgas=quantity_encoder,
                        data=data_encoder,
                        v=quantity_encoder,
                        r=quantity_encoder,
                        s=quantity_encoder)
        data = {
            k: encoders[k](v)
            for k, v in locals().items()
            if k not in ('self', 'encoders') and v is not None
        }
        data['from'] = data.pop('sender')
        assert data.get('from') or (v and r and s)

        res = self.call('eth_sendTransaction', data)
        return data_decoder(res)
Ejemplo n.º 15
0
'''
refer : https://github.com/mbr/tinyrpc/tree/master/examples
import gevent
import tinyrpc
import gevent-websocket
'''

from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc.transports.http import HttpPostClientTransport
from tinyrpc import RPCClient

rpc_client = RPCClient(JSONRPCProtocol(),
                       HttpPostClientTransport('http://127.0.0.1:5000/'))

remote_server = rpc_client.get_proxy()

# call a method called 'reverse_string' with a single string argument
result = remote_server.reverse_string('Hello, World!')

print("Server answered: ", result)
Ejemplo n.º 16
0
class JSONRPCClient(object):
    protocol = JSONRPCProtocol()

    def __init__(self,
                 host='127.0.0.1',
                 port=4000,
                 print_communication=True,
                 privkey=None,
                 sender=None,
                 use_ssl=False,
                 transport=None):
        "specify privkey for local signing"
        if transport is None:
            self.transport = HttpPostClientTransport(
                '{}://{}:{}'.format('https' if use_ssl else 'http', host,
                                    port),
                headers={'content-type': 'application/json'})
        else:
            self.transport = transport
        self.print_communication = print_communication
        self.privkey = privkey
        self._sender = sender
        self.port = port

    def __repr__(self):
        return '<JSONRPCClient @%d>' % self.port

    @property
    def sender(self):
        if self.privkey:
            return privtoaddr(self.privkey)

        if self._sender is None:
            self._sender = self.coinbase

        return self._sender

    @property
    def coinbase(self):
        """ Return the client coinbase address. """
        return address_decoder(self.call('eth_coinbase'))

    def blocknumber(self):
        """ Return the most recent block. """
        return quantity_decoder(self.call('eth_blockNumber'))

    def nonce(self, address):
        if len(address) == 40:
            address = address.decode('hex')

        try:
            res = self.call('eth_nonce', address_encoder(address), 'pending')
            return quantity_decoder(res)
        except JSONRPCClientReplyError as e:
            if e.message == 'Method not found':
                raise JSONRPCClientReplyError(
                    "'eth_nonce' is not supported by your endpoint (pyethapp only). "
                    "For transactions use server-side nonces: "
                    "('eth_sendTransaction' with 'nonce=None')")
            raise e

    def balance(self, account):
        """ Return the balance of the account of given address. """
        res = self.call('eth_getBalance', address_encoder(account), 'pending')
        return quantity_decoder(res)

    def gaslimit(self):
        return quantity_decoder(self.call('eth_gasLimit'))

    def lastgasprice(self):
        return quantity_decoder(self.call('eth_lastGasPrice'))

    def new_abi_contract(self, contract_interface, address):
        warnings.warn('deprecated, use new_contract_proxy', DeprecationWarning)
        return self.new_contract_proxy(contract_interface, address)

    def new_contract_proxy(self, contract_interface, address):
        """ Return a proxy for interacting with a smart contract.

        Args:
            contract_interface: The contract interface as defined by the json.
            address: The contract's address.
        """
        sender = self.sender or privtoaddr(self.privkey)

        return ContractProxy(
            sender,
            contract_interface,
            address,
            self.eth_call,
            self.send_transaction,
        )

    def deploy_solidity_contract(
            self,
            sender,
            contract_name,
            all_contracts,  # pylint: disable=too-many-locals
            libraries,
            constructor_parameters,
            timeout=None,
            gasprice=denoms.wei):

        if contract_name not in all_contracts:
            raise ValueError('Unkonwn contract {}'.format(contract_name))

        libraries = dict(libraries)
        contract = all_contracts[contract_name]
        contract_interface = contract['abi']
        symbols = solidity_unresolved_symbols(contract['bin_hex'])

        if symbols:
            available_symbols = map(solidity_library_symbol,
                                    all_contracts.keys())  # pylint: disable=bad-builtin

            unknown_symbols = set(symbols) - set(available_symbols)
            if unknown_symbols:
                msg = 'Cannot deploy contract, known symbols {}, unresolved symbols {}.'.format(
                    available_symbols,
                    unknown_symbols,
                )
                raise Exception(msg)

            dependencies = deploy_dependencies_symbols(all_contracts)
            deployment_order = dependencies_order_of_build(
                contract_name, dependencies)

            deployment_order.pop()  # remove `contract_name` from the list

            log.debug('Deploing dependencies: {}'.format(
                str(deployment_order)))

            for deploy_contract in deployment_order:
                dependency_contract = all_contracts[deploy_contract]

                hex_bytecode = solidity_resolve_symbols(
                    dependency_contract['bin_hex'], libraries)
                bytecode = hex_bytecode.decode('hex')

                dependency_contract['bin_hex'] = hex_bytecode
                dependency_contract['bin'] = bytecode

                transaction_hash = self.send_transaction(
                    sender,
                    to='',
                    data=bytecode,
                    gasprice=gasprice,
                )

                self.poll(transaction_hash.decode('hex'), timeout=timeout)
                receipt = self.call('eth_getTransactionReceipt',
                                    '0x' + transaction_hash)

                contract_address = receipt['contractAddress']
                contract_address = contract_address[
                    2:]  # remove the hexadecimal prefix 0x from the address

                libraries[deploy_contract] = contract_address

                deployed_code = self.call('eth_getCode', contract_address,
                                          'latest')

                if deployed_code == '0x':
                    raise RuntimeError(
                        "Contract address has no code, check gas usage.")

            hex_bytecode = solidity_resolve_symbols(contract['bin_hex'],
                                                    libraries)
            bytecode = hex_bytecode.decode('hex')

            contract['bin_hex'] = hex_bytecode
            contract['bin'] = bytecode

        if constructor_parameters:
            translator = ContractTranslator(contract_interface)
            parameters = translator.encode_constructor_arguments(
                constructor_parameters)
            bytecode = contract['bin'] + parameters
        else:
            bytecode = contract['bin']

        transaction_hash = self.send_transaction(
            sender,
            to='',
            data=bytecode,
            gasprice=gasprice,
        )

        self.poll(transaction_hash.decode('hex'), timeout=timeout)
        receipt = self.call('eth_getTransactionReceipt',
                            '0x' + transaction_hash)
        contract_address = receipt['contractAddress']

        deployed_code = self.call('eth_getCode', contract_address, 'latest')

        if deployed_code == '0x':
            raise RuntimeError(
                "Deployment of {} failed. Contract address has no code, check gas usage."
                .format(contract_name))

        return ContractProxy(
            sender,
            contract_interface,
            contract_address,
            self.eth_call,
            self.send_transaction,
        )

    def find_block(self, condition):
        """Query all blocks one by one and return the first one for which
        `condition(block)` evaluates to `True`.
        """
        i = 0
        while True:
            block = self.call('eth_getBlockByNumber', quantity_encoder(i),
                              True)
            if condition(block) or not block:
                return block
            i += 1

    def new_filter(self,
                   fromBlock=None,
                   toBlock=None,
                   address=None,
                   topics=None):
        """ Creates a filter object, based on filter options, to notify when
        the state changes (logs). To check if the state has changed, call
        eth_getFilterChanges.
        """

        json_data = {
            'fromBlock': block_tag_encoder(fromBlock or ''),
            'toBlock': block_tag_encoder(toBlock or ''),
        }

        if address is not None:
            json_data['address'] = address_encoder(address)

        if topics is not None:
            if not isinstance(topics, list):
                raise ValueError('topics must be a list')

            json_data['topics'] = [topic_encoder(topic) for topic in topics]

        filter_id = self.call('eth_newFilter', json_data)
        return quantity_decoder(filter_id)

    def filter_changes(self, fid):
        changes = self.call('eth_getFilterChanges', quantity_encoder(fid))
        if not changes:
            return None
        elif isinstance(changes, bytes):
            return data_decoder(changes)
        else:
            decoders = dict(blockHash=data_decoder,
                            transactionHash=data_decoder,
                            data=data_decoder,
                            address=address_decoder,
                            topics=lambda x: [topic_decoder(t) for t in x],
                            blockNumber=quantity_decoder,
                            logIndex=quantity_decoder,
                            transactionIndex=quantity_decoder)
            return [{k: decoders[k](v)
                     for k, v in c.items() if v is not None} for c in changes]

    def call(self, method, *args):
        """ Do the request and returns the result.

        Args:
            method (str): The RPC method.
            args: The encoded arguments expected by the method.
                - Object arguments must be supplied as an dictionary.
                - Quantity arguments must be hex encoded starting with '0x' and
                without left zeros.
                - Data arguments must be hex encoded starting with '0x'
        """
        request = self.protocol.create_request(method, args)
        reply = self.transport.send_message(request.serialize())
        if self.print_communication:
            print json.dumps(json.loads(request.serialize()), indent=2)
            print reply

        jsonrpc_reply = self.protocol.parse_reply(reply)
        if isinstance(jsonrpc_reply, JSONRPCSuccessResponse):
            return jsonrpc_reply.result
        elif isinstance(jsonrpc_reply, JSONRPCErrorResponse):
            raise JSONRPCClientReplyError(jsonrpc_reply.error)
        else:
            raise JSONRPCClientReplyError('Unknown type of JSONRPC reply')

    __call__ = call

    def send_transaction(self,
                         sender,
                         to,
                         value=0,
                         data='',
                         startgas=0,
                         gasprice=10 * denoms.szabo,
                         nonce=None):
        """ Helper to send signed messages.

        This method will use the `privkey` provided in the constructor to
        locally sign the transaction. This requires an extended server
        implementation that accepts the variables v, r, and s.
        """

        if not self.privkey and not sender:
            raise ValueError('Either privkey or sender needs to be supplied.')

        if self.privkey and not sender:
            sender = privtoaddr(self.privkey)

            if nonce is None:
                nonce = self.nonce(sender)
        elif self.privkey:
            if sender != privtoaddr(self.privkey):
                raise ValueError('sender for a different privkey.')

            if nonce is None:
                nonce = self.nonce(sender)
        else:
            if nonce is None:
                nonce = 0

        if not startgas:
            startgas = self.gaslimit() - 1

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

        if self.privkey:
            # add the fields v, r and s
            tx.sign(self.privkey)

        tx_dict = tx.to_dict()

        # rename the fields to match the eth_sendTransaction signature
        tx_dict.pop('hash')
        tx_dict['sender'] = sender
        tx_dict['gasPrice'] = tx_dict.pop('gasprice')
        tx_dict['gas'] = tx_dict.pop('startgas')

        res = self.eth_sendTransaction(**tx_dict)
        assert len(res) in (20, 32)
        return res.encode('hex')

    def eth_sendTransaction(self,
                            nonce=None,
                            sender='',
                            to='',
                            value=0,
                            data='',
                            gasPrice=default_gasprice,
                            gas=default_startgas,
                            v=None,
                            r=None,
                            s=None):
        """ Creates new message call transaction or a contract creation, if the
        data field contains code.

        Note:
            The support for local signing through the variables v,r,s is not
            part of the standard spec, a extended server is required.

        Args:
            from (address): The 20 bytes address the transaction is send from.
            to (address): DATA, 20 Bytes - (optional when creating new
                contract) The address the transaction is directed to.
            gas (int): Gas provided for the transaction execution. It will
                return unused gas.
            gasPrice (int): gasPrice used for each paid gas.
            value (int): Value send with this transaction.
            data (bin): The compiled code of a contract OR the hash of the
                invoked method signature and encoded parameters.
            nonce (int): This allows to overwrite your own pending transactions
                that use the same nonce.
        """

        if to == '' and data.isalnum():
            warnings.warn(
                'Verify that the data parameter is _not_ hex encoded, if this is the case '
                'the data will be double encoded and result in unexpected '
                'behavior.')

        if to == '0' * 40:
            warnings.warn(
                'For contract creating the empty string must be used.')

        json_data = {
            'to': data_encoder(normalize_address(to, allow_blank=True)),
            'value': quantity_encoder(value),
            'gasPrice': quantity_encoder(gasPrice),
            'gas': quantity_encoder(gas),
            'data': data_encoder(data),
        }

        if not sender and not (v and r and s):
            raise ValueError('Either sender or v, r, s needs to be informed.')

        if sender is not None:
            json_data['from'] = address_encoder(sender)

        if v and r and s:
            json_data['v'] = quantity_encoder(v)
            json_data['r'] = quantity_encoder(r)
            json_data['s'] = quantity_encoder(s)

        if nonce is not None:
            json_data['nonce'] = quantity_encoder(nonce)

        res = self.call('eth_sendTransaction', json_data)

        return data_decoder(res)

    def eth_call(self,
                 sender='',
                 to='',
                 value=0,
                 data='',
                 startgas=default_startgas,
                 gasprice=default_gasprice,
                 block_number='latest'):
        """ Executes a new message call immediately without creating a
        transaction on the block chain.

        Args:
            from: The address the transaction is send from.
            to: The address the transaction is directed to.
            gas (int): Gas provided for the transaction execution. eth_call
                consumes zero gas, but this parameter may be needed by some
                executions.
            gasPrice (int): gasPrice used for each paid gas.
            value (int): Integer of the value send with this transaction.
            data (bin): Hash of the method signature and encoded parameters.
                For details see Ethereum Contract ABI.
            block_number: Determines the state of ethereum used in the
                call.
        """

        json_data = dict()

        if sender is not None:
            json_data['from'] = address_encoder(sender)

        if to is not None:
            json_data['to'] = data_encoder(to)

        if value is not None:
            json_data['value'] = quantity_encoder(value)

        if gasprice is not None:
            json_data['gasPrice'] = quantity_encoder(gasprice)

        if startgas is not None:
            json_data['gas'] = quantity_encoder(startgas)

        if data is not None:
            json_data['data'] = data_encoder(data)

        res = self.call('eth_call', json_data, block_number)

        return data_decoder(res)

    def poll(self, transaction_hash, confirmations=None, timeout=None):
        """ Wait until the `transaction_hash` is applied or rejected.
        If timeout is None, this could wait indefinitely!

        Args:
            transaction_hash (hash): Transaction hash that we are waiting for.
            confirmations (int): Number of block confirmations that we will
                wait for.
            timeout (float): Timeout in seconds, raise an Excpetion on
                timeout.
        """
        if transaction_hash.startswith('0x'):
            warnings.warn(
                'transaction_hash seems to be already encoded, this will result '
                'in unexpected behavior')

        if len(transaction_hash) != 32:
            raise ValueError(
                'transaction_hash length must be 32 (it might be hex encode)')

        deadline = None
        if timeout:
            deadline = time.time() + timeout

        transaction_hash = data_encoder(transaction_hash)

        transaction = self.call('eth_getTransactionByHash', transaction_hash)
        while transaction is None or transaction["blockNumber"] is None:
            if deadline and time.time() > deadline:
                raise Exception('timeout when polling for transaction')

            gevent.sleep(.5)
            transaction = self.call('eth_getTransactionByHash',
                                    transaction_hash)

        if confirmations is None:
            return

        # this will wait for both APPLIED and REVERTED transactions
        transaction_block = quantity_decoder(transaction['blockNumber'])
        confirmation_block = transaction_block + confirmations

        block_number = self.blocknumber()
        while confirmation_block > block_number:
            if deadline and time.time() > deadline:
                raise Exception('timeout when waiting for confirmation')

            gevent.sleep(.5)
            block_number = self.blocknumber()
Ejemplo n.º 17
0
# -*- coding: utf-8 -*-
"""Example RPC client for controlling a wotabag.

Requires tinrypc[httpclient] (https://github.com/mbr/tinyrpc).

"""

import time

from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc.transports.http import HttpPostClientTransport
from tinyrpc import RPCClient

rpc_client = RPCClient(
    JSONRPCProtocol(),
    HttpPostClientTransport('http://raspberrypi.local:60715/'))

server = rpc_client.get_proxy(prefix='wotabag.')

# Retrieve server status
result = server.get_status()
print(result)

# Retrieve server playlist
result = server.get_playlist()
print(result)

# Test LED patterns
server.test_pattern()

# Start playback
Ejemplo n.º 18
0
 def __init__(self, port=4000, print_communication=True):
     self.transport = HttpPostClientTransport("http://127.0.0.1:{}".format(port))
     self.print_communication = print_communication
Ejemplo n.º 19
0
class JSONRPCClient(object):
    protocol = JSONRPCProtocol()

    def __init__(self, port=4000, print_communication=True, privkey=None, sender=None):
        "specify privkey for local signing"
        self.transport = HttpPostClientTransport('http://127.0.0.1:{}'.format(port))
        self.print_communication = print_communication
        self.privkey = privkey
        self._sender = sender

    @property
    def sender(self):
        if self.privkey:
            return privtoaddr(self.privkey)
        if self._sender is None:
            self._sender = self.coinbase
        return self._sender


    def call(self, method, *args, **kwargs):
        request = self.protocol.create_request(method, args, kwargs)
        reply = self.transport.send_message(request.serialize())
        if self.print_communication:
            print json.dumps(json.loads(request.serialize()), indent=2)
            print reply
        return self.protocol.parse_reply(reply).result

    __call__ = call

    def find_block(self, condition):
        """Query all blocks one by one and return the first one for which
        `condition(block)` evaluates to `True`.
        """
        i = 0
        while True:
            block = self.call('eth_getBlockByNumber', quantity_encoder(i), True, print_comm=False)
            if condition(block):
                return block
            i += 1

    def eth_sendTransaction(self, nonce=None, sender='', to='', value=0, data='',
                            gasPrice=default_gasprice, gas=default_startgas,
                            v=None, r=None, s=None):
        encoders = dict(nonce=quantity_encoder, sender=address_encoder, to=data_encoder,
                        value=quantity_encoder, gasPrice=quantity_encoder,
                        gas=quantity_encoder, data=data_encoder,
                        v=quantity_encoder, r=quantity_encoder, s=quantity_encoder)
        data = {k: encoders[k](v) for k, v in locals().items()
                if k not in ('self', 'encoders') and v is not None}
        data['from'] = data.pop('sender')
        assert data.get('from') or (v and r and s)
        res = self.call('eth_sendTransaction', data)
        return data_decoder(res)

    def eth_call(self, sender='', to='', value=0, data='',
                 startgas=default_startgas, gasprice=default_gasprice):
        "call on pending block"
        encoders = dict(sender=address_encoder, to=data_encoder,
                        value=quantity_encoder, gasprice=quantity_encoder,
                        startgas=quantity_encoder, data=data_encoder)
        data = {k: encoders[k](v) for k, v in locals().items()
                if k not in ('self', 'encoders') and v is not None}
        for k, v in dict(gasprice='gasPrice', startgas='gas', sender='from').items():
            data[v] = data.pop(k)
        res = self.call('eth_call', data)
        return data_decoder(res)

    def blocknumber(self):
        return quantity_decoder(self.call('eth_blockNumber'))

    def nonce(self, address):
        if len(address) == 40:
            address = address.decode('hex')
        return quantity_decoder(
            self.call('eth_getTransactionCount', address_encoder(address), 'pending'))


    @property
    def coinbase(self):
        return address_decoder(self.call('eth_coinbase'))

    def balance(self, account):
        b = quantity_decoder(
            self.call('eth_getBalance', address_encoder(account), 'pending'))
        return b

    def gaslimit(self):
        return quantity_decoder(self.call('eth_gasLimit'))

    def lastgasprice(self):
        return quantity_decoder(self.call('eth_lastGasPrice'))



    def send_transaction(self, sender, to, value=0, data='', startgas=0, gasprice=10*denoms.szabo):
        "can send a locally signed transaction if privkey is given"
        assert self.privkey or sender
        if self.privkey:
            _sender = sender
            sender = privtoaddr(self.privkey)
            assert sender == _sender
        # fetch nonce
        nonce = self.nonce(sender)
        if not startgas:
            startgas = quantity_decoder(self.call('eth_gasLimit')) - 1

        # create transaction
        tx = Transaction(nonce, gasprice, startgas, to=to, value=value, data=data)
        if self.privkey:
            tx.sign(self.privkey)
        tx_dict = tx.to_dict()
        tx_dict.pop('hash')
        for k, v in dict(gasprice='gasPrice', startgas='gas', sender='from').items():
            tx_dict[v] = tx_dict.pop(k)
        res = self.eth_sendTransaction(**tx_dict)
        assert len(res) in (20, 32)
        return res.encode('hex')

    def new_abi_contract(self, _abi, address):
        sender = self.sender or privtoaddr(self.privkey)
        return ABIContract(sender, _abi, address, self.eth_call, self.send_transaction)
Ejemplo n.º 20
0
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc.transports.http import HttpPostClientTransport
from tinyrpc import RPCClient

rpc_client = RPCClient(
    JSONRPCProtocol(),
    HttpPostClientTransport('http://localhost:5000/v1/jsonrpc')
)

rpc_server = rpc_client.get_proxy()

print "pinging..."
pong = rpc_server.ping()
print "ping response: " + pong
resp = rpc_server.hello("Ashutosh")
print "hello world response: " + resp
Ejemplo n.º 21
0
 def __init__(self, port=4000, print_communication=True, privkey=None, sender=None):
     "specify privkey for local signing"
     self.transport = HttpPostClientTransport('http://127.0.0.1:{}'.format(port))
     self.print_communication = print_communication
     self.privkey = privkey
     self._sender = sender
Ejemplo n.º 22
0
class JSONRPCClient(object):
    """ Ethereum JSON RPC client.

    Args:
        host (str): Ethereum node host address.
        port (int): Ethereum node port number.
        privkey (bin): Local user private key, used to sign transactions.
        nonce_update_interval (float): Update the account nonce every
            `nonce_update_interval` seconds.
        nonce_offset (int): Network's default base nonce number.
    """
    def __init__(self,
                 host,
                 port,
                 privkey,
                 nonce_update_interval=5.0,
                 nonce_offset=0):
        endpoint = 'http://{}:{}'.format(host, port)
        session = requests.Session()
        adapter = requests.adapters.HTTPAdapter(pool_maxsize=50)
        session.mount(endpoint, adapter)

        self.transport = HttpPostClientTransport(
            endpoint,
            post_method=session.post,
            headers={'content-type': 'application/json'},
        )

        self.port = port
        self.privkey = privkey
        self.protocol = JSONRPCProtocol()
        self.sender = privatekey_to_address(privkey)

        self.nonce_last_update = 0
        self.nonce_current_value = None
        self.nonce_lock = Semaphore()
        self.nonce_update_interval = nonce_update_interval
        self.nonce_offset = nonce_offset

    def __repr__(self):
        return '<JSONRPCClient @%d>' % self.port

    def blocknumber(self):
        """ Return the most recent block. """
        return quantity_decoder(self.call('eth_blockNumber'))

    def nonce(self, address):
        if len(address) == 40:
            address = unhexlify(address)

        with self.nonce_lock:
            initialized = self.nonce_current_value is not None
            query_time = now()

            if self.nonce_last_update > query_time:
                # Python's 2.7 time is not monotonic and it's affected by clock
                # resets, force an update.
                self.nonce_update_interval = query_time - self.nonce_update_interval
                needs_update = True

            else:
                last_update_interval = query_time - self.nonce_last_update
                needs_update = last_update_interval > self.nonce_update_interval

            if initialized and not needs_update:
                self.nonce_current_value += 1
                return self.nonce_current_value

            pending_transactions_hex = self.call(
                'eth_getTransactionCount',
                address_encoder(address),
                'pending',
            )
            pending_transactions = quantity_decoder(pending_transactions_hex)
            nonce = pending_transactions + self.nonce_offset

            # we may have hammered the server and not all tx are
            # registered as `pending` yet
            while nonce < self.nonce_current_value:
                log.debug(
                    'nonce on server too low; retrying',
                    server=nonce,
                    local=self.nonce_current_value,
                )

                query_time = now()
                pending_transactions_hex = self.call(
                    'eth_getTransactionCount',
                    address_encoder(address),
                    'pending',
                )
                pending_transactions = quantity_decoder(
                    pending_transactions_hex)
                nonce = pending_transactions + self.nonce_offset

            self.nonce_current_value = nonce
            self.nonce_last_update = query_time

            return self.nonce_current_value

    def balance(self, account):
        """ Return the balance of the account of given address. """
        res = self.call('eth_getBalance', address_encoder(account), 'pending')
        return quantity_decoder(res)

    def gaslimit(self):
        last_block = self.call('eth_getBlockByNumber', 'latest', True)
        gas_limit = quantity_decoder(last_block['gasLimit'])
        return gas_limit

    def new_contract_proxy(self, contract_interface, address):
        """ Return a proxy for interacting with a smart contract.

        Args:
            contract_interface: The contract interface as defined by the json.
            address: The contract's address.
        """
        return ContractProxy(
            self.sender,
            contract_interface,
            address,
            self.eth_call,
            self.send_transaction,
            self.eth_estimateGas,
        )

    def deploy_solidity_contract(
            self,  # pylint: disable=too-many-locals
            sender,
            contract_name,
            all_contracts,
            libraries,
            constructor_parameters,
            contract_path=None,
            timeout=None,
            gasprice=GAS_PRICE):
        """
        Deploy a solidity contract.
        Args:
            sender (address): the sender address
            contract_name (str): the name of the contract to compile
            all_contracts (dict): the json dictionary containing the result of compiling a file
            libraries (list): A list of libraries to use in deployment
            constructor_parameters (tuple): A tuple of arguments to pass to the constructor
            contract_path (str): If we are dealing with solc >= v0.4.9 then the path
                                 to the contract is a required argument to extract
                                 the contract data from the `all_contracts` dict.
            timeout (int): Amount of time to poll the chain to confirm deployment
            gasprice: The gasprice to provide for the transaction
        """

        if contract_name in all_contracts:
            contract_key = contract_name

        elif contract_path is not None:
            _, filename = os.path.split(contract_path)
            contract_key = filename + ':' + contract_name

            if contract_key not in all_contracts:
                raise ValueError('Unknown contract {}'.format(contract_name))
        else:
            raise ValueError(
                'Unknown contract {} and no contract_path given'.format(
                    contract_name))

        libraries = dict(libraries)
        contract = all_contracts[contract_key]
        contract_interface = contract['abi']
        symbols = solidity_unresolved_symbols(contract['bin_hex'])

        if symbols:
            available_symbols = map(solidity_library_symbol,
                                    all_contracts.keys())

            unknown_symbols = set(symbols) - set(available_symbols)
            if unknown_symbols:
                msg = 'Cannot deploy contract, known symbols {}, unresolved symbols {}.'.format(
                    available_symbols,
                    unknown_symbols,
                )
                raise Exception(msg)

            dependencies = deploy_dependencies_symbols(all_contracts)
            deployment_order = dependencies_order_of_build(
                contract_key, dependencies)

            deployment_order.pop()  # remove `contract_name` from the list

            log.debug('Deploying dependencies: {}'.format(
                str(deployment_order)))

            for deploy_contract in deployment_order:
                dependency_contract = all_contracts[deploy_contract]

                hex_bytecode = solidity_resolve_symbols(
                    dependency_contract['bin_hex'], libraries)
                bytecode = unhexlify(hex_bytecode)

                dependency_contract['bin_hex'] = hex_bytecode
                dependency_contract['bin'] = bytecode

                transaction_hash_hex = self.send_transaction(
                    sender,
                    to='',
                    data=bytecode,
                    gasprice=gasprice,
                )
                transaction_hash = unhexlify(transaction_hash_hex)

                self.poll(transaction_hash, timeout=timeout)
                receipt = self.eth_getTransactionReceipt(transaction_hash)

                contract_address = receipt['contractAddress']
                # remove the hexadecimal prefix 0x from the address
                contract_address = contract_address[2:]

                libraries[deploy_contract] = contract_address

                deployed_code = self.eth_getCode(unhexlify(contract_address))

                if deployed_code == '0x':
                    raise RuntimeError(
                        'Contract address has no code, check gas usage.')

            hex_bytecode = solidity_resolve_symbols(contract['bin_hex'],
                                                    libraries)
            bytecode = unhexlify(hex_bytecode)

            contract['bin_hex'] = hex_bytecode
            contract['bin'] = bytecode

        if constructor_parameters:
            translator = ContractTranslator(contract_interface)
            parameters = translator.encode_constructor_arguments(
                constructor_parameters)
            bytecode = contract['bin'] + parameters
        else:
            bytecode = contract['bin']

        transaction_hash_hex = self.send_transaction(
            sender,
            to='',
            data=bytecode,
            gasprice=gasprice,
        )
        transaction_hash = unhexlify(transaction_hash_hex)

        self.poll(transaction_hash, timeout=timeout)
        receipt = self.eth_getTransactionReceipt(transaction_hash)
        contract_address = receipt['contractAddress']

        deployed_code = self.eth_getCode(unhexlify(contract_address[2:]))

        if deployed_code == '0x':
            raise RuntimeError(
                'Deployment of {} failed. Contract address has no code, check gas usage.'
                .format(contract_name, ))

        return self.new_contract_proxy(
            contract_interface,
            contract_address,
        )

    def new_filter(self,
                   fromBlock=None,
                   toBlock=None,
                   address=None,
                   topics=None):
        """ Creates a filter object, based on filter options, to notify when
        the state changes (logs). To check if the state has changed, call
        eth_getFilterChanges.
        """

        json_data = {
            'fromBlock': block_tag_encoder(fromBlock or ''),
            'toBlock': block_tag_encoder(toBlock or ''),
        }

        if address is not None:
            json_data['address'] = address_encoder(address)

        if topics is not None:
            if not isinstance(topics, list):
                raise ValueError('topics must be a list')

            json_data['topics'] = [topic_encoder(topic) for topic in topics]

        filter_id = self.call('eth_newFilter', json_data)
        return quantity_decoder(filter_id)

    def filter_changes(self, fid):
        changes = self.call('eth_getFilterChanges', quantity_encoder(fid))

        if not changes:
            return None

        if isinstance(changes, bytes):
            return data_decoder(changes)

        decoders = {
            'blockHash': data_decoder,
            'transactionHash': data_decoder,
            'data': data_decoder,
            'address': address_decoder,
            'topics': lambda x: [topic_decoder(t) for t in x],
            'blockNumber': quantity_decoder,
            'logIndex': quantity_decoder,
            'transactionIndex': quantity_decoder
        }
        return [{k: decoders[k](v)
                 for k, v in c.items() if v is not None} for c in changes]

    @check_node_connection
    def call(self, method, *args):
        """ Do the request and return the result.

        Args:
            method (str): The RPC method.
            args: The encoded arguments expected by the method.
                - Object arguments must be supplied as a dictionary.
                - Quantity arguments must be hex encoded starting with '0x' and
                without left zeros.
                - Data arguments must be hex encoded starting with '0x'
        """
        request = self.protocol.create_request(method, args)
        reply = self.transport.send_message(request.serialize())

        jsonrpc_reply = self.protocol.parse_reply(reply)
        if isinstance(jsonrpc_reply, JSONRPCSuccessResponse):
            return jsonrpc_reply.result
        elif isinstance(jsonrpc_reply, JSONRPCErrorResponse):
            raise EthNodeCommunicationError(jsonrpc_reply.error)
        else:
            raise EthNodeCommunicationError('Unknown type of JSONRPC reply')

    def send_transaction(self,
                         sender,
                         to,
                         value=0,
                         data='',
                         startgas=0,
                         gasprice=GAS_PRICE,
                         nonce=None):
        """ Helper to send signed messages.

        This method will use the `privkey` provided in the constructor to
        locally sign the transaction. This requires an extended server
        implementation that accepts the variables v, r, and s.
        """

        if not self.privkey and not sender:
            raise ValueError('Either privkey or sender needs to be supplied.')

        if self.privkey:
            privkey_address = privatekey_to_address(self.privkey)
            sender = sender or privkey_address

            if sender != privkey_address:
                raise ValueError('sender for a different privkey.')

            if nonce is None:
                nonce = self.nonce(sender)
        else:
            if nonce is None:
                nonce = 0

        if not startgas:
            startgas = self.gaslimit() - 1

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

        if self.privkey:
            tx.sign(self.privkey)
            result = self.call(
                'eth_sendRawTransaction',
                data_encoder(rlp.encode(tx)),
            )
            return result[2 if result.startswith('0x') else 0:]

        else:

            # rename the fields to match the eth_sendTransaction signature
            tx_dict = tx.to_dict()
            tx_dict.pop('hash')
            tx_dict['sender'] = sender
            tx_dict['gasPrice'] = tx_dict.pop('gasprice')
            tx_dict['gas'] = tx_dict.pop('startgas')

            res = self.eth_sendTransaction(**tx_dict)

        assert len(res) in (20, 32)
        return hexlify(res)

    def eth_sendTransaction(self,
                            nonce=None,
                            sender='',
                            to='',
                            value=0,
                            data='',
                            gasPrice=GAS_PRICE,
                            gas=GAS_PRICE):
        """ Creates new message call transaction or a contract creation, if the
        data field contains code.

        Args:
            sender (address): The 20 bytes address the transaction is sent from.
            to (address): DATA, 20 Bytes - (optional when creating new
                contract) The address the transaction is directed to.
            gas (int): Gas provided for the transaction execution. It will
                return unused gas.
            gasPrice (int): gasPrice used for each unit of gas paid.
            value (int): Value sent with this transaction.
            data (bin): The compiled code of a contract OR the hash of the
                invoked method signature and encoded parameters.
            nonce (int): This allows to overwrite your own pending transactions
                that use the same nonce.
        """

        if to == '' and data.isalnum():
            warnings.warn(
                'Verify that the data parameter is _not_ hex encoded, if this is the case '
                'the data will be double encoded and result in unexpected '
                'behavior.')

        if to == '0' * 40:
            warnings.warn(
                'For contract creation the empty string must be used.')

        if sender is None:
            raise ValueError('sender needs to be provided.')

        json_data = {
            'to': data_encoder(normalize_address(to, allow_blank=True)),
            'value': quantity_encoder(value),
            'gasPrice': quantity_encoder(gasPrice),
            'gas': quantity_encoder(gas),
            'data': data_encoder(data),
            'from': address_encoder(sender),
        }

        if nonce is not None:
            json_data['nonce'] = quantity_encoder(nonce)

        res = self.call('eth_sendTransaction', json_data)

        return data_decoder(res)

    def eth_call(self,
                 sender='',
                 to='',
                 value=0,
                 data='',
                 startgas=GAS_PRICE,
                 gasprice=GAS_PRICE,
                 block_number='latest'):
        """ Executes a new message call immediately without creating a
        transaction on the blockchain.

        Args:
            sender: The address the transaction is sent from.
            to: The address the transaction is directed to.
            gas (int): Gas provided for the transaction execution. eth_call
                consumes zero gas, but this parameter may be needed by some
                executions.
            gasPrice (int): gasPrice used for unit of gas paid.
            value (int): Integer of the value sent with this transaction.
            data (bin): Hash of the method signature and encoded parameters.
                For details see Ethereum Contract ABI.
            block_number: Determines the state of ethereum used in the
                call.
        """

        json_data = format_data_for_call(
            sender,
            to,
            value,
            data,
            startgas,
            gasprice,
        )
        res = self.call('eth_call', json_data, block_number)

        return data_decoder(res)

    def eth_estimateGas(self,
                        sender='',
                        to='',
                        value=0,
                        data='',
                        startgas=GAS_PRICE,
                        gasprice=GAS_PRICE):
        """ Makes a call or transaction, which won't be added to the blockchain
        and returns the used gas, which can be used for estimating the used
        gas.

        Args:
            sender: The address the transaction is sent from.
            to: The address the transaction is directed to.
            gas (int): Gas provided for the transaction execution. eth_call
                consumes zero gas, but this parameter may be needed by some
                executions.
            gasPrice (int): gasPrice used for unit of gas paid.
            value (int): Integer of the value sent with this transaction.
            data (bin): Hash of the method signature and encoded parameters.
                For details see Ethereum Contract ABI.
            block_number: Determines the state of ethereum used in the
                call.
        """

        json_data = format_data_for_call(
            sender,
            to,
            value,
            data,
            startgas,
            gasprice,
        )
        res = self.call('eth_estimateGas', json_data)

        return quantity_decoder(res)

    def eth_getTransactionReceipt(self, transaction_hash):
        """ Returns the receipt of a transaction by transaction hash.

        Args:
            transaction_hash: Hash of a transaction.

        Returns:
            A dict representing the transaction receipt object, or null when no
            receipt was found.
        """
        if transaction_hash.startswith('0x'):
            warnings.warn(
                'transaction_hash seems to be already encoded, this will'
                ' result in unexpected behavior')

        if len(transaction_hash) != 32:
            raise ValueError(
                'transaction_hash length must be 32 (it might be hex encoded)')

        transaction_hash = data_encoder(transaction_hash)
        return self.call('eth_getTransactionReceipt', transaction_hash)

    def eth_getCode(self, address, block='latest'):
        """ Returns code at a given address.

        Args:
            address: An address.
            block: Integer block number, or the string 'latest',
                'earliest' or 'pending'.
        """
        if address.startswith('0x'):
            warnings.warn(
                'address seems to be already encoded, this will result '
                'in unexpected behavior')

        if len(address) != 20:
            raise ValueError(
                'address length must be 20 (it might be hex encoded)')

        return self.call(
            'eth_getCode',
            address_encoder(address),
            block,
        )

    def eth_getTransactionByHash(self, transaction_hash):
        """ Returns the information about a transaction requested by
        transaction hash.
        """

        if transaction_hash.startswith('0x'):
            warnings.warn(
                'transaction_hash seems to be already encoded, this will'
                ' result in unexpected behavior')

        if len(transaction_hash) != 32:
            raise ValueError(
                'transaction_hash length must be 32 (it might be hex encoded)')

        transaction_hash = data_encoder(transaction_hash)
        return self.call('eth_getTransactionByHash', transaction_hash)

    def poll(self, transaction_hash, confirmations=None, timeout=None):
        """ Wait until the `transaction_hash` is applied or rejected.
        If timeout is None, this could wait indefinitely!

        Args:
            transaction_hash (hash): Transaction hash that we are waiting for.
            confirmations (int): Number of block confirmations that we will
                wait for.
            timeout (float): Timeout in seconds, raise an Excpetion on
                timeout.
        """
        if transaction_hash.startswith('0x'):
            warnings.warn(
                'transaction_hash seems to be already encoded, this will'
                ' result in unexpected behavior')

        if len(transaction_hash) != 32:
            raise ValueError(
                'transaction_hash length must be 32 (it might be hex encoded)')

        transaction_hash = data_encoder(transaction_hash)

        deadline = None
        if timeout:
            deadline = gevent.Timeout(timeout)
            deadline.start()

        try:
            # used to check if the transaction was removed, this could happen
            # if gas price is too low:
            #
            # > Transaction (acbca3d6) below gas price (tx=1 Wei ask=18
            # > Shannon). All sequential txs from this address(7d0eae79)
            # > will be ignored
            #
            last_result = None

            while True:
                # Could return None for a short period of time, until the
                # transaction is added to the pool
                transaction = self.call('eth_getTransactionByHash',
                                        transaction_hash)

                # if the transaction was added to the pool and then removed
                if transaction is None and last_result is not None:
                    raise Exception('invalid transaction, check gas price')

                # the transaction was added to the pool and mined
                if transaction and transaction['blockNumber'] is not None:
                    break

                last_result = transaction

                gevent.sleep(.5)

            if confirmations:
                # this will wait for both APPLIED and REVERTED transactions
                transaction_block = quantity_decoder(
                    transaction['blockNumber'])
                confirmation_block = transaction_block + confirmations

                block_number = self.blocknumber()

                while block_number < confirmation_block:
                    gevent.sleep(.5)
                    block_number = self.blocknumber()

        except gevent.Timeout:
            raise Exception('timeout when polling for transaction')

        finally:
            if deadline:
                deadline.cancel()
Ejemplo n.º 23
0
 * we use the tinyrpc python package that allows us to represent the timetagger webserver
   as a python class and will hide all JSON-RPC details from us. 
 * The methods of this 'proxy class' are equivalent to the JSON-RPC interface

"""

#from jsonrpc import ServiceProxy
#timetagger = ServiceProxy('http://localhost:8080/json-rpc')

from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc.transports.http import HttpPostClientTransport
from tinyrpc import RPCClient

rpc_client = RPCClient(
    JSONRPCProtocol(),
    HttpPostClientTransport('http://localhost:8080/json-rpc'))

# create the proxy class
timetagger = rpc_client.get_proxy()

timetagger.getSystemInfo()
timetagger.scanDevices()

res = timetagger.getDeviceList()
dev_id = res[0]['id']  # dev_id = '12520004J3' # FPGA serial number

res = timetagger.getModuleList(device=dev_id)
mod_id = res[0][
    'id']  # mod_id = 'ID-000001' # module created in web application beforehand

timetagger.getDeviceInfo(device=dev_id)