Example #1
0
    def load_from_dict(self, d, prune_provisional=True):
        """ Loads the cache manager from a dict

        Args:
            d (dict): A dict of the type created by to_file.
            prune_provisional (bool): If True, does not insert
                provisionally-marked txns that are older than
                self.PROVISIONAL_TXN_TIMEOUT.
        """
        if "version" not in d or d["version"] != self.CACHE_VERSION:
            return

        if "last_block" in d:
            self.last_block = d['last_block']

        if "addresses" in d:
            # Make the keys integers
            self._address_cache = {
                int(k1): {
                    int(k2): {int(k3): v3
                              for k3, v3 in v2.items()}
                    for k2, v2 in v1.items()
                }
                for k1, v1 in d['addresses'].items()
            }

        if "txns" in d:
            now = time.time()
            for txid in d['txns']:
                t = WalletTransaction._deserialize(d['txns'][txid])
                if t.provisional:
                    if not prune_provisional or \
                       t.provisional > now:
                        self.insert_txn(t,
                                        mark_provisional=True,
                                        expiration=t.provisional)
                else:
                    self.insert_txn(t, mark_provisional=False)

        self._dirty = True
Example #2
0
    def _serialize_cache(self, cache):
        """ Serializes a cache into a dict capable of being used
            as an arg to json.dumps().
        """
        newd = {}
        for k, v in cache.items():
            if isinstance(v, dict):
                newd[k] = self._serialize_cache(v)
            elif isinstance(v, WalletTransaction):
                newd[k] = v._serialize()
            elif isinstance(v, Transaction):
                newd[k] = WalletTransaction.from_transaction(v)._serialize()
            elif isinstance(v, TransactionInput) or isinstance(v, TransactionOutput):
                newd[k] = str(v)
            elif isinstance(v, Hash):
                newd[k] = str(v)
            elif isinstance(v, set):
                newd[k] = list(v)
            else:
                newd[k] = v

        return newd
Example #3
0
    def _serialize_cache(self, cache):
        """ Serializes a cache into a dict capable of being used
            as an arg to json.dumps().
        """
        newd = {}
        for k, v in cache.items():
            if isinstance(v, dict):
                newd[k] = self._serialize_cache(v)
            elif isinstance(v, WalletTransaction):
                newd[k] = v._serialize()
            elif isinstance(v, Transaction):
                newd[k] = WalletTransaction.from_transaction(v)._serialize()
            elif isinstance(v, TransactionInput) or isinstance(v, TransactionOutput):
                newd[k] = str(v)
            elif isinstance(v, Hash):
                newd[k] = str(v)
            elif isinstance(v, set):
                newd[k] = list(v)
            else:
                newd[k] = v

        return newd
Example #4
0
    def load_from_dict(self, d, prune_provisional=True):
        """ Loads the cache manager from a dict

        Args:
            d (dict): A dict of the type created by to_file.
            prune_provisional (bool): If True, does not insert
                provisionally-marked txns that are older than
                self.PROVISIONAL_TXN_TIMEOUT.
        """
        if "version" not in d or d["version"] != self.CACHE_VERSION:
            return

        if "last_block" in d:
            self.last_block = d['last_block']

        if "addresses" in d:
            # Make the keys integers
            self._address_cache = {int(k1): {int(k2): {int(k3): v3
                                                       for k3, v3 in v2.items()}
                                             for k2, v2 in v1.items()}
                                   for k1, v1 in d['addresses'].items()}

        if "txns" in d:
            now = time.time()
            for txid in d['txns']:
                t = WalletTransaction._deserialize(d['txns'][txid])
                if t.provisional:
                    if not prune_provisional or \
                       t.provisional > now:
                        self.insert_txn(t,
                                        mark_provisional=True,
                                        expiration=t.provisional)
                else:
                    self.insert_txn(t, mark_provisional=False)

        self._dirty = True
Example #5
0
    def _sync_txns(self, max_index=0, check_all=False):
        now = time.time()
        if now - self._last_full_update > 20 * 60:
            check_all = True

        for change in [0, 1]:
            found_last = False
            current_last = self.last_indices[change]

            addr_range = 0
            while not found_last:
                # Try a 2 * GAP_LIMIT at a go
                end = addr_range + self.DISCOVERY_INCREMENT
                addresses = {i: self.get_address(change, i)
                             for i in range(addr_range, end)}

                if self.data_provider.can_limit_by_height:
                    min_block = None if check_all else self._cache_manager.last_block
                    txns = self.data_provider.get_transactions(
                        list(addresses.values()),
                        limit=10000,
                        min_block=min_block)
                else:
                    txns = self.data_provider.get_transactions(
                        list(addresses.values()),
                        limit=10000)

                inserted_txns = set()
                for i in sorted(addresses.keys()):
                    addr = addresses[i]

                    self._cache_manager.insert_address(self.index, change, i, addr)

                    addr_has_txns = self._cache_manager.address_has_txns(addr)

                    if not addr_has_txns or addr not in txns or \
                       not bool(txns[addr]):
                        if i - current_last >= self.GAP_LIMIT:
                            found_last = True
                            break

                    if txns[addr]:
                        current_last = i
                        for t in txns[addr]:
                            txid = str(t['transaction'].hash)
                            if txid not in inserted_txns:
                                wt = WalletTransaction.from_transaction(
                                    t['transaction'])
                                wt.block = t['metadata']['block']
                                wt.block_hash = t['metadata']['block_hash']
                                wt.confirmations = t['metadata']['confirmations']
                                if 'network_time' in t['metadata']:
                                    wt.network_time = t['metadata']['network_time']
                                self._cache_manager.insert_txn(wt)
                                inserted_txns.add(txid)

                    if addr_has_txns:
                        current_last = i

                addr_range += self.DISCOVERY_INCREMENT

            self.last_indices[change] = current_last

        self._last_update = time.time()
        if check_all:
            self._last_full_update = self._last_update
Example #6
0
def test_txns():
    txn = WalletTransaction.from_hex('01000000029ccb0665ec780f8b05bf2315a48dfb154dc41f91e8046a59f1c75656826dea5d000000006b483045022100f4d2161473f9d0ba4b5cdbc9e5b7b1d8fca32e3b6bede307352bef6aaa3a08cd022023d8444f78f69de6fd0f6cc391a7ca4de3dc4181220932d01511eb1129fee09e01210328bd51733a7d5bee05368680adef9aaa3f9bb716ec716d5896b1d80afb734d6cffffffff2424cb910235b2059d59023aecfebf6fce4eee31c637e9a0b350491849688727020000006a473044022072de3d707f98adfed3266e0261750cd7b5162732e525d7df17f4e55a55e953b902205046b597acf7acf41e725b459ba6cfe8c03a9d877375cdf483cab9620f92961101210291cbb1304614d86b15f4e8f39e9d8299cd0304ff8b81b5bcf6d9a6f32be649bbffffffff0240420f00000000001976a91434fe777d676fceb3509584c1d7b9f13ee56514d488ace05a0000000000001976a9145237ba33122495420711b3f2cc0463dbb24c9d3988ac00000000')  # nopep8

    txn.block = 374440
    txn.block_hash = Hash('0000000000000000038ee0066680705455d500f287f6c56db7a979c2426a4c02')
    txn.confirmations = 7533

    cm.insert_txn(txn)

    txid = "3779f27a81cdbc435ac258ce5076c211e7a953027aab42573b1b7ce9e50abe8e"
    assert txid in cm._txn_cache

    in_addrs = ["1DpCouKa2evX3f2aELUy7iNdsrYuLLaqWy",
                "1GcmBmvYWJKLFHxrTtx5DqQLV7oHQAkH2c"]

    out_addrs = [("15qCydrcqURADXJHrtMW9m6SpPTa3kqkQb", 1000000),
                 ("18VjAjZ7Au8U75LCHT7aH7mTwKETZwHTpi", 23264)]

    assert len(cm._txns_by_addr.keys()) == 4
    for i, a in enumerate(out_addrs):
        assert a[0] in cm._txns_by_addr
        assert list(cm._deposits_for_addr[a[0]][txid]) == [i]

    for i, a in enumerate(in_addrs):
        assert list(cm._spends_for_addr[a][txid]) == [i]

    # Check input and output caches
    assert txid in cm._inputs_cache
    assert len(cm._inputs_cache[txid]) == 2

    assert txid in cm._outputs_cache
    assert len(cm._outputs_cache[txid]) == 2
    assert cm._outputs_cache[txid][0]['output'] is not None
    assert cm._outputs_cache[txid][1]['output'] is not None
    assert cm._outputs_cache[txid][0]['status'] == CacheManager.UNSPENT
    assert cm._outputs_cache[txid][1]['status'] == CacheManager.UNSPENT

    out_txid1 = "5dea6d825656c7f1596a04e8911fc44d15fb8da41523bf058b0f78ec6506cb9c"
    assert out_txid1 in cm._outputs_cache
    assert cm._outputs_cache[out_txid1][0]['status'] == CacheManager.SPENT

    out_txid2 = "27876849184950b3a0e937c631ee4ece6fbffeec3a02599d05b2350291cb2424"
    assert out_txid2 in cm._outputs_cache
    assert len(cm._outputs_cache[out_txid2].keys()) == 1
    assert cm._outputs_cache[out_txid2][2]['status'] == CacheManager.SPENT

    assert cm.has_txns()
    assert cm.has_txns(0)
    assert not cm.has_txns(1)

    assert cm.have_transaction(txid)
    assert not cm.have_transaction(out_txid1)
    assert not cm.have_transaction(out_txid2)
    assert cm.get_transaction(txid) == txn
    assert cm.get_transaction(out_txid1) is None
    assert cm.get_transaction(out_txid2) is None

    # Check balances on addresses
    addr_balances = cm.get_balances([a[0] for a in out_addrs])
    for addr, exp_bal in out_addrs:
        assert addr_balances[addr] == exp_bal

    # Add a second transaction that deposits into addresses we have,
    # but insert it as unconfirmed
    txn_hex = "01000000028a9acc005a2158758e44242eee8c18fee7a43cda39a358cc783fb578cfa7cf5f000000006a47304402204a00fcb746f90095c1c50e048f1b0616b421617ca27a7a7465d4086a1623731802202404d0fce1b74f41ce1e3c63f61c8574c8cf2a5eae24ac4df714775168a9118c012102d8bfe3fd2d01f3a2b1380c34ccadcd318cafd1246f41258d7d244f409fb44c93ffffffff15857ef158778f603d34bcff74bd7935cb9d6b4a0147eea008be3f67bd395830020000006a4730440220466f93d784aa24bf497929433777fa283a7cd0000625179d3e9f5c75db4ad10f022022857a607665408a5521cbdf145c1fecd85c427ccf939c49f9a0d828a934e2530121021b5c9a9e6c97b4222c97da5a642e3531bce01b757cfdc9b29ac7d1cbf2d10710ffffffff0240420f00000000001976a91433a0a86dd9dab9902157d8d64e05fc8e0dfba16388ac7a1d0300000000001976a914134ca7427089b8f661efc9806a8418f72e57167f88ac00000000"  # nopep8

    txn = WalletTransaction.from_hex(txn_hex)
    cm.insert_txn(txn)

    txid = "d24f3b9f0aa7b6484bcea563f4c254bd24e8163906cbffc727c2b2dad43af61e"
    assert txid in cm._txn_cache

    in_addrs = ["1Ezv6YmYsZvALUaRcZRf8hBdxYni6cm78X",
                "16Mcvb7fYhif94d1RHCn5AE2dm1oXCGnH6"]

    out_addrs = [("15hyvVXH2eJnakwhpqKBf5oTCa3o2bp8m8", 1000000),
                 ("12m3fcaabUgYwWcodgVZUGH6ntFqVrHk5C", 204154)]

    for i, a in enumerate(out_addrs):
        assert a[0] in cm._txns_by_addr
        assert list(cm._deposits_for_addr[a[0]][txid]) == [i]

    for i, a in enumerate(in_addrs):
        assert list(cm._spends_for_addr[a][txid]) == [i]

    # Check input and output caches
    assert txid in cm._inputs_cache
    assert len(cm._inputs_cache[txid]) == 2

    assert txid in cm._outputs_cache
    assert cm._outputs_cache[txid][0]['output'] is not None
    assert cm._outputs_cache[txid][1]['output'] is not None
    assert cm._outputs_cache[txid][0]['status'] == CacheManager.UNSPENT | CacheManager.UNCONFIRMED
    assert cm._outputs_cache[txid][1]['status'] == CacheManager.UNSPENT | CacheManager.UNCONFIRMED

    out_txid1 = "5fcfa7cf78b53f78cc58a339da3ca4e7fe188cee2e24448e7558215a00cc9a8a"
    assert out_txid1 in cm._outputs_cache
    assert cm._outputs_cache[out_txid1][0]['status'] == CacheManager.SPENT | CacheManager.UNCONFIRMED

    out_txid2 = "305839bd673fbe08a0ee47014a6b9dcb3579bd74ffbc343d608f7758f17e8515"
    assert out_txid2 in cm._outputs_cache
    assert len(cm._outputs_cache[out_txid2].keys()) == 1
    assert cm._outputs_cache[out_txid2][2]['status'] == CacheManager.SPENT | CacheManager.UNCONFIRMED

    # Check that confirmed balances are 0 for the out_addrs
    out_a = [a[0] for a in out_addrs]
    conf_addr_balances = cm.get_balances(out_a)
    unconf_addr_balances = cm.get_balances(out_a, True)
    for addr, exp_bal in out_addrs:
        assert conf_addr_balances[addr] == 0
        assert unconf_addr_balances[addr] == exp_bal

    # Check utxos
    conf_addr_utxos = cm.get_utxos(out_a)
    unconf_addr_utxos = cm.get_utxos(out_a, True)
    for addr, exp_bal in out_addrs:
        assert addr not in conf_addr_utxos
        assert addr in unconf_addr_utxos
        assert len(unconf_addr_utxos[addr]) == 1
        utxo = unconf_addr_utxos[addr][0]
        assert utxo.value == exp_bal
        assert utxo.num_confirmations == 0

    # Reinsert the transaction with it as confirmed now
    txn = WalletTransaction.from_hex(txn_hex)
    txn.block = 374442
    txn.block_hash = Hash('000000000000000001de250dcfa47f8313aec2f1f41a56f4fb0d099eb497c2b2')
    txn.confirmations = 7684
    cm.insert_txn(txn)

    assert cm._outputs_cache[txid][0]['status'] == CacheManager.UNSPENT
    assert cm._outputs_cache[txid][1]['status'] == CacheManager.UNSPENT
    assert cm._outputs_cache[out_txid1][0]['status'] == CacheManager.SPENT
    assert cm._outputs_cache[out_txid2][2]['status'] == CacheManager.SPENT

    # Check the balances again
    conf_addr_balances = cm.get_balances(out_a)
    unconf_addr_balances = cm.get_balances(out_a, True)
    for addr, exp_bal in out_addrs:
        assert conf_addr_balances[addr] == exp_bal
        assert unconf_addr_balances[addr] == exp_bal

    # Check utxos again
    conf_addr_utxos = cm.get_utxos(out_a)
    unconf_addr_utxos = cm.get_utxos(out_a, True)
    for addr, exp_bal in out_addrs:
        assert addr in conf_addr_utxos
        assert addr in unconf_addr_utxos
        assert len(conf_addr_utxos[addr]) == 1
        assert len(unconf_addr_utxos[addr]) == 1
        utxo = conf_addr_utxos[addr][0]
        assert utxo.value == exp_bal
        assert utxo.num_confirmations == 7684

    # Insert a transaction that spends from one of the out addrs in
    # the above transactions.

    # 1. Insert it provisionally
    # 2. Re-insert as unconfirmed
    # 3. Re-insert as confirmed

    txn_hex = "01000000021ef63ad4dab2c227c7ffcb063916e824bd54c2f463a5ce4b48b6a70a9f3b4fd2000000006a473044022051008f06f1fc5783364712c7bf175c383ebb92c1001ba9f744f5170d5af00bb9022012baa83b3611b2c0e637d2f5e62dd3f6f4debfca805f8a42df6719a67614824d0121027fc10ccde9240463a86c983d2c8d1301311c9debf510119418b0da7b6fdb7ee7ffffffff8ebe0ae5e97c1b3b5742ab7a0253a9e711c27650ce58c25a43bccd817af27937000000006a473044022076fd5835628d4867b489c4c7afa885de33417a3536276b3f7066155b1bd79c15022030a218c2ca35b27e2beefb2298a0bf6fc9eabe93e07f388a1a3aee878025a7b6012102bed99adff9710dbc3e9f7966037d5824ffb134aeba70aec70e34e7eeb6547a94ffffffff0240420f00000000001976a914952e023bf19047e9a014af4ec067667695d8c99488acf8340f00000000001976a914743281d388add04da28e10a12af09c853f98609888ac00000000"  # nopep8

    txn = WalletTransaction.from_hex(txn_hex)

    # First test with a very short expiration
    cm.insert_txn(txn, mark_provisional=True, expiration=1)

    txid = "6fd3c96d466cd465b40e59be14d023c27f1d0ca13075119d3d6baeebfc587b8c"
    assert txid in cm._txn_cache
    assert cm._txn_cache[txid].provisional
    time.sleep(1.5)
    cm.prune_provisional_txns()
    assert txid not in cm._txn_cache

    # Now do default expiration
    cm.insert_txn(txn, mark_provisional=True)

    assert txid in cm._txn_cache

    in_addrs = ["15hyvVXH2eJnakwhpqKBf5oTCa3o2bp8m8",
                "15qCydrcqURADXJHrtMW9m6SpPTa3kqkQb"]

    out_addrs = [("1EbnoKrmUEe3hsK9gTVfgYAming6BuqM3L", 1000000),
                 ("1BbPtYsbBPFRCwnU5RuMTttraghXQ5JSZm", 996600)]

    for i, a in enumerate(out_addrs):
        assert a[0] in cm._txns_by_addr
        assert list(cm._deposits_for_addr[a[0]][txid]) == [i]

    for i, a in enumerate(in_addrs):
        assert list(cm._spends_for_addr[a][txid]) == [i]

    # Check input and output caches
    assert txid in cm._inputs_cache
    assert len(cm._inputs_cache[txid]) == 2

    assert txid in cm._outputs_cache
    assert cm._outputs_cache[txid][0]['output'] is not None
    assert cm._outputs_cache[txid][1]['output'] is not None
    assert cm._outputs_cache[txid][0]['status'] == CacheManager.UNSPENT | CacheManager.PROVISIONAL | CacheManager.UNCONFIRMED  # nopep8
    assert cm._outputs_cache[txid][1]['status'] == CacheManager.UNSPENT | CacheManager.PROVISIONAL | CacheManager.UNCONFIRMED  # nopep8

    out_txid1 = "d24f3b9f0aa7b6484bcea563f4c254bd24e8163906cbffc727c2b2dad43af61e"
    assert out_txid1 in cm._outputs_cache
    assert cm._outputs_cache[out_txid1][0]['status'] == CacheManager.SPENT | CacheManager.PROVISIONAL | CacheManager.UNCONFIRMED  # nopep8

    out_txid2 = "3779f27a81cdbc435ac258ce5076c211e7a953027aab42573b1b7ce9e50abe8e"
    assert out_txid2 in cm._outputs_cache
    assert len(cm._outputs_cache[out_txid2].keys()) == 2
    assert cm._outputs_cache[out_txid2][0]['status'] == CacheManager.SPENT | CacheManager.PROVISIONAL | CacheManager.UNCONFIRMED  # nopep8

    # Check that confirmed balances are 0 for the out_addrs
    out_a = [a[0] for a in out_addrs]
    conf_addr_balances = cm.get_balances(out_a)
    unconf_addr_balances = cm.get_balances(out_a, True)
    for addr, exp_bal in out_addrs:
        assert conf_addr_balances[addr] == 0
        assert unconf_addr_balances[addr] == exp_bal

    # Check utxos
    conf_addr_utxos = cm.get_utxos(out_a)
    unconf_addr_utxos = cm.get_utxos(out_a, True)
    for addr, exp_bal in out_addrs:
        assert addr not in conf_addr_utxos
        assert addr in unconf_addr_utxos
        assert len(unconf_addr_utxos[addr]) == 1
        utxo = unconf_addr_utxos[addr][0]
        assert utxo.value == exp_bal
        assert utxo.num_confirmations == 0

    # Re-insert as unconfirmed
    txn = WalletTransaction.from_hex(txn_hex)
    cm.insert_txn(txn, mark_provisional=False)

    # Only the statuses should change, so check those.
    assert cm._outputs_cache[txid][0]['status'] == CacheManager.UNSPENT | CacheManager.UNCONFIRMED
    assert cm._outputs_cache[txid][1]['status'] == CacheManager.UNSPENT | CacheManager.UNCONFIRMED
    assert cm._outputs_cache[out_txid1][0]['status'] == CacheManager.SPENT | CacheManager.UNCONFIRMED
    assert cm._outputs_cache[out_txid2][0]['status'] == CacheManager.SPENT | CacheManager.UNCONFIRMED

    # Re-insert as confirmed
    txn = WalletTransaction.from_hex(txn_hex)
    txn.block = 374445
    txn.block_hash = Hash("000000000000000004c241778cbbc269e912df5fe8d856efaea916daa82d2575")
    txn.confirmations = 7781
    cm.insert_txn(txn, mark_provisional=False)

    # Only the statuses should change, so check those.
    assert cm._outputs_cache[txid][0]['status'] == CacheManager.UNSPENT
    assert cm._outputs_cache[txid][1]['status'] == CacheManager.UNSPENT
    assert cm._outputs_cache[out_txid1][0]['status'] == CacheManager.SPENT
    assert cm._outputs_cache[out_txid2][0]['status'] == CacheManager.SPENT

    # Check balances
    out_a = [a[0] for a in out_addrs]
    conf_addr_balances = cm.get_balances(out_a)
    unconf_addr_balances = cm.get_balances(out_a, True)
    for addr, exp_bal in out_addrs:
        assert conf_addr_balances[addr] == exp_bal
        assert unconf_addr_balances[addr] == exp_bal

    # Check utxos
    conf_addr_utxos = cm.get_utxos(out_a)
    unconf_addr_utxos = cm.get_utxos(out_a, True)
    for addr, exp_bal in out_addrs:
        assert addr in conf_addr_utxos
        assert addr in unconf_addr_utxos
        assert len(unconf_addr_utxos[addr]) == 1
        utxo = conf_addr_utxos[addr][0]
        assert utxo.value == exp_bal
        assert utxo.num_confirmations == 7781

    # Check utxos for all addresses that have deposits - we should only have 4
    addrs = ["1DpCouKa2evX3f2aELUy7iNdsrYuLLaqWy",
             "1GcmBmvYWJKLFHxrTtx5DqQLV7oHQAkH2c",
             "15hyvVXH2eJnakwhpqKBf5oTCa3o2bp8m8",
             "15qCydrcqURADXJHrtMW9m6SpPTa3kqkQb",
             "1EbnoKrmUEe3hsK9gTVfgYAming6BuqM3L",
             "1BbPtYsbBPFRCwnU5RuMTttraghXQ5JSZm",
             "1Ezv6YmYsZvALUaRcZRf8hBdxYni6cm78X",
             "16Mcvb7fYhif94d1RHCn5AE2dm1oXCGnH6",
             "12m3fcaabUgYwWcodgVZUGH6ntFqVrHk5C",
             "18VjAjZ7Au8U75LCHT7aH7mTwKETZwHTpi"]

    conf_utxos = cm.get_utxos(addrs)
    assert len(conf_utxos) == 4
    utxo_addrs_values = [("18VjAjZ7Au8U75LCHT7aH7mTwKETZwHTpi", 23264),
                         ("12m3fcaabUgYwWcodgVZUGH6ntFqVrHk5C", 204154),
                         ("1EbnoKrmUEe3hsK9gTVfgYAming6BuqM3L", 1000000),
                         ("1BbPtYsbBPFRCwnU5RuMTttraghXQ5JSZm", 996600)]

    for a, value in utxo_addrs_values:
        assert a in conf_utxos
        assert len(conf_utxos[a]) == 1
        assert conf_utxos[a][0].value == value

    assert "15hyvVXH2eJnakwhpqKBf5oTCa3o2bp8m8" not in conf_utxos
    assert "15qCydrcqURADXJHrtMW9m6SpPTa3kqkQb" not in conf_utxos

    # Now delete the last transaction
    cm._delete_txn(txid)

    assert txid not in cm._txn_cache
    assert txid not in cm._inputs_cache
    assert txid not in cm._outputs_cache

    for in_addr in in_addrs:
        assert in_addr not in cm._spends_for_addr

    for out_addr, _ in out_addrs:
        assert out_addr not in cm._deposits_for_addr

    for out_txid, index in [(out_txid1, 0), (out_txid2, 0)]:
        out = cm._outputs_cache[out_txid][index]
        assert out['status'] == CacheManager.UNSPENT
        assert out['spend_txid'] is None
        assert out['spend_index'] is None
Example #7
0
    def _sync_txns(self, max_index=0, check_all=False):
        now = time.time()
        if now - self._last_full_update > 20 * 60:
            check_all = True

        for change in [0, 1]:
            found_last = False
            current_last = self.last_indices[change]

            addr_range = 0
            while not found_last:
                # Try a 2 * GAP_LIMIT at a go
                end = addr_range + self.DISCOVERY_INCREMENT
                addresses = {i:self.get_address(change, i)
                             for i in range(addr_range, end)}

                if self.data_provider.can_limit_by_height:
                    min_block = None if check_all else self._cache_manager.last_block
                    txns = self.data_provider.get_transactions(
                        list(addresses.values()),
                        limit=10000,
                        min_block=min_block)
                else:
                    txns = self.data_provider.get_transactions(
                        list(addresses.values()),
                        limit=10000)

                inserted_txns = set()
                for i in sorted(addresses.keys()):
                    addr = addresses[i]

                    self._cache_manager.insert_address(self.index, change, i, addr)

                    addr_has_txns = self._cache_manager.address_has_txns(addr)

                    if not addr_has_txns or addr not in txns or \
                       not bool(txns[addr]):
                        if i - current_last >= self.GAP_LIMIT:
                            found_last = True
                            break

                    if txns[addr]:
                        current_last = i
                        for t in txns[addr]:
                            txid = str(t['transaction'].hash)
                            if txid not in inserted_txns:
                                wt = WalletTransaction.from_transaction(
                                    t['transaction'])
                                wt.block = t['metadata']['block']
                                wt.block_hash = t['metadata']['block_hash']
                                wt.confirmations = t['metadata']['confirmations']
                                if 'network_time' in t['metadata']:
                                    wt.network_time = t['metadata']['network_time']
                                self._cache_manager.insert_txn(wt)
                                inserted_txns.add(txid)

                    if addr_has_txns:
                        current_last = i

                addr_range += self.DISCOVERY_INCREMENT

            self.last_indices[change] = current_last

        self._last_update = time.time()
        if check_all:
            self._last_full_update = self._last_update
def test_txns():
    txn = WalletTransaction.from_hex(
        '01000000029ccb0665ec780f8b05bf2315a48dfb154dc41f91e8046a59f1c75656826dea5d000000006b483045022100f4d2161473f9d0ba4b5cdbc9e5b7b1d8fca32e3b6bede307352bef6aaa3a08cd022023d8444f78f69de6fd0f6cc391a7ca4de3dc4181220932d01511eb1129fee09e01210328bd51733a7d5bee05368680adef9aaa3f9bb716ec716d5896b1d80afb734d6cffffffff2424cb910235b2059d59023aecfebf6fce4eee31c637e9a0b350491849688727020000006a473044022072de3d707f98adfed3266e0261750cd7b5162732e525d7df17f4e55a55e953b902205046b597acf7acf41e725b459ba6cfe8c03a9d877375cdf483cab9620f92961101210291cbb1304614d86b15f4e8f39e9d8299cd0304ff8b81b5bcf6d9a6f32be649bbffffffff0240420f00000000001976a91434fe777d676fceb3509584c1d7b9f13ee56514d488ace05a0000000000001976a9145237ba33122495420711b3f2cc0463dbb24c9d3988ac00000000'
    )  # nopep8

    txn.block = 374440
    txn.block_hash = Hash(
        '0000000000000000038ee0066680705455d500f287f6c56db7a979c2426a4c02')
    txn.confirmations = 7533

    cm.insert_txn(txn)

    txid = "3779f27a81cdbc435ac258ce5076c211e7a953027aab42573b1b7ce9e50abe8e"
    assert txid in cm._txn_cache

    in_addrs = [
        "1DpCouKa2evX3f2aELUy7iNdsrYuLLaqWy",
        "1GcmBmvYWJKLFHxrTtx5DqQLV7oHQAkH2c"
    ]

    out_addrs = [("15qCydrcqURADXJHrtMW9m6SpPTa3kqkQb", 1000000),
                 ("18VjAjZ7Au8U75LCHT7aH7mTwKETZwHTpi", 23264)]

    assert len(cm._txns_by_addr.keys()) == 4
    for i, a in enumerate(out_addrs):
        assert a[0] in cm._txns_by_addr
        assert list(cm._deposits_for_addr[a[0]][txid]) == [i]

    for i, a in enumerate(in_addrs):
        assert list(cm._spends_for_addr[a][txid]) == [i]

    # Check input and output caches
    assert txid in cm._inputs_cache
    assert len(cm._inputs_cache[txid]) == 2

    assert txid in cm._outputs_cache
    assert len(cm._outputs_cache[txid]) == 2
    assert cm._outputs_cache[txid][0]['output'] is not None
    assert cm._outputs_cache[txid][1]['output'] is not None
    assert cm._outputs_cache[txid][0]['status'] == CacheManager.UNSPENT
    assert cm._outputs_cache[txid][1]['status'] == CacheManager.UNSPENT

    out_txid1 = "5dea6d825656c7f1596a04e8911fc44d15fb8da41523bf058b0f78ec6506cb9c"
    assert out_txid1 in cm._outputs_cache
    assert cm._outputs_cache[out_txid1][0]['status'] == CacheManager.SPENT

    out_txid2 = "27876849184950b3a0e937c631ee4ece6fbffeec3a02599d05b2350291cb2424"
    assert out_txid2 in cm._outputs_cache
    assert len(cm._outputs_cache[out_txid2].keys()) == 1
    assert cm._outputs_cache[out_txid2][2]['status'] == CacheManager.SPENT

    assert cm.has_txns()
    assert cm.has_txns(0)
    assert not cm.has_txns(1)

    assert cm.have_transaction(txid)
    assert not cm.have_transaction(out_txid1)
    assert not cm.have_transaction(out_txid2)
    assert cm.get_transaction(txid) == txn
    assert cm.get_transaction(out_txid1) is None
    assert cm.get_transaction(out_txid2) is None

    # Check balances on addresses
    addr_balances = cm.get_balances([a[0] for a in out_addrs])
    for addr, exp_bal in out_addrs:
        assert addr_balances[addr] == exp_bal

    # Add a second transaction that deposits into addresses we have,
    # but insert it as unconfirmed
    txn_hex = "01000000028a9acc005a2158758e44242eee8c18fee7a43cda39a358cc783fb578cfa7cf5f000000006a47304402204a00fcb746f90095c1c50e048f1b0616b421617ca27a7a7465d4086a1623731802202404d0fce1b74f41ce1e3c63f61c8574c8cf2a5eae24ac4df714775168a9118c012102d8bfe3fd2d01f3a2b1380c34ccadcd318cafd1246f41258d7d244f409fb44c93ffffffff15857ef158778f603d34bcff74bd7935cb9d6b4a0147eea008be3f67bd395830020000006a4730440220466f93d784aa24bf497929433777fa283a7cd0000625179d3e9f5c75db4ad10f022022857a607665408a5521cbdf145c1fecd85c427ccf939c49f9a0d828a934e2530121021b5c9a9e6c97b4222c97da5a642e3531bce01b757cfdc9b29ac7d1cbf2d10710ffffffff0240420f00000000001976a91433a0a86dd9dab9902157d8d64e05fc8e0dfba16388ac7a1d0300000000001976a914134ca7427089b8f661efc9806a8418f72e57167f88ac00000000"  # nopep8

    txn = WalletTransaction.from_hex(txn_hex)
    cm.insert_txn(txn)

    txid = "d24f3b9f0aa7b6484bcea563f4c254bd24e8163906cbffc727c2b2dad43af61e"
    assert txid in cm._txn_cache

    in_addrs = [
        "1Ezv6YmYsZvALUaRcZRf8hBdxYni6cm78X",
        "16Mcvb7fYhif94d1RHCn5AE2dm1oXCGnH6"
    ]

    out_addrs = [("15hyvVXH2eJnakwhpqKBf5oTCa3o2bp8m8", 1000000),
                 ("12m3fcaabUgYwWcodgVZUGH6ntFqVrHk5C", 204154)]

    for i, a in enumerate(out_addrs):
        assert a[0] in cm._txns_by_addr
        assert list(cm._deposits_for_addr[a[0]][txid]) == [i]

    for i, a in enumerate(in_addrs):
        assert list(cm._spends_for_addr[a][txid]) == [i]

    # Check input and output caches
    assert txid in cm._inputs_cache
    assert len(cm._inputs_cache[txid]) == 2

    assert txid in cm._outputs_cache
    assert cm._outputs_cache[txid][0]['output'] is not None
    assert cm._outputs_cache[txid][1]['output'] is not None
    assert cm._outputs_cache[txid][0][
        'status'] == CacheManager.UNSPENT | CacheManager.UNCONFIRMED
    assert cm._outputs_cache[txid][1][
        'status'] == CacheManager.UNSPENT | CacheManager.UNCONFIRMED

    out_txid1 = "5fcfa7cf78b53f78cc58a339da3ca4e7fe188cee2e24448e7558215a00cc9a8a"
    assert out_txid1 in cm._outputs_cache
    assert cm._outputs_cache[out_txid1][0][
        'status'] == CacheManager.SPENT | CacheManager.UNCONFIRMED

    out_txid2 = "305839bd673fbe08a0ee47014a6b9dcb3579bd74ffbc343d608f7758f17e8515"
    assert out_txid2 in cm._outputs_cache
    assert len(cm._outputs_cache[out_txid2].keys()) == 1
    assert cm._outputs_cache[out_txid2][2][
        'status'] == CacheManager.SPENT | CacheManager.UNCONFIRMED

    # Check that confirmed balances are 0 for the out_addrs
    out_a = [a[0] for a in out_addrs]
    conf_addr_balances = cm.get_balances(out_a)
    unconf_addr_balances = cm.get_balances(out_a, True)
    for addr, exp_bal in out_addrs:
        assert conf_addr_balances[addr] == 0
        assert unconf_addr_balances[addr] == exp_bal

    # Check utxos
    conf_addr_utxos = cm.get_utxos(out_a)
    unconf_addr_utxos = cm.get_utxos(out_a, True)
    for addr, exp_bal in out_addrs:
        assert addr not in conf_addr_utxos
        assert addr in unconf_addr_utxos
        assert len(unconf_addr_utxos[addr]) == 1
        utxo = unconf_addr_utxos[addr][0]
        assert utxo.value == exp_bal
        assert utxo.num_confirmations == 0

    # Reinsert the transaction with it as confirmed now
    txn = WalletTransaction.from_hex(txn_hex)
    txn.block = 374442
    txn.block_hash = Hash(
        '000000000000000001de250dcfa47f8313aec2f1f41a56f4fb0d099eb497c2b2')
    txn.confirmations = 7684
    cm.insert_txn(txn)

    assert cm._outputs_cache[txid][0]['status'] == CacheManager.UNSPENT
    assert cm._outputs_cache[txid][1]['status'] == CacheManager.UNSPENT
    assert cm._outputs_cache[out_txid1][0]['status'] == CacheManager.SPENT
    assert cm._outputs_cache[out_txid2][2]['status'] == CacheManager.SPENT

    # Check the balances again
    conf_addr_balances = cm.get_balances(out_a)
    unconf_addr_balances = cm.get_balances(out_a, True)
    for addr, exp_bal in out_addrs:
        assert conf_addr_balances[addr] == exp_bal
        assert unconf_addr_balances[addr] == exp_bal

    # Check utxos again
    conf_addr_utxos = cm.get_utxos(out_a)
    unconf_addr_utxos = cm.get_utxos(out_a, True)
    for addr, exp_bal in out_addrs:
        assert addr in conf_addr_utxos
        assert addr in unconf_addr_utxos
        assert len(conf_addr_utxos[addr]) == 1
        assert len(unconf_addr_utxos[addr]) == 1
        utxo = conf_addr_utxos[addr][0]
        assert utxo.value == exp_bal
        assert utxo.num_confirmations == 7684

    # Insert a transaction that spends from one of the out addrs in
    # the above transactions.

    # 1. Insert it provisionally
    # 2. Re-insert as unconfirmed
    # 3. Re-insert as confirmed

    txn_hex = "01000000021ef63ad4dab2c227c7ffcb063916e824bd54c2f463a5ce4b48b6a70a9f3b4fd2000000006a473044022051008f06f1fc5783364712c7bf175c383ebb92c1001ba9f744f5170d5af00bb9022012baa83b3611b2c0e637d2f5e62dd3f6f4debfca805f8a42df6719a67614824d0121027fc10ccde9240463a86c983d2c8d1301311c9debf510119418b0da7b6fdb7ee7ffffffff8ebe0ae5e97c1b3b5742ab7a0253a9e711c27650ce58c25a43bccd817af27937000000006a473044022076fd5835628d4867b489c4c7afa885de33417a3536276b3f7066155b1bd79c15022030a218c2ca35b27e2beefb2298a0bf6fc9eabe93e07f388a1a3aee878025a7b6012102bed99adff9710dbc3e9f7966037d5824ffb134aeba70aec70e34e7eeb6547a94ffffffff0240420f00000000001976a914952e023bf19047e9a014af4ec067667695d8c99488acf8340f00000000001976a914743281d388add04da28e10a12af09c853f98609888ac00000000"  # nopep8

    txn = WalletTransaction.from_hex(txn_hex)

    # First test with a very short expiration
    cm.insert_txn(txn, mark_provisional=True, expiration=1)

    txid = "6fd3c96d466cd465b40e59be14d023c27f1d0ca13075119d3d6baeebfc587b8c"
    assert txid in cm._txn_cache
    assert cm._txn_cache[txid].provisional
    time.sleep(1.5)
    cm.prune_provisional_txns()
    assert txid not in cm._txn_cache

    # Now do default expiration
    cm.insert_txn(txn, mark_provisional=True)

    assert txid in cm._txn_cache

    in_addrs = [
        "15hyvVXH2eJnakwhpqKBf5oTCa3o2bp8m8",
        "15qCydrcqURADXJHrtMW9m6SpPTa3kqkQb"
    ]

    out_addrs = [("1EbnoKrmUEe3hsK9gTVfgYAming6BuqM3L", 1000000),
                 ("1BbPtYsbBPFRCwnU5RuMTttraghXQ5JSZm", 996600)]

    for i, a in enumerate(out_addrs):
        assert a[0] in cm._txns_by_addr
        assert list(cm._deposits_for_addr[a[0]][txid]) == [i]

    for i, a in enumerate(in_addrs):
        assert list(cm._spends_for_addr[a][txid]) == [i]

    # Check input and output caches
    assert txid in cm._inputs_cache
    assert len(cm._inputs_cache[txid]) == 2

    assert txid in cm._outputs_cache
    assert cm._outputs_cache[txid][0]['output'] is not None
    assert cm._outputs_cache[txid][1]['output'] is not None
    assert cm._outputs_cache[txid][0][
        'status'] == CacheManager.UNSPENT | CacheManager.PROVISIONAL | CacheManager.UNCONFIRMED  # nopep8
    assert cm._outputs_cache[txid][1][
        'status'] == CacheManager.UNSPENT | CacheManager.PROVISIONAL | CacheManager.UNCONFIRMED  # nopep8

    out_txid1 = "d24f3b9f0aa7b6484bcea563f4c254bd24e8163906cbffc727c2b2dad43af61e"
    assert out_txid1 in cm._outputs_cache
    assert cm._outputs_cache[out_txid1][0][
        'status'] == CacheManager.SPENT | CacheManager.PROVISIONAL | CacheManager.UNCONFIRMED  # nopep8

    out_txid2 = "3779f27a81cdbc435ac258ce5076c211e7a953027aab42573b1b7ce9e50abe8e"
    assert out_txid2 in cm._outputs_cache
    assert len(cm._outputs_cache[out_txid2].keys()) == 2
    assert cm._outputs_cache[out_txid2][0][
        'status'] == CacheManager.SPENT | CacheManager.PROVISIONAL | CacheManager.UNCONFIRMED  # nopep8

    # Check that confirmed balances are 0 for the out_addrs
    out_a = [a[0] for a in out_addrs]
    conf_addr_balances = cm.get_balances(out_a)
    unconf_addr_balances = cm.get_balances(out_a, True)
    for addr, exp_bal in out_addrs:
        assert conf_addr_balances[addr] == 0
        assert unconf_addr_balances[addr] == exp_bal

    # Check utxos
    conf_addr_utxos = cm.get_utxos(out_a)
    unconf_addr_utxos = cm.get_utxos(out_a, True)
    for addr, exp_bal in out_addrs:
        assert addr not in conf_addr_utxos
        assert addr in unconf_addr_utxos
        assert len(unconf_addr_utxos[addr]) == 1
        utxo = unconf_addr_utxos[addr][0]
        assert utxo.value == exp_bal
        assert utxo.num_confirmations == 0

    # Re-insert as unconfirmed
    txn = WalletTransaction.from_hex(txn_hex)
    cm.insert_txn(txn, mark_provisional=False)

    # Only the statuses should change, so check those.
    assert cm._outputs_cache[txid][0][
        'status'] == CacheManager.UNSPENT | CacheManager.UNCONFIRMED
    assert cm._outputs_cache[txid][1][
        'status'] == CacheManager.UNSPENT | CacheManager.UNCONFIRMED
    assert cm._outputs_cache[out_txid1][0][
        'status'] == CacheManager.SPENT | CacheManager.UNCONFIRMED
    assert cm._outputs_cache[out_txid2][0][
        'status'] == CacheManager.SPENT | CacheManager.UNCONFIRMED

    # Re-insert as confirmed
    txn = WalletTransaction.from_hex(txn_hex)
    txn.block = 374445
    txn.block_hash = Hash(
        "000000000000000004c241778cbbc269e912df5fe8d856efaea916daa82d2575")
    txn.confirmations = 7781
    cm.insert_txn(txn, mark_provisional=False)

    # Only the statuses should change, so check those.
    assert cm._outputs_cache[txid][0]['status'] == CacheManager.UNSPENT
    assert cm._outputs_cache[txid][1]['status'] == CacheManager.UNSPENT
    assert cm._outputs_cache[out_txid1][0]['status'] == CacheManager.SPENT
    assert cm._outputs_cache[out_txid2][0]['status'] == CacheManager.SPENT

    # Check balances
    out_a = [a[0] for a in out_addrs]
    conf_addr_balances = cm.get_balances(out_a)
    unconf_addr_balances = cm.get_balances(out_a, True)
    for addr, exp_bal in out_addrs:
        assert conf_addr_balances[addr] == exp_bal
        assert unconf_addr_balances[addr] == exp_bal

    # Check utxos
    conf_addr_utxos = cm.get_utxos(out_a)
    unconf_addr_utxos = cm.get_utxos(out_a, True)
    for addr, exp_bal in out_addrs:
        assert addr in conf_addr_utxos
        assert addr in unconf_addr_utxos
        assert len(unconf_addr_utxos[addr]) == 1
        utxo = conf_addr_utxos[addr][0]
        assert utxo.value == exp_bal
        assert utxo.num_confirmations == 7781

    # Check utxos for all addresses that have deposits - we should only have 4
    addrs = [
        "1DpCouKa2evX3f2aELUy7iNdsrYuLLaqWy",
        "1GcmBmvYWJKLFHxrTtx5DqQLV7oHQAkH2c",
        "15hyvVXH2eJnakwhpqKBf5oTCa3o2bp8m8",
        "15qCydrcqURADXJHrtMW9m6SpPTa3kqkQb",
        "1EbnoKrmUEe3hsK9gTVfgYAming6BuqM3L",
        "1BbPtYsbBPFRCwnU5RuMTttraghXQ5JSZm",
        "1Ezv6YmYsZvALUaRcZRf8hBdxYni6cm78X",
        "16Mcvb7fYhif94d1RHCn5AE2dm1oXCGnH6",
        "12m3fcaabUgYwWcodgVZUGH6ntFqVrHk5C",
        "18VjAjZ7Au8U75LCHT7aH7mTwKETZwHTpi"
    ]

    conf_utxos = cm.get_utxos(addrs)
    assert len(conf_utxos) == 4
    utxo_addrs_values = [("18VjAjZ7Au8U75LCHT7aH7mTwKETZwHTpi", 23264),
                         ("12m3fcaabUgYwWcodgVZUGH6ntFqVrHk5C", 204154),
                         ("1EbnoKrmUEe3hsK9gTVfgYAming6BuqM3L", 1000000),
                         ("1BbPtYsbBPFRCwnU5RuMTttraghXQ5JSZm", 996600)]

    for a, value in utxo_addrs_values:
        assert a in conf_utxos
        assert len(conf_utxos[a]) == 1
        assert conf_utxos[a][0].value == value

    assert "15hyvVXH2eJnakwhpqKBf5oTCa3o2bp8m8" not in conf_utxos
    assert "15qCydrcqURADXJHrtMW9m6SpPTa3kqkQb" not in conf_utxos

    # Now delete the last transaction
    cm._delete_txn(txid)

    assert txid not in cm._txn_cache
    assert txid not in cm._inputs_cache
    assert txid not in cm._outputs_cache

    for in_addr in in_addrs:
        assert in_addr not in cm._spends_for_addr

    for out_addr, _ in out_addrs:
        assert out_addr not in cm._deposits_for_addr

    for out_txid, index in [(out_txid1, 0), (out_txid2, 0)]:
        out = cm._outputs_cache[out_txid][index]
        assert out['status'] == CacheManager.UNSPENT
        assert out['spend_txid'] is None
        assert out['spend_index'] is None