You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1500 lines
63 KiB
1500 lines
63 KiB
from __future__ import unicode_literals
|
|
import binascii
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
import ipaddress
|
|
import json
|
|
import logging
|
|
import requests
|
|
import six
|
|
|
|
from ... import address
|
|
from ... import exceptions
|
|
from ...block import Block
|
|
from ...const import NET_MAIN, NET_TEST, NET_STAGE
|
|
from ...numbers import from_atomic, to_atomic
|
|
from ...transaction import Transaction
|
|
from .exceptions import RPCError, MethodNotFound, Unauthorized
|
|
|
|
|
|
_log = logging.getLogger(__name__)
|
|
|
|
|
|
RESTRICTED_MAX_TRANSACTIONS = 100
|
|
|
|
|
|
class JSONRPCDaemon(object):
|
|
"""
|
|
JSON RPC backend for Monero daemon.
|
|
The offical documentation for the Monero Daemon RPC Protocol can be found at the link below.
|
|
https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#on_get_block_hash
|
|
Much of the in-code documentation of this class is derived from this document.
|
|
|
|
:param protocol: `http` or `https`
|
|
:param host: host name or IP
|
|
:param port: port number
|
|
:param path: path for JSON RPC requests (should not be changed)
|
|
:param timeout: request timeout
|
|
:param verify_ssl_certs: verify SSL certificates when connecting
|
|
:param proxy_url: a proxy to use
|
|
:param prune_transactions: whether to prune transaction data. Saves bandwidth but you may want
|
|
to enable it when you need to retrieve transaction binary blobs.
|
|
"""
|
|
|
|
_METHOD_NOT_FOUND_CODE = -32601
|
|
_KNOWN_LOG_CATEGORIES = [
|
|
'*', 'default', 'net', 'net.http', 'net.p2p',
|
|
'logging', 'net.throttle', 'blockchain.db', 'blockchain.db.lmdb', 'bcutil',
|
|
'checkpoints', 'net.dns', 'net.dl', 'i18n', 'perf',
|
|
'stacktrace', 'updates', 'account', 'cn', 'difficulty',
|
|
'hardfork', 'miner', 'blockchain', 'txpool', 'cn.block_queue',
|
|
'net.cn', 'daemon', 'debugtools.deserialize', 'debugtools.objectsizes', 'device.ledger',
|
|
'wallet.gen_multisig', 'multisig', 'bulletproofs', 'ringct', 'daemon.rpc',
|
|
'wallet.simplewallet', 'WalletAPI', 'wallet.ringdb', 'wallet.wallet2', 'wallet.rpc'
|
|
'tests.core']
|
|
_KNOWN_LOG_LEVELS = ['FATAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'TRACE']
|
|
|
|
_net = None
|
|
_restricted = None
|
|
|
|
def __init__(self, protocol='http', host='127.0.0.1', port=18081, path='/json_rpc',
|
|
user='', password='', timeout=30, verify_ssl_certs=True, proxy_url=None,
|
|
prune_transactions=True):
|
|
self.url = '{protocol}://{host}:{port}'.format(
|
|
protocol=protocol,
|
|
host=host,
|
|
port=port)
|
|
_log.debug("JSONRPC daemon backend URL: {url}".format(url=self.url))
|
|
self.user = user
|
|
self.password = password
|
|
self.timeout = timeout
|
|
self.verify_ssl_certs = verify_ssl_certs
|
|
self.proxies = {protocol: proxy_url}
|
|
self.prune_transactions = prune_transactions
|
|
|
|
def info(self):
|
|
info = self.raw_jsonrpc_request('get_info')
|
|
self._set_net(info)
|
|
return info
|
|
|
|
def net(self):
|
|
if self._net:
|
|
return self._net
|
|
self.info()
|
|
return self._net
|
|
|
|
def restricted(self):
|
|
if self._restricted is None:
|
|
self._set_restricted()
|
|
|
|
return self._restricted
|
|
|
|
def send_transaction(self, blob, relay=True):
|
|
res = self.raw_request('/sendrawtransaction', {
|
|
'tx_as_hex': six.ensure_text(binascii.hexlify(blob)),
|
|
'do_not_relay': not relay})
|
|
if res['status'] == 'OK':
|
|
return res
|
|
raise exceptions.TransactionBroadcastError(
|
|
"{status}: {reason}".format(**res),
|
|
details=res)
|
|
|
|
def mempool(self):
|
|
res = self.raw_request('/get_transaction_pool', {})
|
|
txs = []
|
|
for tx in res.get('transactions', []):
|
|
txs.append(Transaction(
|
|
hash=tx['id_hash'],
|
|
fee=from_atomic(tx['fee']),
|
|
timestamp=datetime.fromtimestamp(tx['receive_time']),
|
|
blob=binascii.unhexlify(tx['tx_blob']),
|
|
json=json.loads(tx['tx_json']),
|
|
confirmations=0))
|
|
return txs
|
|
|
|
def headers(self, start_height, end_height=None):
|
|
end_height = end_height or start_height
|
|
res = self.raw_jsonrpc_request('get_block_headers_range', {
|
|
'start_height': start_height,
|
|
'end_height': end_height})
|
|
if res['status'] == 'OK':
|
|
return res['headers']
|
|
raise exceptions.BackendException(res['status'])
|
|
|
|
def block(self, bhash=None, height=None):
|
|
data = {}
|
|
if bhash:
|
|
data['hash'] = bhash
|
|
if height:
|
|
data['height'] = height
|
|
res = self.raw_jsonrpc_request('get_block', data)
|
|
if res['status'] == 'OK':
|
|
bhdr = res['block_header']
|
|
sub_json = json.loads(res['json'])
|
|
data = {
|
|
'blob': res['blob'],
|
|
'hash': bhdr['hash'],
|
|
'height': bhdr['height'],
|
|
'timestamp': datetime.fromtimestamp(bhdr['timestamp']),
|
|
'version': (bhdr['major_version'], bhdr['minor_version']),
|
|
'difficulty': bhdr['difficulty'],
|
|
'nonce': bhdr['nonce'],
|
|
'orphan': bhdr['orphan_status'],
|
|
'prev_hash': bhdr['prev_hash'],
|
|
'reward': from_atomic(bhdr['reward']),
|
|
'transactions': self.transactions(
|
|
[bhdr['miner_tx_hash']] + sub_json['tx_hashes']),
|
|
}
|
|
return Block(**data)
|
|
raise exceptions.BackendException(res['status'])
|
|
|
|
def transactions(self, hashes):
|
|
"""
|
|
Returns a list of transactions for given hashes. Automatically chunks the request
|
|
into amounts acceptable by a restricted RPC server.
|
|
"""
|
|
|
|
hashes = self._validate_hashlist(hashes)
|
|
result = []
|
|
while len(hashes):
|
|
result.extend(
|
|
self._do_get_transactions(
|
|
hashes[:RESTRICTED_MAX_TRANSACTIONS],
|
|
prune=self.prune_transactions))
|
|
hashes = hashes[RESTRICTED_MAX_TRANSACTIONS:]
|
|
return result
|
|
|
|
def raw_request(self, path, data=None):
|
|
hdr = {'Content-Type': 'application/json'}
|
|
_log.debug(u"Request: {path}\nData: {data}".format(
|
|
path=path,
|
|
data=json.dumps(data, indent=2, sort_keys=True)))
|
|
auth = requests.auth.HTTPDigestAuth(self.user, self.password)
|
|
rsp = requests.post(
|
|
self.url + path, headers=hdr, data=json.dumps(data) if data else None, auth=auth,
|
|
timeout=self.timeout, verify=self.verify_ssl_certs, proxies=self.proxies)
|
|
if rsp.status_code != 200:
|
|
raise RPCError("Invalid HTTP status {code} for path {path}.".format(
|
|
code=rsp.status_code,
|
|
path=path))
|
|
result = rsp.json()
|
|
_ppresult = json.dumps(result, indent=2, sort_keys=True)
|
|
_log.debug(u"Result:\n{result}".format(result=_ppresult))
|
|
return result
|
|
|
|
def raw_jsonrpc_request(self, method, params=None):
|
|
hdr = {'Content-Type': 'application/json'}
|
|
data = {'jsonrpc': '2.0', 'id': 0, 'method': method, 'params': params or {}}
|
|
_log.debug(u"Method: {method}\nParams:\n{params}".format(
|
|
method=method,
|
|
params=json.dumps(params, indent=2, sort_keys=True)))
|
|
auth = requests.auth.HTTPDigestAuth(self.user, self.password)
|
|
rsp = requests.post(
|
|
self.url + '/json_rpc', headers=hdr, data=json.dumps(data), auth=auth,
|
|
timeout=self.timeout, verify=self.verify_ssl_certs, proxies=self.proxies)
|
|
|
|
if rsp.status_code == 401:
|
|
raise Unauthorized("401 Unauthorized. Invalid RPC user name or password.")
|
|
elif rsp.status_code != 200:
|
|
raise RPCError("Invalid HTTP status {code} for method {method}.".format(
|
|
code=rsp.status_code,
|
|
method=method))
|
|
|
|
try:
|
|
result = rsp.json()
|
|
except ValueError as e:
|
|
_log.error(u"Could not parse JSON response from '{url}' during method '{method}'. Response:\n{resp}".format(
|
|
url=self.url, method=method, resp=rsp.text))
|
|
raise RPCError("Daemon returned an unreadable JSON response. It may contain unparseable binary characters.")
|
|
|
|
_ppresult = json.dumps(result, indent=2, sort_keys=True)
|
|
_log.debug(u"Result:\n{result}".format(result=_ppresult))
|
|
|
|
if 'error' in result:
|
|
err = result['error']
|
|
code = err['code']
|
|
msg = err['message']
|
|
|
|
if code == self._METHOD_NOT_FOUND_CODE:
|
|
raise MethodNotFound('Daemon method "{}" not found'.format(method))
|
|
|
|
_log.error(u"JSON RPC error:\n{result}".format(result=_ppresult))
|
|
raise RPCError(
|
|
"Method '{method}' failed with RPC Error of unknown code {code}, "
|
|
"message: {msg}".format(method=method, data=data, result=result, code=code, msg=msg))
|
|
elif 'status' in result['result'] and result['result']['status'] == 'BUSY':
|
|
raise exceptions.DaemonIsBusy(
|
|
"In JSONRPC method '{method}': daemon at {url} is in BUSY mode. RPC command results will be unpredictable "
|
|
"until daemon is fully synced.".format(method=method, url=self.url))
|
|
|
|
return result['result']
|
|
|
|
# JSON RPC Methods (https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#json-rpc-methods)
|
|
|
|
def get_block_count(self):
|
|
"""
|
|
Look up how many blocks are in the longest chain known to the node.
|
|
|
|
Output:
|
|
{
|
|
"count": unsigned int; Number of blocks in longest chain seen by the node.
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
}
|
|
"""
|
|
|
|
return self.raw_jsonrpc_request('get_block_count')
|
|
|
|
def on_get_block_hash(self, height):
|
|
"""
|
|
Look up a block's hash by its height.
|
|
|
|
:param int height: height of the block
|
|
|
|
Output: str; block hash
|
|
"""
|
|
|
|
height = int(height)
|
|
|
|
if height < 0:
|
|
raise ValueError('height < 0')
|
|
|
|
return self.raw_jsonrpc_request('on_get_block_hash', params=[height])
|
|
|
|
def get_block_template(self, wallet_address, reserve_size):
|
|
"""
|
|
Get a block template on which mining a new block.
|
|
|
|
:param str wallet_address: Address of wallet to receive coinbase transactions for successful block.
|
|
:param int reserve_size: number of bytes to make reserved space, maximum is 127
|
|
|
|
Output:
|
|
{
|
|
"blocktemplate_blob": str; Blob on which to try to mine a new block.
|
|
"blockhashing_blob": str; Blob on which to try to find a valid nonce.
|
|
"difficulty": unsigned int; Bottom 64 bits of difficulty of next block.
|
|
"difficuly_top64": unsigned int; Top 64 bits of difficulty of next block.
|
|
"wide_difficulty": str; hex str of difficulty of next block.
|
|
"expected_reward": unsigned int; Coinbase reward expected to be received if block is successfully mined.
|
|
"height": unsigned int; Height on which to mine.
|
|
"prev_hash": str; Hash of the most recent block on which to mine the next block.
|
|
"reserved_offset": unsigned int; Offset in bytes to reserved space in block blob.
|
|
"seed_hash": str;
|
|
"seed_height": unsigned int;
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode
|
|
}
|
|
"""
|
|
|
|
try:
|
|
address.address(wallet_address)
|
|
except ValueError:
|
|
raise ValueError('wallet_address is not in a recognized address form')
|
|
|
|
if reserve_size not in range(128):
|
|
raise ValueError('reserve_size {} is out of bounds'.format(reserve_size))
|
|
|
|
return self.raw_jsonrpc_request('get_block_template', params={
|
|
'wallet_address': wallet_address,
|
|
'reserve_size': reserve_size})
|
|
|
|
def submit_block(self, blobs):
|
|
"""
|
|
Submit a mined block to the network.
|
|
|
|
:param list blobs: list of block hex strs or raw bytes objects
|
|
|
|
Output:
|
|
{
|
|
"status": str; Block submit status
|
|
}
|
|
"""
|
|
|
|
if isinstance(blobs, (bytes, str)):
|
|
blobs = [blobs]
|
|
|
|
blobs = [binascii.hexlify(b).decode() if isinstance(b, bytes) else six.ensure_text(b) for b in blobs]
|
|
|
|
return self.raw_jsonrpc_request('submit_block', params=blobs)
|
|
|
|
def get_last_block_header(self):
|
|
"""
|
|
Block header information for the most recent block is easily retrieved with this method.
|
|
|
|
Output:
|
|
{
|
|
"block_header": {
|
|
"block_size": unsigned int; The block size in bytes.
|
|
"depth": unsigned int; The number of blocks succeeding this block on the blockchain. A larger number means an older block.
|
|
"difficulty" unsigned int; The strength of the Monero network based on mining power.
|
|
"hash": str; The hash of this block.
|
|
"height": unsigned int; The number of blocks preceding this block on the blockchain.
|
|
"major_version": unsigned int; The major version of the monero protocol at this block height.
|
|
"minor_version": unsigned int; The minor version of the monero protocol at this block height.
|
|
"nonce": unsigned int; a cryptographic random one-time number used in mining a Monero block.
|
|
"num_txes": unsigned int; Number of transactions in the block, not counting the coinbase tx.
|
|
"orphan_status": bool; Usually false. If true, this block is not part of the longest chain.
|
|
"prev_hash": str; The hash of the block immediately preceding this block in the chain.
|
|
"reward": unsigned int; The amount of new atomic units generated in this block and rewarded to the miner. Note: 1 XMR = 1e12 atomic units.
|
|
"timestamp": unsigned int; The unix time at which the block was recorded into the blockchain.
|
|
}
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode
|
|
}
|
|
"""
|
|
|
|
return self.raw_jsonrpc_request('get_last_block_header')
|
|
|
|
def get_block_header_by_hash(self, blk_hash):
|
|
"""
|
|
Block header information can be retrieved using a block's hash
|
|
|
|
:param str blk_hash: block hash
|
|
|
|
Output:
|
|
{
|
|
"block_header": {
|
|
"block_size": unsigned int; The block size in bytes.
|
|
"depth": unsigned int; The number of blocks succeeding this block on the blockchain. A larger number means an older block.
|
|
"difficulty" unsigned int; The strength of the Monero network based on mining power.
|
|
"hash": str; The hash of this block.
|
|
"height": unsigned int; The number of blocks preceding this block on the blockchain.
|
|
"major_version": unsigned int; The major version of the monero protocol at this block height.
|
|
"minor_version": unsigned int; The minor version of the monero protocol at this block height.
|
|
"nonce": unsigned int; a cryptographic random one-time number used in mining a Monero block.
|
|
"num_txes": unsigned int; Number of transactions in the block, not counting the coinbase tx.
|
|
"orphan_status": bool; Usually false. If true, this block is not part of the longest chain.
|
|
"prev_hash": str; The hash of the block immediately preceding this block in the chain.
|
|
"reward": unsigned int; The amount of new atomic units generated in this block and rewarded to the miner. Note: 1 XMR = 1e12 atomic units.
|
|
"timestamp": unsigned int; The unix time at which the block was recorded into the blockchain.
|
|
}
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode
|
|
}
|
|
"""
|
|
|
|
if not self._is_valid_256_hex(blk_hash):
|
|
raise ValueError('blk_hash "{}" is not a valid hash'.format(blk_hash))
|
|
|
|
return self.raw_jsonrpc_request('get_block_header_by_hash', params={'hash': blk_hash})
|
|
|
|
def get_block_header_by_height(self, height):
|
|
"""
|
|
Block header information can be retrieved using a block's height
|
|
|
|
:param int height: block height
|
|
|
|
Output:
|
|
{
|
|
"block_header": {
|
|
"block_size": unsigned int; The block size in bytes.
|
|
"depth": unsigned int; The number of blocks succeeding this block on the blockchain. A larger number means an older block.
|
|
"difficulty" unsigned int; The strength of the Monero network based on mining power.
|
|
"hash": str; The hash of this block.
|
|
"height": unsigned int; The number of blocks preceding this block on the blockchain.
|
|
"major_version": unsigned int; The major version of the monero protocol at this block height.
|
|
"minor_version": unsigned int; The minor version of the monero protocol at this block height.
|
|
"nonce": unsigned int; a cryptographic random one-time number used in mining a Monero block.
|
|
"num_txes": unsigned int; Number of transactions in the block, not counting the coinbase tx.
|
|
"orphan_status": bool; Usually false. If true, this block is not part of the longest chain.
|
|
"prev_hash": str; The hash of the block immediately preceding this block in the chain.
|
|
"reward": unsigned int; The amount of new atomic units generated in this block and rewarded to the miner. Note: 1 XMR = 1e12 atomic units.
|
|
"timestamp": unsigned int; The unix time at which the block was recorded into the blockchain.
|
|
}
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode
|
|
}
|
|
"""
|
|
|
|
if height < 0:
|
|
raise ValueError('height < 0')
|
|
|
|
return self.raw_jsonrpc_request('get_block_header_by_height', params={'height': height})
|
|
|
|
def get_block_headers_range(self, start_height, end_height):
|
|
"""
|
|
Similar to get_block_header_by_height, but for a range of blocks. The range is inclusive,
|
|
so the number of blocks returned will be equal to end_height - start_height + 1
|
|
|
|
:param int start_height: starting height
|
|
:param int end_height: ending height
|
|
|
|
Output:
|
|
{
|
|
"headers": list; block_header structures (get_last_block_header) for the blocks in height range.
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode
|
|
}
|
|
"""
|
|
|
|
start_height = int(start_height)
|
|
end_height = int(end_height)
|
|
|
|
if start_height < 0:
|
|
raise ValueError('start_height < 0')
|
|
if end_height < start_height:
|
|
raise ValueError('end_height < start_height')
|
|
|
|
return self.raw_jsonrpc_request('get_block_headers_range', params={
|
|
'start_height': start_height,
|
|
'end_height': end_height})
|
|
|
|
def get_block(self, height=None, hash=None):
|
|
"""
|
|
Full block information can be retrieved by either block height or hash. Either height OR
|
|
hash must be specified, but not both.
|
|
|
|
:param int height: block height
|
|
:param str hash: block hash
|
|
|
|
Output:
|
|
{
|
|
"blob": str; Hexadecimal blob of block information.
|
|
"block_header": A structure containing block header information. See get_last_block_header.
|
|
"json": str; JSON formatted string details:
|
|
"major_version": unsigned int; The major version of the monero protocol at this block height.
|
|
"minor_version": unsigned int; The minor version of the monero protocol at this block height.
|
|
"timestamp": unsigned int; The unix time at which the block was recorded into the blockchain.
|
|
"prev_id": str; The hash of the block immediately preceding this block in the chain.
|
|
"nonce": unsigned int; a cryptographic random one-time number used in mining a Monero block.
|
|
"miner_tx": {
|
|
"version": unsigned int; Transaction version number.
|
|
"unlock_time": unsigned int; The block height when the coinbase transaction becomes spendable.
|
|
"vin": list; List of transaction inputs with the following structure:
|
|
{
|
|
"gen": { (note: miner txs are coinbase txs or "gen")
|
|
"height": This block height, a.k.a. when the coinbase is generated.
|
|
}
|
|
}
|
|
"vout": list; List of transaction outputs with the following structures:
|
|
{
|
|
"amount"; unsigned int; The amount of the output, in atomic units.
|
|
"target": {
|
|
"key": str; public key of one-time output of miner tx.
|
|
}
|
|
}
|
|
"extra"; list; List of integers 0-255, usually the "transaction ID" but can be used to include byte string.
|
|
"rct_signatures" - Contain signatures of tx signers. Coinbased txs do not have signatures.
|
|
"miner_tx_hash": str; hash of the coinbase transaction which belongs to this block.
|
|
"tx_hashes": list; List of str hex hashes of non-coinbase transactions in the block.
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode.
|
|
}
|
|
"""
|
|
|
|
if height is None and hash is None:
|
|
raise ValueError('height or hash must be provided')
|
|
elif height is not None and hash is not None:
|
|
raise ValueError('height and hash can not both be provided')
|
|
|
|
if height is not None:
|
|
if height < 0:
|
|
raise ValueError('{} is not a valid height'.format(height))
|
|
|
|
params = {'height': int(height)}
|
|
else:
|
|
if not self._is_valid_256_hex(hash):
|
|
raise ValueError('blk_hash "{}" is not a valid hash'.format(hash))
|
|
|
|
params = {'hash': hash}
|
|
|
|
return self.raw_jsonrpc_request('get_block', params=params)
|
|
|
|
def get_connections(self):
|
|
"""
|
|
Retrieve information about incoming and outgoing connections to your node.
|
|
|
|
Output:
|
|
{
|
|
"connections": list; List of all connections and their info with the following structure:
|
|
{
|
|
"address": str; The peer's address, actually IPv4 & port
|
|
"avg_download": unsigned int; Average bytes of data downloaded by node.
|
|
"avg_upload": unsigned int; Average bytes of data uploaded by node.
|
|
"connection_id": str; The connection ID
|
|
"current_download": unsigned int; Current bytes downloaded by node.
|
|
"current_upload": unsigned int; Current bytes uploaded by node.
|
|
"height": unsigned int; The peer height
|
|
"host": str; The peer host
|
|
"incoming": bool; Is the node getting information from your node?
|
|
"ip": str; The node's IP address.
|
|
"live_time": unsigned int
|
|
"local_ip": bool
|
|
"localhost": bool
|
|
"peer_id": str; The node's ID on the network.
|
|
"port": str; The port that the node is using to connect to the network.
|
|
"recv_count": unsigned int
|
|
"recv_idle_time": unsigned int
|
|
"send_count": unsigned int
|
|
"send_idle_time": unsigned int
|
|
"state": str
|
|
"support_flags": unsigned int
|
|
}
|
|
}
|
|
"""
|
|
|
|
return self.raw_jsonrpc_request('get_connections')
|
|
|
|
def get_info(self):
|
|
"""
|
|
Retrieve general information about the state of your node and the network.
|
|
|
|
Output:
|
|
{
|
|
"alt_blocks_count": unsigned int; Number of alternative blocks to main chain.
|
|
"block_size_limit": unsigned int; Maximum allowed block size
|
|
"block_size_median": unsigned int; Median block size of latest 100 blocks
|
|
"bootstrap_daemon_address": str; bootstrap node to give immediate usability to wallets while syncing by proxying RPC to it.
|
|
"busy_syncing": bool; States if new blocks are being added (true) or not (false).
|
|
"cumulative_difficulty": unsigned int; Cumulative difficulty of all blocks in the blockchain.
|
|
"difficulty": unsigned int; Network difficulty (analogous to the strength of the network)
|
|
"free_space": unsigned int; Available disk space on the node.
|
|
"grey_peerlist_size": unsigned int; Grey Peerlist Size
|
|
"height": unsigned int; Current length of longest chain known to daemon.
|
|
"height_without_bootstrap": unsigned int; Current length of the local chain of the daemon.
|
|
"incoming_connections_count": unsigned int; Number of peers connected to and pulling from your node.
|
|
"mainnet": bool; States if the node is on the mainnet (true) or not (false).
|
|
"offline": bool; States if the node is offline (true) or online (false).
|
|
"outgoing_connections_count": unsigned int; Number of peers that you are connected to and getting information from.
|
|
"rpc_connections_count": unsigned int; Number of RPC client connected to the daemon (Including this RPC request).
|
|
"stagenet": bool; States if the node is on the stagenet (true) or not (false).
|
|
"start_time": unsigned int; Start time of the daemon, as UNIX time.
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"synchronized": bool; States if the node is synchronized (true) or not (false).
|
|
"target": unsigned int; Current target for next proof of work.
|
|
"target_height": unsigned int; The height of the next block in the chain.
|
|
"testnet": bool; States if the node is on the testnet (true) or not (false).
|
|
"top_block_hash": str; Hash of the highest block in the chain.
|
|
"tx_count": unsigned int; Total number of non-coinbase transaction in the chain.
|
|
"tx_pool_size": unsigned int; Number of transactions that have been broadcast but not included in a block.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode.
|
|
"was_bootstrap_ever_used": bool; States if a bootstrap node has ever been used since the daemon started.
|
|
"white_peerlist_size": unsigned int; White Peerlist Size
|
|
}
|
|
"""
|
|
|
|
return self.raw_jsonrpc_request('get_info')
|
|
|
|
def hard_fork_info(self):
|
|
"""
|
|
Look up information regarding hard fork voting and readiness.
|
|
|
|
Output:
|
|
{
|
|
"earliest_height": unsigned int; Block height at which hard fork would be enabled if voted in.
|
|
"enabled": bool; Tells if hard fork is enforced.
|
|
"state": unsigned int; Current hard fork state: 0 (There is likely a hard fork), 1 (An update is needed to fork properly), or 2 (Everything looks good).
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"threshold": unsigned int; Minimum percent of votes to trigger hard fork. Default is 80.
|
|
"version": unsigned int; The major block version for the fork.
|
|
"votes": unsigned int; Number of votes towards hard fork.
|
|
"voting": unsigned int; Hard fork voting status.
|
|
"window": unsigned int; Number of blocks over which current votes are cast. Default is 10080 blocks.
|
|
}
|
|
"""
|
|
|
|
return self.raw_jsonrpc_request('hard_fork_info')
|
|
|
|
def set_bans(self, ip, ban, seconds=None):
|
|
"""
|
|
Ban (or unban) other nodes by IP for a certain length of time.
|
|
|
|
:param ip: ip address of node to ban in two forms: 'a.b.c.d' str or 32-bit unsigned integer
|
|
:param bool ban: Whether to ban (True) or unban (False)
|
|
:param int seconds: Number of seconds to ban. Optional if "ban" is False
|
|
|
|
Notes:
|
|
* Params "ip", "ban", and "seconds" can be single elements or they can be iterables of the same length. For example:
|
|
|
|
ips = ['229.52.8.67', 611282986]
|
|
should_ban = [True, False]
|
|
seconds = [10000, None]
|
|
daemon.set_bans(ips, should_ban, seconds)
|
|
|
|
* "seconds" can be 0 or None if "ban" is False
|
|
|
|
Output:
|
|
{
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
if isinstance(ip, (str, int)): # If single element paramters
|
|
user_bans = [{
|
|
'ip': ip,
|
|
'ban': ban,
|
|
'seconds': seconds
|
|
}]
|
|
else: # If params are iterables, contrust the list of ban entries
|
|
if not (len(ip) == len(ban) == len(seconds)):
|
|
raise ValueError('"ip", "ban", and "seconds" are not the same length')
|
|
|
|
user_bans = [{
|
|
'ip': ip[i],
|
|
'ban': ban[i],
|
|
'seconds': seconds[i]
|
|
} for i in range(len(ip))]
|
|
|
|
# Construct the bans parameter to be passed to the RPC command
|
|
bans = []
|
|
for ban_entry in user_bans:
|
|
curr_ip = ban_entry['ip']
|
|
curr_ban = bool(ban_entry['ban'])
|
|
curr_seconds = ban_entry['seconds']
|
|
|
|
# validate IP
|
|
ipaddress.IPv4Address(curr_ip)
|
|
|
|
if curr_ban and curr_seconds is None:
|
|
raise ValueError('Whenever "ban" is True, "seconds" must not be None')
|
|
elif curr_seconds is not None and curr_seconds < 0:
|
|
raise ValueError('"seconds" can not be less than zero')
|
|
|
|
ban = {}
|
|
if isinstance(curr_ip, int):
|
|
ban['ip'] = curr_ip
|
|
else:
|
|
ban['host'] = curr_ip
|
|
if curr_seconds is not None:
|
|
ban['seconds'] = curr_seconds
|
|
ban['ban'] = curr_ban
|
|
|
|
bans.append(ban)
|
|
|
|
return self.raw_jsonrpc_request('set_bans', params={'bans': bans})
|
|
|
|
def get_bans(self):
|
|
"""
|
|
Get list of ban entries. For structure of ban entires, see documentation of set_bans method.
|
|
|
|
Output:
|
|
{
|
|
"bans": list; banned nodes with following structure
|
|
{
|
|
"host": str; Banned host (IP in A.B.C.D form).
|
|
"ip": unsigned int; Banned IP address, in Int format.
|
|
"seconds": unsigned int; Local Unix time that IP is banned until.
|
|
}
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
}
|
|
"""
|
|
|
|
resp = self.raw_jsonrpc_request('get_bans')
|
|
|
|
# When there are no ban entries, the node returns a responses with no "bans" field.
|
|
# This is counterintuitive for the user, so I add an empty field if it's not provided.
|
|
if 'bans' not in resp:
|
|
resp['bans'] = []
|
|
|
|
return resp
|
|
|
|
def flush_txpool(self, txids=None):
|
|
"""
|
|
Flush tx ids from transaction pool
|
|
|
|
:param list txids: list of str; transactions IDs to flush from mempool (all tx ids flushed if empty).
|
|
|
|
Output:
|
|
{
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
txids = self._validate_hashlist(txids)
|
|
|
|
resp = self.raw_jsonrpc_request('flush_txpool', params={'txids': txids})
|
|
|
|
if 'transactions' not in resp:
|
|
resp['transactions'] = []
|
|
|
|
return resp
|
|
|
|
def get_output_histogram(self, amounts, min_count=None, max_count=None, unlocked=None, recent_cutoff=None):
|
|
"""
|
|
Get a histogram of output amounts. For all amounts (possibly filtered by parameters), gives the number
|
|
of outputs on the chain for that amount. RingCT outputs counts as 0 amount.
|
|
|
|
:param list amounts: list of unsigned ints in atomic units, or Decimals as full monero amounts
|
|
:param int min_count: lower bound for output number
|
|
:param int max_count: upper bound for output number
|
|
:param bool unlocked: whether to count only unlocked
|
|
:pramrm int recent_cutoff: unsigned int
|
|
|
|
|
|
Output:
|
|
{
|
|
"histogram": list; histogram entries with the following structure
|
|
{
|
|
"amount": unsigned int; Output amount in atomic units.
|
|
"total_instances": unsigned int; number of total instances for given amount.
|
|
"unlocked_instances": unsigned int; number of unlocked instances for given amount.
|
|
"recent_instances": unsigned int; number of recent instances for given amount.
|
|
}
|
|
"status - string; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode.
|
|
}
|
|
"""
|
|
|
|
# Coerce amounts paramter
|
|
if isinstance(amounts, (int, Decimal)):
|
|
amounts = [to_atomic(amounts) if isinstance(amounts, Decimal) else amounts]
|
|
elif amounts is None:
|
|
raise ValueError('amounts is None')
|
|
|
|
# Construct RPC parameters
|
|
params = {'amounts': amounts}
|
|
if min_count is not None:
|
|
params['min_count'] = int(min_count)
|
|
if max_count is not None:
|
|
params['max_count'] = int(max_count)
|
|
if unlocked is not None:
|
|
params['unlocked'] = bool(unlocked)
|
|
if recent_cutoff is not None:
|
|
params['recent_cutoff'] = int(recent_cutoff)
|
|
|
|
return self.raw_jsonrpc_request('get_output_histogram', params=params)
|
|
|
|
def get_coinbase_tx_sum(self, height, count):
|
|
"""
|
|
Get the coinbase amount and the fees amount for n last blocks starting at particular height.
|
|
|
|
:param int height: unsigned int; block height from which getting the amounts
|
|
:param int count: unsigned int; number of blocks to include in the sum
|
|
|
|
Output:
|
|
{
|
|
"emission_amount": unsigned int; amount of coinbase reward in atomic units
|
|
"fee_amount": unsigned int; amount of fees in atomic units
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
}
|
|
"""
|
|
|
|
if height < 0:
|
|
raise ValueError('height < 0')
|
|
elif count < 0:
|
|
raise ValueError('count < 0')
|
|
|
|
return self.raw_jsonrpc_request('get_coinbase_tx_sum', params={'height': height, 'count': count})
|
|
|
|
def get_version(self):
|
|
"""
|
|
Get the node's current version.
|
|
|
|
Output:
|
|
{
|
|
"version": unsigned int;
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode.
|
|
}
|
|
"""
|
|
return self.raw_jsonrpc_request('get_version')
|
|
|
|
def get_fee_estimate(self, grace_blocks=None):
|
|
"""
|
|
Gives an estimation on fees per byte.
|
|
|
|
:param int grace_blocks: Optional; number of previous blocks to include in fee calculation
|
|
|
|
Output:
|
|
{
|
|
"fee": unsigned int; Amount of fees estimated per byte in atomic units
|
|
"quantization_mask": unsigned int; Final fee should be rounded up to an even multiple of this value
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode.
|
|
}
|
|
"""
|
|
|
|
if not isinstance(grace_blocks, (int, type(None))):
|
|
raise TypeError("grace_blocks is not an int")
|
|
elif grace_blocks is not None and grace_blocks < 0:
|
|
raise ValueError("grace_blocks < 0")
|
|
|
|
params = {'grace_blocks': grace_blocks} if grace_blocks else None
|
|
return self.raw_jsonrpc_request('get_fee_estimate', params=params)
|
|
|
|
def get_alternate_chains(self):
|
|
"""
|
|
Display alternative chains seen by the node.
|
|
|
|
|
|
Output:
|
|
{
|
|
"chains": list; chain informations with the following structure
|
|
{
|
|
"block_hash": str; the block hash of the first diverging block of this alternative chain.
|
|
"difficulty": unsigned int; the cumulative difficulty of all blocks in the alternative chain.
|
|
"height": unsigned int; the block height of the first diverging block of this alternative chain.
|
|
"length": unsigned int; the length in blocks of this alternative chain, after divergence.
|
|
}
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
}
|
|
|
|
"""
|
|
|
|
return self.raw_jsonrpc_request('get_alternate_chains')
|
|
|
|
def relay_tx(self, txids):
|
|
"""
|
|
Relay a list of transaction IDs.
|
|
|
|
:param list txids: list of transaction IDs to relay
|
|
|
|
Output:
|
|
{
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
txids = self._validate_hashlist(txids)
|
|
|
|
return self.raw_jsonrpc_request('relay_tx', params={'txids': txids})
|
|
|
|
def sync_info(self):
|
|
"""
|
|
Get synchronisation information.
|
|
|
|
Output:
|
|
{
|
|
"height": unsigned int; current height
|
|
"peers": list; peer informations with the following structure
|
|
{
|
|
"info": dict; structure of connection info, as defined in get_connections
|
|
}
|
|
"spans": list; span informations with the follwing structure (absent if node is fully synced)
|
|
{
|
|
"connection_id": str; connection ID.
|
|
"nblocks": unsigned int; number of blocks in that span.
|
|
"rate": unsigned int; connection rate.
|
|
"remote_address": str; peer address the node is downloading (or has downloaded) the span from.
|
|
"size": unsigned int; total number of bytes in that span's blocks (including txes).
|
|
"speed": unsigned int; connection speed.
|
|
"start_block_height": unsigned int; block height of the first block in that span.
|
|
}
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"target_height": unsigned int; target height the node is syncing from (will be undefined if node is fully synced)
|
|
}
|
|
"""
|
|
|
|
return self.raw_jsonrpc_request('sync_info')
|
|
|
|
# Other RPC Methods (https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#other-daemon-rpc-calls)
|
|
|
|
def get_height(self):
|
|
"""
|
|
Get the node's current height.
|
|
|
|
Outputs:
|
|
{
|
|
"height": unsigned int; Current length of longest chain known to daemon.
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode.
|
|
}
|
|
"""
|
|
|
|
return self.raw_request('/get_height')
|
|
|
|
def get_transactions(self, tx_hashes, decode_as_json=False, prune=False):
|
|
"""
|
|
Look up one or more transactions by hash.
|
|
|
|
:param list txs_hashes: List of transaction hashes to look up.
|
|
:param bool decode_as_json: Optional (false by default). If true, the returned tx info will be decoded into JSON.
|
|
:param bool prune: Optional (false by default). If true, prune blob data, greatly cutting down on response size.
|
|
|
|
|
|
Output:
|
|
{
|
|
"missed_tx": list; (Optional - returned if not empty) Transaction hashes that could not be found.
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"txs": list; transaction informations with the following structure
|
|
{
|
|
"as_hex": stri; Full transaction information as a hex string.
|
|
"as_json": json str; JSON formatted as follows
|
|
{
|
|
"version": unsigned int; Transaction version
|
|
"unlock_time": unsigned int; If not 0, this tells when a transaction output is spendable.
|
|
"vin": list; transaction inputs with the following structure
|
|
{
|
|
"key":
|
|
{
|
|
"amount": unsigned int; The amount of the input, in atomic units.
|
|
"key_offsets": list; integer offets to the input.
|
|
"k_image": str; The key image for the given input
|
|
}
|
|
|
|
}
|
|
"vout": list; transaction inputs with the following structure
|
|
{
|
|
"amount": unsigned int; Amount of transaction output, in atomic units.
|
|
"target":
|
|
{
|
|
"key": str; The stealth public key of the receiver.
|
|
}
|
|
}
|
|
"extra"; list; List of integers 0-255, usually the "transaction ID" but can be used to include byte string.
|
|
signatures - List of signatures used in ring signature to hide the true origin of the transaction.
|
|
}
|
|
"block_height": unsigned int; block height including the transaction
|
|
"block_timestamp": unsigned int; Unix time at chich the block has been added to the blockchain
|
|
"double_spend_seen": boolean; States if the transaction is a double-spend (true) or not (false)
|
|
"in_pool": bool; States if the transaction is in pool (true) or included in a block (false)
|
|
"output_indices": list; list of the tx's output's globals indexes as unsigned ints
|
|
"tx_hash": str; transaction hash
|
|
}
|
|
"txs_as_hex": str; Full transaction information as a hex string (old compatibility parameter)
|
|
"txs_as_json": json str; (Optional - returned if set in inputs. Old compatibility parameter) List of transaction as in as_json above:
|
|
}
|
|
"""
|
|
|
|
tx_hashes = self._validate_hashlist(tx_hashes)
|
|
|
|
return self.raw_request('/get_transactions', data={
|
|
'txs_hashes': tx_hashes,
|
|
'decode_as_json': bool(decode_as_json),
|
|
'prune': bool(prune)})
|
|
|
|
def get_alt_blocks_hashes(self):
|
|
"""
|
|
Get the known blocks hashes which are not on the main chain.
|
|
|
|
Output:
|
|
{
|
|
"blks_hashes"; list; alternative blocks hashes to main chain
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode.
|
|
}
|
|
"""
|
|
|
|
return self.raw_request('/get_alt_blocks_hashes')
|
|
|
|
def is_key_image_spent(self, key_images):
|
|
"""
|
|
Check if outputs have been spent using the key image associated with the output.
|
|
|
|
:param list key_images: List of key image hex strings to check.
|
|
|
|
Key Image Status Legend:
|
|
0 = unspent
|
|
1 = spent in blockchain
|
|
2 = spent in transaction pool
|
|
|
|
Outputs:
|
|
{
|
|
"spent_status": list of unsigned ints; List of statuses for each image checked.
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode.
|
|
}
|
|
"""
|
|
|
|
key_images = self._validate_hashlist(key_images)
|
|
|
|
return self.raw_request('/is_key_image_spent', data={'key_images': key_images})
|
|
|
|
def send_raw_transaction(self, tx_as_hex, do_not_relay=False):
|
|
"""
|
|
Broadcast a raw transaction to the network.
|
|
|
|
:param str tx_as_hex: Full transaction information as hexidecimal string.
|
|
:param bool do_not_relay: Optional; Stop relaying transaction to other nodes (default is false).
|
|
|
|
Output:
|
|
{
|
|
"double_spend": bool; Transaction is a double spend (true) or not (false).
|
|
"fee_too_low": bool; Fee is too low (true) or OK (false).
|
|
"invalid_input": bool; Input is invalid (true) or valid (false).
|
|
"invalid_output": bool; Output is invalid (true) or valid (false).
|
|
"low_mixin": bool; Mixin count is too low (true) or OK (false).
|
|
"not_rct": bool; Transaction is a standard ring transaction (true) or a ring confidential transaction (false).
|
|
"not_relayed": bool; Transaction was not relayed (true) or relayed (false).
|
|
"overspend": bool; Transaction uses more money than available (true) or not (false).
|
|
"reason": str; Additional information. Currently empty or "Not relayed" if transaction was accepted but not relayed.
|
|
"too_big": bool; Transaction size is too big (true) or OK (false).
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode.
|
|
}
|
|
"""
|
|
|
|
tx_as_hex = six.ensure_text(tx_as_hex)
|
|
|
|
return self.raw_request('/send_raw_transaction', data={
|
|
'tx_as_hex': tx_as_hex,
|
|
'do_not_relay': do_not_relay})
|
|
|
|
def start_mining(self, do_background_mining, ignore_battery, miner_address, threads_count):
|
|
"""
|
|
Start mining on the daemon.
|
|
|
|
:param bool do_background_mining: States if the mining should run in background (true) or foreground (false).
|
|
:param bool ignore_battery: States if batery state (on laptop) should be ignored (true) or not (false).
|
|
:param str miner_address: Account address to mine to.
|
|
:param int threads_count: Number of mining thread to run.
|
|
|
|
Output:
|
|
{
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
if isinstance(miner_address, address.Address):
|
|
miner_address = repr(miner_address)
|
|
else:
|
|
try:
|
|
converted_addr = address.address(miner_address)
|
|
except ValueError:
|
|
raise ValueError('miner_address "{}" does not match any recognized address format'.format(miner_address))
|
|
|
|
if not isinstance(converted_addr, address.Address):
|
|
raise ValueError('miner_address must be a "standard" monero address (i.e. it must start with a "4")')
|
|
|
|
if not isinstance(threads_count, int):
|
|
raise TypeError("threads_count must be an int")
|
|
|
|
if threads_count < 0:
|
|
raise ValueError('threads_count < 0')
|
|
|
|
return self.raw_request('/start_mining', data={
|
|
'do_background_mining': bool(do_background_mining),
|
|
'ignore_battery': bool(ignore_battery),
|
|
'miner_address': miner_address,
|
|
'threads_count': threads_count})
|
|
|
|
def stop_mining(self):
|
|
"""
|
|
Stop mining on the daemon.
|
|
|
|
Output:
|
|
{
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
return self.raw_request('/stop_mining')
|
|
|
|
def mining_status(self):
|
|
"""
|
|
Get the mining status of the daemon.
|
|
|
|
Output:
|
|
{
|
|
"active": bool; States if mining is enabled (true) or disabled (false).
|
|
"address": str; Account address daemon is mining to. Empty if not mining.
|
|
"is_background_mining_enabled": bool; States if the mining is running in background (true) or foreground (false).
|
|
"speed": unsigned int; Mining power in hashes per seconds.
|
|
"threads_count": unsigned int; Number of running mining threads.
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
return self.raw_request('/mining_status')
|
|
|
|
def save_bc(self):
|
|
"""
|
|
Save the blockchain. The blockchain does not need saving and is always saved when modified, however it does a sync to
|
|
flush the filesystem cache onto the disk for safety purposes against Operating System or Harware crashes.
|
|
|
|
Output:
|
|
{
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
return self.raw_request('/save_bc')
|
|
|
|
def get_peer_list(self):
|
|
"""
|
|
Get the known peer list.
|
|
|
|
Outputs:
|
|
{
|
|
"gray_list": list; offline peer informations with the following structure
|
|
{
|
|
"host": unsigned int; IP address in integer format.
|
|
"id": stri; Peer id.
|
|
"ip": unsigned int; IP address in integer format.
|
|
"last_seen": unsigned int; unix time at which the peer has been seen for the last time.
|
|
"port": unsigned int; TCP port the peer is using to connect to monero network.
|
|
}
|
|
"white_list": list; online peer informations with the above structure ^^^
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
return self.raw_request('/get_peer_list')
|
|
|
|
def set_log_hash_rate(self, visible):
|
|
"""
|
|
Set the log hash rate display mode.
|
|
|
|
:param bool visible: States if hash rate logs should be visible (true) or hidden (false)
|
|
|
|
Output:
|
|
{
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
resp = self.raw_request('/set_log_hash_rate', data={'visible': bool(visible)})
|
|
|
|
if resp['status'] == 'NOT MINING':
|
|
raise RPCError('The node at "{url}" is not currently mining and therefore cannot set its hash rate log visibility.'
|
|
.format(url=self.url))
|
|
|
|
return resp
|
|
|
|
def set_log_level(self, level):
|
|
"""
|
|
Set the daemon log level. By default, log level is set to 0.
|
|
|
|
:param int level: daemon log level to set from 0 (less verbose) to 4 (most verbose)
|
|
|
|
Output:
|
|
{
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
if level not in range(5):
|
|
raise ValueError('level must an integer between 0 (less verbose) and 4 (more verbose), inclusive')
|
|
|
|
return self.raw_request('/set_log_level', data={'level': level})
|
|
|
|
def set_log_categories(self, categories=None):
|
|
"""
|
|
Set the daemon log categories. Categories are represented as a comma separated list of
|
|
<Category>:<level> (similarly to syslog standard <Facility>:<Severity-level>).
|
|
To get a list of all log categories known by this library, call JSONRPCDaemon.get_all_known_log_categories().
|
|
To get a list of all log levels known by this library, call JSONRPCDaemon.get_all_known_log_levels().
|
|
|
|
:param categories: str or list; Optional, daemon log categories to enable. Accepts list or comma-seperated string.
|
|
|
|
Output:
|
|
{
|
|
"categories": str; comma-seperated string list of daemon log enabled categories
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
if not categories:
|
|
return self.raw_request('/set_log_categories')
|
|
elif isinstance(categories, str):
|
|
categories = categories.split(',')
|
|
|
|
# Validate categories
|
|
for cat_str in categories:
|
|
try:
|
|
category, level = cat_str.split(':')
|
|
|
|
if category not in self._KNOWN_LOG_CATEGORIES:
|
|
_log.warn(u"Unrecognized log category: \"{}\"".format(category))
|
|
|
|
if level not in self._KNOWN_LOG_LEVELS:
|
|
_log.warn(u"Unrecognized log level: \"{}\"".format(level))
|
|
except ValueError:
|
|
raise ValueError('malformed category string: "{}"'.format(cat_str))
|
|
|
|
final_category_str = ','.join(categories)
|
|
|
|
return self.raw_request('/set_log_categories', data={'categories': final_category_str})
|
|
|
|
def get_transaction_pool(self):
|
|
"""
|
|
Show information about valid transactions seen by the node but not yet mined into a block,
|
|
as well as spent key image information for the txpool in the node's memory.
|
|
|
|
Output:
|
|
{
|
|
"spent_key_images": list; spent output key images with following structure
|
|
{
|
|
"id_hash": str; Key image.
|
|
"txs_hashes": list; tx hashes of the txes (usually one) spending that key image.
|
|
}
|
|
"transactions": list; transactions in the mempool with the following structure
|
|
{
|
|
"blob_size": unsigned int; The size of the full transaction blob.
|
|
"double_spend_seen": bool; States if this transaction has been seen as double spend.
|
|
"do_not_relay": bool; States if this transaction should not be relayed
|
|
"fee": unsigned int; The amount of the mining fee included in the transaction, in atomic units.
|
|
"id_hash": str; The transaction ID hash.
|
|
"kept_by_block": bool; States if the tx was included in a block at least once (true) or not (false).
|
|
"last_failed_height": unsigned int; If the transaction validation has previously failed, this tells at what height that occured.
|
|
"last_failed_id_hash": str; Like the previous, this tells the previous transaction ID hash.
|
|
"last_relayed_time": unsigned int; Last unix time at which the transaction has been relayed.
|
|
"max_used_block_height": unsigned int; Tells the height of the most recent block with an output used in this transaction.
|
|
"max_used_block_hash": str; Tells the hash of the most recent block with an output used in this transaction.
|
|
"receive_time": unsigned int; The Unix time that the transaction was first seen on the network by the node.
|
|
"relayed": bool; States if this transaction has been relayed
|
|
"tx_blob": unsigned int; Hexadecimal blob represnting the transaction.
|
|
"tx_json": json string; JSON structure of all information in the transaction (see get_transactions() for structure information)
|
|
}
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
}
|
|
"""
|
|
|
|
return self.raw_request('/get_transaction_pool')
|
|
|
|
def get_transaction_pool_stats(self):
|
|
"""
|
|
Get the transaction pool statistics.
|
|
|
|
Output:
|
|
{
|
|
"pool_stats": {
|
|
"bytes_max": unsigned int; Max transaction size in pool.
|
|
"bytes_med" unsigned int; Median transaction size in pool.
|
|
"bytes_min" unsigned int; Min transaction size in pool.
|
|
"bytes_total": unsigned int; total size of all transactions in pool.
|
|
"histo": list; histogram of transaction sizes with the following structure
|
|
{
|
|
"txs": unsigned int; number of transactions.
|
|
"bytes": unsigned int; size in bytes.
|
|
}
|
|
"histo_98pc": unsigned int; the time 98% of txes are "younger" than.
|
|
"num_10m": unsigned int; number of transactions in pool for more than 10 minutes.
|
|
"num_double_spends": unsigned int; number of double spend transactions.
|
|
"num_failing": unsigned int; number of failing transactions.
|
|
"num_not_relayed": unsigned int; number of non-relayed transactions.
|
|
"oldest": unsigned int; unix time of the oldest transaction in the pool
|
|
"txs_total": unsigned int; total number of transactions.
|
|
}
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode.
|
|
}
|
|
"""
|
|
|
|
return self.raw_request('/get_transaction_pool_stats')
|
|
|
|
def stop_daemon(self):
|
|
"""
|
|
Send a command to the daemon to safely disconnect and shut down.
|
|
|
|
Output:
|
|
{
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
return self.raw_request('/stop_daemon')
|
|
|
|
def get_limit(self):
|
|
"""
|
|
Get daemon bandwidth limits.
|
|
|
|
Outputs:
|
|
{
|
|
"limit_down": unsigned int; Download limit in kBytes per second
|
|
"limit_up": unsigned int; Upload limit in kBytes per second
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode.
|
|
}
|
|
"""
|
|
|
|
return self.raw_request('/get_limit')
|
|
|
|
def set_limit(self, limit_down=-1, limit_up=-1):
|
|
"""
|
|
Set daemon bandwidth limits.
|
|
|
|
:param int limit_down: Download limit in kBytes per second (-1 reset to default, 0 don't change the current limit)
|
|
:param int limit_up: Upload limit in kBytes per second (-1 reset to default, 0 don't change the current limit)
|
|
|
|
Output:
|
|
{
|
|
"limit_down": - unsigned int; Download limit in kBytes per second
|
|
"limit_up": unsigned int; Upload limit in kBytes per second
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
}
|
|
"""
|
|
|
|
if limit_down < -1:
|
|
raise ValueError('invalid limit_down: {}'.format(limit_down))
|
|
elif limit_up < -1:
|
|
raise ValueError('invalid limit_up: {}'.format(limit_up))
|
|
|
|
return self.raw_request('/set_limit', data={'limit_down': limit_down, 'limit_up': limit_up})
|
|
|
|
def out_peers(self, out_peers_arg):
|
|
"""
|
|
Limit number of outgoing peers.
|
|
|
|
:param int out_peers_arg: Max number of outgoing peers. If less than zero, allow unlimited outgoing peers
|
|
|
|
Output:
|
|
{
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
if out_peers_arg < 0:
|
|
out_peers_arg = 2**32 - 1
|
|
elif out_peers_arg > 2**32 - 1:
|
|
raise ValueError('out_peers_arg "{}" is too large'.format(out_peers_arg))
|
|
|
|
return self.raw_request('/out_peers', data={'out_peers': int(out_peers_arg)})
|
|
|
|
def in_peers(self, in_peers_arg):
|
|
"""
|
|
Limit number of incoming peers.
|
|
|
|
:param int in_peers_arg: Max number of incoming peers. If less than zero, allow unlimited incoming peers
|
|
|
|
Output:
|
|
{
|
|
"status": str; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
|
}
|
|
"""
|
|
|
|
if in_peers_arg < 0:
|
|
in_peers_arg = 2**32 - 1
|
|
elif in_peers_arg > 2**32 - 1:
|
|
raise ValueError('in_peers_arg "{}" is too large'.format(in_peers_arg))
|
|
|
|
return self.raw_request('/in_peers', data={'in_peers': int(in_peers_arg)})
|
|
|
|
def get_outs(self, amount, index, get_txid=True):
|
|
"""
|
|
Get information about one-time outputs (stealth addresses).
|
|
|
|
|
|
:param amount: list or single element of output amount. Decimal for full monero amounts or int for atmoic units
|
|
:param index: list of single element of output index. int
|
|
:param bool get_txid: Optional. If true, a txid will included for each output in the response.
|
|
|
|
Output:
|
|
{
|
|
"outs": list; output informations with the following structure
|
|
{
|
|
"height": unsigned int; block height of the output
|
|
"key": str; the public key of the output
|
|
"mask": str;
|
|
"txid": str; transaction id
|
|
"unlocked": bool; States if output is locked (false) or not (true)
|
|
}
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
"untrusted": bool; True for bootstrap mode, False for full sync mode.
|
|
}
|
|
"""
|
|
|
|
if isinstance(index, int): # single element
|
|
outputs = [{'amount': amount if isinstance(amount, int) else to_atomic(amount), 'index': index}]
|
|
else: # multiple elements
|
|
if len(amount) != len(index):
|
|
raise ValueError('length of amount and index do not match')
|
|
|
|
outputs = []
|
|
for a, i in zip(amount, index):
|
|
outputs.append({
|
|
'amount': a if isinstance(a, int) else to_atomic(a),
|
|
'index': i})
|
|
|
|
return self.raw_request('/get_outs', data={
|
|
'outputs': outputs,
|
|
'get_txid': get_txid})
|
|
|
|
def update(self, command, path=None):
|
|
"""
|
|
Update daemon.
|
|
|
|
|
|
:param str command: Command to use, either "check" or "download".
|
|
:param str path: Optional, path where to download the update.
|
|
|
|
|
|
Output:
|
|
{
|
|
"auto_uri": str;
|
|
"hash": str;
|
|
"path": str; path to download the update.
|
|
"update": bool; States if an update is available to download (true) or not (false).
|
|
"user_uri": str;
|
|
"version": str; Version available for download.
|
|
"status": str; General RPC error code. "OK" means everything looks good.
|
|
}
|
|
"""
|
|
|
|
command = six.ensure_text(command)
|
|
path = six.ensure_text(path) if path is not None else None
|
|
|
|
if command not in ('check', 'download'):
|
|
raise ValueError('unrecognized command: "{}"'.format(command))
|
|
|
|
data = {'command': command}
|
|
|
|
if path is not None:
|
|
data['path'] = path
|
|
|
|
return self.raw_request('/update', data=data)
|
|
|
|
# Supporting class methods
|
|
|
|
@classmethod
|
|
def known_log_categories(cls):
|
|
return cls._KNOWN_LOG_CATEGORIES
|
|
|
|
@classmethod
|
|
def known_log_levels(cls):
|
|
return cls._KNOWN_LOG_LEVELS
|
|
|
|
# private methods
|
|
|
|
def _set_net(self, info):
|
|
if info['mainnet']:
|
|
self._net = NET_MAIN
|
|
if info['testnet']:
|
|
self._net = NET_TEST
|
|
if info['stagenet']:
|
|
self._net = NET_STAGE
|
|
|
|
def _set_restricted(self):
|
|
try:
|
|
self.sync_info()
|
|
|
|
self._restricted = False
|
|
except MethodNotFound:
|
|
self._restricted = True
|
|
|
|
def _do_get_transactions(self, hashes, prune):
|
|
res = self.raw_request('/get_transactions', {
|
|
'txs_hashes': hashes,
|
|
'decode_as_json': True,
|
|
'prune': prune})
|
|
if res['status'] != 'OK':
|
|
raise exceptions.BackendException(res['status'])
|
|
txs = []
|
|
for tx in res.get('txs', []):
|
|
as_json = json.loads(tx['as_json'])
|
|
fee = as_json.get('rct_signatures', {}).get('txnFee')
|
|
txs.append(Transaction(
|
|
hash=tx['tx_hash'],
|
|
fee=from_atomic(fee) if fee else None,
|
|
height=None if tx['in_pool'] else tx['block_height'],
|
|
timestamp=datetime.fromtimestamp(
|
|
tx['block_timestamp']) if 'block_timestamp' in tx else None,
|
|
blob=binascii.unhexlify(tx['as_hex']) or None,
|
|
json=as_json))
|
|
|
|
return txs
|
|
|
|
def _is_valid_256_hex(self, hexhash):
|
|
try:
|
|
bytearray.fromhex(hexhash)
|
|
|
|
return len(hexhash) == 64
|
|
except ValueError:
|
|
return False
|
|
|
|
def _validate_hashlist(self, hashes):
|
|
if not hashes:
|
|
return []
|
|
else:
|
|
if isinstance(hashes, str):
|
|
hashes = [hashes]
|
|
else:
|
|
coerce_compatible_str = lambda s: six.ensure_text(str(s))
|
|
hashes = list(map(coerce_compatible_str, hashes))
|
|
|
|
not_valid = lambda h: not self._is_valid_256_hex(h)
|
|
|
|
if any(map(not_valid, hashes)):
|
|
raise ValueError('hashes contains an invalid hash')
|
|
|
|
return hashes
|