예제 #1
0
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)
    hostport = (options.host, options.port)
    mcs = [ObIRCMessageChannel(c) for c in get_irc_mchannels()]
    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] wallet_file_name password',
        description='Create a wallet with the given wallet name and password.')
    add_base_options(parser)
    (options, args) = parser.parse_args()
    if options.wallet_password_stdin:
        stdin = sys.stdin.read()
        password = stdin.encode("utf-8")
    else:
        assert len(
            args
        ) > 1, "must provide password via stdin (see --help), or as second argument."
        password = args[1].encode("utf-8")
    load_program_config(config_path=options.datadir)
    wallet_root_path = os.path.join(jm_single().datadir, "wallets")
    wallet_name = os.path.join(wallet_root_path, args[0])
    if jm_single().config.get("POLICY", "native") == "true":
        walletclass = SegwitWallet
    else:
        walletclass = SegwitLegacyWallet
    wallet = create_wallet(wallet_name, password, 4, walletclass)
    jmprint("recovery_seed:{}".format(wallet.get_mnemonic_words()[0]),
            "important")
    wallet.close()
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_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()[options.mixdepth] == 0:
        jlog.error("Cannot do payjoin from mixdepth " + str(
            options.mixdepth) + ", no coins. Shutting down.")
        sys.exit(EXIT_ARGERROR)
    receiver_manager = JMBIP78ReceiverManager(wallet_service, options.mixdepth,
                                    bip78_amount, options.hsport)
    receiver_manager.start_pj_server_and_tor()
    reactor.run()
def main():
    parser = OptionParser(
        usage='usage: %prog [options] utxo destaddr1 destaddr2 ..',
        description=description,
        formatter=IndentedHelpFormatterWithNL())
    parser.add_option(
        '-t',
        '--utxo-address-type',
        action='store',
        dest='utxo_address_type',
        help=
        ('type of address of coin being spent - one of "p2pkh", "p2wpkh", "p2sh-p2wpkh". '
         'No other scriptpubkey types (e.g. multisig) are supported. If not set, we default '
         'to what is in joinmarket.cfg.'),
        default="")
    add_base_options(parser)
    (options, args) = parser.parse_args()
    load_program_config(config_path=options.datadir)
    if len(args) < 2:
        quit(parser, 'Invalid syntax')
    u = args[0]
    priv = input('input private key for ' + u +
                 ', in WIF compressed format : ')
    u, priv = get_utxo_info(','.join([u, priv]))
    if not u:
        quit(parser, "Failed to parse utxo info: " + u)
    destaddrs = args[1:]
    for d in destaddrs:
        if not validate_address(d):
            quit(parser, "Address was not valid; wrong network?: " + d)
    success, utxo = utxostr_to_utxo(u)
    if not success:
        quit(parser, "Failed to load utxo from string: " + utxo)
    if options.utxo_address_type == "":
        if jm_single().config.get("POLICY", "segwit") == "false":
            utxo_address_type = "p2pkh"
        elif jm_single().config.get("POLICY", "native") == "false":
            utxo_address_type = "p2sh-p2wpkh"
        else:
            utxo_address_type = "p2wpkh"
    else:
        utxo_address_type = options.utxo_address_type
    txsigned = sign(utxo, priv, destaddrs, utxo_address_type)
    if not txsigned:
        log.info(
            "Transaction signing operation failed, see debug messages for details."
        )
        return
    log.info("Got signed transaction:\n" + bintohex(txsigned.serialize()))
    log.info(btc.human_readable_transaction(txsigned))
    if input('Would you like to push to the network? (y/n):')[0] != 'y':
        log.info("You chose not to broadcast the transaction, quitting.")
        return
    jm_single().bc_interface.pushtx(txsigned.serialize())
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] wallet_file_name [password]',
        description='Create a wallet with the given wallet name and password.')
    add_base_options(parser)
    parser.add_option(
        '--recovery-seed-file',
        dest='seed_file',
        default=None,
        help=
        ('File containing a mnemonic recovery phrase. If provided, the wallet '
         'is recovered from this seed instead of being newly generated.'))
    (options, args) = parser.parse_args()
    wallet_name = args[0]
    if options.wallet_password_stdin:
        password = wallet_utils.read_password_stdin()
    else:
        assert len(
            args
        ) > 1, "must provide password via stdin (see --help), or as second argument."
        password = args[1].encode("utf-8")
    seed = options.seed_file and Path(options.seed_file).read_text().rstrip()

    load_program_config(config_path=options.datadir)
    wallet_root_path = os.path.join(jm_single().datadir, "wallets")
    wallet_path = os.path.join(wallet_root_path, wallet_name)
    if jm_single().config.get("POLICY", "native") == "true":
        walletclass = SegwitWalletFidelityBonds
    else:
        # Fidelity Bonds are not available for segwit legacy wallets
        walletclass = SegwitLegacyWallet
    entropy = seed and SegwitLegacyWallet.entropy_from_mnemonic(seed)
    wallet = create_wallet(wallet_path,
                           password,
                           wallet_utils.DEFAULT_MIXDEPTH,
                           walletclass,
                           entropy=entropy)
    jmprint("recovery_seed:{}".format(wallet.get_mnemonic_words()[0]),
            "important")
    wallet.close()
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()
예제 #8
0
def directory_node_startup():
    parser = OptionParser(usage='usage: %prog [options]')
    add_base_options(parser)
    (options, args) = parser.parse_args()
    # for string access, convert to dict:
    options = vars(options)
    if len(args) != 1:
        parser.error(
            'One argument required: string to be published in the MOTD of the directory node.'
        )
        sys.exit(EXIT_ARGERROR)
    operator_message = args[0]
    # It's possible to set `no-blockchain` in the config file, but this just
    # makes it easier for the user:
    load_program_config(config_path=options["datadir"], bs="no-blockchain")
    # note: you *must* only have the onionmc, no IRC, for this to work,
    # and of course you must only have your own d-node configured here:
    mchan_config = get_mchannels()[0]
    node_location = mchan_config["directory_nodes"]
    # before starting, patch the server handshake default to include our MOTD customization
    # default acceptance false; code must switch it on:
    jmdaemon.onionmc.server_handshake_json[
        "motd"] = "DIRECTORY NODE: {}\nJOINMARKET VERSION: {}\n{}".format(
            node_location, JM_CORE_VERSION, operator_message)
    maker = DNMaker()
    jlog.info('starting directory node')
    clientfactory = DNJMClientProtocolFactory(maker, proto_type="MAKER")
    nodaemon = jm_single().config.getint("DAEMON", "no_daemon")
    daemon = bool(nodaemon)
    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,
                  daemon=daemon)
예제 #9
0
def main():
    parser = OptionParser(
        usage='usage: %prog [options] [txid:n]',
        description=
        "Adds one or more utxos to the list that can be used to make "
        "commitments for anti-snooping. Note that this utxo, and its "
        "PUBkey, will be revealed to makers, so consider the privacy "
        "implication. "
        "It may be useful to those who are having trouble making "
        "coinjoins due to several unsuccessful attempts (especially "
        "if your joinmarket wallet is new). "
        "'Utxo' means unspent transaction output, it must not "
        "already be spent. "
        "The options -w, -r and -R offer ways to load these utxos "
        "from a file or wallet. "
        "If you enter a single utxo without these options, you will be "
        "prompted to enter the private key here - it must be in "
        "WIF compressed format. "
        "BE CAREFUL about handling private keys! "
        "Don't do this in insecure environments. "
        "Also note this ONLY works for standard p2wpkh (native segwit) "
        "or p2sh-p2wpkh (nested segwit) utxos.")
    add_base_options(parser)
    parser.add_option(
        '-r',
        '--read-from-file',
        action='store',
        type='str',
        dest='in_file',
        help=
        'name of plain text csv file containing utxos, one per line, format: '
        'txid:N, WIF-compressed-privkey')
    parser.add_option(
        '-R',
        '--read-from-json',
        action='store',
        type='str',
        dest='in_json',
        help=
        'name of json formatted file containing utxos with private keys, as '
        'output from "python wallet-tool.py -p walletname showutxos"')
    parser.add_option(
        '-w',
        '--load-wallet',
        action='store',
        type='str',
        dest='loadwallet',
        help='name of wallet from which to load utxos and use as commitments.')
    parser.add_option(
        '-g',
        '--gap-limit',
        action='store',
        type='int',
        dest='gaplimit',
        default=6,
        help=
        'Only to be used with -w; gap limit for Joinmarket wallet, default 6.')
    parser.add_option(
        '-M',
        '--max-mixdepth',
        action='store',
        type='int',
        dest='maxmixdepth',
        default=5,
        help=
        'Only to be used with -w; number of mixdepths for wallet, default 5.')
    parser.add_option(
        '-d',
        '--delete-external',
        action='store_true',
        dest='delete_ext',
        help='deletes the current list of external commitment utxos',
        default=False)
    parser.add_option(
        '-v',
        '--validate-utxos',
        action='store_true',
        dest='validate',
        help='validate the utxos and pubkeys provided against the blockchain',
        default=False)
    parser.add_option(
        '-o',
        '--validate-only',
        action='store_true',
        dest='vonly',
        help='only validate the provided utxos (file or command line), not add',
        default=False)
    (options, args) = parser.parse_args()
    load_program_config(config_path=options.datadir)
    #TODO; sort out "commit file location" global so this script can
    #run without this hardcoding:
    utxo_data = []
    if options.delete_ext:
        other = options.in_file or options.in_json or options.loadwallet
        if len(args) > 0 or other:
            if input("You have chosen to delete commitments, other arguments "
                     "will be ignored; continue? (y/n)") != 'y':
                jmprint("Quitting", "warning")
                sys.exit(EXIT_SUCCESS)
        c, e = get_podle_commitments()
        jmprint(pformat(e), "info")
        if input("You will remove the above commitments; are you sure? (y/n): "
                 ) != 'y':
            jmprint("Quitting", "warning")
            sys.exit(EXIT_SUCCESS)
        update_commitments(external_to_remove=e)
        jmprint("Commitments deleted.", "important")
        sys.exit(EXIT_SUCCESS)

    #Three options (-w, -r, -R) for loading utxo and privkey pairs from a wallet,
    #csv file or json file.
    if options.loadwallet:
        wallet_path = get_wallet_path(options.loadwallet)
        wallet = open_wallet(wallet_path, gap_limit=options.gaplimit)
        wallet_service = WalletService(wallet)
        if wallet_service.rpc_error:
            sys.exit(EXIT_FAILURE)
        while True:
            if wallet_service.sync_wallet(fast=not options.recoversync):
                break

        # minor note: adding a utxo from an external wallet for commitments, we
        # default to not allowing disabled utxos to avoid a privacy leak, so the
        # user would have to explicitly enable.
        for md, utxos in wallet_service.get_utxos_by_mixdepth().items():
            for utxo, utxodata in utxos.items():
                wif = wallet_service.get_wif_path(utxodata['path'])
                utxo_data.append((utxo, wif))

    elif options.in_file:
        with open(options.in_file, "rb") as f:
            utxo_info = f.readlines()
        for ul in utxo_info:
            ul = ul.rstrip()
            if ul:
                u, priv = get_utxo_info(ul.decode("utf-8"), utxo_binary=True)
                if not u:
                    quit(parser, "Failed to parse utxo info: " + str(ul))
                utxo_data.append((u, priv))
    elif options.in_json:
        if not os.path.isfile(options.in_json):
            jmprint("File: " + options.in_json + " not found.", "error")
            sys.exit(EXIT_FAILURE)
        with open(options.in_json, "rb") as f:
            try:
                utxo_json = json.loads(f.read())
            except:
                jmprint("Failed to read json from " + options.in_json, "error")
                sys.exit(EXIT_FAILURE)
        for u, pva in iteritems(utxo_json):
            utxobin, priv = get_utxo_info(",".join([u, pva["privkey"]]),
                                          utxo_binary=True)
            if not utxobin:
                quit(parser, "Failed to load utxo from json: " + str(u))
            utxo_data.append((utxobin, priv))
    elif len(args) == 1:
        ul = args[0]
        priv = input('input private key for ' + ul +
                     ', in WIF compressed format : ')
        u, priv = get_utxo_info(','.join([ul, priv]), utxo_binary=True)
        if not u:
            quit(parser, "Failed to parse utxo info: " + ul)
        utxo_data.append((u, priv))
    else:
        quit(parser, 'Invalid syntax')
    if options.validate or options.vonly:
        # if the utxos are loaded from a wallet, we use the wallet's
        # txtype to determine the value of `utxo_address_type`; if not,
        # we use joinmarket.cfg.
        if options.loadwallet:
            utxo_address_type = wallet_service.get_txtype()
        else:
            if jm_single().config.get("POLICY", "segwit") == "false":
                utxo_address_type = "p2pkh"
            elif jm_single().config.get("POLICY", "native") == "false":
                utxo_address_type = "p2sh-p2wpkh"
            else:
                utxo_address_type = "p2wpkh"
        if not validate_utxo_data(utxo_data,
                                  utxo_address_type=utxo_address_type):
            quit(parser, "Utxos did not validate, quitting")
    if options.vonly:
        sys.exit(EXIT_SUCCESS)

    #We are adding utxos to the external list
    assert len(utxo_data)
    add_ext_commitments(utxo_data)
예제 #10
0
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_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"),
                  None,
                  snickerfactory=snicker_pf,
                  daemon=daemon)
def main():
    parser = OptionParser(usage='usage: %prog [options] walletname',
                          description=description)
    add_base_options(parser)
    parser.add_option('-m',
                      '--mixdepth',
                      action='store',
                      type='int',
                      dest='mixdepth',
                      help='mixdepth/account, 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(
        '-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)
    parser.add_option(
        '-N',
        '--net-transfer',
        action='store',
        type='int',
        dest='net_transfer',
        help='how many sats are sent to the "receiver", default randomised.',
        default=-1000001)
    (options, args) = parser.parse_args()
    snicker_plugin = JMPluginService("SNICKER")
    load_program_config(config_path=options.datadir,
                        plugin_services=[snicker_plugin])
    if len(args) != 1:
        log.error("Invalid arguments, see --help")
        sys.exit(EXIT_ARGERROR)
    wallet_name = args[0]
    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()
    fee_est = estimate_tx_fee(2, 3, txtype=wallet_service.get_txtype())

    # first, order the utxos in the mixepth by size. Then (this is the
    # simplest algorithm; we could be more sophisticated), choose the
    # *second* largest utxo as the receiver utxo; this ensures that we
    # have enough for the proposer to cover. We consume utxos greedily,
    # meaning we'll at least some of the time, be consolidating.
    utxo_dict = wallet_service.get_utxos_by_mixdepth()[options.mixdepth]
    if not len(utxo_dict) >= 2:
        log.error(
            "Cannot create fake SNICKER tx without at least two utxos, quitting"
        )
        sys.exit(EXIT_ARGERROR)
    # sort utxos by size
    sorted_utxos = sorted(list(utxo_dict.keys()),
                          key=lambda k: utxo_dict[k]['value'],
                          reverse=True)
    # receiver is the second largest:
    receiver_utxo = sorted_utxos[1]
    receiver_utxo_val = utxo_dict[receiver_utxo]
    # gather the other utxos into a list to select from:
    nonreceiver_utxos = [sorted_utxos[0]] + sorted_utxos[2:]
    # get the net transfer in our fake coinjoin:
    if options.net_transfer < -1000001:
        log.error("Net transfer must be greater than negative 1M sats")
        sys.exit(EXIT_ARGERROR)
    if options.net_transfer == -1000001:
        # default; low-ish is more realistic and avoids problems
        # with dusty utxos
        options.net_transfer = random.randint(-1000, 1000)

    # select enough to cover: receiver value + fee + transfer + breathing room
    # we select relatively greedily to support consolidation, since
    # this transaction does not pretend to isolate the coins.
    try:
        available = [{
            'utxo': utxo,
            'value': utxo_dict[utxo]["value"]
        } for utxo in nonreceiver_utxos]
        # selection algos return [{"utxo":..,"value":..}]:
        prop_utxos = {
            x["utxo"]
            for x in select_greedy(
                available, receiver_utxo_val["value"] + fee_est +
                options.net_transfer + 1000)
        }
        prop_utxos = list(prop_utxos)
        prop_utxo_vals = [utxo_dict[prop_utxo] for prop_utxo in prop_utxos]
    except NotEnoughFundsException as e:
        log.error(repr(e))
        sys.exit(EXIT_FAILURE)

    # Due to the fake nature of this transaction, and its distinguishability
    # (not only in trivial output pattern, but also in subset-sum), there
    # is little advantage in making it use different output mixdepths, so
    # here to prevent fragmentation, everything is kept in the same mixdepth.
    receiver_addr, proposer_addr, change_addr = (wallet_service.script_to_addr(
        wallet_service.get_new_script(options.mixdepth, 1)) for _ in range(3))
    # persist index update:
    wallet_service.save_wallet()
    outputs = btc.construct_snicker_outputs(
        sum([x["value"] for x in prop_utxo_vals]), receiver_utxo_val["value"],
        receiver_addr, proposer_addr, change_addr, fee_est,
        options.net_transfer)
    tx = btc.make_shuffled_tx(prop_utxos + [receiver_utxo],
                              outputs,
                              version=2,
                              locktime=0)
    # before signing, check we satisfied the criteria, otherwise
    # this is pointless!
    if not btc.is_snicker_tx(tx):
        log.error("Code error, created non-SNICKER tx, not signing.")
        sys.exit(EXIT_FAILURE)

    # sign all inputs
    # scripts: {input_index: (output_script, amount)}
    our_inputs = {}
    for index, ins in enumerate(tx.vin):
        utxo = (ins.prevout.hash[::-1], ins.prevout.n)
        script = utxo_dict[utxo]['script']
        amount = utxo_dict[utxo]['value']
        our_inputs[index] = (script, amount)
    success, msg = wallet_service.sign_tx(tx, our_inputs)
    if not success:
        log.error("Failed to sign transaction: " + msg)
        sys.exit(EXIT_FAILURE)
    # TODO condition on automatic brdcst or not
    if not jm_single().bc_interface.pushtx(tx.serialize()):
        # this represents an error about state (or conceivably,
        # an ultra-short window in which the spent utxo was
        # consumed in another transaction), but not really
        # an internal logic error, so we do NOT return False
        log.error("Failed to broadcast fake SNICKER coinjoin: " +\
                   bintohex(tx.GetTxid()[::-1]))
        log.info(btc.human_readable_transaction(tx))
        sys.exit(EXIT_FAILURE)
    log.info("Successfully broadcast fake SNICKER coinjoin: " +\
              bintohex(tx.GetTxid()[::-1]))
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',
                      action='store',
                      type='int',
                      dest='txfee',
                      default=None,
                      help='minimum miner fee in satoshis')
    parser.add_option('-f',
                      '--txfee-factor',
                      action='store',
                      type='float',
                      dest='txfee_factor',
                      default=None,
                      help='variance around the average fee, 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"])

    # As per previous note, override non-default command line settings:
    for x in [
            "ordertype", "txfee", "txfee_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 = int(options["txfee"])
    txfee_factor = float(options["txfee_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 / 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, cjfee_a, cjfee_r, ordertype, minsize, txfee_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)
예제 #13
0
def main():
    parser = OptionParser(
        usage='usage: %prog [options] startingblock [endingblock]',
        description=description)
    add_base_options(parser)
    parser.add_option('-f',
                      '--filename',
                      action='store',
                      type='str',
                      dest='candidate_file_name',
                      help='filename to write details of candidate '
                      'transactions, default ./candidates.txt',
                      default='candidates.txt')
    parser.add_option(
        '-j',
        '--include-jm',
        action='store_true',
        dest='include_joinmarket',
        default=True,
        help="scan for Joinmarket coinjoin outputs, as well as SNICKER.")

    (options, args) = parser.parse_args()
    load_program_config(config_path=options.datadir)
    if len(args) not in [1, 2]:
        log.error("Invalid arguments, see --help")
        sys.exit(EXIT_ARGERROR)

    startblock = int(args[0])
    if len(args) == 1:
        endblock = jm_single().bc_interface.get_current_block_height()
    else:
        endblock = int(args[1])

    check_regtest()

    for b in range(startblock, endblock + 1):
        block = jm_single().bc_interface.get_block(b)
        for t in btc.get_transactions_in_block(block):
            if btc.is_snicker_tx(t):
                log.info(found_str("SNICKER", t, b))
                # get list of unspent outputs; if empty, skip,
                # otherwise, persist to candidate file with unspents
                # marked.
                unspents = jm_single().bc_interface.get_unspent_indices(t)
                if len(unspents) == 0:
                    continue
                write_candidate_to_file("SNICKER", t, b, unspents,
                                        options.candidate_file_name)
            # note elif avoids wasting computation if we already found SNICKER:
            elif options.include_joinmarket:
                cj_amount, n = btc.is_jm_tx(t)
                # here we don't care about the stats; the tx is printed anyway.
                if cj_amount:
                    log.info(found_str("Joinmarket coinjoin", t, b))
                    unspents = jm_single().bc_interface.get_unspent_indices(t)
                    if len(unspents) == 0:
                        continue
                    write_candidate_to_file("Joinmarket coinjoin", t, b,
                                            unspents,
                                            options.candidate_file_name)
        log.info("Finished processing block: {}".format(b))
def main():
    parser = OptionParser(
        usage='usage: %prog [options] utxo destaddr1 destaddr2 ..',
        description=
        "For creating multiple utxos from one (for commitments in JM)."
        "Provide a utxo in form txid:N that has some unspent coins;"
        "Specify a list of destination addresses and the coins will"
        "be split equally between them (after bitcoin fees)."
        "You'll be prompted to enter the private key for the utxo"
        "during the run; it must be in WIF compressed format."
        "After the transaction is completed, the utxo strings for"
        "the new outputs will be shown."
        "Note that these utxos will not be ready for use as external"
        "commitments in Joinmarket until 5 confirmations have passed."
        " BE CAREFUL about handling private keys!"
        " Don't do this in insecure environments."
        " Works only with p2pkh ('1') or p2sh-p2wpkh (segwit '3')"
        " utxos - set segwit=False in the POLICY section of"
        " joinmarket.cfg for the former.")
    parser.add_option(
        '-v',
        '--validate-utxos',
        action='store_true',
        dest='validate',
        help='validate the utxos and pubkeys provided against the blockchain',
        default=False)
    parser.add_option(
        '-o',
        '--validate-only',
        action='store_true',
        dest='vonly',
        help='only validate the provided utxos (file or command line), not add',
        default=False)
    parser.add_option(
        '-n',
        '--non-segwit-input',
        action='store_true',
        dest='nonsegwit',
        help=
        'input is p2pkh ("1" address), not segwit; if not used, input is assumed to be segwit type.',
        default=False)
    add_base_options(parser)
    (options, args) = parser.parse_args()
    load_program_config(config_path=options.datadir)
    if len(args) < 2:
        quit(parser, 'Invalid syntax')
    u = args[0]
    priv = input('input private key for ' + u +
                 ', in WIF compressed format : ')
    u, priv = get_utxo_info(','.join([u, priv]))
    if not u:
        quit(parser, "Failed to parse utxo info: " + u)
    destaddrs = args[1:]
    for d in destaddrs:
        if not validate_address(d):
            quit(parser, "Address was not valid; wrong network?: " + d)
    success, utxo = utxostr_to_utxo(u)
    if not success:
        quit(parser, "Failed to load utxo from string: " + utxo)
    txsigned = sign(utxo, priv, destaddrs, segwit=not options.nonsegwit)
    if not txsigned:
        log.info(
            "Transaction signing operation failed, see debug messages for details."
        )
        return
    log.info("Got signed transaction:\n" + bintohex(txsigned.serialize()))
    log.debug("Deserialized:")
    log.debug(pformat(str(txsigned)))
    if input('Would you like to push to the network? (y/n):')[0] != 'y':
        log.info("You chose not to broadcast the transaction, quitting.")
        return
    jm_single().bc_interface.pushtx(txsigned.serialize())
예제 #15
0
def ygmain(ygclass,
           txfee=1000,
           cjfee_a=200,
           cjfee_r=0.002,
           ordertype='swreloffer',
           nickserv_password='',
           minsize=100000,
           gaplimit=6):
    import sys

    parser = OptionParser(usage='usage: %prog [options] [wallet file]')
    add_base_options(parser)
    parser.add_option('-o',
                      '--ordertype',
                      action='store',
                      type='string',
                      dest='ordertype',
                      default=ordertype,
                      help='type of order; can be either reloffer or absoffer')
    parser.add_option('-t',
                      '--txfee',
                      action='store',
                      type='int',
                      dest='txfee',
                      default=txfee,
                      help='minimum miner fee in satoshis')
    parser.add_option('-c',
                      '--cjfee',
                      action='store',
                      type='string',
                      dest='cjfee',
                      default='',
                      help='requested coinjoin fee in satoshis or proportion')
    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=minsize,
                      help='minimum coinjoin size in satoshis')
    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()
    if len(args) < 1:
        parser.error('Needs a wallet')
        sys.exit(EXIT_ARGERROR)
    wallet_name = args[0]
    ordertype = options.ordertype
    txfee = options.txfee
    if ordertype in ('reloffer', 'swreloffer'):
        if options.cjfee != '':
            cjfee_r = options.cjfee
        # minimum size is such that you always net profit at least 20%
        #of the miner fee
        minsize = max(int(1.2 * txfee / float(cjfee_r)), options.minsize)
    elif ordertype in ('absoffer', 'swabsoffer'):
        if options.cjfee != '':
            cjfee_a = int(options.cjfee)
        minsize = options.minsize
    else:
        parser.error('You specified an incorrect offer type which ' +\
                     'can be either swreloffer or swabsoffer')
        sys.exit(EXIT_ARGERROR)
    nickserv_password = options.password

    load_program_config(config_path=options.datadir)

    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()

    maker = ygclass(
        wallet_service,
        [options.txfee, cjfee_a, cjfee_r, options.ordertype, options.minsize])
    jlog.info('starting yield generator')
    clientfactory = JMClientProtocolFactory(maker, proto_type="MAKER")

    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"]:
        startLogging(sys.stdout)
    start_reactor(jm_single().config.get("DAEMON", "daemon_host"),
                  jm_single().config.getint("DAEMON", "daemon_port"),
                  clientfactory,
                  daemon=daemon)
예제 #16
0
def main():
    parser = OptionParser(usage='usage: %prog [options] walletname',
                          description=description)
    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('-g',
                      '--gap-limit',
                      type="int",
                      action='store',
                      dest='gaplimit',
                      help='gap limit for wallet, default=6',
                      default=6)
    add_base_options(parser)
    (options, args) = parser.parse_args()
    load_program_config(config_path=options.datadir)
    check_regtest()
    if len(args) != 1:
        log.error("Invalid arguments, see --help")
        sys.exit(EXIT_ARGERROR)
    wallet_name = args[0]
    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)

    # step 1: do a full recovery style sync. this will pick up
    # all addresses that we expect to match transactions against,
    # from a blank slate Core wallet that originally had no imports.
    if not options.recoversync:
        jmprint("Recovery sync was not set, but using it anyway.")
    while not wallet_service.synced:
        wallet_service.sync_wallet(fast=False)
    # Note that the user may be interrupted above by the rescan
    # request; this is as for normal scripts; after the rescan is done
    # (usually, only once, but, this *IS* needed here, unlike a normal
    # wallet generation event), we just try again.

    # Now all address from HD are imported, we need to grab
    # all the transactions for those addresses; this includes txs
    # that *spend* as well as receive our coins, so will include
    # "first-out" SNICKER txs as well as ordinary spends and JM coinjoins.
    seed_transactions = wallet_service.get_all_transactions()

    # Search for SNICKER txs and add them if they match.
    # We proceed recursively; we find all one-out matches, then
    # all 2-out matches, until we find no new ones and stop.

    if len(seed_transactions) == 0:
        jmprint("No transactions were found for this wallet. Did you rescan?")
        return False

    new_txs = []
    current_block_heights = set()
    for tx in seed_transactions:
        if btc.is_snicker_tx(tx):
            jmprint("Found a snicker tx: {}".format(
                bintohex(tx.GetTxid()[::-1])))
            equal_outs = btc.get_equal_outs(tx)
            if not equal_outs:
                continue
            if all([
                    wallet_service.is_known_script(x.scriptPubKey) == False
                    for x in [a[1] for a in equal_outs]
            ]):
                # it is now *very* likely that one of the two equal
                # outputs is our SNICKER custom output
                # script; notice that in this case, the transaction *must*
                # have spent our inputs, since it didn't recognize ownership
                # of either coinjoin output (and if it did recognize the change,
                # it would have recognized the cj output also).
                # We try to regenerate one of the outputs, but warn if
                # we can't.
                my_indices = get_pubs_and_indices_of_inputs(tx,
                                                            wallet_service,
                                                            ours=True)
                for mypub, mi in my_indices:
                    for eo in equal_outs:
                        for (other_pub, i) in get_pubs_and_indices_of_inputs(
                                tx, wallet_service, ours=False):
                            for (our_pub,
                                 j) in get_pubs_and_indices_of_ancestor_inputs(
                                     tx.vin[mi], wallet_service, ours=True):
                                our_spk = wallet_service.pubkey_to_script(
                                    our_pub)
                                our_priv = wallet_service.get_key_from_addr(
                                    wallet_service.script_to_addr(our_spk))
                                tweak_bytes = btc.ecdh(our_priv[:-1],
                                                       other_pub)
                                tweaked_pub = btc.snicker_pubkey_tweak(
                                    our_pub, tweak_bytes)
                                tweaked_spk = wallet_service.pubkey_to_script(
                                    tweaked_pub)
                                if tweaked_spk == eo[1].scriptPubKey:
                                    # TODO wallet.script_to_addr has a dubious assertion, that's why
                                    # we use btc method directly:
                                    address_found = str(
                                        btc.CCoinAddress.from_scriptPubKey(
                                            btc.CScript(tweaked_spk)))
                                    #address_found = wallet_service.script_to_addr(tweaked_spk)
                                    jmprint(
                                        "Found a new SNICKER output belonging to us."
                                    )
                                    jmprint(
                                        "Output address {} in the following transaction:"
                                        .format(address_found))
                                    jmprint(btc.human_readable_transaction(tx))
                                    jmprint(
                                        "Importing the address into the joinmarket wallet..."
                                    )
                                    # NB for a recovery we accept putting any imported keys all into
                                    # the same mixdepth (0); TODO investigate correcting this, it will
                                    # be a little complicated.
                                    success, msg = wallet_service.check_tweak_matches_and_import(
                                        wallet_service.script_to_addr(our_spk),
                                        tweak_bytes, tweaked_pub,
                                        wallet_service.mixdepth)
                                    if not success:
                                        jmprint(
                                            "Failed to import SNICKER key: {}".
                                            format(msg), "error")
                                        return False
                                    else:
                                        jmprint("... success.")
                                    # we want the blockheight to track where the next-round rescan
                                    # must start from
                                    current_block_heights.add(
                                        wallet_service.
                                        get_transaction_block_height(tx))
                                    # add this transaction to the next round.
                                    new_txs.append(tx)
    if len(new_txs) == 0:
        return True
    seed_transactions.extend(new_txs)
    earliest_new_blockheight = min(current_block_heights)
    jmprint("New SNICKER addresses were imported to the Core wallet; "
            "do rescanblockchain again, starting from block {}, before "
            "restarting this script.".format(earliest_new_blockheight))
    return False
예제 #17
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 receive_payjoin_main(makerclass):
    parser = OptionParser(
        usage='usage: %prog [options] [wallet file] [amount-to-receive]')
    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)

    (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:
        receiving_amount = amount_to_sat(args[1])
    except:
        parser.error("Invalid receiving amount passed: " + receiving_amount)
        sys.exit(EXIT_FAILURE)
    if receiving_amount < 0:
        parser.error("Receiving amount must be a positive number")
        sys.exit(EXIT_FAILURE)
    load_program_config(config_path=options.datadir)

    check_regtest()

    # This workflow requires command line reading; we force info level logging
    # to remove noise, and mostly communicate to the user with the fn
    # log.info (via P2EPMaker.user_info).
    set_logging_level("INFO")

    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()[options.mixdepth] == 0:
        jlog.error("Cannot do payjoin from mixdepth " + str(options.mixdepth) +
                   ", no coins. Shutting down.")
        sys.exit(EXIT_ARGERROR)

    maker = makerclass(wallet_service, options.mixdepth, receiving_amount)

    jlog.info('starting receive-payjoin')
    clientfactory = JMClientProtocolFactory(maker, proto_type="MAKER")

    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"]:
        startLogging(sys.stdout)
    start_reactor(jm_single().config.get("DAEMON", "daemon_host"),
                  jm_single().config.getint("DAEMON", "daemon_port"),
                  clientfactory,
                  daemon=daemon,
                  p2ep=True)
예제 #19
0
def main():
    parser = OptionParser(
        usage=
        'usage: %prog [options] utxo destaddr1 destaddr2 ..',
        description="For creating multiple utxos from one (for commitments in JM)."
                    "Provide a utxo in form txid:N that has some unspent coins;"
                    "Specify a list of destination addresses and the coins will"
                    "be split equally between them (after bitcoin fees)."

                    "You'll be prompted to enter the private key for the utxo"
                    "during the run; it must be in WIF compressed format."
                    "After the transaction is completed, the utxo strings for"

                    "the new outputs will be shown."
                    "Note that these utxos will not be ready for use as external"

                    "commitments in Joinmarket until 5 confirmations have passed."
                    " BE CAREFUL about handling private keys!"
                    " Don't do this in insecure environments."
                    " Works only with p2pkh ('1') or p2sh-p2wpkh (segwit '3')"
                    " utxos - set segwit=False in the POLICY section of"
                    " joinmarket.cfg for the former."
    )
    parser.add_option(
        '-t',
        '--utxo-address-type',
        action='store',
        dest='utxo_address_type',
        help=('type of address of coin being spent - one of "p2pkh", "p2wpkh", "p2sh-p2wpkh". '
        'No other scriptpubkey types (e.g. multisig) are supported. If not set, we default '
        'to what is in joinmarket.cfg.'),
        default=""
    )
    add_base_options(parser)
    (options, args) = parser.parse_args()
    load_program_config(config_path=options.datadir)
    if len(args) < 2:
        quit(parser, 'Invalid syntax')
    u = args[0]
    priv = input(
        'input private key for ' + u + ', in WIF compressed format : ')
    u, priv = get_utxo_info(','.join([u, priv]))
    if not u:
        quit(parser, "Failed to parse utxo info: " + u)
    destaddrs = args[1:]
    for d in destaddrs:
        if not validate_address(d):
            quit(parser, "Address was not valid; wrong network?: " + d)
    success, utxo = utxostr_to_utxo(u)
    if not success:
        quit(parser, "Failed to load utxo from string: " + utxo)
    if options.utxo_address_type == "":
        if jm_single().config.get("POLICY", "segwit") == "false":
            utxo_address_type = "p2pkh"
        elif jm_single().config.get("POLICY", "native") == "false":
            utxo_address_type = "p2sh-p2wpkh"
        else:
            utxo_address_type = "p2wpkh"
    else:
        utxo_address_type = options.utxo_address_type
    txsigned = sign(utxo, priv, destaddrs, utxo_address_type)
    if not txsigned:
        log.info("Transaction signing operation failed, see debug messages for details.")
        return
    log.info("Got signed transaction:\n" + bintohex(txsigned.serialize()))
    log.info(btc.human_readable_transaction(txsigned))
    if input('Would you like to push to the network? (y/n):')[0] != 'y':
        log.info("You chose not to broadcast the transaction, quitting.")
        return
    jm_single().bc_interface.pushtx(txsigned.serialize())
예제 #20
0
def main() -> None:
    parser = OptionParser(
        usage="usage: %prog [options] UTXO or amount",
        description=DESCRIPTION,
    )
    add_base_options(parser)
    parser.add_option(
        "-i",
        "--interest",
        action="store",
        type="float",
        dest="interest",
        help=
        "Interest rate to use for fidelity bond calculation (instead of interest_rate config)",
    )
    parser.add_option(
        "-e",
        "--exponent",
        action="store",
        type="float",
        dest="exponent",
        help=
        "Exponent to use for fidelity bond calculation (instead of bond_value_exponent config)",
    )
    parser.add_option(
        "-m",
        "--months",
        action="store",
        type="int",
        dest="months",
        help=
        "For how many months to calculate the fidelity bond values, each month has its own stats (default 12)",
        default=12,
    )
    parser.add_option(
        "-o",
        "--orderbook",
        action="store",
        type="str",
        dest="path_to_json",
        help="Path to the exported orderbook in JSON format",
    )

    options, args = parser.parse_args()
    load_program_config(config_path=options.datadir)
    if len(args) != 1:
        log.error("Invalid arguments, see --help")
        sys.exit(EXIT_ARGERROR)
    if options.path_to_json:
        try:
            with open(options.path_to_json, "r",
                      encoding="UTF-8") as orderbook:
                orderbook = loads(orderbook.read())
        except FileNotFoundError as exc:
            log.error(exc)
            sys.exit(EXIT_ARGERROR)
    else:
        orderbook = None
    try:
        amount = amount_to_sat(args[0])
        confirm_time = None
    except ValueError:
        # If it's not a valid amount then it has to be a UTXO
        if jm_single().bc_interface is None:
            log.error(
                "For calculation based on UTXO access to Bitcoin Core is required"
            )
            sys.exit(EXIT_FAILURE)
        success, utxo = utxostr_to_utxo(args[0])
        if not success:
            # utxo contains the error message
            log.error(utxo)
            sys.exit(EXIT_ARGERROR)
        utxo_data = jm_single().bc_interface.query_utxo_set(
            utxo, includeconfs=True)[0]
        amount = utxo_data["value"]
        if utxo_data["confirms"] == 0:
            log.warning(
                "Given UTXO is unconfirmed, current time will be used as confirmation time"
            )
            confirm_time = None
        elif utxo_data["confirms"] < 0:
            log.error("Given UTXO is invalid, reason: conflicted")
            sys.exit(EXIT_ARGERROR)
        else:
            current_height = jm_single().bc_interface.get_current_block_height(
            )
            block_hash = jm_single().bc_interface.get_block_hash(
                current_height - utxo_data["confirms"] + 1)
            confirm_time = jm_single().bc_interface.get_block_time(block_hash)

    parameters, results = get_bond_values(amount, options.months, confirm_time,
                                          options.interest, options.exponent,
                                          orderbook)
    jmprint(f"Amount locked: {amount} ({sat_to_btc(amount)} btc)")
    jmprint(
        f"Confirmation time: {datetime.fromtimestamp(parameters['confirm_time'])}"
    )
    jmprint(
        f"Interest rate: {parameters['interest']} ({parameters['interest'] * 100}%)"
    )
    jmprint(f"Exponent: {parameters['exponent']}")
    jmprint(f"\nFIDELITY BOND VALUES (BTC^{parameters['exponent']})")
    jmprint("\nSee /docs/fidelity-bonds.md for complete formula and more")

    for result in results:
        locktime = datetime.fromtimestamp(result["locktime"])
        # Mimic the locktime value the user would have to insert to create such fidelity bond
        jmprint(f"\nLocktime: {locktime.year}-{locktime.month}")
        # Mimic orderbook value
        jmprint(
            f"Bond value: {float(Decimal(result['value']) / Decimal(1e16)):.16f}"
        )
        if options.path_to_json:
            jmprint(
                f"Weight: {result['weight']:.5f} ({result['weight'] * 100:.2f}% of all bonds)"
            )
            jmprint(f"Top {result['percentile']}% of the orderbook by value")