diff --git a/docs/protocol-changes.rst b/docs/protocol-changes.rst index 7acb856..a116453 100644 --- a/docs/protocol-changes.rst +++ b/docs/protocol-changes.rst @@ -52,6 +52,8 @@ Changes * :func:`blockchain.transaction.get` now has an optional parameter *verbose*. + * :func:`blockchain.headers.subscribe` now has an optional parameter + *raw*. New methods ----------- @@ -75,3 +77,4 @@ Deprecated methods :func:`blockchain.scripthash.listunspent`. * :func:`blockchain.address.subscribe`. Switch to :func:`blockchain.scripthash.subscribe`. + * :func:`blockchain.headers.subscribe` with *raw* other than :const:`True`. diff --git a/docs/protocol-methods.rst b/docs/protocol-methods.rst index cb5ed7e..d7ebe89 100644 --- a/docs/protocol-methods.rst +++ b/docs/protocol-methods.rst @@ -243,12 +243,51 @@ Subscribe to receive block headers when a new block is found. **Signature** - .. function:: blockchain.headers.subscribe() + .. function:: blockchain.headers.subscribe(raw=False) + .. versionchanged:: 1.2 + Optional *raw* parameter added. + + * *raw* + + :const:`False` or :const:`True`. The value :const:`False` is + deprecated. **Result** - The coin-specific :ref:`deserialized header ` - of the current block chain tip. + The header of the current block chain tip. If *raw* is + :const:`True` the result is a dictionary with two members: + + * *hex* + + The binary header as a hexadecimal string. + + * *height* + + The height of the header, an integer. + + If *raw* is :const:`False` the result is the coin-specific + :ref:`deserialized header `. + +**Example Result** + + With *raw* :const:`False`:: + + { + "bits": 402858285, + "block_height": 520481, + "merkle_root": "8e8e932eb858fd53cf09943d7efc9a8f674dc1363010ee64907a292d2fb0c25d", + "nonce": 3288656012, + "prev_block_hash": "000000000000000000b512b5d9fc7c5746587268547c04aa92383aaea0080289", + "timestamp": 1520495819, + "version": 536870912 + } + + With *raw* :const:`True`:: + + { + "height": 520481, + "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4" + } **Notifications** @@ -257,13 +296,14 @@ Subscribe to receive block headers when a new block is found. .. function:: blockchain.headers.subscribe(header) - * *header* The coin-specific :ref:`deserialized header - ` of the new block chain tip. + * *header* + + See **Result** above. .. note:: should a new block arrive quickly, perhaps while the server is still processing prior blocks, the server may only notify of the most recent chain tip. The protocol does not guarantee notification - of all intermediate blocks. + of all intermediate block headers. In a similar way the client must be prepared to handle chain reorganisations. Should a re-org happen the new chain tip will not @@ -294,13 +334,6 @@ Subscribe to receive the block height when a new block is found. .. function:: blockchain.numblocks.subscribe(height) -.. note:: should a new block arrive quickly, perhaps while the server - is still processing prior blocks, the server may only notify of the - most recent height. The protocol does not guarantee notification of - all intermediate block heights. Similarly if a chain reorganization - occurs resulting in the same chain height, the client may or may not - receive a notification. - blockchain.relayfee ------------------- @@ -724,8 +757,8 @@ Return the address paid to by a UTXO. **Signature** .. function:: blockchain.utxo.get_address(tx_hash, index) - *Optional in version 1.0.* - *Removed in version 1.1.* + + *Optional in version 1.0. Removed in version 1.1.* *tx_hash* diff --git a/server/controller.py b/server/controller.py index b77a228..f6a9b5e 100644 --- a/server/controller.py +++ b/server/controller.py @@ -297,17 +297,21 @@ class Controller(ServerBase): for session in self.sessions: session.notify_peers(updates) - def electrum_header(self, height): + def raw_header(self, height): '''Return the binary header at the given height.''' - if height in self.header_cache: - return self.header_cache[height] header, n = self.bp.read_headers(height, 1) if n != 1: raise RPCError('height {:,d} out of range'.format(height)) - header = self.coin.electrum_header(header, height) - self.header_cache[height] = header return header + def electrum_header(self, height): + '''Return the deserialized header at the given height.''' + if height not in self.header_cache: + raw_header = self.raw_header(height) + self.header_cache[height] = self.coin.electrum_header(raw_header, + height) + return self.header_cache[height] + def session_delay(self, session): priority = self.session_priority(session) excess = max(0, priority - self.BANDS) diff --git a/server/session.py b/server/session.py index fbf4865..09391ad 100644 --- a/server/session.py +++ b/server/session.py @@ -108,6 +108,7 @@ class ElectrumX(SessionBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.subscribe_headers = False + self.subscribe_headers_raw = False self.subscribe_height = False self.notified_height = None self.max_send = self.env.max_send @@ -164,7 +165,7 @@ class ElectrumX(SessionBase): if height_changed: self.notified_height = height if self.subscribe_headers: - args = (self.controller.electrum_header(height), ) + args = (self.subscribe_headers_result(height), ) self.send_notification('blockchain.headers.subscribe', args) if self.subscribe_height: args = (height, ) @@ -180,12 +181,25 @@ class ElectrumX(SessionBase): '''Return the current flushed database height.''' return self.bp.db_height - def headers_subscribe(self): + def assert_boolean(self, value): + '''Return param value it is boolean otherwise raise an RPCError.''' + if value in (False, True): + return value + raise RPCError('{} should be a boolean value'.format(value)) + + def subscribe_headers_result(self, height): + '''The result of a header subscription for the given height.''' + if self.subscribe_headers_raw: + raw_header = self.controller.raw_header(height) + return {'hex': raw_header.hex(), 'height': height} + return self.controller.electrum_header(height) + + def headers_subscribe(self, raw=False): '''Subscribe to get headers of new blocks.''' self.subscribe_headers = True - height = self.height() - self.notified_height = height - return self.controller.electrum_header(height) + self.subscribe_headers_raw = self.assert_boolean(raw) + self.notified_height = self.height() + return self.subscribe_headers_result(self.height()) def numblocks_subscribe(self): '''Subscribe to get height of new blocks.'''