Remove altcoin-only server and tx code
Deletes the transaction deserializers of other coins (tx.py keeps only the Bitcoin/segwit ones), tx_dash.py, tx_axe.py, and the session, daemon and block processor subclasses that only other coins used. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
73c96101bd
commit
05f5fc9058
6 files changed
+3
-2745
No files matched your search
@@ -904,107 +904,3 @@ class BlockProcessor:
|
||||
self.blocks_event.set()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class DecredBlockProcessor(BlockProcessor):
|
||||
async def calc_reorg_range(self, count):
|
||||
start, count = await super().calc_reorg_range(count)
|
||||
if start > 0:
|
||||
# A reorg in Decred can invalidate the previous block
|
||||
start -= 1
|
||||
count += 1
|
||||
return start, count
|
||||
|
||||
|
||||
class NameIndexBlockProcessor(BlockProcessor):
|
||||
|
||||
def advance_txs_process_outputs(
|
||||
self,
|
||||
txs: Sequence[Tx],
|
||||
*,
|
||||
height: int,
|
||||
) -> None:
|
||||
result = super().advance_txs_process_outputs(txs, height=height)
|
||||
|
||||
tx_num_start = self.db.tx_counts[height - 1] if height > 0 else 0
|
||||
tx_num = tx_num_start
|
||||
script_name_hashX = self.coin.name_hashX_from_script
|
||||
update_touched_hashxs = self.touched_hashxs.update
|
||||
hashXs_by_tx = []
|
||||
append_hashXs = hashXs_by_tx.append
|
||||
|
||||
for tx in txs:
|
||||
hashXs = []
|
||||
append_hashX = hashXs.append
|
||||
|
||||
# Add the new UTXOs and associate them with the name script
|
||||
for txout in tx.outputs:
|
||||
# Get the hashX of the name script. Ignore non-name scripts.
|
||||
hashX = script_name_hashX(txout.pk_script)
|
||||
if hashX:
|
||||
append_hashX(hashX)
|
||||
|
||||
append_hashXs(hashXs)
|
||||
update_touched_hashxs(hashXs)
|
||||
tx_num += 1
|
||||
|
||||
self.db.history.add_unflushed(hashXs_by_tx, tx_num_start)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class LTORBlockProcessor(BlockProcessor):
|
||||
# for Lexicographical Transaction ordering in blocks
|
||||
# (as opposed to Topological order as used in Bitcoin)
|
||||
# note: the base class already does not care about tx ordering inside a block
|
||||
# for the forward (catch-up) direction, as that better parallelizes.
|
||||
# This subclass is only kept for the reverse (backup) direction, for now.
|
||||
# We could also parallelize backup_txs in the base class for better perf,
|
||||
# likely removing the tx ordering assumptions, then we could rm this subclass.
|
||||
|
||||
def backup_txs(self, txs, is_unspendable):
|
||||
undo_info = self.db.read_undo_info(self.height)
|
||||
if undo_info is None:
|
||||
raise ChainError(
|
||||
f'no undo information found for height {self.height:,d}'
|
||||
)
|
||||
|
||||
# Use local vars for speed in the loops
|
||||
put_utxo = self.utxo_cache.__setitem__
|
||||
spend_utxo = self.spend_utxo
|
||||
add_touched_hashx = self.touched_hashxs.add
|
||||
add_touched_outpoint = self.touched_outpoints.add
|
||||
undo_entry_len = HASHX_LEN + TXNUM_LEN + 8
|
||||
|
||||
# Restore coins that had been spent
|
||||
# (may include coins made then spent in this block)
|
||||
n = 0
|
||||
for tx in txs:
|
||||
for txin in tx.inputs:
|
||||
if txin.is_generation():
|
||||
continue
|
||||
undo_item = undo_info[n:n + undo_entry_len]
|
||||
prevout = txin.prev_txid_rev + pack_txoutidx(txin.prev_idx)
|
||||
put_utxo(prevout, undo_item)
|
||||
add_touched_hashx(undo_item[:HASHX_LEN])
|
||||
add_touched_outpoint((txin.prev_txid_rev, txin.prev_idx))
|
||||
n += undo_entry_len
|
||||
|
||||
assert n == len(undo_info)
|
||||
|
||||
# Remove tx outputs made in this block, by spending them.
|
||||
for tx in txs:
|
||||
txid_rev = tx.txid_rev
|
||||
for idx, txout in enumerate(tx.outputs):
|
||||
# Spend the TX outputs. Be careful with unspendable
|
||||
# outputs - we didn't save those in the first place.
|
||||
if is_unspendable(txout.pk_script):
|
||||
continue
|
||||
|
||||
# Get the hashX
|
||||
cache_value = spend_utxo(txid_rev, idx)
|
||||
hashX = cache_value[:HASHX_LEN]
|
||||
add_touched_hashx(hashX)
|
||||
add_touched_outpoint((txid_rev, idx))
|
||||
|
||||
self.tx_count -= len(txs)
|
||||
@@ -11,18 +11,13 @@ daemon.'''
|
||||
import asyncio
|
||||
import itertools
|
||||
import time
|
||||
from calendar import timegm
|
||||
from struct import pack
|
||||
from typing import TYPE_CHECKING, Type, Sequence, Any, Iterable, Optional
|
||||
|
||||
import aiohttp
|
||||
from aiorpcx import JSONRPC
|
||||
|
||||
from electrumx.lib.hash import hash_to_hex_str, hex_str_to_hash
|
||||
from electrumx.lib.tx import DeserializerDecred
|
||||
from electrumx.lib.util import (class_logger, hex_to_bytes, json_deserialize,
|
||||
json_serialize, pack_varint,
|
||||
unpack_le_uint16_from)
|
||||
json_serialize)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from electrumx.lib.coins import Coin
|
||||
@@ -404,235 +399,3 @@ class Daemon:
|
||||
|
||||
If the daemon has not been queried yet this returns None.'''
|
||||
return self._height
|
||||
|
||||
|
||||
class DashDaemon(Daemon):
|
||||
|
||||
async def masternode_broadcast(self, params):
|
||||
'''Broadcast a transaction to the network.'''
|
||||
return await self._send_single('masternodebroadcast', params)
|
||||
|
||||
async def masternode_list(self, params):
|
||||
'''Return the masternode status.'''
|
||||
return await self._send_single('masternodelist', params)
|
||||
|
||||
async def protx(self, params):
|
||||
'''Set of commands to execute ProTx related actions.'''
|
||||
return await self._send_single('protx', params)
|
||||
|
||||
|
||||
class FakeEstimateFeeDaemon(Daemon):
|
||||
'''Daemon that simulates estimatefee and relayfee RPC calls. Coin that
|
||||
wants to use this daemon must define ESTIMATE_FEE & RELAY_FEE'''
|
||||
|
||||
async def estimatefee(self, block_count):
|
||||
'''Return the fee estimate for the given parameters.'''
|
||||
return self.coin.ESTIMATE_FEE
|
||||
|
||||
async def relayfee(self):
|
||||
"""The minimum fee required for a transaction to be relayed on by the server to the
|
||||
bitcoin network. Doesn't guarantee mempool acceptance."""
|
||||
return self.coin.RELAY_FEE
|
||||
|
||||
|
||||
class LegacyRPCDaemon(Daemon):
|
||||
'''Handles connections to a daemon at the given URL.
|
||||
|
||||
This class is useful for daemons that don't have the new 'getblock'
|
||||
RPC call that returns the block in hex, the workaround is to manually
|
||||
recreate the block bytes. The recreated block bytes may not be the exact
|
||||
as in the underlying blockchain but it is good enough for our indexing
|
||||
purposes.'''
|
||||
|
||||
async def raw_blocks(self, bhashes_hum):
|
||||
'''Return the raw binary blocks with the given hex hashes.'''
|
||||
params_iterable = ((h, ) for h in bhashes_hum)
|
||||
block_info = await self._send_vector('getblock', params_iterable)
|
||||
|
||||
blocks = []
|
||||
for i in block_info:
|
||||
raw_block = await self.make_raw_block(i)
|
||||
blocks.append(raw_block)
|
||||
|
||||
# Convert hex string to bytes
|
||||
return blocks
|
||||
|
||||
async def make_raw_header(self, b):
|
||||
pbh = b.get('previousblockhash')
|
||||
if pbh is None:
|
||||
pbh = '0' * 64
|
||||
return b''.join([
|
||||
pack('<L', b.get('version')),
|
||||
hex_str_to_hash(pbh),
|
||||
hex_str_to_hash(b.get('merkleroot')),
|
||||
pack('<L', self.timestamp_safe(b['time'])),
|
||||
pack('<L', int(b.get('bits'), 16)),
|
||||
pack('<L', int(b.get('nonce')))
|
||||
])
|
||||
|
||||
async def make_raw_block(self, b):
|
||||
'''Construct a raw block'''
|
||||
|
||||
header = await self.make_raw_header(b)
|
||||
|
||||
transactions = []
|
||||
if b.get('height') > 0:
|
||||
transactions = await self.getrawtransactions(b.get('tx'), False)
|
||||
|
||||
raw_block = header
|
||||
num_txs = len(transactions)
|
||||
if num_txs > 0:
|
||||
raw_block += pack_varint(num_txs)
|
||||
raw_block += b''.join(transactions)
|
||||
else:
|
||||
raw_block += b'\x00'
|
||||
|
||||
return raw_block
|
||||
|
||||
def timestamp_safe(self, t):
|
||||
if isinstance(t, int):
|
||||
return t
|
||||
return timegm(time.strptime(t, "%Y-%m-%d %H:%M:%S %Z"))
|
||||
|
||||
|
||||
class FakeEstimateLegacyRPCDaemon(LegacyRPCDaemon, FakeEstimateFeeDaemon):
|
||||
pass
|
||||
|
||||
|
||||
class DecredDaemon(Daemon):
|
||||
async def raw_blocks(self, bhashes_hum):
|
||||
'''Return the raw binary blocks with the given hex hashes.'''
|
||||
|
||||
params_iterable = ((h, False) for h in bhashes_hum)
|
||||
blocks = await self._send_vector('getblock', params_iterable)
|
||||
|
||||
raw_blocks = []
|
||||
valid_tx_tree = {}
|
||||
for block in blocks:
|
||||
# Convert to bytes from hex
|
||||
raw_block = hex_to_bytes(block)
|
||||
raw_blocks.append(raw_block)
|
||||
# Check if previous block is valid
|
||||
prev = self.prev_hex_hash(raw_block)
|
||||
votebits = unpack_le_uint16_from(raw_block[100:102])[0]
|
||||
valid_tx_tree[prev] = self.is_valid_tx_tree(votebits)
|
||||
|
||||
processed_raw_blocks = []
|
||||
for hash, raw_block in zip(bhashes_hum, raw_blocks):
|
||||
if hash in valid_tx_tree:
|
||||
is_valid = valid_tx_tree[hash]
|
||||
else:
|
||||
# Do something complicated to figure out if this block is valid
|
||||
header = await self._send_single('getblockheader', (hash, ))
|
||||
if 'nextblockhash' not in header:
|
||||
raise DaemonError(f'Could not find next block for {hash}')
|
||||
next_hash = header['nextblockhash']
|
||||
next_header = await self._send_single('getblockheader',
|
||||
(next_hash, ))
|
||||
is_valid = self.is_valid_tx_tree(next_header['votebits'])
|
||||
|
||||
if is_valid:
|
||||
processed_raw_blocks.append(raw_block)
|
||||
else:
|
||||
# If this block is invalid remove the normal transactions
|
||||
self.logger.info(f'block {hash} is invalidated')
|
||||
processed_raw_blocks.append(self.strip_tx_tree(raw_block))
|
||||
|
||||
return processed_raw_blocks
|
||||
|
||||
@staticmethod
|
||||
def prev_hex_hash(raw_block):
|
||||
return hash_to_hex_str(raw_block[4:36])
|
||||
|
||||
@staticmethod
|
||||
def is_valid_tx_tree(votebits):
|
||||
# Check if previous block was invalidated.
|
||||
return bool(votebits & (1 << 0) != 0)
|
||||
|
||||
def strip_tx_tree(self, raw_block):
|
||||
c = self.coin
|
||||
assert issubclass(c.DESERIALIZER, DeserializerDecred)
|
||||
d = c.DESERIALIZER(raw_block, start=c.BASIC_HEADER_SIZE)
|
||||
d.read_tx_tree() # Skip normal transactions
|
||||
# Create a fake block without any normal transactions
|
||||
return raw_block[:c.BASIC_HEADER_SIZE] + b'\x00' + raw_block[d.cursor:]
|
||||
|
||||
async def height(self):
|
||||
height = await super().height()
|
||||
if height > 0:
|
||||
# Lie about the daemon height as the current tip can be invalidated
|
||||
height -= 1
|
||||
self._height = height
|
||||
return height
|
||||
|
||||
async def mempool_txids_hum(self):
|
||||
mempool = await super().mempool_txids_hum()
|
||||
# Add current tip transactions to the 'fake' mempool.
|
||||
real_height = await self._send_single('getblockcount')
|
||||
tip_hash = await self._send_single('getblockhash', (real_height,))
|
||||
tip = await self.deserialised_block(tip_hash)
|
||||
# Add normal transactions except coinbase
|
||||
mempool += tip['tx'][1:]
|
||||
# Add stake transactions if applicable
|
||||
mempool += tip.get('stx', [])
|
||||
return mempool
|
||||
|
||||
def connector(self):
|
||||
# FIXME allow self signed certificates
|
||||
return aiohttp.TCPConnector(verify_ssl=False)
|
||||
|
||||
|
||||
class PreLegacyRPCDaemon(LegacyRPCDaemon):
|
||||
'''Handles connections to a daemon at the given URL.
|
||||
|
||||
This class is useful for daemons that don't have the new 'getblock'
|
||||
RPC call that returns the block in hex, and need the False parameter
|
||||
for the getblock'''
|
||||
|
||||
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 masternode_broadcast(self, params):
|
||||
'''Broadcast a smartnode to the network.'''
|
||||
return await self._send_single('smartnodebroadcast', params)
|
||||
|
||||
async def masternode_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)
|
||||
|
||||
|
||||
class ZcoinMtpDaemon(Daemon):
|
||||
|
||||
def strip_mtp_data(self, raw_block):
|
||||
if self.coin.is_mtp(raw_block):
|
||||
return \
|
||||
raw_block[:self.coin.MTP_HEADER_DATA_START*2] + \
|
||||
raw_block[self.coin.MTP_HEADER_DATA_END*2:]
|
||||
return raw_block
|
||||
|
||||
async def raw_blocks(self, bhashes_hum):
|
||||
'''Return the raw binary blocks with the given hex hashes.'''
|
||||
params_iterable = ((h, False) for h in bhashes_hum)
|
||||
blocks = await self._send_vector('getblock', params_iterable)
|
||||
# Convert hex string to bytes
|
||||
return [hex_to_bytes(self.strip_mtp_data(block)) for block in blocks]
|
||||
|
||||
async def masternode_broadcast(self, params):
|
||||
'''Broadcast a transaction to the network.'''
|
||||
return await self._send_single('znodebroadcast', params)
|
||||
|
||||
async def masternode_list(self, params):
|
||||
'''Return the masternode status.'''
|
||||
return await self._send_single('znodelist', params)
|
||||
|
||||
async def protx(self, params):
|
||||
'''Set of commands to execute ProTx related actions.'''
|
||||
return await self._send_single('protx', params)
|
||||
@@ -10,7 +10,6 @@
|
||||
import asyncio
|
||||
import codecs
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
import itertools
|
||||
import math
|
||||
import os
|
||||
@@ -2439,335 +2438,3 @@ class LocalRPC(SessionBase):
|
||||
|
||||
def protocol_version_string(self):
|
||||
return 'RPC'
|
||||
|
||||
|
||||
######################################################################
|
||||
# Non-Bitcoin stuff goes strictly below this line.
|
||||
######################################################################
|
||||
|
||||
|
||||
class DashElectrumX(ElectrumX):
|
||||
'''A TCP server that handles incoming Electrum Dash connections.'''
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.mns = set()
|
||||
self.mn_cache_height = 0
|
||||
self.mn_cache = []
|
||||
|
||||
def set_request_handlers(self, ptuple):
|
||||
super().set_request_handlers(ptuple)
|
||||
self.request_handlers.update({
|
||||
'masternode.announce.broadcast': self.phandle_masternode_announce_broadcast,
|
||||
'masternode.subscribe': self.phandle_masternode_subscribe,
|
||||
'masternode.list': self.phandle_masternode_list,
|
||||
'protx.diff': self.phandle_protx_diff,
|
||||
'protx.info': self.phandle_protx_info,
|
||||
})
|
||||
|
||||
async def _notify_inner(
|
||||
self,
|
||||
*,
|
||||
touched_hashxs,
|
||||
touched_outpoints,
|
||||
height_changed,
|
||||
):
|
||||
'''Notify the client about changes in masternode list.'''
|
||||
await super()._notify_inner(
|
||||
touched_hashxs=touched_hashxs,
|
||||
touched_outpoints=touched_outpoints,
|
||||
height_changed=height_changed,
|
||||
)
|
||||
for mn in self.mns.copy():
|
||||
status = await self.daemon_request('masternode_list',
|
||||
('status', mn))
|
||||
await self.send_notification('masternode.subscribe',
|
||||
(mn, status.get(mn)))
|
||||
|
||||
# Masternode command handlers
|
||||
async def phandle_masternode_announce_broadcast(self, signmnb):
|
||||
'''Pass through the masternode announce message to be broadcast
|
||||
by the daemon.
|
||||
|
||||
signmnb: signed masternode broadcast message.'''
|
||||
try:
|
||||
return await self.daemon_request('masternode_broadcast',
|
||||
('relay', signmnb))
|
||||
except DaemonError as e:
|
||||
error, = e.args
|
||||
message = error['message']
|
||||
self.logger.info(f'masternode_broadcast: {message}')
|
||||
raise RPCError(BAD_REQUEST, 'the masternode broadcast was '
|
||||
f'rejected.\n\n{message}\n[{signmnb}]')
|
||||
|
||||
async def phandle_masternode_subscribe(self, collateral):
|
||||
'''Returns the status of masternode.
|
||||
|
||||
collateral: masternode collateral.
|
||||
'''
|
||||
result = await self.daemon_request('masternode_list',
|
||||
('status', collateral))
|
||||
if result is not None:
|
||||
self.mns.add(collateral)
|
||||
return result.get(collateral)
|
||||
return None
|
||||
|
||||
async def phandle_masternode_list(self, payees):
|
||||
'''
|
||||
Returns the list of masternodes.
|
||||
|
||||
payees: a list of masternode payee addresses.
|
||||
'''
|
||||
if not isinstance(payees, list):
|
||||
raise RPCError(BAD_REQUEST, 'expected a list of payees')
|
||||
|
||||
def get_masternode_payment_queue(mns):
|
||||
'''Returns the calculated position in the payment queue for all the
|
||||
valid masterernodes in the given mns list.
|
||||
|
||||
mns: a list of masternodes information.
|
||||
'''
|
||||
now = int(datetime.datetime.utcnow().strftime("%s"))
|
||||
mn_queue = []
|
||||
|
||||
# Only ENABLED masternodes are considered for the list.
|
||||
for line in mns:
|
||||
mnstat = mns[line].split()
|
||||
if mnstat[0] == 'ENABLED':
|
||||
# if last paid time == 0
|
||||
if int(mnstat[5]) == 0:
|
||||
# use active seconds
|
||||
mnstat.append(int(mnstat[4]))
|
||||
else:
|
||||
# now minus last paid
|
||||
delta = now - int(mnstat[5])
|
||||
# if > active seconds, use active seconds
|
||||
if delta >= int(mnstat[4]):
|
||||
mnstat.append(int(mnstat[4]))
|
||||
# use active seconds
|
||||
else:
|
||||
mnstat.append(delta)
|
||||
mn_queue.append(mnstat)
|
||||
mn_queue = sorted(mn_queue, key=lambda x: x[8], reverse=True)
|
||||
return mn_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 masternode.
|
||||
address: masternode 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 masternode payment queue, a custom list
|
||||
# with the masternode 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_mn_list = await self.daemon_request('masternode_list',
|
||||
('full',))
|
||||
mn_payment_queue = get_masternode_payment_queue(full_mn_list)
|
||||
mn_payment_count = len(mn_payment_queue)
|
||||
mn_list = []
|
||||
for key, value in full_mn_list.items():
|
||||
mn_data = value.split()
|
||||
mn_info = {
|
||||
'vin': key,
|
||||
'status': mn_data[0],
|
||||
'protocol': mn_data[1],
|
||||
'payee': mn_data[2],
|
||||
'lastseen': mn_data[3],
|
||||
'activeseconds': mn_data[4],
|
||||
'lastpaidtime': mn_data[5],
|
||||
'lastpaidblock': mn_data[6],
|
||||
'ip': mn_data[7]
|
||||
}
|
||||
mn_info['paymentposition'] = get_payment_position(
|
||||
mn_payment_queue, mn_info['payee']
|
||||
)
|
||||
mn_info['inselection'] = (
|
||||
mn_info['paymentposition'] < mn_payment_count // 10
|
||||
)
|
||||
hashX = self.coin.address_to_hashX(mn_info['payee'])
|
||||
balance = await self.get_balance(hashX)
|
||||
mn_info['balance'] = (sum(balance.values())
|
||||
/ self.coin.VALUE_PER_COIN)
|
||||
mn_list.append(mn_info)
|
||||
cache.clear()
|
||||
cache.extend(mn_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 [mn for mn in cache if mn['payee'] in payees]
|
||||
else:
|
||||
return cache
|
||||
|
||||
async def phandle_protx_diff(self, base_height, height):
|
||||
'''
|
||||
Calculates a diff between two deterministic masternode lists.
|
||||
The result also contains proof data.
|
||||
|
||||
base_height: The starting block height (starting from 1).
|
||||
height: The ending block height.
|
||||
'''
|
||||
if not isinstance(base_height, int) or not isinstance(height, int):
|
||||
raise RPCError(BAD_REQUEST, 'expected a int block heights')
|
||||
|
||||
max_height = self.db.db_height
|
||||
if (not 1 <= base_height <= max_height or
|
||||
not base_height <= height <= max_height):
|
||||
raise RPCError(BAD_REQUEST,
|
||||
f'require 1 <= base_height {base_height:,d} <= '
|
||||
f'height {height:,d} <= '
|
||||
f'chain height {max_height:,d}')
|
||||
|
||||
return await self.daemon_request('protx',
|
||||
('diff', base_height, height))
|
||||
|
||||
async def phandle_protx_info(self, protx_hash):
|
||||
'''
|
||||
Returns detailed information about a deterministic masternode.
|
||||
|
||||
protx_hash: The hash of the initial ProRegTx
|
||||
'''
|
||||
if not isinstance(protx_hash, str):
|
||||
raise RPCError(BAD_REQUEST, 'expected protx hash string')
|
||||
|
||||
res = await self.daemon_request('protx', ('info', protx_hash))
|
||||
if 'wallet' in res:
|
||||
del res['wallet']
|
||||
return res
|
||||
|
||||
|
||||
class SmartCashElectrumX(DashElectrumX):
|
||||
'''A TCP server that handles incoming Electrum-SMART connections.'''
|
||||
|
||||
def set_request_handlers(self, ptuple):
|
||||
super().set_request_handlers(ptuple)
|
||||
self.request_handlers.update({
|
||||
'smartrewards.current': self.phandle_smartrewards_current,
|
||||
'smartrewards.check': self.phandle_smartrewards_check
|
||||
})
|
||||
|
||||
async def phandle_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 phandle_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
|
||||
|
||||
|
||||
class AuxPoWElectrumX(ElectrumX):
|
||||
async def phandle_block_header(self, height, cp_height=0):
|
||||
result = await super().phandle_block_header(height, cp_height)
|
||||
|
||||
# Older protocol versions don't truncate AuxPoW
|
||||
if self.protocol_tuple < (1, 4, 1):
|
||||
return result
|
||||
|
||||
# Not covered by a checkpoint; return full AuxPoW data
|
||||
if cp_height == 0:
|
||||
return result
|
||||
|
||||
# Covered by a checkpoint; truncate AuxPoW data
|
||||
result['header'] = self.truncate_auxpow_single(result['header'])
|
||||
return result
|
||||
|
||||
async def phandle_block_headers(self, start_height, count, cp_height=0):
|
||||
# Older protocol versions don't truncate AuxPoW
|
||||
if self.protocol_tuple < (1, 4, 1):
|
||||
return await super().phandle_block_headers(start_height, count, cp_height)
|
||||
|
||||
# Not covered by a checkpoint; return full AuxPoW data
|
||||
if cp_height == 0:
|
||||
return await super().phandle_block_headers(start_height, count, cp_height)
|
||||
|
||||
result = await super().block_headers_array(start_height, count, cp_height)
|
||||
|
||||
# Covered by a checkpoint; truncate AuxPoW data
|
||||
result['headers'] = self.truncate_auxpow_headers(result['headers'])
|
||||
|
||||
# Return headers in array form
|
||||
if self.protocol_tuple >= (1, 6):
|
||||
return result
|
||||
|
||||
# Return headers in concatenated form
|
||||
result['hex'] = ''.join(result['headers'])
|
||||
del result['headers']
|
||||
return result
|
||||
|
||||
def truncate_auxpow_headers(self, headers):
|
||||
result = []
|
||||
for header in headers:
|
||||
result.append(self.truncate_auxpow_single(header))
|
||||
return result
|
||||
|
||||
def truncate_auxpow_single(self, header: str):
|
||||
# 2 hex chars per byte
|
||||
return header[:2*self.coin.TRUNCATED_HEADER_SIZE]
|
||||
|
||||
|
||||
class NameIndexElectrumX(ElectrumX):
|
||||
def set_request_handlers(self, ptuple):
|
||||
super().set_request_handlers(ptuple)
|
||||
|
||||
if ptuple >= (1, 4, 3):
|
||||
self.request_handlers['blockchain.name.get_value_proof'] = self.phandle_name_get_value_proof
|
||||
|
||||
async def phandle_name_get_value_proof(self, scripthash, cp_height=0):
|
||||
history = await self.phandle_scripthash_get_history(scripthash)
|
||||
|
||||
trimmed_history = []
|
||||
prev_height = None
|
||||
|
||||
for update in history[::-1]:
|
||||
txid = update['tx_hash']
|
||||
height = update['height']
|
||||
|
||||
if (self.coin.NAME_EXPIRATION is not None
|
||||
and prev_height is not None
|
||||
and height < prev_height - self.coin.NAME_EXPIRATION):
|
||||
break
|
||||
|
||||
tx = await self.phandle_transaction_get(txid)
|
||||
update['tx'] = tx
|
||||
del update['tx_hash']
|
||||
|
||||
tx_merkle = await self.phandle_transaction_merkle(txid, height)
|
||||
del tx_merkle['block_height']
|
||||
update['tx_merkle'] = tx_merkle
|
||||
|
||||
if height <= cp_height:
|
||||
header = await self.phandle_block_header(height, cp_height)
|
||||
update['header'] = header
|
||||
|
||||
trimmed_history.append(update)
|
||||
|
||||
if height <= cp_height:
|
||||
break
|
||||
|
||||
prev_height = height
|
||||
|
||||
return {scripthash: trimmed_history}
|
||||
|
||||
|
||||
class NameIndexAuxPoWElectrumX(NameIndexElectrumX, AuxPoWElectrumX):
|
||||
pass
|
||||
Reference in new issue
Block a user