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.
227 lines
7.8 KiB
227 lines
7.8 KiB
"""
|
|
dashd JSONRPC interface
|
|
"""
|
|
import sys
|
|
import os
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'lib'))
|
|
import config
|
|
import base58
|
|
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
|
|
from masternode import Masternode
|
|
from decimal import Decimal
|
|
import time
|
|
|
|
|
|
class DashDaemon():
|
|
def __init__(self, **kwargs):
|
|
host = kwargs.get('host', '127.0.0.1')
|
|
user = kwargs.get('user')
|
|
password = kwargs.get('password')
|
|
port = kwargs.get('port')
|
|
|
|
self.creds = (user, password, host, port)
|
|
|
|
# memoize calls to some dashd methods
|
|
self.governance_info = None
|
|
self.gobject_votes = {}
|
|
|
|
@property
|
|
def rpc_connection(self):
|
|
return AuthServiceProxy("http://{0}:{1}@{2}:{3}".format(*self.creds))
|
|
|
|
@classmethod
|
|
def from_dash_conf(self, dash_dot_conf):
|
|
from dash_config import DashConfig
|
|
config_text = DashConfig.slurp_config_file(dash_dot_conf)
|
|
creds = DashConfig.get_rpc_creds(config_text, config.network)
|
|
|
|
creds[u'host'] = config.rpc_host
|
|
|
|
return self(**creds)
|
|
|
|
def rpc_command(self, *params):
|
|
return self.rpc_connection.__getattr__(params[0])(*params[1:])
|
|
|
|
# common RPC convenience methods
|
|
|
|
def get_masternodes(self):
|
|
mnlist = self.rpc_command('masternodelist', 'full')
|
|
return [Masternode(k, v) for (k, v) in mnlist.items()]
|
|
|
|
def get_current_masternode_vin(self):
|
|
from dashlib import parse_masternode_status_vin
|
|
|
|
my_vin = None
|
|
|
|
try:
|
|
status = self.rpc_command('masternode', 'status')
|
|
mn_outpoint = status.get('outpoint') or status.get('vin')
|
|
my_vin = parse_masternode_status_vin(mn_outpoint)
|
|
except JSONRPCException as e:
|
|
pass
|
|
|
|
return my_vin
|
|
|
|
def governance_quorum(self):
|
|
# TODO: expensive call, so memoize this
|
|
total_masternodes = self.rpc_command('masternode', 'count', 'enabled')
|
|
min_quorum = self.govinfo['governanceminquorum']
|
|
|
|
# the minimum quorum is calculated based on the number of masternodes
|
|
quorum = max(min_quorum, (total_masternodes // 10))
|
|
return quorum
|
|
|
|
@property
|
|
def govinfo(self):
|
|
if (not self.governance_info):
|
|
self.governance_info = self.rpc_command('getgovernanceinfo')
|
|
return self.governance_info
|
|
|
|
# governance info convenience methods
|
|
def superblockcycle(self):
|
|
return self.govinfo['superblockcycle']
|
|
|
|
def last_superblock_height(self):
|
|
height = self.rpc_command('getblockcount')
|
|
cycle = self.superblockcycle()
|
|
return cycle * (height // cycle)
|
|
|
|
def next_superblock_height(self):
|
|
return self.last_superblock_height() + self.superblockcycle()
|
|
|
|
def is_masternode(self):
|
|
return not (self.get_current_masternode_vin() is None)
|
|
|
|
def is_synced(self):
|
|
mnsync_status = self.rpc_command('mnsync', 'status')
|
|
synced = (mnsync_status['IsBlockchainSynced'] and
|
|
mnsync_status['IsMasternodeListSynced'] and
|
|
mnsync_status['IsWinnersListSynced'] and
|
|
mnsync_status['IsSynced'] and
|
|
not mnsync_status['IsFailed'])
|
|
return synced
|
|
|
|
def current_block_hash(self):
|
|
height = self.rpc_command('getblockcount')
|
|
block_hash = self.rpc_command('getblockhash', height)
|
|
return block_hash
|
|
|
|
def get_superblock_budget_allocation(self, height=None):
|
|
if height is None:
|
|
height = self.rpc_command('getblockcount')
|
|
return Decimal(self.rpc_command('getsuperblockbudget', height))
|
|
|
|
def next_superblock_max_budget(self):
|
|
cycle = self.superblockcycle()
|
|
current_block_height = self.rpc_command('getblockcount')
|
|
|
|
last_superblock_height = (current_block_height // cycle) * cycle
|
|
next_superblock_height = last_superblock_height + cycle
|
|
|
|
last_allocation = self.get_superblock_budget_allocation(last_superblock_height)
|
|
next_allocation = self.get_superblock_budget_allocation(next_superblock_height)
|
|
|
|
next_superblock_max_budget = next_allocation
|
|
|
|
return next_superblock_max_budget
|
|
|
|
# "my" votes refers to the current running masternode
|
|
# memoized on a per-run, per-object_hash basis
|
|
def get_my_gobject_votes(self, object_hash):
|
|
import dashlib
|
|
if not self.gobject_votes.get(object_hash):
|
|
my_vin = self.get_current_masternode_vin()
|
|
# if we can't get MN vin from output of `masternode status`,
|
|
# return an empty list
|
|
if not my_vin:
|
|
return []
|
|
|
|
(txid, vout_index) = my_vin.split('-')
|
|
|
|
cmd = ['gobject', 'getcurrentvotes', object_hash, txid, vout_index]
|
|
raw_votes = self.rpc_command(*cmd)
|
|
self.gobject_votes[object_hash] = dashlib.parse_raw_votes(raw_votes)
|
|
|
|
return self.gobject_votes[object_hash]
|
|
|
|
def is_govobj_maturity_phase(self):
|
|
# 3-day period for govobj maturity
|
|
maturity_phase_delta = 1662 # ~(60*24*3)/2.6
|
|
if config.network == 'testnet':
|
|
maturity_phase_delta = 24 # testnet
|
|
|
|
event_block_height = self.next_superblock_height()
|
|
maturity_phase_start_block = event_block_height - maturity_phase_delta
|
|
|
|
current_height = self.rpc_command('getblockcount')
|
|
event_block_height = self.next_superblock_height()
|
|
|
|
# print "current_height = %d" % current_height
|
|
# print "event_block_height = %d" % event_block_height
|
|
# print "maturity_phase_delta = %d" % maturity_phase_delta
|
|
# print "maturity_phase_start_block = %d" % maturity_phase_start_block
|
|
|
|
return (current_height >= maturity_phase_start_block)
|
|
|
|
def we_are_the_winner(self):
|
|
import dashlib
|
|
# find the elected MN vin for superblock creation...
|
|
current_block_hash = self.current_block_hash()
|
|
mn_list = self.get_masternodes()
|
|
winner = dashlib.elect_mn(block_hash=current_block_hash, mnlist=mn_list)
|
|
my_vin = self.get_current_masternode_vin()
|
|
|
|
# print "current_block_hash: [%s]" % current_block_hash
|
|
# print "MN election winner: [%s]" % winner
|
|
# print "current masternode VIN: [%s]" % my_vin
|
|
|
|
return (winner == my_vin)
|
|
|
|
def estimate_block_time(self, height):
|
|
import dashlib
|
|
"""
|
|
Called by block_height_to_epoch if block height is in the future.
|
|
Call `block_height_to_epoch` instead of this method.
|
|
|
|
DO NOT CALL DIRECTLY if you don't want a "Oh Noes." exception.
|
|
"""
|
|
current_block_height = self.rpc_command('getblockcount')
|
|
diff = height - current_block_height
|
|
|
|
if (diff < 0):
|
|
raise Exception("Oh Noes.")
|
|
|
|
future_seconds = dashlib.blocks_to_seconds(diff)
|
|
estimated_epoch = int(time.time() + future_seconds)
|
|
|
|
return estimated_epoch
|
|
|
|
def block_height_to_epoch(self, height):
|
|
"""
|
|
Get the epoch for a given block height, or estimate it if the block hasn't
|
|
been mined yet. Call this method instead of `estimate_block_time`.
|
|
"""
|
|
epoch = -1
|
|
|
|
try:
|
|
bhash = self.rpc_command('getblockhash', height)
|
|
block = self.rpc_command('getblock', bhash)
|
|
epoch = block['time']
|
|
except JSONRPCException as e:
|
|
if e.message == 'Block height out of range':
|
|
epoch = self.estimate_block_time(height)
|
|
else:
|
|
print("error: %s" % e)
|
|
raise e
|
|
|
|
return epoch
|
|
|
|
@property
|
|
def has_sentinel_ping(self):
|
|
getinfo = self.rpc_command('getinfo')
|
|
return (getinfo['protocolversion'] >= config.min_dashd_proto_version_with_sentinel_ping)
|
|
|
|
def ping(self):
|
|
self.rpc_command('sentinelping', config.sentinel_version)
|