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