Browse Source

Implement script hash subscriptions

Best considered experimental

Closes #124
master
Neil Booth 8 years ago
parent
commit
68a8835db6
  1. 39
      server/controller.py
  2. 42
      server/session.py

39
server/controller.py

@ -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:

42
server/session.py

@ -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…
Cancel
Save