Implement script hash subscriptions
Best considered experimental Closes #124
This commit is contained in:
parent
eefa86ffbe
commit
68a8835db6
@ -667,22 +667,29 @@ class Controller(util.LoggedClass):
|
||||
# Helpers for RPC "blockchain" command handlers
|
||||
|
||||
def address_to_hashX(self, address):
|
||||
if isinstance(address, str):
|
||||
try:
|
||||
return self.coin.address_to_hashX(address)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
return self.coin.address_to_hashX(address)
|
||||
except Exception:
|
||||
pass
|
||||
raise RPCError('{} is not a valid address'.format(address))
|
||||
|
||||
def to_tx_hash(self, value):
|
||||
def script_hash_to_hashX(self, script_hash):
|
||||
try:
|
||||
bin_hash = hex_str_to_hash(script_hash)
|
||||
if len(bin_hash) == 32:
|
||||
return bin_hash[:self.coin.HASHX_LEN]
|
||||
except Exception:
|
||||
pass
|
||||
raise RPCError('{} is not a valid script hash'.format(script_hash))
|
||||
|
||||
def assert_tx_hash(self, value):
|
||||
'''Raise an RPCError if the value is not a valid transaction
|
||||
hash.'''
|
||||
if isinstance(value, str) and len(value) == 64:
|
||||
try:
|
||||
bytes.fromhex(value)
|
||||
return value
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
if len(bytes.fromhex(value)) == 32:
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
raise RPCError('{} should be a transaction hash'.format(value))
|
||||
|
||||
def non_negative_integer(self, value):
|
||||
@ -703,7 +710,7 @@ class Controller(util.LoggedClass):
|
||||
except DaemonError as e:
|
||||
raise RPCError('daemon error: {}'.format(e))
|
||||
|
||||
def new_subscription(self, address):
|
||||
def new_subscription(self):
|
||||
if self.subs_room <= 0:
|
||||
self.subs_room = self.max_subs - self.sub_count()
|
||||
if self.subs_room <= 0:
|
||||
@ -853,7 +860,7 @@ class Controller(util.LoggedClass):
|
||||
'''
|
||||
# For some reason Electrum passes a height. We don't require
|
||||
# it in anticipation it might be dropped in the future.
|
||||
tx_hash = self.to_tx_hash(tx_hash)
|
||||
self.assert_tx_hash(tx_hash)
|
||||
return await self.daemon_request('getrawtransaction', tx_hash)
|
||||
|
||||
async def transaction_get_merkle(self, tx_hash, height):
|
||||
@ -863,7 +870,7 @@ class Controller(util.LoggedClass):
|
||||
tx_hash: the transaction hash as a hexadecimal string
|
||||
height: the height of the block it is in
|
||||
'''
|
||||
tx_hash = self.to_tx_hash(tx_hash)
|
||||
self.assert_tx_hash(tx_hash)
|
||||
height = self.non_negative_integer(height)
|
||||
return await self.tx_merkle(tx_hash, height)
|
||||
|
||||
@ -876,7 +883,7 @@ class Controller(util.LoggedClass):
|
||||
# Used only for electrum client command-line requests. We no
|
||||
# longer index by address, so need to request the raw
|
||||
# transaction. So it works for any TXO not just UTXOs.
|
||||
tx_hash = self.to_tx_hash(tx_hash)
|
||||
self.assert_tx_hash(tx_hash)
|
||||
index = self.non_negative_integer(index)
|
||||
raw_tx = await self.daemon_request('getrawtransaction', tx_hash)
|
||||
if not raw_tx:
|
||||
|
||||
@ -121,6 +121,7 @@ class ElectrumX(SessionBase):
|
||||
'blockchain.address.subscribe': self.address_subscribe,
|
||||
'blockchain.headers.subscribe': self.headers_subscribe,
|
||||
'blockchain.numblocks.subscribe': self.numblocks_subscribe,
|
||||
'blockchain.script_hash.subscribe': self.script_hash_subscribe,
|
||||
'blockchain.transaction.broadcast': self.transaction_broadcast,
|
||||
'server.add_peer': self.add_peer,
|
||||
'server.banner': self.banner,
|
||||
@ -142,9 +143,9 @@ class ElectrumX(SessionBase):
|
||||
|
||||
matches = touched.intersection(self.hashX_subs)
|
||||
for hashX in matches:
|
||||
address = self.hashX_subs[hashX]
|
||||
alias = self.hashX_subs[hashX]
|
||||
status = await self.address_status(hashX)
|
||||
changed.append((address, status))
|
||||
changed.append((alias, status))
|
||||
|
||||
if height != self.notified_height:
|
||||
self.notified_height = height
|
||||
@ -161,11 +162,15 @@ class ElectrumX(SessionBase):
|
||||
old_status = self.mempool_statuses[hashX]
|
||||
status = await self.address_status(hashX)
|
||||
if status != old_status:
|
||||
address = self.hashX_subs[hashX]
|
||||
changed.append((address, status))
|
||||
alias = self.hashX_subs[hashX]
|
||||
changed.append((alias, status))
|
||||
|
||||
for address_status in changed:
|
||||
pairs.append(('blockchain.address.subscribe', address_status))
|
||||
for alias_status in changed:
|
||||
if len(alias_status[0]) == 64:
|
||||
method = 'blockchain.script_hash.subscribe'
|
||||
else:
|
||||
method = 'blockchain.address.subscribe'
|
||||
pairs.append((method, alias_status))
|
||||
|
||||
if pairs:
|
||||
self.send_notifications(pairs)
|
||||
@ -232,22 +237,31 @@ class ElectrumX(SessionBase):
|
||||
|
||||
return status
|
||||
|
||||
async def address_subscribe(self, address):
|
||||
'''Subscribe to an address.
|
||||
|
||||
address: the address to subscribe to'''
|
||||
async def hashX_subscribe(self, hashX, alias):
|
||||
# First check our limit.
|
||||
if len(self.hashX_subs) >= self.max_subs:
|
||||
raise RPCError('your address subscription limit {:,d} reached'
|
||||
.format(self.max_subs))
|
||||
|
||||
# Now let the controller check its limit
|
||||
self.controller.new_subscription(address)
|
||||
|
||||
hashX = self.env.coin.address_to_hashX(address)
|
||||
self.hashX_subs[hashX] = address
|
||||
self.controller.new_subscription()
|
||||
self.hashX_subs[hashX] = alias
|
||||
return await self.address_status(hashX)
|
||||
|
||||
async def address_subscribe(self, address):
|
||||
'''Subscribe to an address.
|
||||
|
||||
address: the address to subscribe to'''
|
||||
hashX = self.controller.address_to_hashX(address)
|
||||
return await self.hashX_subscribe(hashX, address)
|
||||
|
||||
async def script_hash_subscribe(self, script_hash):
|
||||
'''Subscribe to a script hash.
|
||||
|
||||
script_hash: the SHA256 hash of the script to subscribe to'''
|
||||
hashX = self.controller.script_hash_to_hashX(script_hash)
|
||||
return await self.hashX_subscribe(hashX, script_hash)
|
||||
|
||||
def server_features(self):
|
||||
'''Returns a dictionary of server features.'''
|
||||
return self.controller.peer_mgr.myself.features
|
||||
|
||||
Loading…
Reference in New Issue
Block a user