コード例 #1
0
        def fetch_transacton_timestamps(result):
            if result.creation_transaction.unconfirmed:
                return result  # return as is
            ps = [self._block_get_by_hash(result.creation_transaction.blockid)]
            if result.spend_transaction != None and not result.spend_transaction.unconfirmed:
                ps.append(
                    self._block_get_by_hash(result.spend_transaction.blockid))
            p = jsasync.wait(*ps)

            def aggregate(results):
                if len(results) == 1:
                    # assign just the creation transacton timestamp
                    _, block = results[0]
                    _assign_block_properties_to_transacton(
                        result.creation_transaction, block)
                    return result
                # assign both creation- and spend transaction timestamp
                _, block_a = results[0]
                _, block_b = results[1]
                if block_a.id.__ne__(result.creation_transaction.blockid):
                    block_c = block_a
                    block_a = block_b
                    block_b = block_c
                _assign_block_properties_to_transacton(
                    result.creation_transaction, block_a)
                _assign_block_properties_to_transacton(
                    result.spend_transaction, block_b)
                return result

            return jsasync.chain(p, aggregate)
コード例 #2
0
    def transaction_put(self, transaction):
        """
        Submit a transaction to an available explorer Node.

        :param transaction: the transaction to push to the client transaction pool
        """
        if isinstance(transaction, TransactionBaseClass):
            transaction = transaction.json()
        if not jsobj.is_js_obj(transaction):
            raise TypeError("transaction is of an invalid type {}".format(
                type(transaction)))

        endpoint = "/transactionpool/transactions"

        def cb(result):
            _, transaction = result
            try:
                return Hash(value=transaction['transactionid']).__str__()
            except (KeyError, ValueError, TypeError) as exc:
                # return a KeyError as an invalid Explorer Response
                raise tferrors.ExplorerInvalidResponse(str(exc), endpoint,
                                                       transaction) from exc

        return jsasync.chain(
            self.explorer_post(endpoint=endpoint, data=transaction), cb)
コード例 #3
0
        def fetch_transacton_block(result):
            transactions = {}
            for transaction in result.transactions:
                if not transaction.unconfirmed:
                    bid = transaction.blockid.__str__()
                    if bid not in transactions:
                        transactions[bid] = []
                    transactions[bid].append(transaction)
            if len(transactions) == 0:
                return result  # return as is, nothing to do

            def generator():
                for blockid in jsobj.get_keys(transactions):
                    yield self._block_get_by_hash(blockid)

            def result_cb(block_result):
                _, block = block_result
                for transaction in transactions[block.get_or('blockid', '')]:
                    _assign_block_properties_to_transacton(transaction, block)

            def aggregate():
                return result

            return jsasync.chain(
                jsasync.promise_pool_new(generator, cb=result_cb), aggregate)
コード例 #4
0
    def unconfirmed_transactions_get(self):
        """
        Get all unconfirmed transactions from an available Explorer node.
        """
        endpoint = "/transactionpool/transactions"

        def cb(result):
            _, resp = result
            try:
                # parse the unconfirmed transactions
                unconfirmed_transactions = []
                resp_transactions = resp['transactions']
                if resp_transactions != None and jsobj.is_js_arr(
                        resp_transactions):
                    for etxn in resp_transactions:
                        # parse the (raw) transaction
                        transaction = transactions.from_json(obj=etxn)
                        # compute the transactionID manually
                        transaction.id = transaction.transaction_id_new()
                        # force it to be unconfirmed
                        transaction.unconfirmed = True
                        # append the transaction to the list of transactions
                        unconfirmed_transactions.append(transaction)
                return unconfirmed_transactions
            except (KeyError, ValueError, TypeError) as exc:
                # return a KeyError as an invalid Explorer Response
                raise tferrors.ExplorerInvalidResponse(str(exc), endpoint,
                                                       transaction) from exc

        return jsasync.chain(self.explorer_get(endpoint=endpoint), cb)
コード例 #5
0
    def condition_get(self, height=None):
        """
        Get the latest (coin) mint condition or the (coin) mint condition at the specified block height.

        @param height: if defined the block height at which to look up the (coin) mint condition (if none latest block will be used)
        """
        # define the endpoint
        endpoint = "/explorer/mintcondition"
        if height != None:
            if not isinstance(height, (int, str)):
                raise TypeError("invalid block height given")
            if isinstance(height, str):
                height = jsstr.to_int(height)
            endpoint += "/{}".format(height)

        # define the cb to get the mint condition
        def cb(result):
            _, resp = result
            try:
                # return the decoded mint condition
                return ConditionTypes.from_json(obj=resp['mintcondition'])
            except KeyError as exc:
                # return a KeyError as an invalid Explorer Response
                raise tferrors.ExplorerInvalidResponse(str(exc), endpoint,
                                                       resp) from exc

        # get + parse the mint condition as a promise
        return jsasync.chain(self._client.explorer_get(endpoint=endpoint), cb)
コード例 #6
0
    def blockchain_info_get(self):
        """
        Get the current blockchain info, using the last known block, as reported by an explorer.
        """
        def get_block(result):
            used_addr, raw_block = result
            address = used_addr

            def get_block_with_tag(result):
                return ('b', (address, result))

            blockid = Hash.from_json(obj=raw_block['blockid'])
            return jsasync.chain(self.block_get(blockid), get_block_with_tag)

        def get_constants_with_tag(result):
            return ('c', result)

        def get_info(results):
            if len(results) != 2:
                raise RuntimeError(
                    "expected 2 values as result, but received: {}".format(
                        results))
            d = dict(results)
            _, raw_constants = d['c']
            info = raw_constants['chaininfo']
            constants = BlockchainConstants(
                info['Name'],
                info['ChainVersion'],
                info['NetworkName'],
            )
            address, last_block = d['b']
            return ExplorerBlockchainInfo(
                constants=constants,
                last_block=last_block,
                explorer_address=address,
            )

        return jsasync.chain(
            jsasync.wait(
                jsasync.chain(self.explorer_get(endpoint="/explorer"),
                              get_block),
                jsasync.chain(
                    self.explorer_get(endpoint="/explorer/constants"),
                    get_constants_with_tag),
            ), get_info)
コード例 #7
0
 def f(reason):
     if isinstance(reason, tferrors.ExplorerUserError):
         raise reason  # no need to retry user errors
     jslog.debug(
         "retrying on another server, previous POST call failed: {}"
         .format(reason))
     # do the request and check the response
     return jsasync.chain(
         jshttp.http_post(address, endpoint, s, headers), resolve)
コード例 #8
0
        def get_block(result):
            used_addr, raw_block = result
            address = used_addr

            def get_block_with_tag(result):
                return ('b', (address, result))

            blockid = Hash.from_json(obj=raw_block['blockid'])
            return jsasync.chain(self.block_get(blockid), get_block_with_tag)
コード例 #9
0
        def fetch_transacton_timestamps(result):
            _, transaction = result
            p = self._block_get_by_hash(transaction.blockid)

            def aggregate(result):
                _, block = result
                _assign_block_properties_to_transacton(transaction, block)
                return transaction

            return jsasync.chain(p, aggregate)
コード例 #10
0
    def _block_get_by_height(self, value):
        endpoint = "/explorer/blocks/{}".format(value)

        def get_block_prop(result):
            _, result = result
            block = result.get_or('block', None)
            if block == None:
                raise tferrors.ExplorerInvalidResponse(
                    "block property is undefined", endpoint, result)
            return (endpoint, block)

        return jsasync.chain(self.explorer_get(endpoint=endpoint),
                             get_block_prop)
コード例 #11
0
 def block_get(self, value):
     """
     Get a block from an available explorer Node.
     
     @param value: the identifier or height that points to the desired block
     """
     p = None
     # get the explorer block
     if isinstance(value, int):
         p = self._block_get_by_height(value)
     else:
         p = self._block_get_by_hash(value)
     return jsasync.chain(p, self._block_get_parse_cb)
コード例 #12
0
    def data_get(self, endpoint):
        """
        get data from an explorer at the endpoint from any explorer that is available
        on one of the given urls. The list of urls is traversed in random order until
        an explorer returns with a 200 OK status.

        @param endpoint: the endpoint to get the data from
        """
        # if we need to update our consensus about the explorers, do this first
        cp = self._update_consensus_if_needed()
        if cp == None:
            return self._data_get_body(endpoint)
        return jsasync.chain(cp, lambda _: self._data_get_body(endpoint))
コード例 #13
0
    def _update_consensus_if_needed(self):
        now = int(datetime.now().timestamp())
        if now - self._last_consensus_update_time < 60 * 5:
            return None  # nothing to do
        self._last_consensus_update_time = now

        # probe all explorer nodes for consensus
        def generator():
            for addr in self._addresses:
                yield jsasync.catch_promise(
                    jsasync.chain(jshttp.http_get(addr, "/explorer"),
                                  self._height_result_cb),
                    self._height_error_cb_new(addr))

        def result_cb(results):
            d = jsobj.new_dict()
            a = {}
            for addr, height in results:
                if height not in a:
                    a[height] = []
                a[height].append(addr)
                d[height] = d.get_or(height, 0) + 1
            c_height = -1
            c_height_votes = -1
            for height, votes in jsobj.get_items(d):
                if votes > c_height_votes or (votes == c_height_votes
                                              and height > c_height):
                    c_height = height
                    c_height_votes = votes
            if c_height == -1:
                jslog.error(
                    "update_consensus of explorer (HTTP): no explorer addresses are available"
                )
                # assign all addresses and hope for the best
                all_addresses = []
                for _, addresses in jsobj.get_items(a):
                    all_addresses = jsarr.concat(all_addresses, addresses)
                self._consensus_addresses = addresses
            else:
                # select the explorer addresses with the desired height
                self._consensus_addresses = a[c_height]

        return jsasync.chain(jsasync.promise_pool_new(generator), result_cb)
コード例 #14
0
    def transaction_get(self, txid):
        """
        Get a transaction from an available explorer Node.

        @param txid: the identifier (bytes, bytearray, hash or string) that points to the desired transaction
        """
        txid = self._normalize_id(txid)
        endpoint = "/explorer/hashes/" + txid

        def cb(result):
            _, result = result
            try:
                if result['hashtype'] != 'transactionid':
                    raise tferrors.ExplorerInvalidResponse(
                        "expected hash type 'transactionid' not '{}'".format(
                            result['hashtype']), endpoint, result)
                txnresult = result['transaction']
                if txnresult['id'] != txid:
                    raise tferrors.ExplorerInvalidResponse(
                        "expected transaction ID '{}' not '{}'".format(
                            txid, txnresult['id']), endpoint, result)
                return self._transaction_from_explorer_transaction(
                    txnresult, endpoint=endpoint, resp=result)
            except KeyError as exc:
                # return a KeyError as an invalid Explorer Response
                raise tferrors.ExplorerInvalidResponse(str(exc), endpoint,
                                                       result) from exc

        # fetch timestamps seperately
        # TODO: make a pull request in Rivine to return [parent] block optionally with (or instead of) transaction.
        def fetch_transacton_timestamps(result):
            _, transaction = result
            p = self._block_get_by_hash(transaction.blockid)

            def aggregate(result):
                _, block = result
                _assign_block_properties_to_transacton(transaction, block)
                return transaction

            return jsasync.chain(p, aggregate)

        return jsasync.chain(self.explorer_get(endpoint=endpoint), cb,
                             fetch_transacton_timestamps)
コード例 #15
0
    def _block_get_by_hash(self, value):
        blockid = self._normalize_id(value)
        endpoint = "/explorer/hashes/" + blockid

        def get_block_prop(result):
            _, result = result
            try:
                if result['hashtype'] != 'blockid':
                    raise tferrors.ExplorerInvalidResponse(
                        "expected hash type 'blockid' not '{}'".format(
                            result['hashtype']), endpoint, result)
                block = result['block']
                if block['blockid'] != blockid:
                    raise tferrors.ExplorerInvalidResponse(
                        "expected block ID '{}' not '{}'".format(
                            blockid.__str__(), block['blockid']), endpoint,
                        result)
                return (endpoint, block)
            except KeyError as exc:
                raise tferrors.ExplorerInvalidResponse(str(exc), endpoint,
                                                       result) from exc

        return jsasync.chain(self.explorer_get(endpoint=endpoint),
                             get_block_prop)
コード例 #16
0
    def _data_get_body(self, endpoint):
        indices = list(range(len(self._consensus_addresses)))
        random.shuffle(indices)

        def resolve(result):
            if result.code == 200:
                return (result.address, jsobj.as_dict(result.data))
            if result.code == 204 or (result.code == 400 and
                                      ('unrecognized hash' in result.data
                                       or 'not found' in result.data)):
                raise tferrors.ExplorerNoContent(
                    "GET: no content available (code: 204)", endpoint)
            if result.code == 400:  # are there other error codes?
                raise tferrors.ExplorerBadRequest(
                    "error (code: {}): {}".format(result.code, result.data),
                    endpoint)
            raise tferrors.ExplorerServerError(
                "error (code: {}): {}".format(result.code, result.data),
                endpoint)

        address = self._consensus_addresses[indices[0]]
        if not isinstance(address, str):
            raise TypeError(
                "explorer address expected to be a string, not {}".format(
                    type(address)))
        # do the request and check the response
        p = jsasync.chain(jshttp.http_get(address, endpoint), resolve)

        # factory for our fallback callbacks
        # called to try on another server in case
        # a non-user error occured on the previous one
        def create_fallback_catch_cb(address):
            def f(reason):
                if isinstance(reason, tferrors.ExplorerUserError):
                    raise reason  # no need to retry user errors
                jslog.debug(
                    "retrying on another server, previous GET call failed: {}".
                    format(reason))
                # do the request and check the response
                return jsasync.chain(jshttp.http_get(address, endpoint),
                                     resolve)

            return f

        # for any remaining index, do the same logic, but as a chained catch
        for idx in indices[1:]:
            address = self._consensus_addresses[idx]
            if not isinstance(address, str):
                raise TypeError(
                    "explorer address expected to be a string, not {}".format(
                        type(address)))
            cb = create_fallback_catch_cb(address)
            p = jsasync.catch_promise(p, cb)

        # define final catch cb, as a last fallback
        def final_catch(reason):
            # pass on user errors
            if isinstance(reason, tferrors.ExplorerUserError):
                raise reason  # no need to retry user errors
            jslog.debug(
                "servers exhausted, previous GET call failed as well: {}".
                format(reason))
            raise tferrors.ExplorerNotAvailable("no explorer was available",
                                                endpoint,
                                                self._consensus_addresses)

        # return the final promise chain
        return jsasync.catch_promise(p, final_catch)
コード例 #17
0
 def generator():
     for addr in self._addresses:
         yield jsasync.catch_promise(
             jsasync.chain(jshttp.http_get(addr, "/explorer"),
                           self._height_result_cb),
             self._height_error_cb_new(addr))
コード例 #18
0
    def _output_get(self, id, expected_hash_type):
        """
        Get an output from an available explorer Node.

        Returns (output, creation_txn, spend_txn).

        @param id: the identifier (bytes, bytearray, hash or string) that points to the desired output
        @param expected_hash_type: one of ('coinoutputid', 'blockstakeoutputid')
        """
        if expected_hash_type not in ('coinoutputid', 'blockstakeoutputid'):
            raise ValueError(
                "expected hash type should be one of ('coinoutputid', 'blockstakeoutputid'), not {}"
                .format(expected_hash_type))
        id = self._normalize_id(id)
        endpoint = "/explorer/hashes/" + id

        def cb(result):
            _, result = result
            try:
                hash_type = result['hashtype']
                if hash_type != expected_hash_type:
                    raise tferrors.ExplorerInvalidResponse(
                        "expected hash type '{}', not '{}'".format(
                            expected_hash_type, hash_type), endpoint, result)
                tresp = result['transactions']
                lresp = len(tresp)
                if lresp not in (1, 2):
                    raise tferrors.ExplorerInvalidResponse(
                        "expected one or two transactions to be returned, not {}"
                        .format(lresp), endpoint, result)
                # parse the transaction(s)
                creation_txn = tresp[0]
                spend_txn = None
                if lresp == 2:
                    if tresp[1]['height'] > creation_txn['height']:
                        spend_txn = tresp[1]
                    else:
                        spend_txn = creation_txn
                        creation_txn = tresp[1]
                creation_txn = self._transaction_from_explorer_transaction(
                    creation_txn, endpoint=endpoint, resp=result)
                if spend_txn != None:
                    spend_txn = self._transaction_from_explorer_transaction(
                        spend_txn, endpoint=endpoint, resp=result)
                # collect the output
                output = None
                for out in (creation_txn.coin_outputs
                            if hash_type == 'coinoutputid' else
                            creation_txn.blockstake_outputs):
                    if str(out.id) == id:
                        output = out
                        break
                if output == None:
                    raise tferrors.ExplorerInvalidResponse(
                        "expected output {} to be part of creation Tx, but it wasn't"
                        .format(id), endpoint, result)
                # return the output and related transaction(s)
                return ExplorerOutputResult(output, creation_txn, spend_txn)
            except KeyError as exc:
                # return a KeyError as an invalid Explorer Response
                raise tferrors.ExplorerInvalidResponse(str(exc), endpoint,
                                                       result) from exc

        # fetch timestamps seperately
        # TODO: make a pull request in Rivine to return timestamps together with regular results,
        #       as it is rediculous to have to do this
        def fetch_transacton_timestamps(result):
            if result.creation_transaction.unconfirmed:
                return result  # return as is
            ps = [self._block_get_by_hash(result.creation_transaction.blockid)]
            if result.spend_transaction != None and not result.spend_transaction.unconfirmed:
                ps.append(
                    self._block_get_by_hash(result.spend_transaction.blockid))
            p = jsasync.wait(*ps)

            def aggregate(results):
                if len(results) == 1:
                    # assign just the creation transacton timestamp
                    _, block = results[0]
                    _assign_block_properties_to_transacton(
                        result.creation_transaction, block)
                    return result
                # assign both creation- and spend transaction timestamp
                _, block_a = results[0]
                _, block_b = results[1]
                if block_a.id.__ne__(result.creation_transaction.blockid):
                    block_c = block_a
                    block_a = block_b
                    block_b = block_c
                _assign_block_properties_to_transacton(
                    result.creation_transaction, block_a)
                _assign_block_properties_to_transacton(
                    result.spend_transaction, block_b)
                return result

            return jsasync.chain(p, aggregate)

        # return as chained promise
        return jsasync.chain(self.explorer_get(endpoint=endpoint), cb,
                             fetch_transacton_timestamps)
コード例 #19
0
    def _data_post_body(self, endpoint, data):
        indices = list(range(len(self._consensus_addresses)))
        random.shuffle(indices)

        headers = {
            'Content-Type': 'Application/json;charset=UTF-8',
        }
        s = data
        if not isinstance(s, str):
            s = jsjson.json_dumps(s)

        def resolve(result):
            if result.code == 200:
                return (result.address, jsobj.as_dict(result.data))
            if result.code == 400:  # are there other error codes?
                jslog.warning(
                    "invalid data object posted to {}:".format(endpoint), s)
                raise tferrors.ExplorerBadRequest(
                    "error (code: {}): {}".format(result.code, result.data),
                    endpoint)
            raise tferrors.ExplorerServerPostError(
                "POST: unexpected error (code: {}): {}".format(
                    result.code, result.data),
                endpoint,
                data=data)

        address = self._consensus_addresses[indices[0]]
        if not isinstance(address, str):
            raise TypeError(
                "explorer address expected to be a string, not {}".format(
                    type(address)))
        # do the request and check the response
        p = jsasync.chain(jshttp.http_post(address, endpoint, s, headers),
                          resolve)

        # factory for our fallback callbacks
        # called to try on another server in case
        # a non-user error occured on the previous one
        def create_fallback_catch_cb(address):
            def f(reason):
                if isinstance(reason, tferrors.ExplorerUserError):
                    raise reason  # no need to retry user errors
                jslog.debug(
                    "retrying on another server, previous POST call failed: {}"
                    .format(reason))
                # do the request and check the response
                return jsasync.chain(
                    jshttp.http_post(address, endpoint, s, headers), resolve)

            return f

        # for any remaining index, do the same logic, but as a chained catch
        for idx in indices[1:]:
            address = self._consensus_addresses[idx]
            if not isinstance(address, str):
                raise TypeError(
                    "explorer address expected to be a string, not {}".format(
                        type(address)))
            cb = create_fallback_catch_cb(address)
            p = jsasync.catch_promise(p, cb)

        # define final catch cb, as a last fallback
        def final_catch(reason):
            # pass on user errors
            if isinstance(reason, tferrors.ExplorerUserError):
                raise reason  # no need to retry user errors
            jslog.debug(
                "servers exhausted, previous POST call failed as well: {}".
                format(reason))
            raise tferrors.ExplorerNotAvailable("no explorer was available",
                                                endpoint,
                                                self._consensus_addresses)

        # return the final promise chain
        return jsasync.catch_promise(p, final_catch)
コード例 #20
0
    def unlockhash_get(self, target):
        """
        Get all transactions linked to the given unlockhash (target),
        as well as other information such as the multisig addresses linked to the given unlockhash (target).

        target can be any of:
            - None: unlockhash of the Free-For-All wallet will be used
            - str (or unlockhash/bytes/bytearray): target is assumed to be the unlockhash of a personal wallet
            - list: target is assumed to be the addresses of a MultiSig wallet where all owners (specified as a list of addresses) have to sign
            - tuple (addresses, sigcount): target is a sigcount-of-addresscount MultiSig wallet

        @param target: the target wallet to look up transactions for in the explorer, see above for more info
        """
        unlockhash = ConditionTypes.from_recipient(target).unlockhash.__str__()
        endpoint = "/explorer/hashes/" + unlockhash

        def catch_no_content(reason):
            if isinstance(reason, tferrors.ExplorerNoContent):
                return ExplorerUnlockhashResult(
                    unlockhash=UnlockHash.from_json(unlockhash),
                    transactions=[],
                    multisig_addresses=None,
                    erc20_info=None,
                )
            # pass on any other reason
            raise reason

        def cb(result):
            _, resp = result
            try:
                if resp['hashtype'] != 'unlockhash':
                    raise tferrors.ExplorerInvalidResponse(
                        "expected hash type 'unlockhash' not '{}'".format(
                            resp['hashtype']), endpoint, resp)
                # parse the transactions
                transactions = []
                resp_transactions = resp['transactions']
                if resp_transactions != None and jsobj.is_js_arr(
                        resp_transactions):
                    for etxn in resp_transactions:
                        # parse the explorer transaction
                        transaction = self._transaction_from_explorer_transaction(
                            etxn, endpoint=endpoint, resp=resp)
                        # append the transaction to the list of transactions
                        transactions.append(transaction)
                # collect all multisig addresses
                multisig_addresses = [
                    UnlockHash.from_json(obj=uh)
                    for uh in resp.get_or('multisigaddresses', None) or []
                ]
                for addr in multisig_addresses:
                    if addr.uhtype.__ne__(UnlockHashType.MULTI_SIG):
                        raise tferrors.ExplorerInvalidResponse(
                            "invalid unlock hash type {} for MultiSignature Address (expected: 3)"
                            .format(addr.uhtype.value), endpoint, resp)
                erc20_info = None
                if 'erc20info' in resp:
                    info = resp['erc20info']
                    erc20_info = ERC20AddressInfo(
                        address_tft=UnlockHash.from_json(info['tftaddress']),
                        address_erc20=ERC20Address.from_json(
                            info['erc20address']),
                        confirmations=int(info['confirmations']),
                    )

                # sort the transactions by height
                def txn_arr_sort(a, b):
                    height_a = pow(2, 64) if a.height < 0 else a.height
                    height_b = pow(2, 64) if b.height < 0 else b.height
                    if height_a < height_b:
                        return -1
                    if height_a > height_b:
                        return 1
                    tx_order_a = pow(
                        2,
                        64) if a.transaction_order < 0 else a.transaction_order
                    tx_order_b = pow(
                        2,
                        64) if b.transaction_order < 0 else b.transaction_order
                    if tx_order_a < tx_order_b:
                        return -1
                    if tx_order_a > tx_order_b:
                        return 1
                    return 0

                transactions = jsarr.sort(transactions,
                                          txn_arr_sort,
                                          reverse=True)

                # return explorer data for the unlockhash
                return ExplorerUnlockhashResult(
                    unlockhash=UnlockHash.from_json(unlockhash),
                    transactions=transactions,
                    multisig_addresses=multisig_addresses,
                    erc20_info=erc20_info,
                )
            except KeyError as exc:
                # return a KeyError as an invalid Explorer Response
                raise tferrors.ExplorerInvalidResponse(str(exc), endpoint,
                                                       resp) from exc

        # fetch timestamps seperately
        # TODO: make a pull request in Rivine to return timestamps together with regular results,
        #       as it is rediculous to have to do this
        def fetch_transacton_block(result):
            transactions = {}
            for transaction in result.transactions:
                if not transaction.unconfirmed:
                    bid = transaction.blockid.__str__()
                    if bid not in transactions:
                        transactions[bid] = []
                    transactions[bid].append(transaction)
            if len(transactions) == 0:
                return result  # return as is, nothing to do

            def generator():
                for blockid in jsobj.get_keys(transactions):
                    yield self._block_get_by_hash(blockid)

            def result_cb(block_result):
                _, block = block_result
                for transaction in transactions[block.get_or('blockid', '')]:
                    _assign_block_properties_to_transacton(transaction, block)

            def aggregate():
                return result

            return jsasync.chain(
                jsasync.promise_pool_new(generator, cb=result_cb), aggregate)

        return jsasync.catch_promise(
            jsasync.chain(self.explorer_get(endpoint=endpoint), cb,
                          fetch_transacton_block), catch_no_content)