def summaries(self):
        # Return lists of (tx_hash, fee, has_unconfirmed_inputs) by hashX
        summaries = defaultdict(list)
        utxos = self.mempool_utxos()
        for tx_hash, tx in self.txs.items():
            fee = 0
            hashXs = set()
            has_ui = False
            for n, input in enumerate(tx.inputs):
                if is_gen_outpoint(input.prev_hash, input.prev_idx):
                    continue
                has_ui = has_ui or (input.prev_hash in self.txs)
                prevout = (input.prev_hash, input.prev_idx)
                if prevout in utxos:
                    hashX, value = utxos[prevout]
                else:
                    hashX, value = self.db_utxos[prevout]
                hashXs.add(hashX)
                fee += value

            for output in tx.outputs:
                hashXs.add(coin.hashX_from_script(output.pk_script))
                fee -= output.value

            summary = (tx_hash, fee, has_ui)
            for hashX in hashXs:
                summaries[hashX].append(summary)
        return summaries
async def test_notifications():
    # Tests notifications over a cycle of:
    # 1) A first batch of txs come in
    # 2) A second batch of txs come in
    # 3) A block comes in confirming the first batch only
    api = API()
    api.initialize()
    mempool = MemPool(coin, api, refresh_secs=0.001, log_status_secs=0)
    event = Event()

    n = len(api.ordered_adds) // 2
    raw_txs = api.raw_txs.copy()
    txs = api.txs.copy()
    first_hashes = api.ordered_adds[:n]
    first_touched = api.touched(first_hashes)
    second_hashes = api.ordered_adds[n:]
    second_touched = api.touched(second_hashes)

    async with TaskGroup() as group:
        # First batch enters the mempool
        api.raw_txs = {hash: raw_txs[hash] for hash in first_hashes}
        api.txs = {hash: txs[hash] for hash in first_hashes}
        first_utxos = api.mempool_utxos()
        first_spends = api.mempool_spends()
        await group.spawn(mempool.keep_synchronized, event)
        await event.wait()
        assert len(api.on_mempool_calls) == 1
        touched, height = api.on_mempool_calls[0]
        assert height == api._height == api._cached_height
        assert touched == first_touched
        # Second batch enters the mempool
        api.raw_txs = raw_txs
        api.txs = txs
        await event.wait()
        assert len(api.on_mempool_calls) == 2
        touched, height = api.on_mempool_calls[1]
        assert height == api._height == api._cached_height
        # Touched is incremental
        assert touched == second_touched
        # Block found; first half confirm
        new_height = 2
        api._height = new_height
        api.db_utxos.update(first_utxos)
        for spend in first_spends:
            if is_gen_outpoint(*spend):
                continue
            del api.db_utxos[spend]
        api.raw_txs = {hash: raw_txs[hash] for hash in second_hashes}
        api.txs = {hash: txs[hash] for hash in second_hashes}
        await event.wait()
        assert len(api.on_mempool_calls) == 3
        touched, height = api.on_mempool_calls[2]
        assert height == api._height == api._cached_height == new_height
        assert touched == first_touched
        await group.cancel_remaining()
 def spends(self):
     # Return spends indexed by hashX
     spends = defaultdict(list)
     utxos = self.mempool_utxos()
     for tx_hash, tx in self.txs.items():
         for n, input in enumerate(tx.inputs):
             prevout = (input.prev_hash, input.prev_idx)
             if is_gen_outpoint(input.prev_hash, input.prev_idx):
                 continue
             if prevout in utxos:
                 hashX, value = utxos.pop(prevout)
             else:
                 hashX, value = self.db_utxos[prevout]
             spends[hashX].append(prevout)
     return spends
Example #4
0
    def advance_txs(self, txs):
        self.tx_hashes.append(b''.join(tx_hash for tx, tx_hash in txs))

        # Use local vars for speed in the loops
        undo_info = []
        tx_num = self.tx_count
        script_hashX = self.coin.hashX_from_script
        s_pack = pack
        put_utxo = self.utxo_cache.__setitem__
        spend_utxo = self.spend_utxo
        undo_info_append = undo_info.append
        update_touched = self.touched.update
        hashXs_by_tx = []
        append_hashXs = hashXs_by_tx.append

        for tx, tx_hash in txs:
            hashXs = []
            append_hashX = hashXs.append
            tx_numb = s_pack('<I', tx_num)

            # Spend the inputs
            for txin in tx.inputs:
                if is_gen_outpoint(txin.prev_hash, txin.prev_idx):
                    continue
                cache_value = spend_utxo(txin.prev_hash, txin.prev_idx)
                undo_info_append(cache_value)
                append_hashX(cache_value[:-12])

            # Add the new UTXOs
            for idx, txout in enumerate(tx.outputs):
                # Get the hashX.  Ignore unspendable outputs
                hashX = script_hashX(txout.pk_script)
                if hashX:
                    append_hashX(hashX)
                    put_utxo(tx_hash + s_pack('<H', idx),
                             hashX + tx_numb + s_pack('<Q', txout.value))

            append_hashXs(hashXs)
            update_touched(hashXs)
            tx_num += 1

        self.db.history.add_unflushed(hashXs_by_tx, self.tx_count)

        self.tx_count = tx_num
        self.db.tx_counts.append(tx_num)

        return undo_info
Example #5
0
    def _accept_transactions(self, tx_map, utxo_map, touched):
        '''Accept transactions in tx_map to the mempool if all their inputs
        can be found in the existing mempool or a utxo_map from the
        DB.

        Returns an (unprocessed tx_map, unspent utxo_map) pair.
        '''
        hashXs = self.hashXs
        txs = self.txs

        deferred = {}
        unspent = set(utxo_map)
        # Try to find all prevouts so we can accept the TX
        for hash, tx in tx_map.items():
            in_pairs = []
            try:
                for prevout in tx.prevouts:
                    # Skip generation like prevouts
                    if is_gen_outpoint(*prevout):
                        continue
                    utxo = utxo_map.get(prevout)
                    if not utxo:
                        prev_hash, prev_index = prevout
                        # Raises KeyError if prev_hash is not in txs
                        utxo = txs[prev_hash].out_pairs[prev_index]
                    in_pairs.append(utxo)
            except KeyError:
                deferred[hash] = tx
                continue

            # Spend the prevouts
            unspent.difference_update(tx.prevouts)

            # Save the in_pairs, compute the fee and accept the TX
            tx.in_pairs = tuple(in_pairs)
            # Avoid negative fees if dealing with generation-like transactions
            # because some in_parts would be missing
            tx.fee = max(0, (sum(v for _, v in tx.in_pairs) -
                             sum(v for _, v in tx.out_pairs)))
            txs[hash] = tx

            for hashX, value in itertools.chain(tx.in_pairs, tx.out_pairs):
                touched.add(hashX)
                hashXs[hashX].add(hash)

        return deferred, {prevout: utxo_map[prevout] for prevout in unspent}
 def balance_deltas(self):
     # Return mempool balance deltas indexed by hashX
     deltas = defaultdict(int)
     utxos = self.mempool_utxos()
     for tx_hash, tx in self.txs.items():
         for n, input in enumerate(tx.inputs):
             prevout = (input.prev_hash, input.prev_idx)
             if is_gen_outpoint(input.prev_hash, input.prev_idx):
                 continue
             if prevout in utxos:
                 utxos.pop(prevout)
             else:
                 hashX, value = self.db_utxos[prevout]
                 deltas[hashX] -= value
     for hashX, value in utxos.values():
         deltas[hashX] += value
     return deltas
    def touched(self, tx_hashes):
        touched = set()
        utxos = self.mempool_utxos()
        for tx_hash in tx_hashes:
            tx = self.txs[tx_hash]
            for n, input in enumerate(tx.inputs):
                if is_gen_outpoint(input.prev_hash, input.prev_idx):
                    continue
                prevout = (input.prev_hash, input.prev_idx)
                if prevout in utxos:
                    hashX, value = utxos[prevout]
                else:
                    hashX, value = self.db_utxos[prevout]
                touched.add(hashX)

            for output in tx.outputs:
                touched.add(coin.hashX_from_script(output.pk_script))
        return touched
Example #8
0
    async def _fetch_and_accept(self, hashes, all_hashes, touched):
        '''Fetch a list of mempool transactions.'''
        hex_hashes_iter = (hash_to_hex_str(hash) for hash in hashes)
        raw_txs = await self.api.raw_transactions(hex_hashes_iter)

        def deserialize_txs():    # This function is pure
            to_hashX = self.coin.hashX_from_script
            deserializer = self.coin.DESERIALIZER

            txs = {}
            for hash, raw_tx in zip(hashes, raw_txs):
                # The daemon may have evicted the tx from its
                # mempool or it may have gotten in a block
                if not raw_tx:
                    continue
                tx, tx_size = deserializer(raw_tx).read_tx_and_vsize()
                # Convert the inputs and outputs into (hashX, value) pairs
                txin_pairs = tuple((txin.prev_hash, txin.prev_idx)
                                   for txin in tx.inputs)
                txout_pairs = tuple((to_hashX(txout.pk_script), txout.value)
                                    for txout in tx.outputs)
                txs[hash] = MemPoolTx(txin_pairs, None, txout_pairs,
                                      0, tx_size)
            return txs

        # Thread this potentially slow operation so as not to block
        tx_map = await run_in_thread(deserialize_txs)

        # Determine all prevouts not in the mempool, and fetch the
        # UTXO information from the database.  Failed prevout lookups
        # return None - concurrent database updates happen - which is
        # relied upon by _accept_transactions. Ignore prevouts that are
        # generation-like.
        prevouts = tuple(prevout for tx in tx_map.values()
                         for prevout in tx.prevouts
                         if (prevout[0] not in all_hashes and
                             not is_gen_outpoint(*prevout)))
        utxos = await self.api.lookup_utxos(prevouts)
        utxo_map = {prevout: utxo for prevout, utxo in zip(prevouts, utxos)}

        return self._accept_transactions(tx_map, utxo_map, touched)
Example #9
0
    def backup_txs(self, txs):
        # Prevout values, in order down the block (coinbase first if present)
        # undo_info is in reverse block order
        undo_info = self.db.read_undo_info(self.height)
        if undo_info is None:
            raise ChainError(
                'no undo information found for height {:,d}'.format(
                    self.height))
        n = len(undo_info)

        # Use local vars for speed in the loops
        s_pack = pack
        put_utxo = self.utxo_cache.__setitem__
        spend_utxo = self.spend_utxo
        script_hashX = self.coin.hashX_from_script
        touched = self.touched
        undo_entry_len = 12 + HASHX_LEN

        for tx, tx_hash in reversed(txs):
            for idx, txout in enumerate(tx.outputs):
                # Spend the TX outputs.  Be careful with unspendable
                # outputs - we didn't save those in the first place.
                hashX = script_hashX(txout.pk_script)
                if hashX:
                    cache_value = spend_utxo(tx_hash, idx)
                    touched.add(cache_value[:-12])

            # Restore the inputs
            for txin in reversed(tx.inputs):
                if is_gen_outpoint(txin.prev_hash, txin.prev_idx):
                    continue
                n -= undo_entry_len
                undo_item = undo_info[n:n + undo_entry_len]
                put_utxo(txin.prev_hash + s_pack('<H', txin.prev_idx),
                         undo_item)
                touched.add(undo_item[:-12])

        assert n == 0
        self.tx_count -= len(txs)