diff --git a/server/controller.py b/server/controller.py index 914f879..067cb95 100644 --- a/server/controller.py +++ b/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: diff --git a/server/session.py b/server/session.py index 2e2d258..2b2a5c7 100644 --- a/server/session.py +++ b/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