diff --git a/docs/protocol-changes.rst b/docs/protocol-changes.rst index 6094f24..54b8d10 100644 --- a/docs/protocol-changes.rst +++ b/docs/protocol-changes.rst @@ -133,6 +133,11 @@ Changes * Optional *cp_height* argument added to :func:`blockchain.block.header` and :func:`blockchain.block.headers` +New methods +----------- + + * :func:`blockchain.transaction.id_from_pos` + Removed methods --------------- diff --git a/docs/protocol-methods.rst b/docs/protocol-methods.rst index 3aeb094..c1cc056 100644 --- a/docs/protocol-methods.rst +++ b/docs/protocol-methods.rst @@ -671,6 +671,70 @@ and height. "pos": 710 } +blockchain.transaction.id_from_pos +================================== + +Return a transaction hash and optionally a merkle proof, +given a block height and a position in the block. + +**Signature** + + .. function:: blockchain.transaction.id_from_pos(height, tx_pos, merkle=False) + .. versionadded:: 1.4 + + *height* + + A block height in the main chain, an integer. + + *tx_pos* + + A zero-based index of the transaction in the given block, an integer. + + *merkle* + + Whether a merkle proof should also be returned, a boolean. + +**Result** + + If *merkle* is :const:`False`, the transaction hash as a hexadecimal string. + If :const:`True`, a dictionary with the following keys: + + * *tx_hash* + + The transaction hash as a hexadecimal string. + + * *merkle* + + A list of transaction hashes the current hash is paired with, + recursively, in order to trace up to obtain merkle root of the + block, deepest pairing first. + +**Example Results** + +When *merkle* is :const:`False`:: + + "fc12dfcb4723715a456c6984e298e00c479706067da81be969e8085544b0ba08" + +When *merkle* is :const:`True`:: + + { + "tx_hash": "fc12dfcb4723715a456c6984e298e00c479706067da81be969e8085544b0ba08", + "merkle": + [ + "928c4275dfd6270349e76aa5a49b355eefeb9e31ffbe95dd75fed81d219a23f8", + "5f35bfb3d5ef2ba19e105dcd976928e675945b9b82d98a93d71cbad0e714d04e", + "f136bcffeeed8844d54f90fc3ce79ce827cd8f019cf1d18470f72e4680f99207", + "6539b8ab33cedf98c31d4e5addfe40995ff96c4ea5257620dfbf86b34ce005ab", + "7ecc598708186b0b5bd10404f5aeb8a1a35fd91d1febbb2aac2d018954885b1e", + "a263aae6c470b9cde03b90675998ff6116f3132163911fafbeeb7843095d3b41", + "c203983baffe527edb4da836bc46e3607b9a36fa2c6cb60c1027f0964d971b29", + "306d89790df94c4632d652d142207f53746729a7809caa1c294b895a76ce34a9", + "c0b4eff21eea5e7974fe93c62b5aab51ed8f8d3adad4583c7a84a98f9e428f04", + "f0bd9d2d4c4cf00a1dd7ab3b48bbbb4218477313591284dcc2d7ca0aaa444e8d", + "503d3349648b985c1b571f59059e4da55a57b0163b08cc50379d73be80c4c8f3" + ] + } + mempool.get_fee_histogram ========================= diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 6a3cbb5..4412697 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -1068,6 +1068,17 @@ class ElectrumX(SessionBase): block = await self.daemon_request('deserialised_block', block_hash) return block_hash, block['tx'] + def _get_merkle_branch(self, tx_hashes, tx_pos): + '''Return a merkle branch to a transaction. + + tx_hashes: ordered list of hex strings of tx hashes in a block + tx_pos: index of transaction in tx_hashes to create branch for + ''' + hashes = [hex_str_to_hash(hash) for hash in tx_hashes] + branch, root = self.bp.merkle.branch_and_root(hashes, tx_pos) + branch = [hash_to_hex_str(hash) for hash in branch] + return branch + async def transaction_merkle(self, tx_hash, height): '''Return the markle tree to a confirmed transaction given its hash and height. @@ -1082,12 +1093,29 @@ class ElectrumX(SessionBase): except ValueError: raise RPCError(BAD_REQUEST, f'tx hash {tx_hash} not in ' f'block {block_hash} at height {height:,d}') + branch = self._get_merkle_branch(tx_hashes, pos) + return {"block_height": height, "merkle": branch, "pos": pos} - hashes = [hex_str_to_hash(hash) for hash in tx_hashes] - branch, root = self.bp.merkle.branch_and_root(hashes, pos) - branch = [hash_to_hex_str(hash) for hash in branch] + async def transaction_id_from_pos(self, height, tx_pos, merkle=False): + '''Return the txid and optionally a merkle proof, given + a block height and position in the block. + ''' + tx_pos = non_negative_integer(tx_pos) + if merkle not in (True, False): + raise RPCError(BAD_REQUEST, f'"merkle" must be a boolean') - return {"block_height": height, "merkle": branch, "pos": pos} + block_hash, tx_hashes = await self.block_hash_and_tx_hashes(height) + try: + tx_hash = tx_hashes[tx_pos] + except IndexError: + raise RPCError(BAD_REQUEST, f'no tx at position {tx_pos:,d} in ' + f'block {block_hash} at height {height:,d}') + branch = self._get_merkle_branch(tx_hashes, tx_pos) + + if merkle: + return {"tx_hash": tx_hash, "merkle": branch} + else: + return tx_hash def set_protocol_handlers(self, ptuple): self.protocol_tuple = ptuple @@ -1126,6 +1154,8 @@ class ElectrumX(SessionBase): 'blockchain.block.header': self.block_header, 'blockchain.block.headers': self.block_headers, 'blockchain.headers.subscribe': self.headers_subscribe, + 'blockchain.transaction.id_from_pos': + self.transaction_id_from_pos, }) elif ptuple >= (1, 3): handlers.update({