class DashdInterface(WndUtils): def __init__(self, config, window, connection=None, on_connection_begin_callback=None, on_connection_try_fail_callback=None, on_connection_finished_callback=None): WndUtils.__init__(self, app_path=config.app_path) assert isinstance(config, AppConfig) self.config = config # conn configurations are used from the first item in the list; if one fails, then next is taken if connection: # this parameter is used for testing specific connection self.connections = [connection] else: # get connection list orderd by priority of use self.connections = self.config.get_ordered_conn_list() self.cur_conn_index = 0 if self.connections: self.cur_conn_def = self.connections[self.cur_conn_index] else: self.cur_conn_def = None # below is the connection with which particular RPC call has started; if connection is switched because of # problems with some nodes, switching stops if we close round and return to the starting connection self.starting_conn = None self.ssh = None self.window = window self.active = False self.rpc_url = None self.proxy = None self.http_conn = None # HTTPConnection object passed to the AuthServiceProxy (for convinient connection reset) self.on_connection_begin_callback = on_connection_begin_callback self.on_connection_try_fail_callback = on_connection_try_fail_callback self.on_connection_finished_callback = on_connection_finished_callback self.last_error_message = None def apply_new_cfg(self): """ Called after any of connection config changed. """ # get connection list orderd by priority of use self.disconnect() self.connections = self.config.get_ordered_conn_list() self.cur_conn_index = 0 if not len(self.connections): raise Exception( 'There is no connections to Dash network enabled in the configuration.' ) self.cur_conn_def = self.connections[self.cur_conn_index] def disconnect(self): if self.active: if self.ssh: self.ssh.disconnect() del self.ssh self.ssh = None self.active = False def mark_call_begin(self): self.starting_conn = self.cur_conn_def def switch_to_next_config(self): """ If there is another dashd config not used recently, switch to it. Called only when there was a problem with current connection config. :return: True if successfully switched ot False if there was no another config """ if self.cur_conn_def: self.config.conn_cfg_failure( self.cur_conn_def) # mark connection as defective if self.cur_conn_index < len(self.connections) - 1: idx = self.cur_conn_index + 1 else: idx = 0 conn = self.connections[idx] if conn != self.starting_conn: self.disconnect() self.cur_conn_index = idx self.cur_conn_def = conn if not self.open(): return self.switch_to_next_config() else: return True else: return False def mark_cur_conn_cfg_is_ok(self): if self.cur_conn_def: self.config.conn_cfg_success(self.cur_conn_def) def open(self): """ Opens connection to dash RPC. If it fails, then the next enabled conn config will be used, if any exists. :return: True if successfully connected, False if user cancelled the operation. If all of the attempts fail, then appropriate exception will be raised. """ try: if not self.cur_conn_def: raise Exception( 'There is no connections to Dash network enabled in the configuration.' ) while True: try: if self.open_internal(): break else: if not self.switch_to_next_config(): return False except UserCancelledConnection: return False except (socket.gaierror, ConnectionRefusedError, TimeoutError, socket.timeout) as e: # exceptions raised by not likely functioning dashd node; try to switch to another node # if there is any in the config if not self.switch_to_next_config(): raise e # couldn't use another conn config, raise exception else: break except Exception as e: self.last_error_message = str(e) raise return True def open_internal(self): """ Try to establish connection to dash RPC daemon for current connection config. :return: True, if connection successfully establishes, False if user Cancels the operation (not always cancelling will be possible - only when user is prompted for a password). """ if not self.active: if self.cur_conn_def.use_ssh_tunnel: # RPC over SSH while True: self.ssh = DashdSSH( self.cur_conn_def.ssh_conn_cfg.host, self.cur_conn_def.ssh_conn_cfg.port, self.cur_conn_def.ssh_conn_cfg.username) try: logging.info('starting ssh.connect') self.ssh.connect() logging.info('finished ssh.connect') break except Exception as e: logging.error('error in ssh.connect') raise # configure SSH tunnel # get random local unprivileged port number to establish SSH tunnel success = False local_port = None for try_nr in range(1, 10): try: logging.info('beginning ssh.open_tunnel') local_port = randint(2000, 50000) self.ssh.open_tunnel(local_port, self.cur_conn_def.host, int(self.cur_conn_def.port)) success = True break except Exception as e: logging.error('error in ssh.open_tunnel loop') pass logging.info('finished ssh.open_tunnel loop') if not success: logging.error('finished ssh.open_tunnel loop with error') return False else: rpc_user = self.cur_conn_def.username rpc_password = self.cur_conn_def.password rpc_host = '127.0.0.1' # SSH tunnel on loopback rpc_port = local_port else: # direct RPC rpc_host = self.cur_conn_def.host rpc_port = self.cur_conn_def.port rpc_user = self.cur_conn_def.username rpc_password = self.cur_conn_def.password if self.cur_conn_def.use_ssl: self.rpc_url = 'https://' self.http_conn = httplib.HTTPSConnection( rpc_host, rpc_port, timeout=5, context=ssl._create_unverified_context()) else: self.rpc_url = 'http://' self.http_conn = httplib.HTTPConnection(rpc_host, rpc_port, timeout=5) logging.info('AuthServiceProxy begin') self.rpc_url += rpc_user + ':' + rpc_password + '@' + rpc_host + ':' + str( rpc_port) self.proxy = AuthServiceProxy(self.rpc_url, timeout=1000, connection=self.http_conn) logging.info('AuthServiceProxy end') try: if self.on_connection_begin_callback: try: # make the owner know, we are connecting logging.info('on_connection_begin_callback begin') self.on_connection_begin_callback() logging.info('on_connection_begin_callback end') except: pass # check the connection logging.info('starting http_conn.connect()') self.http_conn.connect() logging.info('finished http_conn.connect()') if self.on_connection_finished_callback: try: # make the owner know, we successfully finished connection self.on_connection_finished_callback() except: pass except: if self.on_connection_try_fail_callback: try: # make the owner know, connection attempt failed self.on_connection_try_fail_callback() except: pass raise finally: logging.info('http_conn.close()') self.http_conn.close() # timeout hase been initially set to 5 seconds to perform 'quick' connection test self.http_conn.timeout = 20 self.active = True return self.active def get_active_conn_description(self): if self.cur_conn_def: return self.cur_conn_def.get_description() else: return '???' @control_rpc_call def getblockcount(self): if self.open(): return self.proxy.getblockcount() else: raise Exception('Not connected') @control_rpc_call def getblockhash(self, block): if self.open(): return self.proxy.getblockhash(block) else: raise Exception('Not connected') @control_rpc_call def getinfo(self): if self.open(): return self.proxy.getinfo() else: raise Exception('Not connected') @control_rpc_call def issynchronized(self): if self.open(): # if connecting to HTTP(S) proxy do not check if dash daemon is synchronized if self.cur_conn_def.is_http_proxy(): return True else: syn = self.proxy.mnsync('status') return syn.get('IsSynced') else: raise Exception('Not connected') @control_rpc_call def mnsync(self): if self.open(): # if connecting to HTTP(S) proxy do not call this function - it will not be exposed if self.cur_conn_def.is_http_proxy(): return {} else: return self.proxy.mnsync('status') else: raise Exception('Not connected') @control_rpc_call def masternodebroadcast(self, what, hexto): if self.open(): return self.proxy.masternodebroadcast(what, hexto) else: raise Exception('Not connected') @control_rpc_call def get_masternodelist(self, *args): if self.open(): return self.proxy.masternodelist(*args) else: raise Exception('Not connected') @control_rpc_call def get_masternodeaddr(self): if self.open(): return self.proxy.masternodelist('addr') else: raise Exception('Not connected') @control_rpc_call def getaddressbalance(self, address): if self.open(): return self.proxy.getaddressbalance({ 'addresses': [address] }).get('balance') else: raise Exception('Not connected') @control_rpc_call def getaddressutxos(self, addresses): if self.open(): return self.proxy.getaddressutxos({'addresses': addresses}) else: raise Exception('Not connected') @control_rpc_call def getrawtransaction(self, txid, verbose): if self.open(): return self.proxy.getrawtransaction(txid, verbose) else: raise Exception('Not connected') @control_rpc_call def getblockhash(self, blockid): if self.open(): return self.proxy.getblockhash(blockid) else: raise Exception('Not connected') @control_rpc_call def getblockheader(self, blockhash): if self.open(): return self.proxy.getblockheader(blockhash) else: raise Exception('Not connected') @control_rpc_call def validateaddress(self, address): if self.open(): return self.proxy.validateaddress(address) else: raise Exception('Not connected') @control_rpc_call def decoderawtransaction(self, tx): if self.open(): return self.proxy.decoderawtransaction(tx) else: raise Exception('Not connected') @control_rpc_call def sendrawtransaction(self, tx): if self.open(): return self.proxy.sendrawtransaction(tx) else: raise Exception('Not connected')
class chainxdSSH(object): def __init__(self, host, port, username): self.host = host self.port = port self.username = username self.ssh = None self.channel = None self.fw_channel = None self.connected = False self.ssh_thread = None def __del__(self): self.disconnect() def remote_command(self, cmd): channel = None try: channel = self.ssh.get_transport().open_session() channel.exec_command(cmd) ret_code = channel.recv_exit_status() if ret_code == 0: for idx in range(1, 20): if channel.recv_ready(): break time.sleep(0.1) if not channel.recv_ready(): raise Exception('Data not ready') data = channel.recv(500) return data.decode().split('\n') else: for idx in range(1, 20): if channel.recv_stderr_ready(): break time.sleep(0.1) if channel.recv_stderr_ready(): data = channel.recv_stderr(500) error = data.decode() raise Exception(error) else: raise UnknownError('Unknown error executing remote command: ' + cmd) finally: if channel: channel.close() def connect(self): import paramiko self.ssh = paramiko.SSHClient() self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) password = None pass_message = None while True: try: self.ssh.connect(self.host, port=int(self.port), username=self.username, password=password) self.connected = True if password: SshPassCache.save_password(self.username, self.host, password) break except PasswordRequiredException as e: # private key with password protection is used; ask user for password pass_message = "Enter passphrase for <b>private key</b> or password for %s" % \ (self.username + '@' + self.host) while True: password = SshPassCache.get_password(self.username, self.host, message=pass_message) if password: break chain_port = self.remote_port except AuthenticationException as e: # This exception will be raised in the following cases: # 1. a private key with password protectection is used but the user enters incorrect password # 2. a private key exists but user's public key is not added to the server's allowed keys # 3. normal login to server is performed but the user enters bad password # So, in the first case, the second query for password will ask for normal password to server, not # for a private key. WndUtils.errorMsg(message='Incorrect password, try again...') while True: password = SshPassCache.get_password(self.username, self.host, message=pass_message) if password: break except SSHException as e: if e.args and e.args[0] == 'No authentication methods available': while True: password = SshPassCache.get_password(self.username, self.host) if password: break else: raise except Exception as e: raise def open_tunnel(self, local_port, remote_ip, remote_port): if self.connected: ready_event = threading.Event() self.ssh_thread = SSHTunnelThread(local_port, remote_ip, remote_port, self.ssh.get_transport(), ready_event) self.ssh_thread.start() ready_event.wait(10) print('Started local port forwarding 127.0.0.1:%s -> %s:%s' % (str(local_port), remote_ip, str(remote_port))) else: raise Exception('SSH not connected') self.config = config # conn configurations are used from the first item in the list; if one fails, then next is taken if connection: # this parameter is used for testing specific connection self.connections = [connection] else: # get connection list orderd by priority of use self.connections = self.config.get_ordered_conn_list() self.cur_conn_index = 0 if self.connections: self.cur_conn_def = self.connections[self.cur_conn_index] else: self.cur_conn_def = None # below is the connection with which particular RPC call has started; if connection is switched because of # problems with some nodes, switching stops if we close round and return to the starting connection self.starting_conn = None self.ssh = None self.window = window self.active = False self.rpc_url = None self.proxy = None self.http_conn = None # HTTPConnection object passed to the AuthServiceProxy (for convinient connection reset) self.on_connection_begin_callback = on_connection_begin_callback self.on_connection_try_fail_callback = on_connection_try_fail_callback self.on_connection_finished_callback = on_connection_finished_callback self.last_error_message = None def apply_new_cfg(self): """ Called after any of connection config changed. """ # get connection list orderd by priority of use self.disconnect() self.connections = self.config.get_ordered_conn_list() self.cur_conn_index = 0 if not len(self.connections): raise Exception('There is no connections to chainx network enabled in the configuration.') self.cur_conn_def = self.connections[self.cur_conn_index] def disconnect(self): if self.active: if self.ssh: self.ssh.disconnect() del self.ssh self.ssh = None self.active = False def mark_call_begin(self): self.starting_conn = self.cur_conn_def def switch_to_next_config(self): """ If there is another chainxd config not used recently, switch to it. Called only when there was a problem with current connection config. :return: True if successfully switched ot False if there was no another config """ if self.cur_conn_def: self.config.conn_cfg_failure(self.cur_conn_def) # mark connection as defective if self.cur_conn_index < len(self.connections)-1: idx = self.cur_conn_index + 1 else: idx = 0 conn = self.connections[idx] if conn != self.starting_conn: self.disconnect() self.cur_conn_index = idx self.cur_conn_def = conn if not self.open(): return self.switch_to_next_config() else: return True else: return False def mark_cur_conn_cfg_is_ok(self): if self.cur_conn_def: self.config.conn_cfg_success(self.cur_conn_def) def open(self): """ Opens connection to chainx RPC. If it fails, then the next enabled conn config will be used, if any exists. :return: True if successfully connected, False if user cancelled the operation. If all of the attempts fail, then appropriate exception will be raised. """ try: if not self.cur_conn_def: raise Exception('There is no connections to chainx network enabled in the configuration.') while True: try: if self.open_internal(): break else: if not self.switch_to_next_config(): return False except UserCancelledConnection: return False except (socket.gaierror, ConnectionRefusedError, TimeoutError, socket.timeout) as e: # exceptions raised by not likely functioning chainxd node; try to switch to another node # if there is any in the config if not self.switch_to_next_config(): raise e # couldn't use another conn config, raise exception else: break except Exception as e: self.last_error_message = str(e) raise return True def open_internal(self): """ Try to establish connection to chainxd RPC daemon for current connection config. :return: True, if connection successfully establishes, False if user Cancels the operation (not always cancelling will be possible - only when user is prompted for a password). """ if not self.active: if self.cur_conn_def.use_ssh_tunnel: # RPC over SSH while True: self.ssh = ChainxdSSH(self.cur_conn_def.ssh_conn_cfg.host, self.cur_conn_def.ssh_conn_cfg.port, self.cur_conn_def.ssh_conn_cfg.username) try: logging.info('starting ssh.connect') self.ssh.connect() logging.info('finished ssh.connect') break except Exception as e: logging.error('error in ssh.connect') raise # configure SSH tunnel # get random local unprivileged port number to establish SSH tunnel success = False local_port = None for try_nr in range(1, 10): try: logging.info('beginning ssh.open_tunnel') local_port = randint(2000, 50000) self.ssh.open_tunnel(local_port, self.cur_conn_def.host, int(self.cur_conn_def.port)) success = True break except Exception as e: logging.error('error in ssh.open_tunnel loop') pass logging.info('finished ssh.open_tunnel loop') if not success: logging.error('finished ssh.open_tunnel loop with error') return False else: rpc_user = self.cur_conn_def.username rpc_password = self.cur_conn_def.password rpc_host = '127.0.0.1' # SSH tunnel on loopback rpc_port = local_port else: # direct RPC rpc_host = self.cur_conn_def.host rpc_port = self.cur_conn_def.port rpc_user = self.cur_conn_def.username rpc_password = self.cur_conn_def.password if self.cur_conn_def.use_ssl: self.rpc_url = 'https://' self.http_conn = httplib.HTTPSConnection(rpc_host, rpc_port, timeout=5, context=ssl._create_unverified_context()) else: self.rpc_url = 'http://' self.http_conn = httplib.HTTPConnection(rpc_host, rpc_port, timeout=5) logging.info('AuthServiceProxy begin') self.rpc_url += rpc_user + ':' + rpc_password + '@' + rpc_host + ':' + str(rpc_port) self.proxy = AuthServiceProxy(self.rpc_url, timeout=1000, connection=self.http_conn) logging.info('AuthServiceProxy end') try: if self.on_connection_begin_callback: try: # make the owner know, we are connecting logging.info('on_connection_begin_callback begin') self.on_connection_begin_callback() logging.info('on_connection_begin_callback end') except: pass # check the connection logging.info('starting http_conn.connect()') self.http_conn.connect() logging.info('finished http_conn.connect()') if self.on_connection_finished_callback: try: # make the owner know, we successfully finished connection self.on_connection_finished_callback() except: pass except: if self.on_connection_try_fail_callback: try: # make the owner know, connection attempt failed self.on_connection_try_fail_callback() except: pass raise finally: logging.info('http_conn.close()') self.http_conn.close() # timeout hase been initially set to 5 seconds to perform 'quick' connection test self.http_conn.timeout = 20 self.active = True return self.active def get_active_conn_description(self): if self.cur_conn_def: return self.cur_conn_def.get_description() else: return '???' @control_rpc_call def getblockcount(self): if self.open(): return self.proxy.getblockcount() else: raise Exception('Not connected') @control_rpc_call def getblockhash(self, block): if self.open(): return self.proxy.getblockhash(block) else: raise Exception('Not connected') @control_rpc_call def getinfo(self): if self.open(): return self.proxy.getinfo() else: raise Exception('Not connected') @control_rpc_call def issynchronized(self): if self.open(): # if connecting to HTTP(S) proxy do not check if chainxd daemon is synchronized if self.cur_conn_def.is_http_proxy(): return True else: syn = self.proxy.mnsync('status') return syn.get('IsSynced') else: raise Exception('Not connected') @control_rpc_call def mnsync(self): if self.open(): # if connecting to HTTP(S) proxy do not call this function - it will not be exposed if self.cur_conn_def.is_http_proxy(): return {} else: return self.proxy.mnsync('status') else: raise Exception('Not connected') @control_rpc_call def masternodebroadcast(self, what, hexto): if self.open(): return self.proxy.masternodebroadcast(what, hexto) else: raise Exception('Not connected') @control_rpc_call def get_masternodelist(self, *args): if self.open(): return self.proxy.masternodelist(*args) else: raise Exception('Not connected') @control_rpc_call def get_masternodeaddr(self): if self.open(): return self.proxy.masternodelist('addr') else: raise Exception('Not connected') @control_rpc_call def getaddressbalance(self, address): if self.open(): return self.proxy.getaddressbalance({'addresses': [address]}).get('balance') else: raise Exception('Not connected') @control_rpc_call def getaddressutxos(self, addresses): if self.open(): return self.proxy.getaddressutxos({'addresses': addresses}) else: raise Exception('Not connected') @control_rpc_call def getrawtransaction(self, txid, verbose): if self.open(): return self.proxy.getrawtransaction(txid, verbose) else: raise Exception('Not connected') @control_rpc_call def getblockhash(self, blockid): if self.open(): return self.proxy.getblockhash(blockid) else: raise Exception('Not connected') @control_rpc_call def getblockheader(self, blockhash): if self.open(): return self.proxy.getblockheader(blockhash) else: raise Exception('Not connected') @control_rpc_call def validateaddress(self, address): if self.open(): return self.proxy.validateaddress(address) else: raise Exception('Not connected') @control_rpc_call def decoderawtransaction(self, tx): if self.open(): return self.proxy.decoderawtransaction(tx) else: raise Exception('Not connected') @control_rpc_call def sendrawtransaction(self, tx): if self.open(): return self.proxy.sendrawtransaction(tx) else: raise Exception('Not connected')
class RpcClient: def __init__(self): self.rpc_ip, self.rpc_port, self.rpc_user, self.rpc_passwd = readRPCfile() rpc_url = "http://%s:%s@%s:%d" % (self.rpc_user, self.rpc_passwd, self.rpc_ip, self.rpc_port) try: self.conn = AuthServiceProxy(rpc_url, timeout=8) except JSONRPCException as e: err_msg = 'remote or local PIVX-cli running?' printException(getCallerName(), getFunctionName(), err_msg, e) except Exception as e: err_msg = 'remote or local PIVX-cli running?' printException(getCallerName(), getFunctionName(), err_msg, e) def decodeRawTransaction(self, rawTx): try: return self.conn.decoderawtransaction(rawTx) except Exception as e: err_msg = 'error in decodeRawTransaction' printException(getCallerName(), getFunctionName(), err_msg, e.args) def getAddressUtxos(self, addresses): try: return self.conn.getaddressutxos({'addresses': addresses}) except Exception as e: err_msg = "error in getAddressUtxos" if str(e.args[0]) != "Request-sent": printException(getCallerName(), getFunctionName(), err_msg, e.args) else: printException(getCallerName(), getFunctionName(), err_msg, e.args) raise e def getBlockCount(self): try: n = self.conn.getblockcount() return n except Exception as e: err_msg = 'remote or local PIVX-cli running?' if str(e.args[0]) != "Request-sent": printException(getCallerName(), getFunctionName(), err_msg, e.args) else: printException(getCallerName(), getFunctionName(), err_msg, e.args) def getBlockHash(self, blockNum): try: h = self.conn.getblockhash(blockNum) return h except Exception as e: err_msg = 'remote or local PIVX-cli running?' if str(e.args[0]) != "Request-sent": printException(getCallerName(), getFunctionName(), err_msg, e.args) else: printException(getCallerName(), getFunctionName(), err_msg, e.args) def getFeePerKb(self): try: # get transaction data from last 10 blocks feePerKb = float(self.conn.getfeeinfo(10)['feeperkb']) return (feePerKb if feePerKb > MINIMUM_FEE else MINIMUM_FEE) except Exception as e: err_msg = 'error in getFeePerKb' if str(e.args[0]) != "Request-sent": printException(getCallerName(), getFunctionName(), err_msg, e.args) else: printException(getCallerName(), getFunctionName(), err_msg, e.args) def getMNStatus(self, address): try: mnStatusList = self.conn.listmasternodes(address) if not mnStatusList: return None mnStatus = mnStatusList[0] mnStatus['mnCount'] = self.conn.getmasternodecount()['enabled'] return mnStatus except Exception as e: err_msg = "error in getMNStatus" if str(e.args[0]) != "Request-sent": printException(getCallerName(), getFunctionName(), err_msg, e.args) else: printException(getCallerName(), getFunctionName(), err_msg, e.args) def getProtocolVersion(self): try: prot_version = self.conn.getinfo().get('protocolversion') return int(prot_version) except Exception as e: err_msg = 'error in getProtocolVersion' printException(getCallerName(), getFunctionName(), err_msg, e.args) return DEFAULT_PROTOCOL_VERSION def getRawTransaction(self, txid): try: return self.conn.getrawtransaction(txid) except Exception as e: err_msg = "is Blockchain synced?" if str(e.args[0]) != "Request-sent": printException(getCallerName(), getFunctionName(), err_msg, e.args) return None def getStatus(self): status = False n = 0 try: n = self.conn.getblockcount() if n > 0: status = True except Exception as e: # If loading block index set lastBlock=1 if str(e.args[0]) == "Loading block index..." or str(e.args[0]) == "Verifying blocks...": printDbg(str(e.args[0])) n = 1 #else: #err_msg = "Error while contacting RPC server" #printException(getCallerName(), getFunctionName(), err_msg, e.args) return status, n def getStatusMess(self, status=None): if status == None: status = self.getStatus() if status: return "RPC status: CONNECTED!!!" else: return "RPC status: NOT CONNECTED. remote or local PIVX-cli running?" def isBlockchainSynced(self): try: return self.conn.mnsync('status').get("IsBlockchainSynced") except Exception as e: err_msg = "error in isBlockchainSynced" printException(getCallerName(), getFunctionName(), err_msg, e.args) return False def decodemasternodebroadcast(self, work): try: return self.conn.decodemasternodebroadcast(work.strip()) except Exception as e: err_msg = "error in decodemasternodebroadcast" printException(getCallerName(), getFunctionName(), err_msg, e.args) return "" def relaymasternodebroadcast(self, work): try: return self.conn.relaymasternodebroadcast(work.strip()) except Exception as e: err_msg = "error in relaymasternodebroadcast" printException(getCallerName(), getFunctionName(), err_msg, e.args) return "" def sendRawTransaction(self, tx_hex): try: tx_id = self.conn.sendrawtransaction(tx_hex) return tx_id except Exception as e: err_msg = 'error in rpcClient.sendRawTransaction' if str(e.args[0]) != "Request-sent": printException(getCallerName(), getFunctionName(), err_msg, e.args) else: printException(getCallerName(), getFunctionName(), err_msg, e.args) def verifyMessage(self, pivxaddress, signature, message): try: return self.conn.verifymessage(pivxaddress, signature, message) except Exception as e: err_msg = "error in verifyMessage" printException(getCallerName(), getFunctionName(), err_msg, e.args)
class RpcClient: def __init__(self): # Lock for threads self.lock = threading.Lock() self.rpc_ip, self.rpc_port, self.rpc_user, self.rpc_passwd = readRPCfile( ) rpc_url = "http://%s:%s@%s:%d" % (self.rpc_user, self.rpc_passwd, self.rpc_ip, self.rpc_port) try: self.lock.acquire() self.conn = AuthServiceProxy(rpc_url, timeout=120) except JSONRPCException as e: err_msg = 'remote or local PIVX-cli running?' printException(getCallerName(), getFunctionName(), err_msg, e) except Exception as e: err_msg = 'remote or local PIVX-cli running?' printException(getCallerName(), getFunctionName(), err_msg, e) finally: self.lock.release() def decodeRawTransaction(self, rawTx): try: self.lock.acquire() res = self.conn.decoderawtransaction(rawTx) except Exception as e: err_msg = 'error in decodeRawTransaction' printException(getCallerName(), getFunctionName(), err_msg, e.args) res = None finally: self.lock.release() return res def getAddressUtxos(self, addresses): try: self.lock.acquire() res = self.conn.getaddressutxos({'addresses': addresses}) except Exception as e: err_msg = "error in getAddressUtxos" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = None finally: self.lock.release() return res def getBlockCount(self): try: self.lock.acquire() n = self.conn.getblockcount() except Exception as e: err_msg = 'remote or local PIVX-cli running?' if str(e.args[0]) != "Request-sent": printException(getCallerName(), getFunctionName(), err_msg, e.args) n = 0 finally: self.lock.release() return n def getBlockHash(self, blockNum): try: self.lock.acquire() h = self.conn.getblockhash(blockNum) except Exception as e: err_msg = 'remote or local PIVX-cli running?' printException(getCallerName(), getFunctionName(), err_msg, e.args) h = None finally: self.lock.release() return h def getBudgetVotes(self, proposal): try: self.lock.acquire() votes = self.conn.getbudgetvotes(proposal) except Exception as e: err_msg = 'remote or local PIVX-cli running?' printException(getCallerName(), getFunctionName(), err_msg, e.args) votes = {} finally: self.lock.release() return votes def getFeePerKb(self): try: self.lock.acquire() # get transaction data from last 200 blocks feePerKb = float(self.conn.getfeeinfo(200)['feeperkb']) res = (feePerKb if feePerKb > MINIMUM_FEE else MINIMUM_FEE) except Exception as e: err_msg = 'error in getFeePerKb' printException(getCallerName(), getFunctionName(), err_msg, e.args) res = MINIMUM_FEE finally: self.lock.release() return res def getMNStatus(self, address): try: self.lock.acquire() mnStatusList = self.conn.listmasternodes(address) if not mnStatusList: return None mnStatus = mnStatusList[0] mnStatus['mnCount'] = self.conn.getmasternodecount()['enabled'] except Exception as e: err_msg = "error in getMNStatus" printException(getCallerName(), getFunctionName(), err_msg, e.args) mnStatus = None finally: self.lock.release() return mnStatus def getMasternodeCount(self): try: self.lock.acquire() ans = self.conn.getmasternodecount() except Exception as e: err_msg = "error in getMasternodeCount" printException(getCallerName(), getFunctionName(), err_msg, e.args) ans = None finally: self.lock.release() return ans def getMasternodes(self): mnList = {} mnList['last_update'] = now() score = [] try: self.lock.acquire() masternodes = self.conn.listmasternodes() except Exception as e: err_msg = "error in getMasternodes" printException(getCallerName(), getFunctionName(), err_msg, e.args) masternodes = [] finally: self.lock.release() for mn in masternodes: if mn.get('status') == 'ENABLED': if mn.get('lastpaid') == 0: mn['score'] = mn.get('activetime') else: lastpaid_ago = now() - mn.get('lastpaid') mn['score'] = min(lastpaid_ago, mn.get('activetime')) else: mn['score'] = 0 score.append(mn) score.sort(key=lambda x: x['score'], reverse=True) for mn in masternodes: mn['queue_pos'] = score.index(mn) mnList['masternodes'] = masternodes return mnList def getNextSuperBlock(self): try: self.lock.acquire() n = self.conn.getnextsuperblock() except Exception as e: err_msg = 'remote or local PIVX-cli running?' if str(e.args[0]) != "Request-sent": printException(getCallerName(), getFunctionName(), err_msg, e.args) n = 0 finally: self.lock.release() return n def getProposals(self): proposals = [] try: self.lock.acquire() data = self.conn.getbudgetinfo() except Exception as e: err_msg = "error getting proposals" printException(getCallerName(), getFunctionName(), err_msg, e.args) data = [] finally: self.lock.release() for p in data: new_proposal = Proposal(p.get('Name'), p.get('URL'), p.get('Hash'), p.get('FeeHash'), p.get('BlockStart'), p.get('BlockEnd'), p.get('TotalPaymentCount'), p.get('RemainingPaymentCount'), p.get('PaymentAddress'), p.get('Yeas'), p.get('Nays'), p.get('Abstains'), float(p.get('TotalPayment')), float(p.get('MonthlyPayment'))) proposals.append(new_proposal) return proposals def getProposalsProjection(self): proposals = [] try: self.lock.acquire() data = self.conn.getbudgetprojection() except Exception as e: err_msg = "error getting proposals projection" printException(getCallerName(), getFunctionName(), err_msg, e.args) data = [] finally: self.lock.release() for p in data: new_proposal = Proposal(p.get('Name'), p.get('URL'), p.get('Hash'), p.get('FeeHash'), p.get('BlockStart'), p.get('BlockEnd'), p.get('TotalPaymentCount'), p.get('RemainingPaymentCount'), p.get('PaymentAddress'), p.get('Yeas'), p.get('Nays'), p.get('Abstains'), p.get('TotalPayment'), p.get('MonthlyPayment')) new_proposal = {} new_proposal['Name'] = p.get('Name') new_proposal['Allotted'] = float(p.get("Alloted")) new_proposal['Votes'] = p.get('Yeas') - p.get('Nays') new_proposal['Total_Allotted'] = float(p.get('TotalBudgetAlloted')) proposals.append(new_proposal) return proposals def getProtocolVersion(self): try: self.lock.acquire() prot_version = self.conn.getinfo().get('protocolversion') res = int(prot_version) except Exception as e: err_msg = 'error in getProtocolVersion' printException(getCallerName(), getFunctionName(), err_msg, e.args) res = DEFAULT_PROTOCOL_VERSION finally: self.lock.release() return res def getRawTransaction(self, txid): try: self.lock.acquire() res = self.conn.getrawtransaction(txid) except Exception as e: err_msg = "is Blockchain synced?" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = None finally: self.lock.release() return res def getStatus(self): status = False statusMess = "Unable to connect to a PIVX RPC server.\n" statusMess += "Either the local PIVX wallet is not open, or the remote RPC server is not responding." n = 0 try: self.lock.acquire() n = self.conn.getblockcount() if n > 0: status = True statusMess = "Connected to PIVX RPC client" except Exception as e: # If loading block index set lastBlock=1 if str(e.args[0]) == "Loading block index..." or str( e.args[0]) == "Verifying blocks...": printDbg(str(e.args[0])) statusMess = "PIVX wallet is connected but still synchronizing / verifying blocks" n = 1 elif str(e.args[0]) != "Request-sent" and str( e.args[0]) != "10061": err_msg = "Error while contacting RPC server" printException(getCallerName(), getFunctionName(), err_msg, e.args) finally: self.lock.release() return status, statusMess, n def isBlockchainSynced(self): try: self.lock.acquire() res = self.conn.mnsync('status').get("IsBlockchainSynced") except Exception as e: if str(e.args[0]) != "Request-sent": err_msg = "error in isBlockchainSynced" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = False finally: self.lock.release() return res def mnBudgetRawVote(self, mn_tx_hash, mn_tx_index, proposal_hash, vote, time, vote_sig): try: self.lock.acquire() res = self.conn.mnbudgetrawvote(mn_tx_hash, mn_tx_index, proposal_hash, vote, time, vote_sig) except Exception as e: err_msg = "error in mnBudgetRawVote" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = None finally: self.lock.release() return res def decodemasternodebroadcast(self, work): try: self.lock.acquire() res = self.conn.decodemasternodebroadcast(work.strip()) except Exception as e: err_msg = "error in decodemasternodebroadcast" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = "" finally: self.lock.release() return res def relaymasternodebroadcast(self, work): try: self.lock.acquire() res = self.conn.relaymasternodebroadcast(work.strip()) except Exception as e: err_msg = "error in relaymasternodebroadcast" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = "" finally: self.lock.release() return res def sendRawTransaction(self, tx_hex, use_swiftx): try: self.lock.acquire() tx_id = self.conn.sendrawtransaction(tx_hex, True, bool(use_swiftx)) except Exception as e: err_msg = 'error in rpcClient.sendRawTransaction' printException(getCallerName(), getFunctionName(), err_msg, e.args) tx_id = None finally: self.lock.release() return tx_id def verifyMessage(self, pivxaddress, signature, message): try: self.lock.acquire() res = self.conn.verifymessage(pivxaddress, signature, message) except Exception as e: err_msg = "error in verifyMessage" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = False finally: self.lock.release() return res
class DashdInterface(WndUtils): def __init__(self, window, on_connection_initiated_callback=None, on_connection_failed_callback=None, on_connection_successful_callback=None, on_connection_disconnected_callback=None): WndUtils.__init__(self, app_config=None) self.config = None self.db_intf = None self.connections = [] self.cur_conn_index = 0 self.cur_conn_def = None # below is the connection with which particular RPC call has started; if connection is switched because of # problems with some nodes, switching stops if we close round and return to the starting connection self.starting_conn = None self.masternodes = [] # cached list of all masternodes (Masternode object) self.masternodes_by_ident = {} self.payment_queue = [] self.ssh = None self.window = window self.active = False self.rpc_url = None self.proxy = None self.http_conn = None # HTTPConnection object passed to the AuthServiceProxy (for convinient connection reset) self.on_connection_initiated_callback = on_connection_initiated_callback self.on_connection_failed_callback = on_connection_failed_callback self.on_connection_successful_callback = on_connection_successful_callback self.on_connection_disconnected_callback = on_connection_disconnected_callback self.last_error_message = None self.http_lock = threading.Lock() def initialize(self, config: AppConfig, connection=None, for_testing_connections_only=False): self.config = config self.app_config = config self.db_intf = self.config.db_intf # conn configurations are used from the first item in the list; if one fails, then next is taken if connection: # this parameter is used for testing specific connection self.connections = [connection] else: # get connection list orderd by priority of use self.connections = self.config.get_ordered_conn_list() self.cur_conn_index = 0 if self.connections: self.cur_conn_def = self.connections[self.cur_conn_index] else: self.cur_conn_def = None if not for_testing_connections_only: self.load_data_from_db_cache() def load_data_from_db_cache(self): self.masternodes.clear() self.masternodes_by_ident.clear() cur = self.db_intf.get_cursor() cur2 = self.db_intf.get_cursor() db_modified = False try: tm_start = time.time() db_correction_duration = 0.0 logging.debug("Reading masternodes' data from DB") cur.execute("SELECT id, ident, status, protocol, payee, last_seen, active_seconds," " last_paid_time, last_paid_block, IP from MASTERNODES where dmt_active=1") for row in cur.fetchall(): db_id = row[0] ident = row[1] # correct duplicated masternodes issue mn_first = self.masternodes_by_ident.get(ident) if mn_first is not None: continue # delete duplicated (caused by breaking the app while loading) tm_start_1 = time.time() cur2.execute('DELETE from MASTERNODES where ident=? and id<>?', (ident, db_id)) if cur2.rowcount > 0: db_modified = True db_correction_duration += (time.time() - tm_start_1) mn = Masternode() mn.db_id = db_id mn.ident = ident mn.status = row[2] mn.protocol = row[3] mn.payee = row[4] mn.lastseen = row[5] mn.activeseconds = row[6] mn.lastpaidtime = row[7] mn.lastpaidblock = row[8] mn.ip = row[9] self.masternodes.append(mn) self.masternodes_by_ident[mn.ident] = mn tm_diff = time.time() - tm_start logging.info('DB read time of %d MASTERNODES: %s s, db fix time: %s' % (len(self.masternodes), str(tm_diff), str(db_correction_duration))) self.update_mn_queue_values() except Exception as e: logging.exception('SQLite initialization error') finally: if db_modified: self.db_intf.commit() self.db_intf.release_cursor() self.db_intf.release_cursor() def reload_configuration(self): """Called after modification of connections' configuration or changes having impact on the file name associated to database cache.""" # get connection list orderd by priority of use self.disconnect() self.connections = self.config.get_ordered_conn_list() self.cur_conn_index = 0 if len(self.connections): self.cur_conn_def = self.connections[self.cur_conn_index] self.load_data_from_db_cache() else: self.cur_conn_def = None def disconnect(self): if self.active: logging.debug('Disconnecting') if self.ssh: self.ssh.disconnect() del self.ssh self.ssh = None self.active = False if self.on_connection_disconnected_callback: self.on_connection_disconnected_callback() def mark_call_begin(self): self.starting_conn = self.cur_conn_def def switch_to_next_config(self): """ If there is another dashd config not used recently, switch to it. Called only when there was a problem with current connection config. :return: True if successfully switched or False if there was no another config """ if self.cur_conn_def: self.config.conn_cfg_failure(self.cur_conn_def) # mark connection as defective if self.cur_conn_index < len(self.connections)-1: idx = self.cur_conn_index + 1 else: idx = 0 conn = self.connections[idx] if conn != self.starting_conn and conn != self.cur_conn_def: logging.debug("Trying to switch to another connection: %s" % conn.get_description()) self.disconnect() self.cur_conn_index = idx self.cur_conn_def = conn if not self.open(): return self.switch_to_next_config() else: return True else: logging.warning('Failed to connect: no another connection configurations.') return False def mark_cur_conn_cfg_is_ok(self): if self.cur_conn_def: self.config.conn_cfg_success(self.cur_conn_def) def open(self): """ Opens connection to dash RPC. If it fails, then the next enabled conn config will be used, if any exists. :return: True if successfully connected, False if user cancelled the operation. If all of the attempts fail, then appropriate exception will be raised. """ try: if not self.cur_conn_def: raise Exception('There is no connections to NIX network enabled in the configuration.') while True: try: if self.open_internal(): break else: if not self.switch_to_next_config(): return False except UserCancelledConnection: return False except (socket.gaierror, ConnectionRefusedError, TimeoutError, socket.timeout, NoValidConnectionsError) as e: # exceptions raised by not likely functioning dashd node; try to switch to another node # if there is any in the config if not self.switch_to_next_config(): raise e # couldn't use another conn config, raise exception else: break except Exception as e: self.last_error_message = str(e) raise return True def reset_connection(self): """ Called when communication errors are detected while sending RPC commands. Here we are closing the SSH-tunnel (if used) and HTTP connection object to prepare for another try. :return: """ if self.active: if self.http_conn: self.http_conn.close() if self.ssh: self.ssh.disconnect() self.active = False def open_internal(self): """ Try to establish connection to dash RPC daemon for current connection config. :return: True, if connection successfully establishes, False if user Cancels the operation (not always cancelling will be possible - only when user is prompted for a password). """ if not self.active: logging.debug("Trying to open connection: %s" % self.cur_conn_def.get_description()) try: # make the owner know, we are connecting if self.on_connection_initiated_callback: self.on_connection_initiated_callback() except: pass if self.cur_conn_def.use_ssh_tunnel: # RPC over SSH if self.ssh is None: self.ssh = DashdSSH(self.cur_conn_def.ssh_conn_cfg.host, self.cur_conn_def.ssh_conn_cfg.port, self.cur_conn_def.ssh_conn_cfg.username) try: logging.debug('starting ssh.connect') self.ssh.connect() logging.debug('finished ssh.connect') except Exception as e: logging.error('error in ssh.connect') try: # make the owner know, connection attempt failed if self.on_connection_failed_callback: self.on_connection_failed_callback() except: logging.exception('on_connection_try_fail_callback call exception') raise # configure SSH tunnel # get random local unprivileged port number to establish SSH tunnel success = False local_port = None for try_nr in range(1, 10): try: logging.debug(f'beginning ssh.open_tunnel, try: {try_nr}') local_port = randint(2000, 50000) self.ssh.open_tunnel(local_port, self.cur_conn_def.host, int(self.cur_conn_def.port)) success = True break except Exception as e: logging.exception('error in ssh.open_tunnel loop: ' + str(e)) logging.debug('finished ssh.open_tunnel loop') if not success: logging.error('finished ssh.open_tunnel loop with error') return False else: rpc_user = self.cur_conn_def.username rpc_password = self.cur_conn_def.password rpc_host = '127.0.0.1' # SSH tunnel on loopback rpc_port = local_port else: # direct RPC rpc_host = self.cur_conn_def.host rpc_port = self.cur_conn_def.port rpc_user = self.cur_conn_def.username rpc_password = self.cur_conn_def.password if self.cur_conn_def.use_ssl: self.rpc_url = 'https://' self.http_conn = httplib.HTTPSConnection(rpc_host, rpc_port, timeout=5, context=ssl._create_unverified_context()) else: self.rpc_url = 'http://' self.http_conn = httplib.HTTPConnection(rpc_host, rpc_port, timeout=5) self.rpc_url += rpc_user + ':' + rpc_password + '@' + rpc_host + ':' + str(rpc_port) logging.debug('AuthServiceProxy configured to: %s' % self.rpc_url) self.proxy = AuthServiceProxy(self.rpc_url, timeout=1000, connection=self.http_conn) try: # check the connection self.http_conn.connect() logging.debug('Successfully connected AuthServiceProxy') try: # make the owner know, we successfully finished connection if self.on_connection_successful_callback: self.on_connection_successful_callback() except: logging.exception('on_connection_finished_callback call exception') except: logging.exception('Connection failed') try: # make the owner know, connection attempt failed if self.on_connection_failed_callback: self.on_connection_failed_callback() if self.ssh: # if there is a ssh connection established earlier, disconnect it because apparently it isn't # functioning self.ssh.disconnect() except: logging.exception('on_connection_try_fail_callback call exception') raise finally: logging.debug('http_conn.close()') self.http_conn.close() # timeout hase been initially set to 5 seconds to perform 'quick' connection test self.http_conn.timeout = 20 self.active = True return self.active def get_active_conn_description(self): if self.cur_conn_def: return self.cur_conn_def.get_description() else: return '???' @control_rpc_call def getblockcount(self): if self.open(): return self.proxy.getblockcount() else: raise Exception('Not connected') @control_rpc_call def getinfo(self, verify_node: bool = True): if self.open(): info = self.proxy.getnetworkinfo() if verify_node: node_under_testnet = info.get('testnet') if self.config.is_testnet() and not node_under_testnet: raise Exception('This RPC node works under NIX MAINNET, but your current configuration is ' 'for TESTNET.') elif self.config.is_mainnet() and node_under_testnet: raise Exception('This RPC node works under NIX TESTNET, but your current configuration is ' 'for MAINNET.') return info else: raise Exception('Not connected') @control_rpc_call def issynchronized(self): if self.open(): # if connecting to HTTP(S) proxy do not check if dash daemon is synchronized if self.cur_conn_def.is_http_proxy(): return True else: syn = self.proxy.ghostsync('status') return syn.get('IsSynced') else: raise Exception('Not connected') @control_rpc_call def ghostsync(self): if self.open(): # if connecting to HTTP(S) proxy do not call this function - it will not be exposed if self.cur_conn_def.is_http_proxy(): return {} else: return self.proxy.ghostsync('status') else: raise Exception('Not connected') @control_rpc_call def ghostnodebroadcast(self, what, hexto): if self.open(): # if what == 'relay': if False: # FIXME: relay does not report correct status without 3rd parameter due to bug in nixd return self.proxy.ghostnodebroadcast(what, hexto, "not-safe") else: return self.proxy.ghostnodebroadcast(what, hexto) else: raise Exception('Not connected') def update_mn_queue_values(self): """ Updates masternode payment queue order values. """ start_tm = time.time() self.payment_queue = [] d = datetime.datetime.utcnow() now = int(time.mktime((d.year, d.month, d.day, d.hour, d.minute, d.second, 0, 0, 0))) for mn in self.masternodes: if mn.status == 'ENABLED': # estimate payment queue position: after loading all masternodes # queue_position will be used to sort mn list and count the real queue position if mn.lastpaidtime == 0: mn.queue_position = mn.activeseconds else: lastpaid_ago = now - mn.lastpaidtime mn.queue_position = min(lastpaid_ago, mn.activeseconds) self.payment_queue.append(mn) else: mn.queue_position = None duration1 = time.time() - start_tm self.payment_queue.sort(key=lambda x: x.queue_position, reverse=True) duration2 = time.time() - start_tm for mn in self.masternodes: if mn.status == 'ENABLED': mn.queue_position = self.payment_queue.index(mn) else: mn.queue_position = None duration3 = time.time() - start_tm logging.info('Masternode queue build time1: %s, time2: %s, time3: %s' % (str(duration1), str(duration2), str(duration3))) @control_rpc_call def get_ghostnodelist(self, *args, data_max_age=MASTERNODES_CACHE_VALID_SECONDS): """ Returns masternode list, read from the Dash network or from the internal cache. :param args: arguments passed to the 'ghostnodelist' RPC call :param data_max_age: maximum age (in seconds) of the cached masternode data to used; if the cache is older than 'data_max_age', then an RPC call is performed to load newer masternode data; value of 0 forces reading of the new data from the network :return: list of Masternode objects, matching the 'args' arguments """ def parse_mns(mns_raw): """ Parses dictionary of strings returned from the RPC to Masternode object list. :param mns_raw: Dict of masternodes in format of RPC ghostnodelist command :return: list of Masternode object """ tm_begin = time.time() ret_list = [] for mn_id in mns_raw.keys(): mn_raw = mns_raw.get(mn_id) mn_raw = mn_raw.strip() elems = mn_raw.split() if len(elems) >= 8: mn = Masternode() # (status, protocol, payee, lastseen, activeseconds, lastpaidtime, pastpaidblock, ip) mn.status, mn.protocol, mn.payee, mn.lastseen, mn.activeseconds, mn.lastpaidtime, \ mn.lastpaidblock, mn.ip = elems mn.lastseen = int(mn.lastseen) mn.activeseconds = int(mn.activeseconds) mn.lastpaidtime = int(mn.lastpaidtime) mn.lastpaidblock = int(mn.lastpaidblock) mn.ident = mn_id ret_list.append(mn) duration = time.time() - tm_begin logging.info('Parse ghostnodelist time: ' + str(duration)) return ret_list def update_masternode_data(existing_mn, new_data, cursor): # update cached masternode's properties existing_mn.modified = False existing_mn.monitor_changes = True existing_mn.ident = new_data.ident existing_mn.status = new_data.status existing_mn.protocol = new_data.protocol existing_mn.payee = new_data.payee existing_mn.lastseen = new_data.lastseen existing_mn.activeseconds = new_data.activeseconds existing_mn.lastpaidtime = new_data.lastpaidtime existing_mn.lastpaidblock = new_data.lastpaidblock existing_mn.ip = new_data.ip # ... and finally update MN db record if cursor and existing_mn.modified: cursor.execute("UPDATE MASTERNODES set ident=?, status=?, protocol=?, payee=?," " last_seen=?, active_seconds=?, last_paid_time=?, " " last_paid_block=?, ip=?" "WHERE id=?", (new_data.ident, new_data.status, new_data.protocol, new_data.payee, new_data.lastseen, new_data.activeseconds, new_data.lastpaidtime, new_data.lastpaidblock, new_data.ip, existing_mn.db_id)) if self.open(): if len(args) == 1 and args[0] == 'full': last_read_time = app_cache.get_value(f'MasternodesLastReadTime_{self.app_config.dash_network}', 0, int) logging.info("MasternodesLastReadTime: %d" % last_read_time) if self.masternodes and data_max_age > 0 and \ int(time.time()) - last_read_time < data_max_age: logging.info('Using cached ghostnodelist (data age: %s)' % str(int(time.time()) - last_read_time)) return self.masternodes else: logging.info('Loading masternode list from NIX daemon...') mns = self.proxy.ghostnodelist(*args) mns = parse_mns(mns) logging.info('Finished loading masternode list') # mark already cached masternodes to identify those to delete for mn in self.masternodes: mn.marker = False # save masternodes to the db cache db_modified = False cur = None try: if self.db_intf.db_active: cur = self.db_intf.get_cursor() for mn in mns: # check if newly-read masternode already exists in the cache existing_mn = self.masternodes_by_ident.get(mn.ident) if not existing_mn: mn.marker = True self.masternodes.append(mn) self.masternodes_by_ident[mn.ident] = mn if self.db_intf.db_active: cur.execute("INSERT INTO MASTERNODES(ident, status, protocol, payee, last_seen," " active_seconds, last_paid_time, last_paid_block, ip, dmt_active," " dmt_create_time) " "VALUES (?,?,?,?,?,?,?,?,?,?,?)", (mn.ident, mn.status, mn.protocol, mn.payee, mn.lastseen, mn.activeseconds, mn.lastpaidtime, mn.lastpaidblock, mn.ip, 1, datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) mn.db_id = cur.lastrowid db_modified = True else: existing_mn.marker = True update_masternode_data(existing_mn, mn, cur) db_modified = True # remove from the cache masternodes that no longer exist for mn_index in reversed(range(len(self.masternodes))): mn = self.masternodes[mn_index] if not mn.marker: if self.db_intf.db_active: cur.execute("UPDATE MASTERNODES set dmt_active=0, dmt_deactivation_time=?" "WHERE ID=?", (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), mn.db_id)) db_modified = True self.masternodes_by_ident.pop(mn.ident,0) del self.masternodes[mn_index] app_cache.set_value(f'MasternodesLastReadTime_{self.app_config.dash_network}', int(time.time())) self.update_mn_queue_values() finally: if db_modified: self.db_intf.commit() if cur is not None: self.db_intf.release_cursor() return self.masternodes else: mns = self.proxy.ghostnodelist(*args) mns = parse_mns(mns) return mns else: raise Exception('Not connected') @control_rpc_call def getaddressbalance(self, addresses): if self.open(): return self.proxy.getaddressbalance({'addresses': addresses}) else: raise Exception('Not connected') @control_rpc_call def getaddressutxos(self, addresses): if self.open(): return self.proxy.getaddressutxos({'addresses': addresses}) else: raise Exception('Not connected') @control_rpc_call def getrawtransaction(self, txid, verbose): if self.open(): return json_cache_wrapper(self.proxy.getrawtransaction, self, 'tx-' + str(verbose) + '-' + txid)(txid, verbose) else: raise Exception('Not connected') @control_rpc_call def getblockhash(self, blockid): if self.open(): return json_cache_wrapper(self.proxy.getblockhash, self, 'blockhash-' + str(blockid))(blockid) else: raise Exception('Not connected') @control_rpc_call def getblockheader(self, blockhash): if self.open(): return json_cache_wrapper(self.proxy.getblockheader, self, 'blockheader-' + str(blockhash))(blockhash) else: raise Exception('Not connected') @control_rpc_call def validateaddress(self, address): if self.open(): return self.proxy.validateaddress(address) else: raise Exception('Not connected') @control_rpc_call def decoderawtransaction(self, rawtx): if self.open(): return self.proxy.decoderawtransaction(rawtx) else: raise Exception('Not connected') @control_rpc_call def sendrawtransaction(self, tx, use_instant_send): if self.open(): return self.proxy.sendrawtransaction(tx, False) else: raise Exception('Not connected') @control_rpc_call def getcurrentvotes(self, hash): if self.open(): return self.proxy.getcurrentvotes(hash) else: raise Exception('Not connected') @control_rpc_call def gobject(self, *args): if self.open(): return self.proxy.gobject(*args) else: raise Exception('Not connected') @control_rpc_call def masternode(self, *args): if self.open(): return self.proxy.masternode(*args) else: raise Exception('Not connected') @control_rpc_call def getgovernanceinfo(self): if self.open(): return self.proxy.getgovernanceinfo() else: raise Exception('Not connected') @control_rpc_call def getsuperblockbudget(self, block_index): if self.open(): return self.proxy.getsuperblockbudget(block_index) else: raise Exception('Not connected') @control_rpc_call def voteraw(self, masternode_tx_hash, masternode_tx_index, governance_hash, vote_signal, vote, sig_time, vote_sig): if self.open(): return self.proxy.voteraw(masternode_tx_hash, masternode_tx_index, governance_hash, vote_signal, vote, sig_time, vote_sig) else: raise Exception('Not connected')
class DashdInterface(WndUtils): def __init__(self, config, window): WndUtils.__init__(self) assert isinstance(config, AppConfig) self.config = config self.last_connect_method = config.dashd_connect_method self.ssh = None self.window = window self.active = False self.rpc_url = None self.proxy = None self.http_conn = None # HTTPConnection object passed to the AuthServiceProxy (for convinient connection reset) def disconnect(self): if self.active: if self.last_connect_method == 'rpc_ssh' and self.ssh: self.ssh.disconnect() del self.ssh self.ssh = None self.active = False def open(self): # TODO: support openning dialogs from inside a thread if not self.active: rpc_host = None rpc_port = None rpc_user = None rpc_password = None if self.config.dashd_connect_method == 'rpc_ssh': # RPC over SSH while True: password = SshPassCache.get_password(self.window, self.config.ros_ssh_username, self.config.ros_ssh_host) if not password: return False self.ssh = DashdSSH(self.config.ros_ssh_host, self.config.ros_ssh_port, self.config.ros_ssh_username, password) try: self.ssh.connect() SshPassCache.save_password(self.config.ros_ssh_username, self.config.ros_ssh_host, password) break except AuthenticationException as e: self.errorMsg(str(e)) except TimeoutError as e: self.errorMsg(str(e)) return False except Exception as e: self.errorMsg(str(e)) # configure SSH tunnel # get random local unprivileged port number to establish SSH tunnel success = False local_port = None for try_nr in range(1, 10): try: local_port = randint(2000, 50000) self.ssh.open_tunnel(local_port, self.config.ros_rpc_bind_ip, int(self.config.ros_rpc_bind_port)) success = True break except Exception as e: pass if not success: return False else: rpc_user = self.config.ros_rpc_username rpc_password = self.config.ros_rpc_password rpc_host = '127.0.0.1' # SSH tunnel on loopback rpc_port = local_port elif self.config.dashd_connect_method == 'rpc': # direct RPC rpc_host = self.config.rpc_ip rpc_port = self.config.rpc_port rpc_user = self.config.rpc_user rpc_password = self.config.rpc_password else: raise Exception('Invalid connection method') self.rpc_url = 'http://' + rpc_user + ':' + rpc_password + '@' + rpc_host + ':' + str(rpc_port) self.http_conn = httplib.HTTPConnection(rpc_host, rpc_port, timeout=1000) self.proxy = AuthServiceProxy(self.rpc_url, timeout=1000, connection=self.http_conn) self.active = True return self.active @control_rpc_call def getblockcount(self): if self.open(): return self.proxy.getblockcount() else: raise Exception('Not connected') @control_rpc_call def getblockhash(self, block): if self.open(): return self.proxy.getblockhash(block) else: raise Exception('Not connected') @control_rpc_call def getinfo(self): if self.open(): return self.proxy.getinfo() else: raise Exception('Not connected') @control_rpc_call def issynchronized(self): if self.open(): syn = self.proxy.mnsync('status') return syn.get('IsSynced') else: raise Exception('Not connected') @control_rpc_call def masternodebroadcast(self, what, hexto): if self.open(): return self.proxy.masternodebroadcast(what, hexto) else: raise Exception('Not connected') @control_rpc_call def get_masternodelist(self): if self.open(): return self.proxy.masternodelist() else: raise Exception('Not connected') @control_rpc_call def get_masternodeaddr(self): if self.open(): return self.proxy.masternodelist('addr') else: raise Exception('Not connected') @control_rpc_call def getaddressbalance(self, address): if self.open(): return self.proxy.getaddressbalance({'addresses': [address]}).get('balance') else: raise Exception('Not connected') @control_rpc_call def getaddressutxos(self, addresses): if self.open(): return self.proxy.getaddressutxos({'addresses': addresses}) else: raise Exception('Not connected') @control_rpc_call def getrawtransaction(self, txid, verbose): if self.open(): return self.proxy.getrawtransaction(txid, verbose) else: raise Exception('Not connected') @control_rpc_call def getblockhash(self, blockid): if self.open(): return self.proxy.getblockhash(blockid) else: raise Exception('Not connected') @control_rpc_call def getblockheader(self, blockhash): if self.open(): return self.proxy.getblockheader(blockhash) else: raise Exception('Not connected') @control_rpc_call def validateaddress(self, address): if self.open(): return self.proxy.validateaddress(address) else: raise Exception('Not connected') @control_rpc_call def decoderawtransaction(self, tx): if self.open(): return self.proxy.decoderawtransaction(tx) else: raise Exception('Not connected') @control_rpc_call def sendrawtransaction(self, tx): if self.open(): return self.proxy.sendrawtransaction(tx) else: raise Exception('Not connected')
class RpcClient: def __init__(self): # Lock for threads self.lock = threading.Lock() self.rpc_ip, self.rpc_port, self.rpc_user, self.rpc_passwd = readRPCfile( ) rpc_url = "http://%s:%s@%s:%d" % (self.rpc_user, self.rpc_passwd, self.rpc_ip, self.rpc_port) try: self.lock.acquire() self.conn = AuthServiceProxy(rpc_url, timeout=8) except JSONRPCException as e: err_msg = 'remote or local PIVX-cli running?' printException(getCallerName(), getFunctionName(), err_msg, e) except Exception as e: err_msg = 'remote or local PIVX-cli running?' printException(getCallerName(), getFunctionName(), err_msg, e) finally: self.lock.release() def decodeRawTransaction(self, rawTx): try: self.lock.acquire() res = self.conn.decoderawtransaction(rawTx) except Exception as e: err_msg = 'error in decodeRawTransaction' printException(getCallerName(), getFunctionName(), err_msg, e.args) res = None finally: self.lock.release() return res def getAddressUtxos(self, addresses): try: self.lock.acquire() res = self.conn.getaddressutxos({'addresses': addresses}) except Exception as e: err_msg = "error in getAddressUtxos" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = None finally: self.lock.release() return res def getBlockCount(self): try: self.lock.acquire() n = self.conn.getblockcount() except Exception as e: err_msg = 'remote or local PIVX-cli running?' if str(e.args[0]) != "Request-sent": printException(getCallerName(), getFunctionName(), err_msg, e.args) n = 0 finally: self.lock.release() return n def getBlockHash(self, blockNum): try: self.lock.acquire() h = self.conn.getblockhash(blockNum) except Exception as e: err_msg = 'remote or local PIVX-cli running?' printException(getCallerName(), getFunctionName(), err_msg, e.args) h = None finally: self.lock.release() return h def getFeePerKb(self): try: self.lock.acquire() # get transaction data from last 10 blocks feePerKb = float(self.conn.getfeeinfo(10)['feeperkb']) res = (feePerKb if feePerKb > MINIMUM_FEE else MINIMUM_FEE) except Exception as e: err_msg = 'error in getFeePerKb' printException(getCallerName(), getFunctionName(), err_msg, e.args) res = MINIMUM_FEE finally: self.lock.release() return res def getMNStatus(self, address): try: self.lock.acquire() mnStatusList = self.conn.listmasternodes(address) if not mnStatusList: return None mnStatus = mnStatusList[0] mnStatus['mnCount'] = self.conn.getmasternodecount()['enabled'] except Exception as e: err_msg = "error in getMNStatus" printException(getCallerName(), getFunctionName(), err_msg, e.args) mnStatus = None finally: self.lock.release() return mnStatus def getMasternodes(self): mnList = {} mnList['last_update'] = now() score = [] try: self.lock.acquire() masternodes = self.conn.listmasternodes() except Exception as e: err_msg = "error in getMasternodes" printException(getCallerName(), getFunctionName(), err_msg, e.args) masternodes = [] finally: self.lock.release() for mn in masternodes: if mn.get('status') == 'ENABLED': if mn.get('lastpaid') == 0: mn['score'] = mn.get('activetime') else: lastpaid_ago = now() - mn.get('lastpaid') mn['score'] = min(lastpaid_ago, mn.get('activetime')) else: mn['score'] = 0 score.append(mn) score.sort(key=lambda x: x['score'], reverse=True) for mn in masternodes: mn['queue_pos'] = score.index(mn) mnList['masternodes'] = masternodes return mnList def getProtocolVersion(self): try: self.lock.acquire() prot_version = self.conn.getinfo().get('protocolversion') res = int(prot_version) except Exception as e: err_msg = 'error in getProtocolVersion' printException(getCallerName(), getFunctionName(), err_msg, e.args) res = DEFAULT_PROTOCOL_VERSION finally: self.lock.release() return res def getRawTransaction(self, txid): try: self.lock.acquire() res = self.conn.getrawtransaction(txid) except Exception as e: err_msg = "is Blockchain synced?" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = None finally: self.lock.release() return res def getStatus(self): status = False n = 0 try: self.lock.acquire() n = self.conn.getblockcount() if n > 0: status = True except Exception as e: # If loading block index set lastBlock=1 if str(e.args[0]) == "Loading block index..." or str( e.args[0]) == "Verifying blocks...": printDbg(str(e.args[0])) n = 1 else: err_msg = "Error while contacting RPC server" printException(getCallerName(), getFunctionName(), err_msg, e.args) finally: self.lock.release() return status, n def getStatusMess(self, status=None): if status == None: status = self.getStatus() if status: return "Connected to PIVX RPC client" else: return "Unable to connect to a PIVX RPC server, needed to broadcast messages to the network. Either the local PIVX wallet is not open, or the remote RPC server is not responding." def isBlockchainSynced(self): try: self.lock.acquire() res = self.conn.mnsync('status').get("IsBlockchainSynced") except Exception as e: if str(e.args[0]) != "Request-sent": err_msg = "error in isBlockchainSynced" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = False finally: self.lock.release() return res def decodemasternodebroadcast(self, work): try: self.lock.acquire() res = self.conn.decodemasternodebroadcast(work.strip()) except Exception as e: err_msg = "error in decodemasternodebroadcast" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = "" finally: self.lock.release() return res def relaymasternodebroadcast(self, work): try: self.lock.acquire() res = self.conn.relaymasternodebroadcast(work.strip()) except Exception as e: err_msg = "error in relaymasternodebroadcast" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = "" finally: self.lock.release() return res def sendRawTransaction(self, tx_hex): try: self.lock.acquire() tx_id = self.conn.sendrawtransaction(tx_hex) except Exception as e: err_msg = 'error in rpcClient.sendRawTransaction' printException(getCallerName(), getFunctionName(), err_msg, e.args) tx_id = None finally: self.lock.release() return tx_id def verifyMessage(self, pivxaddress, signature, message): try: self.lock.acquire() res = self.conn.verifymessage(pivxaddress, signature, message) except Exception as e: err_msg = "error in verifyMessage" printException(getCallerName(), getFunctionName(), err_msg, e.args) res = False finally: self.lock.release() return res
class RpcClient: @process_RPC_exceptions def __init__(self, rpc_protocol, rpc_host, rpc_user, rpc_password): # Lock for threads self.lock = threading.RLock() self.rpc_url = "%s://%s:%s@%s" % (rpc_protocol, rpc_user, rpc_password, rpc_host) host, port = rpc_host.split(":") if rpc_protocol == "https": self.httpConnection = httplib.HTTPSConnection( host, port, timeout=20, context=ssl._create_unverified_context()) else: self.httpConnection = httplib.HTTPConnection(host, port, timeout=20) self.conn = AuthServiceProxy(self.rpc_url, timeout=1000, connection=self.httpConnection) self.httpConnection.connect() @process_RPC_exceptions def decodeRawTransaction(self, rawTx): res = None with self.lock: res = self.conn.decoderawtransaction(rawTx) return res @process_RPC_exceptions def getAddressUtxos(self, addresses): res = None with self.lock: res = self.conn.getaddressutxos({'addresses': addresses}) return res @process_RPC_exceptions def getBlockCount(self): n = 0 with self.lock: n = self.conn.getblockcount() return n @process_RPC_exceptions def getBlockHash(self, blockNum): h = None with self.lock: h = self.conn.getblockhash(blockNum) return h @process_RPC_exceptions def getBudgetVotes(self, proposal): votes = {} with self.lock: votes = self.conn.getbudgetvotes(proposal) return votes @process_RPC_exceptions def getFeePerKb(self): res = MINIMUM_FEE with self.lock: # get transaction data from last 200 blocks feePerKb = float(self.conn.getfeeinfo(200)['feeperkb']) res = (feePerKb if feePerKb > MINIMUM_FEE else MINIMUM_FEE) return res @process_RPC_exceptions def getMNStatus(self, address): mnStatus = None with self.lock: mnStatusList = self.conn.listmasternodes(address) if not mnStatusList: return None mnStatus = mnStatusList[0] mnStatus['mnCount'] = self.conn.getmasternodecount()['enabled'] return mnStatus @process_RPC_exceptions def getMasternodeCount(self): ans = None with self.lock: ans = self.conn.getmasternodecount() return ans @process_RPC_exceptions def getMasternodes(self): mnList = {} score = [] masternodes = [] with self.lock: masternodes = self.conn.listmasternodes() for mn in masternodes: if mn.get('status') == 'ENABLED': # compute masternode score if mn.get('lastpaid') == 0: mn['score'] = mn.get('activetime') else: lastpaid_ago = now() - mn.get('lastpaid') mn['score'] = min(lastpaid_ago, mn.get('activetime')) else: mn['score'] = 0 score.append(mn) # sort masternodes by decreasing score score.sort(key=lambda x: x['score'], reverse=True) # save masternode position in the payment queue for mn in masternodes: mn['queue_pos'] = score.index(mn) mnList['masternodes'] = masternodes return mnList @process_RPC_exceptions def getNextSuperBlock(self): n = 0 with self.lock: n = self.conn.getnextsuperblock() return n @process_RPC_exceptions def getProposals(self): proposals = [] data = [] with self.lock: # get proposals JSON data data = self.conn.getbudgetinfo() for p in data: # create proposal Object new_proposal = Proposal(p.get('Name'), p.get('URL'), p.get('Hash'), p.get('FeeHash'), p.get('BlockStart'), p.get('BlockEnd'), p.get('TotalPaymentCount'), p.get('RemainingPaymentCount'), p.get('PaymentAddress'), p.get('Yeas'), p.get('Nays'), p.get('Abstains'), float(p.get('TotalPayment')), float(p.get('MonthlyPayment'))) # append object to list proposals.append(new_proposal) # return proposals list return proposals @process_RPC_exceptions def getProposalsProjection(self): data = [] proposals = [] with self.lock: # get budget projection JSON data data = self.conn.getbudgetprojection() for p in data: # create proposal-projection dictionary new_proposal = {} new_proposal['Name'] = p.get('Name') new_proposal['Allotted'] = float(p.get("Alloted")) new_proposal['Votes'] = p.get('Yeas') - p.get('Nays') new_proposal['Total_Allotted'] = float(p.get('TotalBudgetAlloted')) # append dictionary to list proposals.append(new_proposal) # return proposals list return proposals @process_RPC_exceptions def getProtocolVersion(self): res = DEFAULT_PROTOCOL_VERSION with self.lock: prot_version = self.conn.getinfo().get('protocolversion') res = int(prot_version) return res @process_RPC_exceptions def getRawTransaction(self, txid): res = None with self.lock: res = self.conn.getrawtransaction(txid) return res @process_RPC_exceptions def getStatus(self): status = False statusMess = "Unable to connect to a PIVX RPC server.\n" statusMess += "Either the local PIVX wallet is not open, or the remote RPC server is not responding." n = 0 response_time = None with self.lock: n, response_time = timeThis(self.conn.getblockcount) if n is None: n = 0 if n > 0: status = True statusMess = "Connected to PIVX Blockchain" return status, statusMess, n, response_time @process_RPC_exceptions def isBlockchainSynced(self): res = False response_time = None with self.lock: status, response_time = timeThis(self.conn.mnsync, 'status') if status is not None: res = status.get("IsBlockchainSynced") return res, response_time @process_RPC_exceptions def mnBudgetRawVote(self, mn_tx_hash, mn_tx_index, proposal_hash, vote, time, vote_sig): res = None with self.lock: res = self.conn.mnbudgetrawvote(mn_tx_hash, mn_tx_index, proposal_hash, vote, time, vote_sig) return res @process_RPC_exceptions def decodemasternodebroadcast(self, work): res = "" with self.lock: res = self.conn.decodemasternodebroadcast(work.strip()) return res @process_RPC_exceptions def relaymasternodebroadcast(self, work): res = "" with self.lock: res = self.conn.relaymasternodebroadcast(work.strip()) return res @process_RPC_exceptions def sendRawTransaction(self, tx_hex, use_swiftx): tx_id = None with self.lock: tx_id = self.conn.sendrawtransaction(tx_hex, True, bool(use_swiftx)) return tx_id @process_RPC_exceptions def verifyMessage(self, pivxaddress, signature, message): res = False with self.lock: res = self.conn.verifymessage(pivxaddress, signature, message) return res