Example #1
0
    def oid_to_order(self, cjorder, oid, amount):
        total_amount = amount + cjorder.txfee
        mix_balance = self.wallet.get_balance_by_mixdepth()
        max_mix = max(mix_balance, key=mix_balance.get)

        filtered_mix_balance = [
            m for m in mix_balance.iteritems() if m[1] >= total_amount
        ]
        log.debug('mix depths that have enough = ' + str(filtered_mix_balance))
        filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0])
        mixdepth = filtered_mix_balance[0][0]
        log.debug('filling offer, mixdepth=' + str(mixdepth))

        # mixdepth is the chosen depth we'll be spending from
        cj_addr = self.wallet.get_internal_addr(
            (mixdepth + 1) % self.wallet.max_mix_depth)
        change_addr = self.wallet.get_internal_addr(mixdepth)

        utxos = self.wallet.select_utxos(mixdepth, total_amount)
        my_total_in = sum([va['value'] for va in utxos.values()])
        real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount)
        change_value = my_total_in - amount - cjorder.txfee + real_cjfee
        if change_value <= jm_single().DUST_THRESHOLD:
            log.debug(('change value={} below dust threshold, '
                       'finding new utxos').format(change_value))
            try:
                utxos = self.wallet.select_utxos(
                    mixdepth, total_amount + jm_single().DUST_THRESHOLD)
            except Exception:
                log.debug('dont have the required UTXOs to make a '
                          'output above the dust threshold, quitting')
                return None, None, None

        return utxos, cj_addr, change_addr
    def oid_to_order(self, cjorder, oid, amount):
        total_amount = amount + cjorder.txfee
        mix_balance = self.wallet.get_balance_by_mixdepth()
        max_mix = max(mix_balance, key=mix_balance.get)

        filtered_mix_balance = [m
                                for m in mix_balance.iteritems()
                                if m[1] >= total_amount]
        log.debug('mix depths that have enough = ' + str(filtered_mix_balance))
        filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0])
        mixdepth = filtered_mix_balance[0][0]
        log.debug('filling offer, mixdepth=' + str(mixdepth))

        # mixdepth is the chosen depth we'll be spending from
        cj_addr = self.wallet.get_internal_addr((mixdepth + 1) %
                                                self.wallet.max_mix_depth)
        change_addr = self.wallet.get_internal_addr(mixdepth)

        utxos = self.wallet.select_utxos(mixdepth, total_amount)
        my_total_in = sum([va['value'] for va in utxos.values()])
        real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount)
        change_value = my_total_in - amount - cjorder.txfee + real_cjfee
        if change_value <= jm_single().DUST_THRESHOLD:
            log.debug(('change value={} below dust threshold, '
                       'finding new utxos').format(change_value))
            try:
                utxos = self.wallet.select_utxos(
                    mixdepth, total_amount + jm_single().DUST_THRESHOLD)
            except Exception:
                log.debug('dont have the required UTXOs to make a '
                          'output above the dust threshold, quitting')
                return None, None, None

        return utxos, cj_addr, change_addr
Example #3
0
    def oid_to_order(self, cjorder, oid, amount):
        total_amount = amount + cjorder.txfee
        mix_balance = self.wallet.get_balance_by_mixdepth()
        filtered_mix_balance = [
            m for m in mix_balance.iteritems() if m[1] >= total_amount
        ]
        if not filtered_mix_balance:
            return None, None, None
        log.debug('mix depths that have enough, filtered_mix_balance = ' +
                  str(filtered_mix_balance))

        # use mix depth that has the closest amount of coins to what this transaction needs
        # keeps coins moving through mix depths more quickly
        # and its more likely to use txos of a similiar size to this transaction
        filtered_mix_balance = sorted(
            filtered_mix_balance,
            key=lambda x: x[1])  #sort smallest to largest usable amount

        log.debug('sorted order of filtered_mix_balance = ' +
                  str(filtered_mix_balance))

        mixdepth = filtered_mix_balance[0][0]

        log.debug('filling offer, mixdepth=' + str(mixdepth))

        # mixdepth is the chosen depth we'll be spending from
        cj_addr = self.wallet.get_internal_addr(
            (mixdepth + 1) % self.wallet.max_mix_depth)
        change_addr = self.wallet.get_internal_addr(mixdepth)

        utxos = self.wallet.select_utxos(mixdepth, total_amount)
        my_total_in = sum([va['value'] for va in utxos.values()])
        real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount)
        change_value = my_total_in - amount - cjorder.txfee + real_cjfee
        if change_value <= jm_single().DUST_THRESHOLD:
            log.debug(
                'change value=%d below dust threshold, finding new utxos' %
                (change_value))
            try:
                utxos = self.wallet.select_utxos(
                    mixdepth, total_amount + jm_single().DUST_THRESHOLD)
            except Exception:
                log.debug(
                    'dont have the required UTXOs to make a output above the dust threshold, quitting'
                )
                return None, None, None

        return utxos, cj_addr, change_addr
    def oid_to_order(self, cjorder, oid, amount):
        total_amount = amount + cjorder.txfee
        mix_balance = self.wallet.get_balance_by_mixdepth()
        filtered_mix_balance = [m
                                for m in mix_balance.iteritems()
                                if m[1] >= total_amount]
        if not filtered_mix_balance:
            return None, None, None
        log.debug('mix depths that have enough, filtered_mix_balance = ' + str(
            filtered_mix_balance))

        # use mix depth that has the closest amount of coins to what this transaction needs
        # keeps coins moving through mix depths more quickly
        # and its more likely to use txos of a similiar size to this transaction
        filtered_mix_balance = sorted(
            filtered_mix_balance,
            key=lambda x: x[1])  #sort smallest to largest usable amount

        log.debug('sorted order of filtered_mix_balance = ' + str(
            filtered_mix_balance))

        mixdepth = filtered_mix_balance[0][0]

        log.debug('filling offer, mixdepth=' + str(mixdepth))

        # mixdepth is the chosen depth we'll be spending from
        cj_addr = self.wallet.get_internal_addr((mixdepth + 1) %
                                                self.wallet.max_mix_depth)
        change_addr = self.wallet.get_internal_addr(mixdepth)

        utxos = self.wallet.select_utxos(mixdepth, total_amount)
        my_total_in = sum([va['value'] for va in utxos.values()])
        real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount)
        change_value = my_total_in - amount - cjorder.txfee + real_cjfee
        if change_value <= jm_single().DUST_THRESHOLD:
            log.debug('change value=%d below dust threshold, finding new utxos'
                      % (change_value))
            try:
                utxos = self.wallet.select_utxos(
                    mixdepth, total_amount + jm_single().DUST_THRESHOLD)
            except Exception:
                log.debug(
                    'dont have the required UTXOs to make a output above the dust threshold, quitting')
                return None, None, None

        return utxos, cj_addr, change_addr
Example #5
0
def create_depth_chart(db, cj_amount, args=None):
    if args is None:
        args = {}
    sqlorders = db.execute('SELECT * FROM orderbook;').fetchall()
    orderfees = sorted([
        calc_cj_fee(o['ordertype'], o['cjfee'], cj_amount) / 1e8
        for o in sqlorders if o['minsize'] <= cj_amount <= o['maxsize']
    ])

    if len(orderfees) == 0:
        return 'No orders at amount ' + str(cj_amount / 1e8)
    fig = plt.figure()
    scale = args.get("scale")
    if (scale is not None) and (scale[0] == "log"):
        orderfees = [float(fee) for fee in orderfees]
        if orderfees[0] > 0:
            ratio = orderfees[-1] / orderfees[0]
            step = ratio**0.0333  # 1/30
            bins = [orderfees[0] * (step**i) for i in range(30)]
        else:
            ratio = orderfees[-1] / 1e-8  # single satoshi placeholder
            step = ratio**0.0333  # 1/30
            bins = [1e-8 * (step**i) for i in range(30)]
            bins[0] = orderfees[0]  # replace placeholder
        plt.xscale('log')
    else:
        bins = 30
    if len(orderfees
           ) == 1:  # these days we have liquidity, but just in case...
        plt.hist(orderfees, bins, rwidth=0.8, range=(0, orderfees[0] * 2))
    else:
        plt.hist(orderfees, bins, rwidth=0.8)
    plt.grid()
    plt.title('CoinJoin Orderbook Depth Chart for amount=' +
              str(cj_amount / 1e8) + 'btc')
    plt.xlabel('CoinJoin Fee / btc')
    plt.ylabel('Frequency')
    return get_graph_html(fig)
Example #6
0
def create_depth_chart(db, cj_amount, args=None):
    if args is None:
        args = {}
    sqlorders = db.execute('SELECT * FROM orderbook;').fetchall()
    orderfees = sorted([calc_cj_fee(o['ordertype'], o['cjfee'], cj_amount) / 1e8
                        for o in sqlorders
                        if o['minsize'] <= cj_amount <= o[
                            'maxsize']])

    if len(orderfees) == 0:
        return 'No orders at amount ' + str(cj_amount / 1e8)
    fig = plt.figure()
    scale = args.get("scale")
    if (scale is not None) and (scale[0] == "log"):
        orderfees = [float(fee) for fee in orderfees]
        if orderfees[0] > 0:
            ratio = orderfees[-1] / orderfees[0]
            step = ratio ** 0.0333  # 1/30
            bins = [orderfees[0] * (step ** i) for i in range(30)]
        else:
            ratio = orderfees[-1] / 1e-8  # single satoshi placeholder
            step = ratio ** 0.0333  # 1/30
            bins = [1e-8 * (step ** i) for i in range(30)]
            bins[0] = orderfees[0]  # replace placeholder
        plt.xscale('log')
    else:
        bins = 30
    if len(orderfees) == 1:  # these days we have liquidity, but just in case...
        plt.hist(orderfees, bins, rwidth=0.8, range=(0, orderfees[0] * 2))
    else:
        plt.hist(orderfees, bins, rwidth=0.8)
    plt.grid()
    plt.title('CoinJoin Orderbook Depth Chart for amount=' + str(cj_amount /
                                                                 1e8) + 'btc')
    plt.xlabel('CoinJoin Fee / btc')
    plt.ylabel('Frequency')
    return get_graph_html(fig)
    def oid_to_order(self, cjorder, oid, amount):
        '''Coins rotate circularly from max mixdepth back to mixdepth 0'''
        mix_balance = self.wallet.get_balance_by_mixdepth()
        total_amount = amount + cjorder.txfee
        log.debug('amount, txfee, total_amount = ' + str(amount) + str(
            cjorder.txfee) + str(total_amount))

        # look for exact amount available with no change
        filtered_mix_balance = [m
                                for m in mix_balance.iteritems()
                                if m[1] == total_amount]
        if filtered_mix_balance:
            log.debug('mix depths that have the exact amount needed = ' + str(
                filtered_mix_balance))
        else:
            log.debug('no mix depths contain the exact amount needed.')
            filtered_mix_balance = [m
                                    for m in mix_balance.iteritems()
                                    if m[1] >= total_amount]
            log.debug('mix depths that have enough = ' + str(
                filtered_mix_balance))
            filtered_mix_balance = [m
                                    for m in mix_balance.iteritems()
                                    if m[1] >= total_amount + min_output_size]
            log.debug('mix depths that have enough with min_output_size, ' +
                      str(filtered_mix_balance))
            try:
                len(filtered_mix_balance) > 0
            except Exception:
                log.debug('No mix depths have enough funds to cover the ' +
                          'amount, cjfee, and min_output_size.')
                return None, None, None

        # prioritize by mixdepths sequencially
        # keep coins moving towards last mixdepth, clumps once they get there
        # makes sure coins sent to mixdepth 0 will get mixed to max mixdepth
        filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0])

        # clumping. push all coins towards the largest mixdepth
        # the largest amount of coins are available to join with (since joins always come from a single depth)
        # the maker commands a higher fee for the larger amounts 
        # order ascending but circularly with largest last
        # note, no need to consider max_offer_size here
        #largest_mixdepth = sorted(
        #    filtered_mix_balance,
        #    key=lambda x: x[1],)[-1]  # find largest amount
        #smb = sorted(filtered_mix_balance,
        #             key=lambda x: x[0])  # seq of mixdepth num
        #next_index = smb.index(largest_mixdepth) + 1
        #mmd = self.wallet.max_mix_depth
        #filtered_mix_balance = smb[next_index % mmd:] + smb[:next_index % mmd]

        # use mix depth that has the closest amount of coins to what this transaction needs
        # keeps coins moving through mix depths more quickly
        # and its more likely to use txos of a similiar size to this transaction
        # sort smallest to largest usable amount
        #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[1])

        # use mix depth with the most coins, 
        # creates a more even distribution across mix depths
        # and a more diverse txo selection in each depth
        # sort largest to smallest amount
        #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[1], reverse=True)

        # use a random usable mixdepth. 
        # warning, could expose more txos to malicous taker requests
        #filtered_mix_balance = random.choice(filtered_mix_balance)

        log.debug('sorted order of filtered_mix_balance = ' + str(
            filtered_mix_balance))

        mixdepth = filtered_mix_balance[0][0]

        log.debug('filling offer, mixdepth=' + str(mixdepth))

        # mixdepth is the chosen depth we'll be spending from
        cj_addr = self.wallet.get_internal_addr((mixdepth + 1) %
                                                self.wallet.max_mix_depth)
        change_addr = self.wallet.get_internal_addr(mixdepth)

        utxos = self.wallet.select_utxos(mixdepth, total_amount)
        my_total_in = sum([va['value'] for va in utxos.values()])
        real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount)
        change_value = my_total_in - amount - cjorder.txfee + real_cjfee
        if change_value <= min_output_size:
            log.debug('change value=%d below dust threshold, finding new utxos'
                      % (change_value))
            try:
                utxos = self.wallet.select_utxos(mixdepth,
                                                 total_amount + min_output_size)
            except Exception:
                log.debug(
                    'dont have the required UTXOs to make a output above the dust threshold, quitting')
                return None, None, None

        return utxos, cj_addr, change_addr
    def create_my_orders(self):
        mix_balance = self.wallet.get_balance_by_mixdepth()
        log.debug('mix_balance = ' + str(mix_balance))
        log.debug('mix_btcance = ' + str([(x, y / 1e8)
                                          for x, y in mix_balance.iteritems()]))
        sorted_mix_balance = sorted(
            list(mix_balance.iteritems()),
            key=lambda a: a[1])  #sort by size

        largest_mixdepth_size = sorted_mix_balance[-1][1]
        if largest_mixdepth_size <= min_output_size:
            print("ALERT: not enough funds available in wallet")
            return []

        if override_offers:
            log.debug('override_offers = \n' + '\n'.join([str(
                o) for o in override_offers]))
            # make sure custom offers dont create a negative net
            for offer in override_offers:
                if offer['ordertype'] == 'absorder':
                    profit = offer['cjfee']
                    needed = 'make txfee be less then the cjfee'
                elif offer['ordertype'] == 'relorder':
                    profit = calc_cj_fee(offer['ordertype'], offer['cjfee'],
                                         offer['minsize'])
                    if float(offer['cjfee']) > 0:
                        needed = 'set minsize to ' + str(int(int(offer[
                            'txfee'] / float(offer['cjfee']))))
                if int(offer['txfee']) > profit:
                    print("ALERT: negative yield")
                    print('-> ' + str(offer))
                    print(needed)
                    # if you really wanted to, you could comment out the next line.
                    sys.exit(0)
            return override_offers

        offer_lowx = max(offer_low, min_output_size)
        if offer_high:
            offer_highx = min(offer_high,
                              largest_mixdepth_size - min_output_size)
        else:
            offer_highx = largest_mixdepth_size - min_output_size
            # note, subtracting mix_output_size here to make minimum size change
            # todo, make an offer for exactly the max size with no change

            # Offers
        if offer_spread == 'fibonacci':
            offer_levels = fib_seq(offer_lowx,
                                   offer_highx,
                                   num_offers,
                                   upper_bound=True)
            offer_levels = [int(round(x)) for x in offer_levels]
        elif offer_spread == 'evenly':
            first_upper_bound = (offer_highx - offer_lowx) / num_offers
            offer_levels = list(range(first_upper_bound, offer_highx, (
                offer_highx - first_upper_bound) / (num_offers - 1)))
            offer_levels = offer_levels[0:(num_offers - 1)] + [offer_highx]
        elif offer_spread == 'random':
            offer_levels = sorted([random.randrange(offer_lowx, offer_highx)
                                   for n in range(num_offers - 1)] +
                                  [random.randrange(offer_highx - (
                                      offer_highx / num_offers), offer_highx)])
        elif offer_spread == 'bymixdepth':
            offer_levels = []
            for m in sorted_mix_balance:
                if m[1] == 0:
                    continue
                elif m[1] <= offer_lowx:
                    # todo, low mix balances get an absolute offer
                    continue
                elif m[1] > offer_highx:
                    offer_levels += [offer_highx]
                    break
                else:
                    offer_levels += [m[1]]
            # note, offer_levels len can be less then num_offers here
        elif offer_spread == 'custom':
            assert len(custom_offers) == num_offers
            offer_levels = [
                int((decimal.Decimal(str(x))).quantize(0))
                for x in sorted(custom_offers)
            ]
            if offer_levels[-1] > offer_highx:
                log.debug(
                    'ALERT: Your custom offers exceeds you max offer size.')
                log.debug('offer = ' + str(offer_levels[-1]) + ' offer_highx = '
                          + str(offer_highx))
                sys.exit(0)
        else:
            log.debug('invalid offer_spread = ' + str(offer_spread))
            sys.exit(0)

        # CJFees
        cjfee_lowx = decimal.Decimal(str(cjfee_low)) / 100
        cjfee_highx = decimal.Decimal(str(cjfee_high)) / 100
        if cjfee_spread == 'fibonacci':
            cjfee_levels = fib_seq(cjfee_lowx, cjfee_highx, num_offers)
            cjfee_levels = ["%0.7f" % x for x in cjfee_levels]
        elif cjfee_spread == 'evenly':
            cjfee_levels = drange(cjfee_lowx, cjfee_highx,
                                  (cjfee_highx - cjfee_lowx) /
                                  (num_offers - 1))  # evenly spaced
            cjfee_levels = ["%0.7f" % x for x in cjfee_levels]
        elif cjfee_spread == 'random':
            cjfee_levels = sorted(
                ["%0.7f" % random.uniform(
                    float(cjfee_lowx), float(cjfee_highx))
                 for n in range(num_offers)])  # randomly spaced
        elif cjfee_spread == 'custom':
            cjfee_levels = [str(decimal.Decimal(str(x)) / 100)
                            for x in custom_cjfees]
            leftout = num_offers - len(cjfee_levels)
            while leftout > 0:
                log.debug('ALERT: cjfee_custom has too few items')
                cjfee_levels.append(cjfee_levels[-1])
                leftout -= 1
        else:
            log.debug('invalid cjfee_spread = ' + str(cjfee_spread))
            sys.exit(0)

        # TXFees
        if txfee_spread == 'fibonacci':
            txfee_levels = fib_seq(txfee_low, txfee_high, num_offers)
            txfee_levels = [int(round(x)) for x in txfee_levels]
        elif txfee_spread == 'evenly':
            txfee_levels = list(range(txfee_low, txfee_high, (
                txfee_high - txfee_low) / (num_offers - 1)))
            txfee_levels = txfee_levels[0:(num_offers - 1)] + [txfee_high]
        elif txfee_spread == 'random':
            txfee_levels = sorted([random.randrange(txfee_low, txfee_high)
                                   for n in range(num_offers - 1)] +
                                  [random.randrange(txfee_high - (
                                      txfee_high / num_offers), txfee_high)])
        elif txfee_spread == 'custom':
            txfee_levels = [x for x in custom_txfees]
        else:
            log.debug('invalid txfee_spread = ' + str(txfee_spread))
            sys.exit(0)

        log.debug('offer_levels = ' + str(offer_levels))
        lower_bound_balances = [offer_lowx] + [x for x in offer_levels[:-1]]
        if offer_spread == 'bymixdepth':
            cjfee_levels = cjfee_levels[-len(offer_levels):]
            txfee_levels = txfee_levels[-len(offer_levels):]
        offer_ranges = zip(offer_levels, lower_bound_balances, cjfee_levels,
                           txfee_levels)
        log.debug('offer_ranges = ' + str(offer_ranges))
        offers = []
        oid = 0

        # create absorders for mixdepth dust
        offer_levels = []
        for m in sorted_mix_balance:
            if m[1] == 0:
                continue
            #elif False: # disabled
            #elif m[1] <= 2e8:  # absorder all mixdepths less then
            elif m[1] <= offer_lowx:
                offer = {'oid': oid,
                         'ordertype': 'absorder',
                         'minsize': m[1],
                         'maxsize': m[1],
                         'txfee': 0,
                         'cjfee': 0}
                #'txfee': txfee_low,
                #'cjfee': min_revenue}
                oid += 1
                offers.append(offer)
            elif m[1] > offer_lowx:
                break

        for upper, lower, cjfee, txfee in offer_ranges:
            cjfee = float(cjfee)
            if cjfee == 0:
                min_needed = profit_req_per_transaction + txfee
            elif cjfee > 0:
                min_needed = int((profit_req_per_transaction + txfee + 1) / cjfee)
            elif cjfee < 0:
                sys.exit('negative fee not supported here')
            if min_needed <= lower:
                # create a regular relorder
                offer = {'oid': oid,
                         'ordertype': 'relorder',
                         'minsize': lower,
                         'maxsize': upper,
                         'txfee': txfee,
                         'cjfee': cjfee}
            elif min_needed > lower and min_needed < upper:
                # create two offers. An absolute for lower bound need, and relorder for the rest
                offer = {'oid': oid,
                         'ordertype': 'absorder',
                         'minsize': lower,
                         'maxsize': min_needed - 1,
                         'txfee': txfee,
                         'cjfee': profit_req_per_transaction + txfee}
                oid += 1
                offers.append(offer)
                offer = {'oid': oid,
                         'ordertype': 'relorder',
                         'minsize': min_needed,
                         'maxsize': upper,
                         'txfee': txfee,
                         'cjfee': cjfee}
            elif min_needed >= upper:
                # just create an absolute offer
                offer = {'oid': oid,
                         'ordertype': 'absorder',
                         'minsize': lower,
                         'maxsize': upper,
                         'txfee': txfee,
                         'cjfee': profit_req_per_transaction + txfee}
                # todo: combine neighboring absorders into a single one
            oid += 1
            offers.append(offer)

        deluxe_offer_display = []
        header = 'oid'.rjust(5)
        header += 'type'.rjust(7)
        header += 'minsize btc'.rjust(15)
        header += 'maxsize btc'.rjust(15)
        header += 'min revenue satosh'.rjust(22)
        header += 'max revenue satosh'.rjust(22)
        deluxe_offer_display.append(header)
        for o in offers:
            line = str(o['oid']).rjust(5)
            if o['ordertype'] == 'absorder':
                line += 'abs'.rjust(7)
            elif o['ordertype'] == 'relorder':
                line += 'rel'.rjust(7)
            line += str(o['minsize'] / 1e8).rjust(15)
            line += str(o['maxsize'] / 1e8).rjust(15)
            if o['ordertype'] == 'absorder':
                line += str(o['cjfee']).rjust(22)
            elif o['ordertype'] == 'relorder':
                line += str(int(float(o['cjfee']) * int(o['minsize']))).rjust(
                    22)
                line += str(int(float(o['cjfee']) * int(o['maxsize']))).rjust(
                    22)
            deluxe_offer_display.append(line)

        log.debug('deluxe offer display = \n' + '\n'.join([str(
            x) for x in deluxe_offer_display]))

        log.debug('generated offers = \n' + '\n'.join([str(o) for o in offers]))

        # sanity check
        for offer in offers:
            assert offer['minsize'] >= 0
            assert offer['maxsize'] > 0
            assert offer['minsize'] <= offer['maxsize']

        return offers
Example #9
0
    def oid_to_order(self, cjorder, oid, amount):
        """The only change from *basic here (for now) is that
        we choose outputs to avoid increasing the max_mixdepth
        as much as possible, thus avoiding reannouncement as
        much as possible.
        """
        total_amount = amount + cjorder.txfee
        mix_balance = self.wallet.get_balance_by_mixdepth()
        max_mix = max(mix_balance, key=mix_balance.get)
        min_mix = min(mix_balance, key=mix_balance.get)

        filtered_mix_balance = [
            m for m in mix_balance.iteritems() if m[1] >= total_amount
        ]
        if not filtered_mix_balance:
            return None, None, None

        log.debug('mix depths that have enough = ' + str(filtered_mix_balance))

        # Avoid the max mixdepth wherever possible, to avoid changing the
        # offer. Algo:
        #"mixdepth" is the mixdepth we are spending FROM, so it is also
        # the destination of change.
        #"cjoutdepth" is the mixdepth we are sending coinjoin out to.
        #
        # Find a mixdepth, in the set that have enough, which is
        # not the maximum, and choose any from that set as "mixdepth".
        # If not possible, it means only the max_mix depth has enough,
        # so must choose "mixdepth" to be that.
        # To find the cjoutdepth: ensure that max != min, if so it means
        # we had only one depth; in that case, just set "cjoutdepth"
        # to the next mixdepth. Otherwise, we set "cjoutdepth" to the minimum.

        nonmax_mix_balance = [
            m for m in filtered_mix_balance if m[0] != max_mix
        ]
        if not nonmax_mix_balance:
            log.debug("Could not spend from a mixdepth which is not max")
            mixdepth = max_mix
        else:
            mixdepth = nonmax_mix_balance[0][0]
        log.info('filling offer, mixdepth=' + str(mixdepth))

        # mixdepth is the chosen depth we'll be spending from
        # min_mixdepth is the one we want to send our cjout TO,
        # to minimize chance of it becoming the largest, and reannouncing
        # offer.
        if mixdepth == min_mix:
            cjoutmix = (mixdepth + 1) % self.wallet.max_mix_depth
            # don't send cjout to max
            if cjoutmix == max_mix:
                cjoutmix = (cjoutmix + 1) % self.wallet.max_mix_depth
        else:
            cjoutmix = min_mix
        cj_addr = self.wallet.get_internal_addr(cjoutmix)
        change_addr = self.wallet.get_internal_addr(mixdepth)

        utxos = self.wallet.select_utxos(mixdepth, total_amount)
        my_total_in = sum([va['value'] for va in utxos.values()])
        real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount)
        change_value = my_total_in - amount - cjorder.txfee + real_cjfee
        if change_value <= jm_single().DUST_THRESHOLD:
            log.debug(('change value={} below dust threshold, '
                       'finding new utxos').format(change_value))
            try:
                utxos = self.wallet.select_utxos(
                    mixdepth, total_amount + jm_single().DUST_THRESHOLD)
            except Exception:
                log.info(
                    'dont have the required UTXOs to make a '
                    'output above the dust threshold, quitting. '
                    'This can sometimes happen and does not require user action.'
                )
                return None, None, None

        return utxos, cj_addr, change_addr
Example #10
0
    def oid_to_order(self, cjorder, oid, amount):
        '''Coins rotate circularly from max mixdepth back to mixdepth 0'''
        mix_balance = self.wallet.get_balance_by_mixdepth()
        total_amount = amount + cjorder.txfee
        log.debug('amount, txfee, total_amount = ' + str(amount) +
                  str(cjorder.txfee) + str(total_amount))

        # look for exact amount available with no change
        # not supported because change output required
        # needs this fixed https://github.com/JoinMarket-Org/joinmarket/issues/418
        #filtered_mix_balance = [m
        #                        for m in mix_balance.iteritems()
        #                        if m[1] == total_amount]
        #if filtered_mix_balance:
        #    log.debug('mix depths that have the exact amount needed = ' + str(
        #        filtered_mix_balance))
        #else:
        #    log.debug('no mix depths contain the exact amount needed.')

        filtered_mix_balance = [
            m for m in mix_balance.iteritems() if m[1] >= (total_amount)
        ]
        log.debug('mix depths that have enough = ' + str(filtered_mix_balance))
        filtered_mix_balance = [
            m for m in mix_balance.iteritems()
            if m[1] >= total_amount + output_size_min
        ]
        log.debug('mix depths that have enough with output_size_min, ' +
                  str(filtered_mix_balance))
        try:
            len(filtered_mix_balance) > 0
        except Exception:
            log.debug('No mix depths have enough funds to cover the ' +
                      'amount, cjfee, and output_size_min.')
            return None, None, None

        # slinky clumping: push all coins towards the largest mixdepth,
        # then spend from the largest mixdepth into the next mixdepth.
        # the coins stay in the next mixdepth until they are all there,
        # and then get spent into the next mixdepth, ad infinitum.
        lmd = sorted(
            filtered_mix_balance,
            key=lambda x: x[1],
        )[-1]
        smb = sorted(filtered_mix_balance, key=lambda x: x[0])  # seq of md num
        mmd = self.wallet.max_mix_depth
        nmd = (lmd[0] + 1) % mmd
        if nmd not in [x[0] for x in smb]:  # use all usable
            next_si = (smb.index(lmd) + 1) % len(smb)
            filtered_mix_balance = smb[next_si:] + smb[:next_si]
        else:
            nmd = [x for x in smb if x[0] == nmd][0]
            others = [x for x in smb if x != nmd and x != lmd]
            if not others:  # just these two remain, prioritize largest
                filtered_mix_balance = [lmd, nmd]
            else:  # use all usable
                if [x for x in others if x[1] >= nmd[1]]:
                    next_si = (smb.index(lmd) + 1) % len(smb)
                    filtered_mix_balance = smb[next_si:] + smb[:next_si]
                else:  # others are not large, dont use nmd
                    next_si = (smb.index(lmd) + 2) % len(smb)
                    filtered_mix_balance = smb[next_si:] + smb[:next_si]

        # prioritize by mixdepths ascending
        # keep coins moving towards last mixdepth, clumps there.
        # makes sure coins sent to mixdepth 0 will get mixed to mixdepth 5
        #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0])

        # use mix depth with the most coins,
        # creates a more even distribution across mix depths
        # and a more diverse txo selection in each depth
        # sort largest to smallest amount
        #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[1], reverse=True)

        # use a random usable mixdepth.
        # warning, could expose more txos to malicous taker requests
        #filtered_mix_balance = [random.choice(filtered_mix_balance)]

        log.debug('sorted order of filtered_mix_balance = ' +
                  str(filtered_mix_balance))
        mixdepth = filtered_mix_balance[0][0]
        log.debug('filling offer, mixdepth=' + str(mixdepth))
        # mixdepth is the chosen depth we'll be spending from
        cj_addr = self.wallet.get_internal_addr(
            (mixdepth + 1) % self.wallet.max_mix_depth)
        change_addr = self.wallet.get_internal_addr(mixdepth)
        utxos = self.wallet.select_utxos(mixdepth, total_amount)
        my_total_in = sum([va['value'] for va in utxos.values()])
        real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount)
        change_value = my_total_in - amount - cjorder.txfee + real_cjfee
        if change_value <= output_size_min:
            log.debug(
                'change value=%d below dust threshold, finding new utxos' %
                (change_value))
            try:
                utxos = self.wallet.select_utxos(
                    mixdepth, total_amount + output_size_min)
            except Exception:
                log.debug(
                    'dont have the required UTXOs to make a output above the dust threshold, quitting'
                )
                return None, None, None
        return utxos, cj_addr, change_addr
Example #11
0
    def oid_to_order(self, cjorder, oid, amount):
        '''Coins rotate circularly from max mixdepth back to mixdepth 0'''
        mix_balance = self.wallet.get_balance_by_mixdepth()
        total_amount = amount + cjorder.txfee
        log.debug('amount, txfee, total_amount = ' + str(amount) +
                  str(cjorder.txfee) + str(total_amount))

        # look for exact amount available with no change
        filtered_mix_balance = [
            m for m in mix_balance.iteritems() if m[1] == total_amount
        ]
        if filtered_mix_balance:
            log.debug('mix depths that have the exact amount needed = ' +
                      str(filtered_mix_balance))
        else:
            log.debug('no mix depths contain the exact amount needed.')
            filtered_mix_balance = [
                m for m in mix_balance.iteritems() if m[1] >= total_amount
            ]
            log.debug('mix depths that have enough = ' +
                      str(filtered_mix_balance))
            filtered_mix_balance = [
                m for m in mix_balance.iteritems()
                if m[1] >= total_amount + min_output_size
            ]
            log.debug('mix depths that have enough with min_output_size, ' +
                      str(filtered_mix_balance))
            try:
                len(filtered_mix_balance) > 0
            except Exception:
                log.debug('No mix depths have enough funds to cover the ' +
                          'amount, cjfee, and min_output_size.')
                return None, None, None

        # prioritize by mixdepths sequencially
        # keep coins moving towards last mixdepth, clumps once they get there
        # makes sure coins sent to mixdepth 0 will get mixed to max mixdepth
        filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0])

        # clumping. push all coins towards the largest mixdepth
        # the largest amount of coins are available to join with (since joins always come from a single depth)
        # the maker commands a higher fee for the larger amounts
        # order ascending but circularly with largest last
        # note, no need to consider max_offer_size here
        #largest_mixdepth = sorted(
        #    filtered_mix_balance,
        #    key=lambda x: x[1],)[-1]  # find largest amount
        #smb = sorted(filtered_mix_balance,
        #             key=lambda x: x[0])  # seq of mixdepth num
        #next_index = smb.index(largest_mixdepth) + 1
        #mmd = self.wallet.max_mix_depth
        #filtered_mix_balance = smb[next_index % mmd:] + smb[:next_index % mmd]

        # use mix depth that has the closest amount of coins to what this transaction needs
        # keeps coins moving through mix depths more quickly
        # and its more likely to use txos of a similiar size to this transaction
        # sort smallest to largest usable amount
        #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[1])

        # use mix depth with the most coins,
        # creates a more even distribution across mix depths
        # and a more diverse txo selection in each depth
        # sort largest to smallest amount
        #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[1], reverse=True)

        # use a random usable mixdepth.
        # warning, could expose more txos to malicous taker requests
        #filtered_mix_balance = random.choice(filtered_mix_balance)

        log.debug('sorted order of filtered_mix_balance = ' +
                  str(filtered_mix_balance))

        mixdepth = filtered_mix_balance[0][0]

        log.debug('filling offer, mixdepth=' + str(mixdepth))

        # mixdepth is the chosen depth we'll be spending from
        cj_addr = self.wallet.get_internal_addr(
            (mixdepth + 1) % self.wallet.max_mix_depth)
        change_addr = self.wallet.get_internal_addr(mixdepth)

        utxos = self.wallet.select_utxos(mixdepth, total_amount)
        my_total_in = sum([va['value'] for va in utxos.values()])
        real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount)
        change_value = my_total_in - amount - cjorder.txfee + real_cjfee
        if change_value <= min_output_size:
            log.debug(
                'change value=%d below dust threshold, finding new utxos' %
                (change_value))
            try:
                utxos = self.wallet.select_utxos(
                    mixdepth, total_amount + min_output_size)
            except Exception:
                log.debug(
                    'dont have the required UTXOs to make a output above the dust threshold, quitting'
                )
                return None, None, None

        return utxos, cj_addr, change_addr
Example #12
0
    def create_my_orders(self):
        mix_balance = self.wallet.get_balance_by_mixdepth()
        log.debug('mix_balance = ' + str(mix_balance))
        log.debug('mix_btcance = ' +
                  str([(x, y / 1e8) for x, y in mix_balance.iteritems()]))
        sorted_mix_balance = sorted(list(mix_balance.iteritems()),
                                    key=lambda a: a[1])  #sort by size

        largest_mixdepth_size = sorted_mix_balance[-1][1]
        if largest_mixdepth_size <= min_output_size:
            print("ALERT: not enough funds available in wallet")
            return []

        if override_offers:
            log.debug('override_offers = \n' +
                      '\n'.join([str(o) for o in override_offers]))
            # make sure custom offers dont create a negative net
            for offer in override_offers:
                if offer['ordertype'] == 'absorder':
                    profit = offer['cjfee']
                    needed = 'make txfee be less then the cjfee'
                elif offer['ordertype'] == 'relorder':
                    profit = calc_cj_fee(offer['ordertype'], offer['cjfee'],
                                         offer['minsize'])
                    if float(offer['cjfee']) > 0:
                        needed = 'set minsize to ' + str(
                            int(int(offer['txfee'] / float(offer['cjfee']))))
                if int(offer['txfee']) > profit:
                    print("ALERT: negative yield")
                    print('-> ' + str(offer))
                    print(needed)
                    # if you really wanted to, you could comment out the next line.
                    sys.exit(0)
            return override_offers

        offer_lowx = max(offer_low, min_output_size)
        if offer_high:
            offer_highx = min(offer_high,
                              largest_mixdepth_size - min_output_size)
        else:
            offer_highx = largest_mixdepth_size - min_output_size
            # note, subtracting mix_output_size here to make minimum size change
            # todo, make an offer for exactly the max size with no change

            # Offers
        if offer_spread == 'fibonacci':
            offer_levels = fib_seq(offer_lowx,
                                   offer_highx,
                                   num_offers,
                                   upper_bound=True)
            offer_levels = [int(round(x)) for x in offer_levels]
        elif offer_spread == 'evenly':
            first_upper_bound = (offer_highx - offer_lowx) / num_offers
            offer_levels = list(
                range(first_upper_bound, offer_highx,
                      (offer_highx - first_upper_bound) / (num_offers - 1)))
            offer_levels = offer_levels[0:(num_offers - 1)] + [offer_highx]
        elif offer_spread == 'random':
            offer_levels = sorted([
                random.randrange(offer_lowx, offer_highx)
                for n in range(num_offers - 1)
            ] + [
                random.randrange(offer_highx -
                                 (offer_highx / num_offers), offer_highx)
            ])
        elif offer_spread == 'bymixdepth':
            offer_levels = []
            for m in sorted_mix_balance:
                if m[1] == 0:
                    continue
                elif m[1] <= offer_lowx:
                    # todo, low mix balances get an absolute offer
                    continue
                elif m[1] > offer_highx:
                    offer_levels += [offer_highx]
                    break
                else:
                    offer_levels += [m[1]]
            # note, offer_levels len can be less then num_offers here
        elif offer_spread == 'custom':
            assert len(custom_offers) == num_offers
            offer_levels = [
                int((decimal.Decimal(str(x))).quantize(0))
                for x in sorted(custom_offers)
            ]
            if offer_levels[-1] > offer_highx:
                log.debug(
                    'ALERT: Your custom offers exceeds you max offer size.')
                log.debug('offer = ' + str(offer_levels[-1]) +
                          ' offer_highx = ' + str(offer_highx))
                sys.exit(0)
        else:
            log.debug('invalid offer_spread = ' + str(offer_spread))
            sys.exit(0)

        # CJFees
        cjfee_lowx = decimal.Decimal(str(cjfee_low)) / 100
        cjfee_highx = decimal.Decimal(str(cjfee_high)) / 100
        if cjfee_spread == 'fibonacci':
            cjfee_levels = fib_seq(cjfee_lowx, cjfee_highx, num_offers)
            cjfee_levels = ["%0.7f" % x for x in cjfee_levels]
        elif cjfee_spread == 'evenly':
            cjfee_levels = drange(cjfee_lowx, cjfee_highx,
                                  (cjfee_highx - cjfee_lowx) /
                                  (num_offers - 1))  # evenly spaced
            cjfee_levels = ["%0.7f" % x for x in cjfee_levels]
        elif cjfee_spread == 'random':
            cjfee_levels = sorted([
                "%0.7f" % random.uniform(float(cjfee_lowx), float(cjfee_highx))
                for n in range(num_offers)
            ])  # randomly spaced
        elif cjfee_spread == 'custom':
            cjfee_levels = [
                str(decimal.Decimal(str(x)) / 100) for x in custom_cjfees
            ]
            leftout = num_offers - len(cjfee_levels)
            while leftout > 0:
                log.debug('ALERT: cjfee_custom has too few items')
                cjfee_levels.append(cjfee_levels[-1])
                leftout -= 1
        else:
            log.debug('invalid cjfee_spread = ' + str(cjfee_spread))
            sys.exit(0)

        # TXFees
        if txfee_spread == 'fibonacci':
            txfee_levels = fib_seq(txfee_low, txfee_high, num_offers)
            txfee_levels = [int(round(x)) for x in txfee_levels]
        elif txfee_spread == 'evenly':
            txfee_levels = list(
                range(txfee_low, txfee_high,
                      (txfee_high - txfee_low) / (num_offers - 1)))
            txfee_levels = txfee_levels[0:(num_offers - 1)] + [txfee_high]
        elif txfee_spread == 'random':
            txfee_levels = sorted([
                random.randrange(txfee_low, txfee_high)
                for n in range(num_offers - 1)
            ] + [
                random.randrange(txfee_high -
                                 (txfee_high / num_offers), txfee_high)
            ])
        elif txfee_spread == 'custom':
            txfee_levels = [x for x in custom_txfees]
        else:
            log.debug('invalid txfee_spread = ' + str(txfee_spread))
            sys.exit(0)

        log.debug('offer_levels = ' + str(offer_levels))
        lower_bound_balances = [offer_lowx] + [x for x in offer_levels[:-1]]
        if offer_spread == 'bymixdepth':
            cjfee_levels = cjfee_levels[-len(offer_levels):]
            txfee_levels = txfee_levels[-len(offer_levels):]
        offer_ranges = zip(offer_levels, lower_bound_balances, cjfee_levels,
                           txfee_levels)
        log.debug('offer_ranges = ' + str(offer_ranges))
        offers = []
        oid = 0

        # create absorders for mixdepth dust
        offer_levels = []
        for m in sorted_mix_balance:
            if m[1] == 0:
                continue
            #elif False: # disabled
            #elif m[1] <= 2e8:  # absorder all mixdepths less then
            elif m[1] <= offer_lowx:
                offer = {
                    'oid': oid,
                    'ordertype': 'absorder',
                    'minsize': m[1],
                    'maxsize': m[1],
                    'txfee': 0,
                    'cjfee': 0
                }
                #'txfee': txfee_low,
                #'cjfee': min_revenue}
                oid += 1
                offers.append(offer)
            elif m[1] > offer_lowx:
                break

        for upper, lower, cjfee, txfee in offer_ranges:
            cjfee = float(cjfee)
            if cjfee == 0:
                min_needed = profit_req_per_transaction + txfee
            elif cjfee > 0:
                min_needed = int(
                    (profit_req_per_transaction + txfee + 1) / cjfee)
            elif cjfee < 0:
                sys.exit('negative fee not supported here')
            if min_needed <= lower:
                # create a regular relorder
                offer = {
                    'oid': oid,
                    'ordertype': 'relorder',
                    'minsize': lower,
                    'maxsize': upper,
                    'txfee': txfee,
                    'cjfee': cjfee
                }
            elif min_needed > lower and min_needed < upper:
                # create two offers. An absolute for lower bound need, and relorder for the rest
                offer = {
                    'oid': oid,
                    'ordertype': 'absorder',
                    'minsize': lower,
                    'maxsize': min_needed - 1,
                    'txfee': txfee,
                    'cjfee': profit_req_per_transaction + txfee
                }
                oid += 1
                offers.append(offer)
                offer = {
                    'oid': oid,
                    'ordertype': 'relorder',
                    'minsize': min_needed,
                    'maxsize': upper,
                    'txfee': txfee,
                    'cjfee': cjfee
                }
            elif min_needed >= upper:
                # just create an absolute offer
                offer = {
                    'oid': oid,
                    'ordertype': 'absorder',
                    'minsize': lower,
                    'maxsize': upper,
                    'txfee': txfee,
                    'cjfee': profit_req_per_transaction + txfee
                }
                # todo: combine neighboring absorders into a single one
            oid += 1
            offers.append(offer)

        deluxe_offer_display = []
        header = 'oid'.rjust(5)
        header += 'type'.rjust(7)
        header += 'minsize btc'.rjust(15)
        header += 'maxsize btc'.rjust(15)
        header += 'min revenue satosh'.rjust(22)
        header += 'max revenue satosh'.rjust(22)
        deluxe_offer_display.append(header)
        for o in offers:
            line = str(o['oid']).rjust(5)
            if o['ordertype'] == 'absorder':
                line += 'abs'.rjust(7)
            elif o['ordertype'] == 'relorder':
                line += 'rel'.rjust(7)
            line += str(o['minsize'] / 1e8).rjust(15)
            line += str(o['maxsize'] / 1e8).rjust(15)
            if o['ordertype'] == 'absorder':
                line += str(o['cjfee']).rjust(22)
            elif o['ordertype'] == 'relorder':
                line += str(int(float(o['cjfee']) *
                                int(o['minsize']))).rjust(22)
                line += str(int(float(o['cjfee']) *
                                int(o['maxsize']))).rjust(22)
            deluxe_offer_display.append(line)

        log.debug('deluxe offer display = \n' +
                  '\n'.join([str(x) for x in deluxe_offer_display]))

        log.debug('generated offers = \n' + '\n'.join([str(o)
                                                       for o in offers]))

        # sanity check
        for offer in offers:
            assert offer['minsize'] >= 0
            assert offer['maxsize'] > 0
            assert offer['minsize'] <= offer['maxsize']

        return offers
Example #13
0
    def create_my_orders(self):
        mix_balance = self.wallet.get_balance_by_mixdepth()
        log.debug('mix_balance = ' + str(mix_balance))
        nondust_mix_balance = dict([(m, b)
                                    for m, b in mix_balance.iteritems()
                                    if b > jm_single().DUST_THRESHOLD])
        if len(nondust_mix_balance) == 0:
            log.debug('do not have any coins left')
            return []
        #sorts the mixdepth_balance map by balance size
        sorted_mix_balance = sorted(
            list(mix_balance.iteritems()),
            key=lambda a: a[1],
            reverse=True)
        minsize = int(
            1.5 * txfee / float(min(cjfee))
        )  #minimum size is such that you always net profit at least 50% of the miner fee
        filtered_mix_balance = [f for f in sorted_mix_balance if f[1] > minsize]
        delta = mix_levels - len(filtered_mix_balance)
        log.debug('minsize=' + str(minsize) + ' calc\'d with cjfee=' + str(min(
            cjfee)))
        lower_bound_balances = filtered_mix_balance[1:] + [(-1, minsize)]
        mix_balance_min = [
            (mxb[0], mxb[1], minb[1])
            for mxb, minb in zip(filtered_mix_balance, lower_bound_balances)
        ]
        mix_balance_min = mix_balance_min[::-1]  #reverse list order
        thecjfee = cjfee[::-1]

        log.debug('mixdepth_balance_min = ' + str(mix_balance_min))
        orders = []
        oid = 0
        for mix_bal_min in mix_balance_min:
            mixdepth, balance, mins = mix_bal_min
            #the maker class reads specific keys from the dict, but others
            # are allowed in there and will be ignored
            order = {'oid': oid + 1,
                     'ordertype': 'relorder',
                     'minsize': max(mins - jm_single().DUST_THRESHOLD,
                                    jm_single().DUST_THRESHOLD) + 1,
                     'maxsize': max(balance - jm_single().DUST_THRESHOLD,
                                    jm_single().DUST_THRESHOLD),
                     'txfee': txfee,
                     'cjfee': thecjfee[oid + delta],
                     'mixdepth': mixdepth}
            oid += 1
            orders.append(order)

        absorder_size = min(minsize, sorted_mix_balance[0][1])
        if absorder_size != 0:
            lowest_cjfee = thecjfee[min(oid, len(thecjfee) - 1)]
            absorder_fee = calc_cj_fee('relorder', lowest_cjfee, minsize)
            log.debug('absorder fee = ' + str(absorder_fee) + ' uses cjfee=' +
                      str(lowest_cjfee))
            #the absorder is always oid=0
            order = {'oid': 0,
                     'ordertype': 'absorder',
                     'minsize': jm_single().DUST_THRESHOLD + 1,
                     'maxsize': absorder_size - jm_single().DUST_THRESHOLD,
                     'txfee': txfee,
                     'cjfee': absorder_fee}
            orders = [order] + orders
        log.debug('generated orders = \n' + '\n'.join([str(o) for o in orders]))

        # sanity check
        for order in orders:
            assert order['minsize'] >= 0
            assert order['maxsize'] > 0
            assert order['minsize'] <= order['maxsize']

        return orders
Example #14
0
    def oid_to_order(self, cjorder, oid, amount):
        """The only change from *basic here (for now) is that
        we choose outputs to avoid increasing the max_mixdepth
        as much as possible, thus avoiding reannouncement as
        much as possible.
        """
        total_amount = amount + cjorder.txfee
        mix_balance = self.wallet.get_balance_by_mixdepth()
        max_mix = max(mix_balance, key=mix_balance.get)
        min_mix = min(mix_balance, key=mix_balance.get)

        filtered_mix_balance = [m
                                for m in mix_balance.iteritems()
                                if m[1] >= total_amount]
        if not filtered_mix_balance:
            return None, None, None

        log.debug('mix depths that have enough = ' + str(filtered_mix_balance))

        #Avoid the max mixdepth wherever possible, to avoid changing the
        #offer. Algo: 
        #"mixdepth" is the mixdepth we are spending FROM, so it is also
        #the destination of change.
        #"cjoutdepth" is the mixdepth we are sending coinjoin out to.
        #
        #Find a mixdepth, in the set that have enough, which is
        #not the maximum, and choose any from that set as "mixdepth".
        #If not possible, it means only the max_mix depth has enough,
        #so must choose "mixdepth" to be that.
        #To find the cjoutdepth: ensure that max != min, if so it means
        #we had only one depth; in that case, just set "cjoutdepth"
        #to the next mixdepth. Otherwise, we set "cjoutdepth" to the minimum.

        nonmax_mix_balance = [m for m in filtered_mix_balance if m[0] != max_mix]
        if not nonmax_mix_balance:
            log.debug("Could not spend from a mixdepth which is not max")
            mixdepth = max_mix
        else:
            mixdepth = nonmax_mix_balance[0][0]
        log.debug('filling offer, mixdepth=' + str(mixdepth))

        # mixdepth is the chosen depth we'll be spending from
        # min_mixdepth is the one we want to send our cjout TO,
        # to minimize chance of it becoming the largest, and reannouncing offer.
        if mixdepth == min_mix:
            cjoutmix = (mixdepth + 1) % self.wallet.max_mix_depth
            #don't send cjout to max
            if cjoutmix == max_mix:
                cjoutmix = (cjoutmix + 1) % self.wallet.max_mix_depth
        else:
            cjoutmix = min_mix
        cj_addr = self.wallet.get_internal_addr(cjoutmix)
        change_addr = self.wallet.get_internal_addr(mixdepth)

        utxos = self.wallet.select_utxos(mixdepth, total_amount)
        my_total_in = sum([va['value'] for va in utxos.values()])
        real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount)
        change_value = my_total_in - amount - cjorder.txfee + real_cjfee
        if change_value <= jm_single().DUST_THRESHOLD:
            log.debug(('change value={} below dust threshold, '
                       'finding new utxos').format(change_value))
            try:
                utxos = self.wallet.select_utxos(
                    mixdepth, total_amount + jm_single().DUST_THRESHOLD)
            except Exception:
                log.debug('dont have the required UTXOs to make a '
                          'output above the dust threshold, quitting')
                return None, None, None

        return utxos, cj_addr, change_addr
    def oid_to_order(self, cjorder, oid, amount):
        '''Coins rotate circularly from max mixdepth back to mixdepth 0'''
        mix_balance = self.wallet.get_balance_by_mixdepth()
        total_amount = amount + cjorder.txfee
        log.debug('amount, txfee, total_amount = ' + str(amount) + str(
            cjorder.txfee) + str(total_amount))

        # look for exact amount available with no change
        # not supported because change output required
        # needs this fixed https://github.com/JoinMarket-Org/joinmarket/issues/418
        #filtered_mix_balance = [m
        #                        for m in mix_balance.iteritems()
        #                        if m[1] == total_amount]
        #if filtered_mix_balance:
        #    log.debug('mix depths that have the exact amount needed = ' + str(
        #        filtered_mix_balance))
        #else:
        #    log.debug('no mix depths contain the exact amount needed.')

        filtered_mix_balance = [m
                                for m in mix_balance.iteritems()
                                if m[1] >= (total_amount)]
        log.debug('mix depths that have enough = ' + str(filtered_mix_balance))
        filtered_mix_balance = [m
                                for m in mix_balance.iteritems()
                                if m[1] >= total_amount + output_size_min]
        log.debug('mix depths that have enough with output_size_min, ' + str(
            filtered_mix_balance))
        try:
            len(filtered_mix_balance) > 0
        except Exception:
            log.debug('No mix depths have enough funds to cover the ' +
                      'amount, cjfee, and output_size_min.')
            return None, None, None

        # slinky clumping: push all coins towards the largest mixdepth,
        # then spend from the largest mixdepth into the next mixdepth.
        # the coins stay in the next mixdepth until they are all there,
        # and then get spent into the next mixdepth, ad infinitum.
        lmd = sorted(filtered_mix_balance, key=lambda x: x[1],)[-1]
        smb = sorted(filtered_mix_balance, key=lambda x: x[0])  # seq of md num
        mmd = self.wallet.max_mix_depth
        nmd = (lmd[0] + 1) % mmd
        if nmd not in [x[0] for x in smb]:  # use all usable
            next_si = (smb.index(lmd) + 1) % len(smb)
            filtered_mix_balance = smb[next_si:] + smb[:next_si]
        else:
            nmd = [x for x in smb if x[0] == nmd][0]
            others = [x for x in smb if x != nmd and x != lmd]
            if not others:  # just these two remain, prioritize largest
                filtered_mix_balance = [lmd, nmd]
            else:  # use all usable
                if [x for x in others if x[1] >= nmd[1]]:
                    next_si = (smb.index(lmd) + 1) % len(smb)
                    filtered_mix_balance = smb[next_si:] + smb[:next_si]
                else:  # others are not large, dont use nmd
                    next_si = (smb.index(lmd) + 2) % len(smb)
                    filtered_mix_balance = smb[next_si:] + smb[:next_si]

        # prioritize by mixdepths ascending
        # keep coins moving towards last mixdepth, clumps there.
        # makes sure coins sent to mixdepth 0 will get mixed to mixdepth 5
        #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0])

        # use mix depth with the most coins, 
        # creates a more even distribution across mix depths
        # and a more diverse txo selection in each depth
        # sort largest to smallest amount
        #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[1], reverse=True)

        # use a random usable mixdepth. 
        # warning, could expose more txos to malicous taker requests
        #filtered_mix_balance = [random.choice(filtered_mix_balance)]

        log.debug('sorted order of filtered_mix_balance = ' + str(
            filtered_mix_balance))
        mixdepth = filtered_mix_balance[0][0]
        log.debug('filling offer, mixdepth=' + str(mixdepth))
        # mixdepth is the chosen depth we'll be spending from
        cj_addr = self.wallet.get_internal_addr((mixdepth + 1) %
                                                self.wallet.max_mix_depth)
        change_addr = self.wallet.get_internal_addr(mixdepth)
        utxos = self.wallet.select_utxos(mixdepth, total_amount)
        my_total_in = sum([va['value'] for va in utxos.values()])
        real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount)
        change_value = my_total_in - amount - cjorder.txfee + real_cjfee
        if change_value <= output_size_min:
            log.debug('change value=%d below dust threshold, finding new utxos'
                      % (change_value))
            try:
                utxos = self.wallet.select_utxos(mixdepth,
                                                 total_amount + output_size_min)
            except Exception:
                log.debug(
                    'dont have the required UTXOs to make a output above the dust threshold, quitting')
                return None, None, None
        return utxos, cj_addr, change_addr