monero/utils/python-rpc/framework/daemon.py
jeffro256 b0bf49a65a
blockchain_db: add k-anonymity to txid fetching
Read more about k-anonymity [here](https://en.wikipedia.org/wiki/K-anonymity). We implement this feature in the monero daemon for transactions
by providing a "Txid Template", which is simply a txid with all but `num_matching_bits` bits zeroed out, and the number `num_matching_bits`. We add an operation to `BlockchainLMDB` called
`get_txids_loose` which takes a txid template and returns all txids in the database (chain and mempool) that satisfy that template. Thus, a client can
ask about a specific transaction from a daemon without revealing the exact transaction they are inquiring about. The client can control the statistical
chance that other TXIDs (besides the one in question) match the txid template sent to the daemon up to a power of 2. For example, if a client sets their `num_matching_bits`
to 5, then statistically any txid has a 1/(2^5) chance to match. With `num_matching_bits`=10, there is a 1/(2^10) chance, so on and so forth.

Co-authored-by: ACK-J <60232273+ACK-J@users.noreply.github.com>
2023-08-01 17:25:25 -05:00

684 lines
22 KiB
Python

# Copyright (c) 2018-2023, The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Daemon class to make rpc calls and store state."""
from .rpc import JSONRPC
class Daemon(object):
def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0, restricted_rpc = False):
base = 18480 if restricted_rpc else 18180
self.host = host
self.port = port
self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else base+idx))
def getblocktemplate(self, address, prev_block = "", client = ""):
getblocktemplate = {
'method': 'getblocktemplate',
'params': {
'client': client,
'wallet_address': address,
'reserve_size' : 1,
'prev_block' : prev_block,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(getblocktemplate)
get_block_template = getblocktemplate
def get_miner_data(self):
get_miner_data = {
'method': 'get_miner_data',
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_miner_data)
def calc_pow(self, major_version, height, block_blob, seed_hash = ''):
calc_pow = {
'method': 'calc_pow',
'params': {
'major_version': major_version,
'height': height,
'block_blob' : block_blob,
'seed_hash' : seed_hash,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(calc_pow)
def add_aux_pow(self, blocktemplate_blob, aux_pow, client = ""):
add_aux_pow = {
'method': 'add_aux_pow',
'params': {
'blocktemplate_blob': blocktemplate_blob,
'aux_pow' : aux_pow,
'client' : client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(add_aux_pow)
def send_raw_transaction(self, tx_as_hex, do_not_relay = False, do_sanity_checks = True, client = ""):
send_raw_transaction = {
'client': client,
'tx_as_hex': tx_as_hex,
'do_not_relay': do_not_relay,
'do_sanity_checks': do_sanity_checks,
}
return self.rpc.send_request("/send_raw_transaction", send_raw_transaction)
sendrawtransaction = send_raw_transaction
def submitblock(self, block):
submitblock = {
'method': 'submitblock',
'params': [ block ],
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(submitblock)
submit_block = submitblock
def getblock(self, hash = '', height = 0, fill_pow_hash = False, client = ""):
getblock = {
'method': 'getblock',
'params': {
'client': client,
'hash': hash,
'height': height,
'fill_pow_hash': fill_pow_hash,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(getblock)
get_block = getblock
def getlastblockheader(self, client = ""):
getlastblockheader = {
'method': 'getlastblockheader',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(getlastblockheader)
get_last_block_header = getlastblockheader
def getblockheaderbyhash(self, hash = "", hashes = [], client = ""):
getblockheaderbyhash = {
'method': 'getblockheaderbyhash',
'params': {
'client': client,
'hash': hash,
'hashes': hashes,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(getblockheaderbyhash)
get_block_header_by_hash = getblockheaderbyhash
def getblockheaderbyheight(self, height, client = ""):
getblockheaderbyheight = {
'method': 'getblockheaderbyheight',
'params': {
'client': client,
'height': height,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(getblockheaderbyheight)
get_block_header_by_height = getblockheaderbyheight
def getblockheadersrange(self, start_height, end_height, fill_pow_hash = False, client = ""):
getblockheadersrange = {
'method': 'getblockheadersrange',
'params': {
'client': client,
'start_height': start_height,
'end_height': end_height,
'fill_pow_hash': fill_pow_hash,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(getblockheadersrange)
get_block_headers_range = getblockheadersrange
def get_connections(self, client = ""):
get_connections = {
'client': client,
'method': 'get_connections',
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_connections)
def get_info(self, client = ""):
get_info = {
'method': 'get_info',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_info)
getinfo = get_info
def hard_fork_info(self, client = ""):
hard_fork_info = {
'method': 'hard_fork_info',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(hard_fork_info)
def generateblocks(self, address, blocks=1, prev_block = "", starting_nonce = 0):
generateblocks = {
'method': 'generateblocks',
'params': {
'amount_of_blocks' : blocks,
'reserve_size' : 20,
'wallet_address': address,
'prev_block': prev_block,
'starting_nonce': starting_nonce,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(generateblocks)
def get_height(self, client = ""):
get_height = {
'method': 'get_height',
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_request("/get_height", get_height)
getheight = get_height
def pop_blocks(self, nblocks = 1):
pop_blocks = {
'nblocks' : nblocks,
}
return self.rpc.send_request("/pop_blocks", pop_blocks)
def start_mining(self, miner_address, threads_count = 0, do_background_mining = False, ignore_battery = False):
start_mining = {
'miner_address' : miner_address,
'threads_count' : threads_count,
'do_background_mining' : do_background_mining,
'ignore_battery' : ignore_battery,
}
return self.rpc.send_request('/start_mining', start_mining)
def stop_mining(self):
stop_mining = {
}
return self.rpc.send_request('/stop_mining', stop_mining)
def mining_status(self):
mining_status = {
}
return self.rpc.send_request('/mining_status', mining_status)
def get_transaction_pool(self, client = ""):
get_transaction_pool = {
'client': client,
}
return self.rpc.send_request('/get_transaction_pool', get_transaction_pool)
def get_transaction_pool_hashes(self, client = ""):
get_transaction_pool_hashes = {
'client': client,
}
return self.rpc.send_request('/get_transaction_pool_hashes', get_transaction_pool_hashes)
def get_transaction_pool_stats(self, client = ""):
get_transaction_pool_stats = {
'client': client,
}
return self.rpc.send_request('/get_transaction_pool_stats', get_transaction_pool_stats)
def flush_txpool(self, txids = []):
flush_txpool = {
'method': 'flush_txpool',
'params': {
'txids': txids
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(flush_txpool)
def get_version(self):
get_version = {
'method': 'get_version',
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_version)
def get_bans(self):
get_bans = {
'method': 'get_bans',
'params': {
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_bans)
def set_bans(self, bans = []):
set_bans = {
'method': 'set_bans',
'params': {
'bans': bans
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(set_bans)
def banned(self, address = ''):
banned = {
'method': 'banned',
'params': {
'address': address
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(banned)
def set_bootstrap_daemon(self, address, username = '', password = ''):
set_bootstrap_daemon = {
'address': address,
'username': username,
'password': password,
}
return self.rpc.send_request('/set_bootstrap_daemon', set_bootstrap_daemon)
def get_public_nodes(self, gray = False, white = True):
get_public_nodes = {
'gray': gray,
'white': white,
}
return self.rpc.send_request('/get_public_nodes', get_public_nodes)
def get_transactions(self, txs_hashes = [], decode_as_json = False, prune = False, split = False, client = ""):
get_transactions = {
'client': client,
'txs_hashes': txs_hashes,
'decode_as_json': decode_as_json,
'prune': prune,
'split': split,
}
return self.rpc.send_request('/get_transactions', get_transactions)
gettransactions = get_transactions
def get_outs(self, outputs = [], get_txid = False, client = ""):
get_outs = {
'client': client,
'outputs': outputs,
'get_txid': get_txid,
}
return self.rpc.send_request('/get_outs', get_outs)
def get_coinbase_tx_sum(self, height, count, client = ""):
get_coinbase_tx_sum = {
'method': 'get_coinbase_tx_sum',
'params': {
'client': client,
'height': height,
'count': count,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_coinbase_tx_sum)
def get_output_distribution(self, amounts = [], from_height = 0, to_height = 0, cumulative = False, binary = False, compress = False, client = ""):
get_output_distribution = {
'method': 'get_output_distribution',
'params': {
'client': client,
'amounts': amounts,
'from_height': from_height,
'to_height': to_height,
'cumulative': cumulative,
'binary': binary,
'compress': compress,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_output_distribution)
def get_output_histogram(self, amounts = [], min_count = 0, max_count = 0, unlocked = False, recent_cutoff = 0, client = ""):
get_output_histogram = {
'method': 'get_output_histogram',
'params': {
'client': client,
'amounts': amounts,
'min_count': min_count,
'max_count': max_count,
'unlocked': unlocked,
'recent_cutoff': recent_cutoff,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_output_histogram)
def set_log_level(self, level):
set_log_level = {
'level': level,
}
return self.rpc.send_request('/set_log_level', set_log_level)
def set_log_categories(self, categories = ''):
set_log_categories = {
'categories': categories,
}
return self.rpc.send_request('/set_log_categories', set_log_categories)
def get_alt_blocks_hashes(self, client = ""):
get_alt_blocks_hashes = {
'client': client,
}
return self.rpc.send_request('/get_alt_blocks_hashes', get_alt_blocks_hashes)
def get_alternate_chains(self, client = ""):
get_alternate_chains = {
'method': 'get_alternate_chains',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_alternate_chains)
def get_fee_estimate(self, grace_blocks = 0):
get_fee_estimate = {
'method': 'get_fee_estimate',
'params': {
'grace_blocks': grace_blocks,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_fee_estimate)
def is_key_image_spent(self, key_images = [], client = ""):
is_key_image_spent = {
'key_images': key_images,
'client': client,
}
return self.rpc.send_request('/is_key_image_spent', is_key_image_spent)
def save_bc(self):
save_bc = {
}
return self.rpc.send_request('/save_bc', save_bc)
def get_peer_list(self):
get_peer_list = {
}
return self.rpc.send_request('/get_peer_list', get_peer_list)
def set_log_hash_rate(self, visible):
set_log_hash_rate = {
'visible': visible,
}
return self.rpc.send_request('/set_log_hash_rate', set_log_hash_rate)
def stop_daemon(self):
stop_daemon = {
}
return self.rpc.send_request('/stop_daemon', stop_daemon)
def get_net_stats(self):
get_net_stats = {
}
return self.rpc.send_request('/get_net_stats', get_net_stats)
def get_limit(self):
get_limit = {
}
return self.rpc.send_request('/get_limit', get_limit)
def set_limit(self, limit_down, limit_up):
set_limit = {
'limit_down': limit_down,
'limit_up': limit_up,
}
return self.rpc.send_request('/set_limit', set_limit)
def out_peers(self, out_peers):
out_peers = {
'out_peers': out_peers,
}
return self.rpc.send_request('/out_peers', out_peers)
def in_peers(self, in_peers):
in_peers = {
'in_peers': in_peers,
}
return self.rpc.send_request('/in_peers', in_peers)
def update(self, command, path = None):
update = {
'command': command,
'path': path,
}
return self.rpc.send_request('/update', update)
def get_block_count(self):
get_block_count = {
'method': 'get_block_count',
'params': {
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_block_count)
getblockcount = get_block_count
def get_block_hash(self, height):
get_block_hash = {
'method': 'get_block_hash',
'params': [height],
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_block_hash)
on_get_block_hash = get_block_hash
on_getblockhash = get_block_hash
def relay_tx(self, txids = [], client = ""):
relay_tx = {
'method': 'relay_tx',
'params': {
'txids': txids,
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(relay_tx)
def sync_info(self, client = ""):
sync_info = {
'method': 'sync_info',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(sync_info)
def get_txpool_backlog(self, client = ""):
get_txpool_backlog = {
'method': 'get_txpool_backlog',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_txpool_backlog)
def prune_blockchain(self, check = False):
prune_blockchain = {
'method': 'prune_blockchain',
'params': {
'check': check,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(prune_blockchain)
def flush_cache(self, bad_txs = False):
flush_cache = {
'method': 'flush_cache',
'params': {
'bad_txs': bad_txs,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(flush_cache)
def get_txids_loose(self, txid_template, num_matching_bits):
get_txids_loose = {
'method': 'get_txids_loose',
'params': {
'txid_template': txid_template,
'num_matching_bits': num_matching_bits
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_txids_loose)
def sync_txpool(self):
sync_txpool = {
'method': 'sync_txpool',
'params': {
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(sync_txpool)
def rpc_access_info(self, client):
rpc_access_info = {
'method': 'rpc_access_info',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(rpc_access_info)
def rpc_access_submit_nonce(self, client, nonce, cookie):
rpc_access_submit_nonce = {
'method': 'rpc_access_submit_nonce',
'params': {
'client': client,
'nonce': nonce,
'cookie': cookie,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(rpc_access_submit_nonce)
def rpc_access_pay(self, client, paying_for, payment):
rpc_access_pay = {
'method': 'rpc_access_pay',
'params': {
'client': client,
'paying_for': paying_for,
'payment': payment,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(rpc_access_pay)
def rpc_access_tracking(self, clear = False):
rpc_access_tracking = {
'method': 'rpc_access_tracking',
'params': {
'clear': clear,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(rpc_access_tracking)
def rpc_access_data(self):
rpc_access_data = {
'method': 'rpc_access_data',
'params': {
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(rpc_access_data)
def rpc_access_account(self, client, delta_balance = 0):
rpc_access_account = {
'method': 'rpc_access_account',
'params': {
'client': client,
'delta_balance': delta_balance,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(rpc_access_account)