def address_to_hashX(self, address): if isinstance(address, str): try: return self.coin.address_to_hashX(address) except Exception: pass raise RPCError('{} is not a valid address'.format(address))
def new_subscription(self): if self.subs_room <= 0: self.subs_room = self.max_subs - self.sub_count() if self.subs_room <= 0: raise RPCError('server subscription limit {:,d} reached' .format(self.max_subs)) self.subs_room -= 1
async def tx_merkle(self, tx_hash, height): '''tx_hash is a hex string.''' hex_hashes = await self.daemon_request('block_hex_hashes', height, 1) block = await self.daemon_request('deserialised_block', hex_hashes[0]) tx_hashes = block['tx'] try: pos = tx_hashes.index(tx_hash) except ValueError: raise RPCError('tx hash {} not in block {} at height {:,d}'.format( tx_hash, hex_hashes[0], height)) idx = pos hashes = [hex_str_to_hash(txh) for txh in tx_hashes] merkle_branch = [] while len(hashes) > 1: if len(hashes) & 1: hashes.append(hashes[-1]) idx = idx - 1 if (idx & 1) else idx + 1 merkle_branch.append(hash_to_str(hashes[idx])) idx //= 2 hashes = [ double_sha256(hashes[n] + hashes[n + 1]) for n in range(0, len(hashes), 2) ] return {"block_height": height, "merkle": merkle_branch, "pos": pos}
def server_version(self, client_name=None, protocol_version=None): '''Returns the server version as a string. client_name: a string identifying the client protocol_version: the protocol version spoken by the client ''' if client_name: self.client = str(client_name)[:17] try: self.client_version = tuple( int(part) for part in self.client.split('.')) except Exception: pass # Find the highest common protocol version. Disconnect if # that protocol version in unsupported. ptuple = util.protocol_version(protocol_version, version.PROTOCOL_MIN, version.PROTOCOL_MAX) # From protocol version 1.1, protocol_version cannot be omitted if ptuple is None or (ptuple >= (1, 1) and protocol_version is None): self.log_info('unsupported protocol version request {}'.format( protocol_version)) raise RPCError( 'unsupported protocol version: {}'.format(protocol_version), JSONRPC.FATAL_ERROR) self.set_protocol_handlers(ptuple) # The return value depends on the protocol version if ptuple < (1, 1): return version.VERSION else: return (version.VERSION, self.protocol_version)
def scripthash_to_hashX(self, scripthash): try: bin_hash = hex_str_to_hash(scripthash) if len(bin_hash) == 32: return bin_hash[:self.coin.HASHX_LEN] except Exception: pass raise RPCError('{} is not a valid script hash'.format(scripthash))
def rpc_daemon_url(self, daemon_url=None): '''Replace the daemon URL.''' daemon_url = daemon_url or self.env.daemon_url try: self.daemon.set_urls(self.env.coin.daemon_urls(daemon_url)) except Exception as e: raise RPCError('an error occured: {}'.format(e)) return 'now using daemon at {}'.format(self.daemon.logged_url())
def rpc_reorg(self, count=3): '''Force a reorg of the given number of blocks. count: number of blocks to reorg (default 3) ''' count = self.non_negative_integer(count) if not self.bp.force_chain_reorg(count): raise RPCError('still catching up with daemon') return 'scheduled a reorg of {:,d} blocks'.format(count)
def assert_tx_hash(self, value): '''Raise an RPCError if the value is not a valid transaction hash.''' try: if len(util.hex_to_bytes(value)) == 32: return except Exception: pass raise RPCError('{} should be a transaction hash'.format(value))
async def hash160_contract_subscribe(self, hash160, contract_addr): if len(self.contract_subs) >= self.max_subs: raise RPCError( 'your contract subscription limit {:,d} reached'.format( self.max_subs)) hashY = self.controller.coin.hash160_contract_to_hashY( hash160, contract_addr) self.contract_subs[hashY] = (hash160, contract_addr) return await self.hash160_contract_status(hash160, contract_addr)
async def hashX_subscribe(self, hashX, alias): # First check our limit. if len(self.hashX_subs) >= self.max_subs: raise RPCError('your address subscription limit {:,d} reached' .format(self.max_subs)) # Now let the controller check its limit self.controller.new_subscription() self.hashX_subs[hashX] = alias return await self.address_status(hashX)
def electrum_header(self, height): '''Return the binary header at the given height.''' if not 0 <= height <= self.bp.db_height: raise RPCError('height {:,d} out of range'.format(height)) if height in self.header_cache: return self.header_cache[height] header = self.bp.read_headers(height, 1) header = self.coin.electrum_header(header, height) self.header_cache[height] = header return header
def non_negative_integer(self, value): '''Return param value it is or can be converted to a non-negative integer, otherwise raise an RPCError.''' try: value = int(value) if value >= 0: return value except ValueError: pass raise RPCError('{} should be a non-negative integer'.format(value))
def to_tx_hash(self, value): '''Raise an RPCError if the value is not a valid transaction hash.''' if isinstance(value, str) and len(value) == 64: try: bytes.fromhex(value) return value except ValueError: pass raise RPCError('{} should be a transaction hash'.format(value))
async def new_subscription(self, address): if self.subs_room <= 0: self.subs_room = self.max_subs - self.sub_count() if self.subs_room <= 0: raise RPCError( 'server subscription limit {:,d} reached'.format( self.max_subs)) self.subs_room -= 1 hashX = self.address_to_hashX(address) status = await self.address_status(hashX) return hashX, status
async def masternode_announce_broadcast(self, signmnb): '''Pass through the masternode announce message to be broadcast by the daemon.''' try: return await self.daemon.masternode_broadcast(['relay', signmnb]) except DaemonError as e: error, = e.args message = error['message'] self.log_info('masternode_broadcast: {}'.format(message)) raise RPCError('the masternode broadcast was rejected.' '\n\n{}\n[{}]'.format(message, signmnb))
def for_each_session(self, session_ids, operation): if not isinstance(session_ids, list): raise RPCError('expected a list of session IDs') result = [] for session_id in session_ids: session = self.lookup_session(session_id) if session: result.append(operation(session)) else: result.append('unknown session: {}'.format(session_id)) return result
async def listvoterbills(self, addr): '''Get bill list that addr voted. addr: the address''' # This returns errors as JSON RPC errors, as is natural try: return await self.daemon.listvoterbills([addr]) except DaemonError as e: error, = e.args message = error['message'] self.log_info('listvoterbills: {}'.format(message), throttle=True) raise RPCError('the name was rejected by network rules.' '\n\n{}\n[{}]'.format(message, addr))
async def createwitness(self, address): '''Create a witness on the blockchain. address: the address with witness number attached''' # This returns errors as JSON RPC errors, as is natural try: return await self.daemon.getnetworkinfo() except DaemonError as e: error, = e.args message = error['message'] self.log_info('createwitness: {}'.format(message), throttle=True) raise RPCError('the address was rejected by network rules.' '\n\n{}\n[{}]'.format(message, address))
async def address_subscribe(self, address): '''Subscribe to an address. address: the address to subscribe to''' # First check our limit. if len(self.hashX_subs) >= self.max_subs: raise RPCError( 'your address subscription limit {:,d} reached'.format( self.max_subs)) # Now let the controller check its limit hashX, status = await self.controller.new_subscription(address) self.hashX_subs[hashX] = address return status
async def listcommitteevoters(self, name): '''Get who voted to committee name. name: the name of one committee''' # This returns errors as JSON RPC errors, as is natural try: return await self.daemon.listcommitteevoters([name]) except DaemonError as e: error, = e.args message = error['message'] self.log_info('listcommitteevoters: {}'.format(message), throttle=True) raise RPCError('the name was rejected by network rules.' '\n\n{}\n[{}]'.format(message, name))
def block_get_chunk(self, index): '''Return a chunk of block headers as a hexadecimal string. index: the chunk index''' index = self.controller.non_negative_integer(index) self.chunk_indices.append(index) self.chunk_indices = self.chunk_indices[-5:] # -2 allows backing up a single chunk but no more. if index <= max(self.chunk_indices[:-2], default=-1): msg = ('chunk indices not advancing (wrong network?): {}'.format( self.chunk_indices)) # INVALID_REQUEST triggers a disconnect raise RPCError(msg, JSONRPC.INVALID_REQUEST) return self.controller.get_chunk(index)
async def listvoteddelegates(self, address): '''Get vote inof according address. address: the address with vote info attached''' # This returns errors as JSON RPC errors, as is natural try: return await self.daemon.listvoteddelegates([address]) except DaemonError as e: error, = e.args message = error['message'] self.log_info('listvoteddelegates: {}'.format(message), throttle=True) raise RPCError('the address was rejected by network rules.' '\n\n{}\n[{}]'.format(message, address))
async def listreceivedvotes(self, name): '''Get voted inof according name. name: the name of one delegate''' # This returns errors as JSON RPC errors, as is natural try: return await self.daemon.listreceivedvotes([name]) except DaemonError as e: error, = e.args message = error['message'] self.log_info('listreceivedvotes: {}'.format(message), throttle=True) raise RPCError('the name was rejected by network rules.' '\n\n{}\n[{}]'.format(message, name))
async def transaction_broadcast(self, raw_tx): '''Broadcast a raw transaction to the network. raw_tx: the raw transaction as a hexadecimal string''' # This returns errors as JSON RPC errors, as is natural try: tx_hash = await self.daemon.sendrawtransaction([raw_tx]) self.txs_sent += 1 self.log_info('sent tx: {}'.format(tx_hash)) self.controller.sent_tx(tx_hash) return tx_hash except DaemonError as e: error, = e.args message = error['message'] self.log_info('sendrawtransaction: {}'.format(message), throttle=True) raise RPCError('the transaction was rejected by network rules.' '\n\n{}\n[{}]'.format(message, raw_tx))
async def address_get_proof(self, address): '''Return the UTXO proof of an address.''' hashX = self.address_to_hashX(address) raise RPCError('address.get_proof is not yet implemented') async def address_listunspent(self, address): '''Return the list of UTXOs of an address.''' hashX = self.address_to_hashX(address) script = self.pay_to_address_script(address) scriptKey = script.hex() return [{'tx_hash': hash_to_str(utxo.tx_hash), 'tx_pos': utxo.tx_pos, 'height': utxo.height, 'value': utxo.value, 'script': scriptKey} for utxo in sorted(await self.get_utxos(hashX))] async def scripthash_listunspent(self, scripthash): '''Return the list of UTXOs of a scripthash.''' hashX = self.scripthash_to_hashX(scripthash) return [{'tx_hash': hash_to_str(utxo.tx_hash), 'tx_pos': utxo.tx_pos, 'height': utxo.height, 'value': utxo.value} for utxo in sorted(await self.get_utxos(hashX))] def block_get_header(self, height): '''The deserialized header at a given height. height: the header's height''' height = self.non_negative_integer(height) return self.electrum_header(height) async def estimatefee(self, number): '''The estimated transaction fee per kilobyte to be paid for a transaction to be included within a certain number of blocks. number: the number of blocks ''' number = self.non_negative_integer(number) return await self.daemon_request('estimatefee', [number]) async def relayfee(self): '''The minimum fee a low-priority tx must pay in order to be accepted to the daemon's memory pool.''' return await self.daemon_request('relayfee') async def transaction_get(self, tx_hash): '''Return the serialized raw transaction given its hash tx_hash: the transaction hash as a hexadecimal string ''' self.assert_tx_hash(tx_hash) return await self.daemon_request('getrawtransaction', tx_hash) async def transaction_get_1_0(self, tx_hash, height=None): '''Return the serialized raw transaction given its hash tx_hash: the transaction hash as a hexadecimal string height: ignored, do not use ''' # For some reason Electrum protocol 1.0 passes a height. return await self.transaction_get(tx_hash) async def transaction_get_merkle(self, tx_hash, height): '''Return the markle tree to a confirmed transaction given its hash and height. tx_hash: the transaction hash as a hexadecimal string height: the height of the block it is in ''' self.assert_tx_hash(tx_hash) height = self.non_negative_integer(height) return await self.tx_merkle(tx_hash, height) async def utxo_get_address(self, tx_hash, index): '''Returns the address sent to in a UTXO, or null if the UTXO cannot be found. tx_hash: the transaction hash of the UTXO index: the index of the UTXO in the transaction''' # Used only for electrum client command-line requests. We no # longer index by address, so need to request the raw # transaction. So it works for any TXO not just UTXOs. self.assert_tx_hash(tx_hash) index = self.non_negative_integer(index) raw_tx = await self.daemon_request('getrawtransaction', tx_hash) if not raw_tx: return None raw_tx = util.hex_to_bytes(raw_tx) tx, tx_hash = self.coin.DESERIALIZER(raw_tx).read_tx() if index >= len(tx.outputs): return None return self.coin.address_from_script(tx.outputs[index].pk_script) # Signal, exception handlers. def on_signal(self, signame): '''Call on receipt of a signal to cleanly shutdown.''' self.logger.warning('received {} signal, initiating shutdown' .format(signame)) self.initiate_shutdown() def on_exception(self, loop, context): '''Suppress spurious messages it appears we cannot control.''' message = context.get('message') if message not in self.SUPPRESS_MESSAGES: if not ('task' in context and 'accept_connection2()' in repr(context.get('task'))): loop.default_exception_handler(context) def run(self): # Install signal handlers and exception handler loop = self.loop for signame in ('SIGINT', 'SIGTERM'): loop.add_signal_handler(getattr(signal, signame), partial(self.on_signal, signame)) loop.set_exception_handler(self.on_exception) # Run the main loop to completion future = asyncio.ensure_future(self.main_loop()) loop.run_until_complete(future) loop.close()
def assert_boolean(self, value): '''Return param value it is boolean otherwise raise an RPCError.''' if value in (False, True): return value raise RPCError('{} should be a boolean value'.format(value))
async def daemon_request(self, method, *args): '''Catch a DaemonError and convert it to an RPCError.''' try: return await getattr(self.daemon, method)(*args) except DaemonError as e: raise RPCError('daemon error: {}'.format(e))
def pay_to_address_script(self, address): try: return self.coin.pay_to_address_script(address) except Exception: pass raise RPCError('{} is not a valid address'.format(address))
def raw_header(self, height): '''Return the binary header at the given height.''' header, n = self.bp.read_headers(height, 1) if n != 1: raise RPCError('height {:,d} out of range'.format(height)) return header
async def address_get_proof(self, address): '''Return the UTXO proof of an address.''' hashX = self.address_to_hashX(address) raise RPCError('address.get_proof is not yet implemented')