Skip to content

Commit

Permalink
Add SmartCash Support
Browse files Browse the repository at this point in the history
  • Loading branch information
rc125 committed Nov 11, 2018
1 parent cf92ece commit cc4b3e0
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 1 deletion.
29 changes: 28 additions & 1 deletion electrumx/lib/coins.py
Expand Up @@ -45,7 +45,7 @@
import electrumx.lib.tx as lib_tx
import electrumx.server.block_processor as block_proc
import electrumx.server.daemon as daemon
from electrumx.server.session import ElectrumX, DashElectrumX
from electrumx.server.session import ElectrumX, DashElectrumX, SmartCashElectrumX


Block = namedtuple("Block", "raw header transactions")
Expand Down Expand Up @@ -2300,3 +2300,30 @@ def header_hash(cls, header):
return double_sha256(header)
else:
return hex_str_to_hash(CivXTestnet.GENESIS_HASH)


class SmartCash(Coin):
NAME = "SmartCash"
SHORTNAME = "SMART"
NET = "mainnet"
P2PKH_VERBYTE = bytes.fromhex("3f")
P2SH_VERBYTES = [bytes.fromhex("12")]
WIF_BYTE = bytes.fromhex("bf")
GENESIS_HASH = ('000007acc6970b812948d14ea5a0a13d'
'b0fdd07d5047c7e69101fa8b361e05a4')
DESERIALIZER = lib_tx.DeserializerSmartCash
RPC_PORT = 9679
REORG_LIMIT = 5000
TX_COUNT = 1115016
TX_COUNT_HEIGHT = 541656
TX_PER_BLOCK = 1
ENCODE_CHECK = partial(Base58.encode_check, hash_fn=lib_tx.DeserializerSmartCash.keccak)
DECODE_CHECK = partial(Base58.decode_check, hash_fn=lib_tx.DeserializerSmartCash.keccak)
HEADER_HASH = lib_tx.DeserializerSmartCash.keccak
DAEMON = daemon.SmartCashDaemon
SESSIONCLS = SmartCashElectrumX

@classmethod
def header_hash(cls, header):
'''Given a header return the hash.'''
return cls.HEADER_HASH(header)
15 changes: 15 additions & 0 deletions electrumx/lib/tx.py
Expand Up @@ -623,3 +623,18 @@ def _read_tx_parts(self, produce_hash=True):
expiry,
witness
), tx_hash, self.cursor - start


class DeserializerSmartCash(Deserializer):

@staticmethod
def keccak(data):
from Cryptodome.Hash import keccak
keccak_hash = keccak.new(digest_bits=256)
keccak_hash.update(data)
return keccak_hash.digest()

def read_tx_and_hash(self):
from electrumx.lib.hash import sha256
start = self.cursor
return self.read_tx(), sha256(self.binary[start:self.cursor])
15 changes: 15 additions & 0 deletions electrumx/server/daemon.py
Expand Up @@ -457,3 +457,18 @@ class PreLegacyRPCDaemon(LegacyRPCDaemon):
async def deserialised_block(self, hex_hash):
'''Return the deserialised block with the given hex hash.'''
return await self._send_single('getblock', (hex_hash, False))


class SmartCashDaemon(Daemon):

async def smartnode_broadcast(self, params):
'''Broadcast a smartnode to the network.'''
return await self._send_single('smartnodebroadcast', params)

async def smartnode_list(self, params):
'''Return the smartnode status.'''
return await self._send_single('smartnodelist', params)

async def smartrewards(self, params):
'''Return smartrewards data.'''
return await self._send_single('smartrewards', params)
159 changes: 159 additions & 0 deletions electrumx/server/session.py
Expand Up @@ -1430,3 +1430,162 @@ def get_payment_position(payment_queue, address):
return [mn for mn in cache if mn['payee'] in payees]
else:
return cache


class SmartCashElectrumX(ElectrumX):
'''A TCP server that handles incoming Electrum-SMART connections.'''

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.sns = set()

def set_request_handlers(self, ptuple):
super().set_request_handlers(ptuple)
self.request_handlers.update({
'smartnode.announce.broadcast': self.smartnode_announce_broadcast,
'smartnode.subscribe': self.smartnode_subscribe,
'smartnode.list': self.smartnode_list,
'smartrewards.current': self.smartrewards_current,
'smartrewards.check': self.smartrewards_check
})

async def notify(self, touched, height_changed):
'''Notify the client about changes in smartnode list.'''
await super().notify(touched, height_changed)
for sn in self.sns:
status = await self.daemon_request('smartnode_list', ['status', sn])
await self.send_notification('smartnode.subscribe', [sn, status.get(sn)])

# Smartnode command handlers
async def smartnode_announce_broadcast(self, signsnb):
'''Pass through the smartnode announce message to be broadcast by the daemon.
signsnb: signed smartnode broadcast message.'''
try:
return await self.daemon_request('smartnode_broadcast', ['relay', signsnb])
except DaemonError as e:
error, = e.args
message = error['message']
self.logger.info(f'smartnode_broadcast: {message}')
raise RPCError(BAD_REQUEST, 'the smartnode broadcast was '
f'rejected.\n\n{message}\n[{signsnb}]')

async def smartnode_subscribe(self, collateral):
'''Returns the status of smartnode.
collateral: smartnode collateral.'''
result = await self.daemon_request('smartnode_list', ['status', collateral])
if result is not None:
self.sns.add(collateral)
return result.get(collateral)
return None

async def smartnode_list(self, payees):
'''
Returns the list of smartnodes.
payees: a list of smartnode payee addresses.
'''
if not isinstance(payees, list):
raise RPCError(BAD_REQUEST, 'expected a list of payees')

def get_smartnode_payment_queue(sns):
'''Returns the calculated position in the payment queue for all the
valid smartnodes in the given mns list.
sns: a list of smartnodes information.
'''
now = int(datetime.datetime.utcnow().strftime("%s"))
sn_queue = []

# Only ENABLED masternodes are considered for the list.
for line in sns:
snstat = sns[line].split()
if snstat[0] == 'ENABLED':
# if last paid time == 0
if int(snstat[5]) == 0:
# use active seconds
snstat.append(int(snstat[4]))
else:
# now minus last paid
delta = now - int(snstat[5])
# if > active seconds, use active seconds
if delta >= int(snstat[4]):
snstat.append(int(snstat[4]))
# use active seconds
else:
snstat.append(delta)
sn_queue.append(snstat)
sn_queue = sorted(sn_queue, key=lambda x: x[8], reverse=True)
return sn_queue

def get_payment_position(payment_queue, address):
'''
Returns the position of the payment list for the given address.
payment_queue: position in the payment queue for the smartnode.
address: smartnode payee address.
'''
position = -1
for pos, mn in enumerate(payment_queue, start=1):
if mn[2] == address:
position = pos
break
return position

# Accordingly with the smartnode payment queue, a custom list
# with the smartnode information including the payment
# position is returned.
cache = self.session_mgr.mn_cache
if not cache or self.session_mgr.mn_cache_height != self.db.db_height:
full_sn_list = await self.daemon_request('smartnode_list',
['full'])
sn_payment_queue = get_smartnode_payment_queue(full_sn_list)
sn_payment_count = len(sn_payment_queue)
sn_list = []
for key, value in full_sn_list.items():
sn_data = value.split()
sn_info = {}
sn_info['vin'] = key
sn_info['status'] = sn_data[0]
sn_info['protocol'] = sn_data[1]
sn_info['payee'] = sn_data[2]
sn_info['lastseen'] = sn_data[3]
sn_info['activeseconds'] = sn_data[4]
sn_info['lastpaidtime'] = sn_data[5]
sn_info['lastpaidblock'] = sn_data[6]
sn_info['ip'] = sn_data[7]
sn_info['paymentposition'] = get_payment_position(
sn_payment_queue, sn_info['payee'])
sn_info['inselection'] = (
sn_info['paymentposition'] < sn_payment_count // 10)
balance = await self.address_get_balance(sn_info['payee'])
sn_info['balance'] = (sum(balance.values())
/ self.coin.VALUE_PER_COIN)
sn_list.append(sn_info)
cache.clear()
cache.extend(sn_list)
self.session_mgr.mn_cache_height = self.db.db_height

# If payees is an empty list the whole masternode list is returned
if payees:
return [sn for sn in cache if sn['payee'] in payees]
else:
return cache

async def smartrewards_current(self):
'''Returns the current smartrewards info.'''
result = await self.daemon_request('smartrewards', ['current'])
if result is not None:
return result
return None

async def smartrewards_check(self, addr):
'''
Returns the status of an address
addr: a single smartcash address
'''
result = await self.daemon_request('smartrewards', ['check', addr])
if result is not None:
return result
return None
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -12,6 +12,7 @@
# "blake256" package is required to sync Decred network.
# "xevan_hash" package is required to sync Xuez network.
# "groestlcoin_hash" package is required to sync Groestlcoin network.
# "pycryptodomex" package is required to sync SmartCash network.
install_requires=['aiorpcX>=0.10.1,<0.11', 'attrs',
'plyvel', 'pylru', 'aiohttp >= 2'],
packages=setuptools.find_packages(include=('electrumx*',)),
Expand Down

0 comments on commit cc4b3e0

Please sign in to comment.