def main():
    global bond_exponent
    parser = OptionParser(
        usage='usage: %prog [options]',
        description='Runs a webservice which shows the orderbook.')
    add_base_options(parser)
    parser.add_option('-H',
                      '--host',
                      action='store',
                      type='string',
                      dest='host',
                      default='localhost',
                      help='hostname or IP to bind to, default=localhost')
    parser.add_option('-p',
                      '--port',
                      action='store',
                      type='int',
                      dest='port',
                      help='port to listen on, default=62601',
                      default=62601)
    (options, args) = parser.parse_args()
    load_program_config(config_path=options.datadir)
    # needed to display notional units of FB valuation
    bond_exponent = jm_single().config.get("POLICY", "bond_value_exponent")
    try:
        float(bond_exponent)
    except ValueError:
        log.error("Invalid entry for bond_value_exponent, should be decimal "
                  "number: {}".format(bond_exponent))
        sys.exit(EXIT_FAILURE)
    check_and_start_tor()
    hostport = (options.host, options.port)
    mcs = []
    chan_configs = get_mchannels(mode="PASSIVE")
    for c in chan_configs:
        if "type" in c and c["type"] == "onion":
            mcs.append(OnionMessageChannel(c))
        else:
            # default is IRC; TODO allow others
            mcs.append(IRCMessageChannel(c))
    IRCMessageChannel.on_privmsg = on_privmsg
    OnionMessageChannel.on_privmsg = on_privmsg
    mcc = MessageChannelCollection(mcs)
    mcc.set_nick(get_dummy_nick())
    taker = ObBasic(mcc, hostport)
    log.info("Starting ob-watcher")
    mcc.run()
def main():
    parser = OptionParser(
        usage='usage: %prog [options]',
        description='Runs a webservice which shows the orderbook.')
    add_base_options(parser)
    parser.add_option('-H',
                      '--host',
                      action='store',
                      type='string',
                      dest='host',
                      default='localhost',
                      help='hostname or IP to bind to, default=localhost')
    parser.add_option('-p',
                      '--port',
                      action='store',
                      type='int',
                      dest='port',
                      help='port to listen on, default=62601',
                      default=62601)
    (options, args) = parser.parse_args()
    load_program_config(config_path=options.datadir)
    check_and_start_tor()
    hostport = (options.host, options.port)
    mcs = []
    chan_configs = get_mchannels(mode="PASSIVE")
    for c in chan_configs:
        if "type" in c and c["type"] == "onion":
            mcs.append(OnionMessageChannel(c))
        else:
            # default is IRC; TODO allow others
            mcs.append(IRCMessageChannel(c))
    IRCMessageChannel.on_privmsg = on_privmsg
    OnionMessageChannel.on_privmsg = on_privmsg
    mcc = MessageChannelCollection(mcs)
    mcc.set_nick(get_dummy_nick())
    taker = ObBasic(mcc, hostport)
    log.info("Starting ob-watcher")
    mcc.run()
def main():
    parser = get_sendpayment_parser()
    (options, args) = parser.parse_args()
    load_program_config(config_path=options.datadir)

    if options.schedule == '':
        if ((len(args) < 2) or (btc.is_bip21_uri(args[1]) and len(args) != 2)
                or (not btc.is_bip21_uri(args[1]) and len(args) != 3)):
            parser.error(
                "Joinmarket sendpayment (coinjoin) needs arguments:"
                " wallet, amount, destination address or wallet, bitcoin_uri.")
            sys.exit(EXIT_ARGERROR)

    check_and_start_tor()

    #without schedule file option, use the arguments to create a schedule
    #of a single transaction
    sweeping = False
    bip78url = None
    if options.schedule == '':
        if btc.is_bip21_uri(args[1]):
            parsed = btc.decode_bip21_uri(args[1])
            try:
                amount = parsed['amount']
            except KeyError:
                parser.error("Given BIP21 URI does not contain amount.")
                sys.exit(EXIT_ARGERROR)
            destaddr = parsed['address']
            if "pj" in parsed:
                # note that this is a URL; its validity
                # checking is deferred to twisted.web.client.Agent
                bip78url = parsed["pj"]
                # setting makercount only for fee sanity check.
                # note we ignore any user setting and enforce N=0,
                # as this is a flag in the code for a non-JM coinjoin;
                # for the fee sanity check, note that BIP78 currently
                # will only allow small fee changes, so N=0 won't
                # be very inaccurate.
                jmprint("Attempting to pay via payjoin.", "info")
                options.makercount = 0
        else:
            amount = btc.amount_to_sat(args[1])
            if amount == 0:
                sweeping = True
            destaddr = args[2]
        mixdepth = options.mixdepth
        addr_valid, errormsg = validate_address(destaddr)
        command_to_burn = (is_burn_destination(destaddr) and sweeping
                           and options.makercount == 0)
        if not addr_valid and not command_to_burn:
            jmprint('ERROR: Address invalid. ' + errormsg, "error")
            if is_burn_destination(destaddr):
                jmprint(
                    "The required options for burning coins are zero makers" +
                    " (-N 0), sweeping (amount = 0) and not using BIP78 Payjoin",
                    "info")
            sys.exit(EXIT_ARGERROR)
        if sweeping == False and amount < jm_single().DUST_THRESHOLD:
            jmprint(
                'ERROR: Amount ' + btc.amount_to_str(amount) +
                ' is below dust threshold ' +
                btc.amount_to_str(jm_single().DUST_THRESHOLD) + '.', "error")
            sys.exit(EXIT_ARGERROR)
        if (options.makercount != 0
                and options.makercount < jm_single().config.getint(
                    "POLICY", "minimum_makers")):
            jmprint(
                'ERROR: Maker count ' + str(options.makercount) +
                ' below minimum_makers (' +
                str(jm_single().config.getint("POLICY", "minimum_makers")) +
                ') in joinmarket.cfg.', "error")
            sys.exit(EXIT_ARGERROR)
        schedule = [[
            options.mixdepth, amount, options.makercount, destaddr, 0.0,
            NO_ROUNDING, 0
        ]]
    else:
        if len(args) > 1:
            parser.error("Schedule files are not compatible with "
                         "payment destination/amount arguments.")
            sys.exit(EXIT_ARGERROR)
        result, schedule = get_schedule(options.schedule)
        if not result:
            log.error(
                "Failed to load schedule file, quitting. Check the syntax.")
            log.error("Error was: " + str(schedule))
            sys.exit(EXIT_FAILURE)
        mixdepth = 0
        for s in schedule:
            if s[1] == 0:
                sweeping = True
            #only used for checking the maximum mixdepth required
            mixdepth = max([mixdepth, s[0]])

    wallet_name = args[0]

    check_regtest()

    if options.pickorders:
        chooseOrdersFunc = pick_order
        if sweeping:
            jmprint('WARNING: You may have to pick offers multiple times',
                    "warning")
            jmprint('WARNING: due to manual offer picking while sweeping',
                    "warning")
    else:
        chooseOrdersFunc = options.order_choose_fn

    # If tx_fees are set manually by CLI argument, override joinmarket.cfg:
    if int(options.txfee) > 0:
        jm_single().config.set("POLICY", "tx_fees", str(options.txfee))

    maxcjfee = (1, float('inf'))
    if not options.pickorders and options.makercount != 0:
        maxcjfee = get_max_cj_fee_values(jm_single().config, options)
        log.info("Using maximum coinjoin fee limits per maker of {:.4%}, {} "
                 "".format(maxcjfee[0], btc.amount_to_str(maxcjfee[1])))

    log.info('starting sendpayment')

    max_mix_depth = max([mixdepth, options.amtmixdepths - 1])

    wallet_path = get_wallet_path(wallet_name, None)
    wallet = open_test_wallet_maybe(
        wallet_path,
        wallet_name,
        max_mix_depth,
        wallet_password_stdin=options.wallet_password_stdin,
        gap_limit=options.gaplimit)
    wallet_service = WalletService(wallet)
    if wallet_service.rpc_error:
        sys.exit(EXIT_FAILURE)
    # in this script, we need the wallet synced before
    # logic processing for some paths, so do it now:
    while not wallet_service.synced:
        wallet_service.sync_wallet(fast=not options.recoversync)
    # the sync call here will now be a no-op:
    wallet_service.startService()

    # Dynamically estimate a realistic fee, for coinjoins.
    # At this point we do not know even the number of our own inputs, so
    # we guess conservatively with 2 inputs and 2 outputs each.
    if options.makercount != 0:
        fee_per_cp_guess = estimate_tx_fee(2,
                                           2,
                                           txtype=wallet_service.get_txtype())
        log.debug("Estimated miner/tx fee for each cj participant: " +
                  btc.amount_to_str(fee_per_cp_guess))

    # From the estimated tx fees, check if the expected amount is a
    # significant value compared the the cj amount; currently enabled
    # only for single join (the predominant, non-advanced case)
    if options.schedule == '' and options.makercount != 0:
        total_cj_amount = amount
        if total_cj_amount == 0:
            total_cj_amount = wallet_service.get_balance_by_mixdepth()[
                options.mixdepth]
            if total_cj_amount == 0:
                raise ValueError(
                    "No confirmed coins in the selected mixdepth. Quitting")
        exp_tx_fees_ratio = (
            (1 + options.makercount) * fee_per_cp_guess) / total_cj_amount
        if exp_tx_fees_ratio > 0.05:
            jmprint(
                'WARNING: Expected bitcoin network miner fees for this coinjoin'
                ' amount are roughly {:.1%}'.format(exp_tx_fees_ratio),
                "warning")
            if input('You might want to modify your tx_fee'
                     ' settings in joinmarket.cfg. Still continue? (y/n):'
                     )[0] != 'y':
                sys.exit('Aborted by user.')
        else:
            log.info(
                "Estimated miner/tx fees for this coinjoin amount: {:.1%}".
                format(exp_tx_fees_ratio))

    custom_change = None
    if options.customchange != '':
        addr_valid, errormsg = validate_address(options.customchange)
        if not addr_valid:
            parser.error(
                "The custom change address provided is not valid\n{}".format(
                    errormsg))
            sys.exit(EXIT_ARGERROR)
        custom_change = options.customchange
        if destaddr and custom_change == destaddr:
            parser.error("The custom change address cannot be the same as the "
                         "destination address.")
            sys.exit(EXIT_ARGERROR)
        if sweeping:
            parser.error(sweep_custom_change_warning)
            sys.exit(EXIT_ARGERROR)
        if bip78url:
            parser.error(
                "Custom change is not currently supported "
                "with Payjoin. Please retry without a custom change address.")
            sys.exit(EXIT_ARGERROR)
        if options.makercount > 0:
            if not options.answeryes and input(general_custom_change_warning +
                                               " (y/n):")[0] != "y":
                sys.exit(EXIT_ARGERROR)
            engine_recognized = True
            try:
                change_addr_type = wallet_service.get_outtype(custom_change)
            except EngineError:
                engine_recognized = False
            if (not engine_recognized) or (change_addr_type !=
                                           wallet_service.get_txtype()):
                if not options.answeryes and input(
                        nonwallet_custom_change_warning + " (y/n):")[0] != "y":
                    sys.exit(EXIT_ARGERROR)

    if options.makercount == 0 and not bip78url:
        tx = direct_send(wallet_service,
                         amount,
                         mixdepth,
                         destaddr,
                         options.answeryes,
                         with_final_psbt=options.with_psbt,
                         optin_rbf=options.rbf,
                         custom_change_addr=custom_change)
        if options.with_psbt:
            log.info(
                "This PSBT is fully signed and can be sent externally for "
                "broadcasting:")
            log.info(tx.to_base64())
        return

    if wallet.get_txtype() == 'p2pkh':
        jmprint(
            "Only direct sends (use -N 0) are supported for "
            "legacy (non-segwit) wallets.", "error")
        sys.exit(EXIT_ARGERROR)

    def filter_orders_callback(orders_fees, cjamount):
        orders, total_cj_fee = orders_fees
        log.info("Chose these orders: " + pprint.pformat(orders))
        log.info('total cj fee = ' + str(total_cj_fee))
        total_fee_pc = 1.0 * total_cj_fee / cjamount
        log.info('total coinjoin fee = ' +
                 str(float('%.3g' % (100.0 * total_fee_pc))) + '%')
        WARNING_THRESHOLD = 0.02  # 2%
        if total_fee_pc > WARNING_THRESHOLD:
            log.info('\n'.join(['=' * 60] * 3))
            log.info('WARNING   ' * 6)
            log.info('\n'.join(['=' * 60] * 1))
            log.info(
                'OFFERED COINJOIN FEE IS UNUSUALLY HIGH. DOUBLE/TRIPLE CHECK.')
            log.info('\n'.join(['=' * 60] * 1))
            log.info('WARNING   ' * 6)
            log.info('\n'.join(['=' * 60] * 3))
        if not options.answeryes:
            if input('send with these orders? (y/n):')[0] != 'y':
                return False
        return True

    def taker_finished(res, fromtx=False, waittime=0.0, txdetails=None):
        if fromtx == "unconfirmed":
            #If final entry, stop *here*, don't wait for confirmation
            if taker.schedule_index + 1 == len(taker.schedule):
                reactor.stop()
            return
        if fromtx:
            if res:
                txd, txid = txdetails
                reactor.callLater(waittime * 60,
                                  clientfactory.getClient().clientStart)
            else:
                #a transaction failed; we'll try to repeat without the
                #troublemakers.
                #If this error condition is reached from Phase 1 processing,
                #and there are less than minimum_makers honest responses, we
                #just give up (note that in tumbler we tweak and retry, but
                #for sendpayment the user is "online" and so can manually
                #try again).
                #However if the error is in Phase 2 and we have minimum_makers
                #or more responses, we do try to restart with the honest set, here.
                if taker.latest_tx is None:
                    #can only happen with < minimum_makers; see above.
                    log.info("A transaction failed but there are insufficient "
                             "honest respondants to continue; giving up.")
                    reactor.stop()
                    return
                #This is Phase 2; do we have enough to try again?
                taker.add_honest_makers(
                    list(
                        set(taker.maker_utxo_data.keys()).symmetric_difference(
                            set(taker.nonrespondants))))
                if len(taker.honest_makers) < jm_single().config.getint(
                        "POLICY", "minimum_makers"):
                    log.info("Too few makers responded honestly; "
                             "giving up this attempt.")
                    reactor.stop()
                    return
                jmprint("We failed to complete the transaction. The following "
                      "makers responded honestly: " + str(taker.honest_makers) +\
                      ", so we will retry with them.", "warning")
                #Now we have to set the specific group we want to use, and hopefully
                #they will respond again as they showed honesty last time.
                #we must reset the number of counterparties, as well as fix who they
                #are; this is because the number is used to e.g. calculate fees.
                #cleanest way is to reset the number in the schedule before restart.
                taker.schedule[taker.schedule_index][2] = len(
                    taker.honest_makers)
                log.info("Retrying with: " +
                         str(taker.schedule[taker.schedule_index][2]) +
                         " counterparties.")
                #rewind to try again (index is incremented in Taker.initialize())
                taker.schedule_index -= 1
                taker.set_honest_only(True)
                reactor.callLater(5.0, clientfactory.getClient().clientStart)
        else:
            if not res:
                log.info("Did not complete successfully, shutting down")
            #Should usually be unreachable, unless conf received out of order;
            #because we should stop on 'unconfirmed' for last (see above)
            else:
                log.info("All transactions completed correctly")
            reactor.stop()

    nodaemon = jm_single().config.getint("DAEMON", "no_daemon")
    daemon = True if nodaemon == 1 else False
    dhost = jm_single().config.get("DAEMON", "daemon_host")
    dport = jm_single().config.getint("DAEMON", "daemon_port")
    if bip78url:
        # TODO sanity check wallet type is segwit
        manager = parse_payjoin_setup(args[1], wallet_service,
                                      options.mixdepth)
        reactor.callWhenRunning(send_payjoin, manager)
        # JM is default, so must be switched off explicitly in this call:
        start_reactor(dhost,
                      dport,
                      bip78=True,
                      jm_coinjoin=False,
                      daemon=daemon)
        return

    else:
        taker = Taker(wallet_service,
                      schedule,
                      order_chooser=chooseOrdersFunc,
                      max_cj_fee=maxcjfee,
                      callbacks=(filter_orders_callback, None, taker_finished),
                      custom_change_address=custom_change)
    clientfactory = JMClientProtocolFactory(taker)

    if jm_single().config.get("BLOCKCHAIN", "network") == "regtest":
        startLogging(sys.stdout)
    start_reactor(dhost, dport, clientfactory, daemon=daemon)
def receive_payjoin_main():
    parser = OptionParser(
        usage='usage: %prog [options] [wallet file] [amount-to-receive]')
    add_base_options(parser)
    parser.add_option(
        '-P',
        '--hs-port',
        action='store',
        type='int',
        dest='hsport',
        default=80,
        help='port on which to serve the ephemeral hidden service.')
    parser.add_option('-g',
                      '--gap-limit',
                      action='store',
                      type="int",
                      dest='gaplimit',
                      default=6,
                      help='gap limit for wallet, default=6')
    parser.add_option('-m',
                      '--mixdepth',
                      action='store',
                      type='int',
                      dest='mixdepth',
                      default=0,
                      help="mixdepth to source coins from")
    parser.add_option('-a',
                      '--amtmixdepths',
                      action='store',
                      type='int',
                      dest='amtmixdepths',
                      help='number of mixdepths in wallet, default 5',
                      default=5)

    (options, args) = parser.parse_args()
    if len(args) < 2:
        parser.error(
            'Needs a wallet, and a receiving amount in bitcoins or satoshis')
        sys.exit(EXIT_ARGERROR)
    wallet_name = args[0]
    try:
        # amount is stored internally in sats, but will be decimal in URL.
        bip78_amount = amount_to_sat(args[1])
    except:
        parser.error("Invalid receiving amount passed: " + bip78_amount)
        sys.exit(EXIT_FAILURE)
    if bip78_amount < 0:
        parser.error("Receiving amount must be a positive number")
        sys.exit(EXIT_FAILURE)
    load_program_config(config_path=options.datadir)

    check_and_start_tor()

    check_regtest()

    wallet_path = get_wallet_path(wallet_name, None)
    max_mix_depth = max([options.mixdepth, options.amtmixdepths - 1])
    wallet = open_test_wallet_maybe(
        wallet_path,
        wallet_name,
        max_mix_depth,
        wallet_password_stdin=options.wallet_password_stdin,
        gap_limit=options.gaplimit)
    wallet_service = WalletService(wallet)

    while not wallet_service.synced:
        wallet_service.sync_wallet(fast=not options.recoversync)
    wallet_service.startService()
    # having enforced wallet sync, we can check if we have coins
    # to do payjoin in the mixdepth
    if wallet_service.get_balance_by_mixdepth(
            minconfs=1)[options.mixdepth] == 0:
        jlog.error("Cannot do payjoin from mixdepth " + str(options.mixdepth) +
                   ", no confirmed coins. Shutting down.")
        sys.exit(EXIT_ARGERROR)
    receiver_manager = JMBIP78ReceiverManager(wallet_service, options.mixdepth,
                                              bip78_amount, options.hsport)
    reactor.callWhenRunning(receiver_manager.initiate)
    nodaemon = jm_single().config.getint("DAEMON", "no_daemon")
    daemon = True if nodaemon == 1 else False
    dhost = jm_single().config.get("DAEMON", "daemon_host")
    dport = jm_single().config.getint("DAEMON", "daemon_port")
    # JM is default, so must be switched off explicitly in this call:
    start_reactor(dhost, dport, bip78=True, jm_coinjoin=False, daemon=daemon)
Esempio n. 5
0
def receive_snicker_main():
    usage = """ Use this script to receive proposals for SNICKER
coinjoins, parse them and then broadcast coinjoins
that fit your criteria. See the SNICKER section of
joinmarket.cfg to set your criteria.
The only argument to this script is the (JM) wallet
file against which to check.
Once all proposals have been parsed, the script will
quit.
Usage: %prog [options] wallet file [proposal]
"""
    parser = OptionParser(usage=usage)
    add_base_options(parser)
    parser.add_option('-g', '--gap-limit', action='store', type="int",
                      dest='gaplimit', default=6,
                      help='gap limit for wallet, default=6')
    parser.add_option('-m', '--mixdepth', action='store', type='int',
                      dest='mixdepth', default=0,
                      help="mixdepth to source coins from")
    parser.add_option('-a',
                      '--amtmixdepths',
                      action='store',
                      type='int',
                      dest='amtmixdepths',
                      help='number of mixdepths in wallet, default 5',
                      default=5)
    parser.add_option(
        '-n',
        '--no-upload',
        action='store_true',
        dest='no_upload',
        default=False,
        help="if set, we read the proposal from the command line"
    )

    (options, args) = parser.parse_args()
    if len(args) < 1:
        parser.error('Needs a wallet file as argument')
        sys.exit(EXIT_ARGERROR)
    wallet_name = args[0]
    snicker_plugin = JMPluginService("SNICKER")
    load_program_config(config_path=options.datadir,
                        plugin_services=[snicker_plugin])

    check_and_start_tor()

    check_regtest()

    wallet_path = get_wallet_path(wallet_name, None)
    max_mix_depth = max([options.mixdepth, options.amtmixdepths - 1])
    wallet = open_test_wallet_maybe(
        wallet_path, wallet_name, max_mix_depth,
        wallet_password_stdin=options.wallet_password_stdin,
        gap_limit=options.gaplimit)
    wallet_service = WalletService(wallet)
    snicker_plugin.start_plugin_logging(wallet_service)
    while not wallet_service.synced:
        wallet_service.sync_wallet(fast=not options.recoversync)
    wallet_service.startService()

    nodaemon = jm_single().config.getint("DAEMON", "no_daemon")
    daemon = True if nodaemon == 1 else False
    snicker_r = SNICKERReceiver(wallet_service)
    if options.no_upload:
        proposal = args[1]
        snicker_r.process_proposals([proposal])
        return
    servers = jm_single().config.get("SNICKER", "servers").split(",")
    snicker_pf = SNICKERClientProtocolFactory(snicker_r, servers, oneshot=True)
    start_reactor(jm_single().config.get("DAEMON", "daemon_host"),
                  jm_single().config.getint("DAEMON", "daemon_port"),
                  None, snickerfactory=snicker_pf,
                  daemon=daemon)
def main():
    parser = OptionParser(
        usage=
        'usage: %prog [options] walletname hex-tx input-index output-index net-transfer',
        description=description)
    add_base_options(parser)
    parser.add_option('-m',
                      '--mixdepth',
                      action='store',
                      type='int',
                      dest='mixdepth',
                      help='mixdepth/account to spend from, default=0',
                      default=0)
    parser.add_option('-g',
                      '--gap-limit',
                      action='store',
                      type='int',
                      dest='gaplimit',
                      default=6,
                      help='gap limit for Joinmarket wallet, default 6.')
    parser.add_option(
        '-n',
        '--no-upload',
        action='store_true',
        dest='no_upload',
        default=False,
        help="if set, we don't upload the new proposal to the servers")
    parser.add_option(
        '-f',
        '--txfee',
        action='store',
        type='int',
        dest='txfee',
        default=-1,
        help='Bitcoin miner tx_fee to use for transaction(s). A number higher '
        'than 1000 is used as "satoshi per KB" tx fee. A number lower than that '
        'uses the dynamic fee estimation of your blockchain provider as '
        'confirmation target. This temporarily overrides the "tx_fees" setting '
        'in your joinmarket.cfg. Works the same way as described in it. Check '
        'it for examples.')
    parser.add_option('-a',
                      '--amtmixdepths',
                      action='store',
                      type='int',
                      dest='amtmixdepths',
                      help='number of mixdepths in wallet, default 5',
                      default=5)
    (options, args) = parser.parse_args()
    snicker_plugin = JMPluginService("SNICKER")
    load_program_config(config_path=options.datadir,
                        plugin_services=[snicker_plugin])
    if len(args) != 5:
        jmprint("Invalid arguments, see --help")
        sys.exit(EXIT_ARGERROR)
    wallet_name, hextx, input_index, output_index, net_transfer = args
    input_index, output_index, net_transfer = [
        int(x) for x in [input_index, output_index, net_transfer]
    ]

    check_and_start_tor()

    check_regtest()

    # If tx_fees are set manually by CLI argument, override joinmarket.cfg:
    if int(options.txfee) > 0:
        jm_single().config.set("POLICY", "tx_fees", str(options.txfee))
    max_mix_depth = max([options.mixdepth, options.amtmixdepths - 1])
    wallet_path = get_wallet_path(wallet_name, None)
    wallet = open_test_wallet_maybe(
        wallet_path,
        wallet_name,
        max_mix_depth,
        wallet_password_stdin=options.wallet_password_stdin,
        gap_limit=options.gaplimit)
    wallet_service = WalletService(wallet)
    if wallet_service.rpc_error:
        sys.exit(EXIT_FAILURE)
    snicker_plugin.start_plugin_logging(wallet_service)
    # in this script, we need the wallet synced before
    # logic processing for some paths, so do it now:
    while not wallet_service.synced:
        wallet_service.sync_wallet(fast=not options.recoversync)
    # the sync call here will now be a no-op:
    wallet_service.startService()

    # now that the wallet is available, we can construct a proposal
    # before encrypting it:
    originating_tx = btc.CMutableTransaction.deserialize(hextobin(hextx))
    txid1 = originating_tx.GetTxid()[::-1]
    # the proposer wallet needs to choose a single utxo, from his selected
    # mixdepth, that is bigger than the output amount of tx1 at the given
    # index.
    fee_est = estimate_tx_fee(2, 3, txtype=wallet_service.get_txtype())
    amt_required = originating_tx.vout[output_index].nValue + fee_est

    prop_utxo_dict = wallet_service.select_utxos(options.mixdepth,
                                                 amt_required)
    prop_utxos = list(prop_utxo_dict)
    prop_utxo_vals = [prop_utxo_dict[x] for x in prop_utxos]
    # get the private key for that utxo
    priv = wallet_service.get_key_from_addr(
        wallet_service.script_to_addr(prop_utxo_vals[0]['script']))
    # construct the arguments for the snicker proposal:
    our_input_utxos = [
        btc.CMutableTxOut(x['value'], x['script']) for x in prop_utxo_vals
    ]

    # destination must be a different mixdepth:
    prop_destn_spk = wallet_service.get_new_script(
        (options.mixdepth + 1) % (wallet_service.mixdepth + 1), 1)
    change_spk = wallet_service.get_new_script(options.mixdepth, 1)
    their_input = (txid1, output_index)
    # we also need to extract the pubkey of the chosen input from
    # the witness; we vary this depending on our wallet type:
    pubkey, msg = btc.extract_pubkey_from_witness(originating_tx, input_index)
    if not pubkey:
        log.error("Failed to extract pubkey from transaction: {}".format(msg))
        sys.exit(EXIT_FAILURE)
    encrypted_proposal = wallet_service.create_snicker_proposal(
        prop_utxos,
        their_input,
        our_input_utxos,
        originating_tx.vout[output_index],
        net_transfer,
        fee_est,
        priv,
        pubkey,
        prop_destn_spk,
        change_spk,
        version_byte=1) + b"," + bintohex(pubkey).encode('utf-8')
    if options.no_upload:
        jmprint(encrypted_proposal.decode("utf-8"))
        sys.exit(EXIT_SUCCESS)

    nodaemon = jm_single().config.getint("DAEMON", "no_daemon")
    daemon = True if nodaemon == 1 else False
    snicker_client = SNICKERPostingClient([encrypted_proposal])
    servers = jm_single().config.get("SNICKER", "servers").split(",")
    snicker_pf = SNICKERClientProtocolFactory(snicker_client, servers)
    start_reactor(jm_single().config.get("DAEMON", "daemon_host"),
                  jm_single().config.getint("DAEMON", "daemon_port"),
                  snickerfactory=snicker_pf,
                  jm_coinjoin=False,
                  daemon=daemon)
Esempio n. 7
0
def ygmain(ygclass, nickserv_password='', gaplimit=6):
    import sys

    parser = OptionParser(usage='usage: %prog [options] [wallet file]')
    add_base_options(parser)
    # A note about defaults:
    # We want command line settings to override config settings.
    # This would naturally mean setting `default=` arguments here, to the
    # values in the config.
    # However, we cannot load the config until we know the datadir.
    # The datadir is a setting in the command line options, so we have to
    # call parser.parse_args() before we know the datadir.
    # Hence we do the following: set all modifyable-by-config arguments to
    # default "None" initially; call parse_args(); then call load_program_config
    # and override values of "None" with what is set in the config.
    # (remember, the joinmarket defaultconfig always sets every value, even if
    # the user doesn't).
    parser.add_option('-o',
                      '--ordertype',
                      action='store',
                      type='string',
                      dest='ordertype',
                      default=None,
                      help='type of order; can be either reloffer or absoffer')
    parser.add_option(
        '-t',
        '--txfee-contribution',
        action='store',
        type='int',
        dest='txfee_contribution',
        default=None,
        help=
        'the average transaction fee contribution you\'re adding to coinjoin transactions'
    )
    parser.add_option(
        '-f',
        '--txfee-contribution-factor',
        action='store',
        type='float',
        dest='txfee_contribution_factor',
        default=None,
        help=
        'variance around the average transaction fee contribution, decimal fraction'
    )
    parser.add_option('-a',
                      '--cjfee-a',
                      action='store',
                      type='string',
                      dest='cjfee_a',
                      default=None,
                      help='requested coinjoin fee (absolute) in satoshis')
    parser.add_option('-r',
                      '--cjfee-r',
                      action='store',
                      type='string',
                      dest='cjfee_r',
                      default=None,
                      help='requested coinjoin fee (relative) as a decimal')
    parser.add_option('-j',
                      '--cjfee-factor',
                      action='store',
                      type='float',
                      dest='cjfee_factor',
                      default=None,
                      help='variance around the average fee, decimal fraction')
    parser.add_option('-p',
                      '--password',
                      action='store',
                      type='string',
                      dest='password',
                      default=nickserv_password,
                      help='irc nickserv password')
    parser.add_option('-s',
                      '--minsize',
                      action='store',
                      type='int',
                      dest='minsize',
                      default=None,
                      help='minimum coinjoin size in satoshis')
    parser.add_option('-z',
                      '--size-factor',
                      action='store',
                      type='float',
                      dest='size_factor',
                      default=None,
                      help='variance around all offer sizes, decimal fraction')
    parser.add_option('-g',
                      '--gap-limit',
                      action='store',
                      type="int",
                      dest='gaplimit',
                      default=gaplimit,
                      help='gap limit for wallet, default=' + str(gaplimit))
    parser.add_option('-m',
                      '--mixdepth',
                      action='store',
                      type='int',
                      dest='mixdepth',
                      default=None,
                      help="highest mixdepth to use")
    (options, args) = parser.parse_args()
    # for string access, convert to dict:
    options = vars(options)
    if len(args) < 1:
        parser.error('Needs a wallet')
        sys.exit(EXIT_ARGERROR)

    load_program_config(config_path=options["datadir"])

    check_and_start_tor()

    # As per previous note, override non-default command line settings:
    for x in [
            "ordertype", "txfee_contribution", "txfee_contribution_factor",
            "cjfee_a", "cjfee_r", "cjfee_factor", "minsize", "size_factor"
    ]:
        if options[x] is None:
            options[x] = jm_single().config.get("YIELDGENERATOR", x)
    wallet_name = args[0]
    ordertype = options["ordertype"]
    txfee_contribution = int(options["txfee_contribution"])
    txfee_contribution_factor = float(options["txfee_contribution_factor"])
    cjfee_factor = float(options["cjfee_factor"])
    size_factor = float(options["size_factor"])
    if ordertype == 'reloffer':
        cjfee_r = options["cjfee_r"]
        # minimum size is such that you always net profit at least 20%
        #of the miner fee
        minsize = max(int(1.2 * txfee_contribution / float(cjfee_r)),
                      int(options["minsize"]))
        cjfee_a = None
    elif ordertype == 'absoffer':
        cjfee_a = int(options["cjfee_a"])
        minsize = int(options["minsize"])
        cjfee_r = None
    else:
        parser.error('You specified an incorrect offer type which ' +\
                     'can be either reloffer or absoffer')
        sys.exit(EXIT_ARGERROR)
    nickserv_password = options["password"]

    if jm_single().bc_interface is None:
        jlog.error("Running yield generator requires configured " +
                   "blockchain source.")
        sys.exit(EXIT_FAILURE)

    wallet_path = get_wallet_path(wallet_name, None)
    wallet = open_test_wallet_maybe(
        wallet_path,
        wallet_name,
        options["mixdepth"],
        wallet_password_stdin=options["wallet_password_stdin"],
        gap_limit=options["gaplimit"])

    wallet_service = WalletService(wallet)
    while not wallet_service.synced:
        wallet_service.sync_wallet(fast=not options["recoversync"])
    wallet_service.startService()

    txtype = wallet_service.get_txtype()
    if txtype == "p2wpkh":
        prefix = "sw0"
    elif txtype == "p2sh-p2wpkh":
        prefix = "sw"
    elif txtype == "p2pkh":
        prefix = ""
    else:
        jlog.error("Unsupported wallet type for yieldgenerator: " + txtype)
        sys.exit(EXIT_ARGERROR)

    ordertype = prefix + ordertype
    jlog.debug("Set the offer type string to: " + ordertype)

    maker = ygclass(wallet_service, [
        txfee_contribution, cjfee_a, cjfee_r, ordertype, minsize,
        txfee_contribution_factor, cjfee_factor, size_factor
    ])
    jlog.info('starting yield generator')
    clientfactory = JMClientProtocolFactory(maker, proto_type="MAKER")
    if jm_single().config.get("SNICKER", "enabled") == "true":
        if jm_single().config.get("BLOCKCHAIN", "network") == "mainnet":
            jlog.error("You have enabled SNICKER on mainnet, this is not "
                       "yet supported for yieldgenerators; either use "
                       "signet/regtest/testnet, or run SNICKER manually "
                       "with snicker/receive-snicker.py.")
            sys.exit(EXIT_ARGERROR)
        snicker_r = SNICKERReceiver(wallet_service)
        servers = jm_single().config.get("SNICKER", "servers").split(",")
        snicker_factory = SNICKERClientProtocolFactory(snicker_r, servers)
    else:
        snicker_factory = None
    nodaemon = jm_single().config.getint("DAEMON", "no_daemon")
    daemon = True if nodaemon == 1 else False
    if jm_single().config.get("BLOCKCHAIN",
                              "network") in ["regtest", "testnet", "signet"]:
        startLogging(sys.stdout)
    start_reactor(jm_single().config.get("DAEMON", "daemon_host"),
                  jm_single().config.getint("DAEMON", "daemon_port"),
                  clientfactory,
                  snickerfactory=snicker_factory,
                  daemon=daemon)
Esempio n. 8
0
            d.addCallback(self.create_onion_ep)
        # TODO: add errbacks to the next two calls in
        # the chain:
        d.addCallback(self.onion_listen)
        d.addCallback(self.print_host)

    def shutdown(self):
        self.tor_connection.protocol.transport.loseConnection()
        process_shutdown(self.mode)
        self.info_callback("Hidden service shutdown complete")
        if self.shutdown_callback:
            self.shutdown_callback()

def snicker_server_start(port, local_port=None, hsdir=None):
    ssm = SNICKERServerManager(port, local_port=local_port, hsdir=hsdir)
    ssm.start_snicker_server_and_tor()

if __name__ == "__main__":
    load_program_config(bs="no-blockchain")
    check_and_start_tor()
    # in testing, we can optionally use ephemeral;
    # in testing or prod we can use persistent:
    if len(sys.argv) < 2:
        snicker_server_start(80)
    else:
        port = int(sys.argv[1])
        local_port = int(sys.argv[2])
        hsdir = sys.argv[3]
        snicker_server_start(port, local_port, hsdir)
    reactor.run()