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)
Ejemplo n.º 2
0
def test_start_ygs(setup_ygrunner, num_ygs, wallet_structures, fb_indices,
                   mean_amt, malicious, deterministic):
    """Set up some wallets, for the ygs and 1 sp.
    Then start the ygs in background and publish
    the seed of the sp wallet for easy import into -qt
    """
    if jm_single().config.get("POLICY", "native") == "true":
        walletclass = SegwitWallet
    else:
        # TODO add Legacy
        walletclass = SegwitLegacyWallet

    wallet_services = make_wallets(num_ygs + 1,
                                   wallet_structures=wallet_structures,
                                   mean_amt=mean_amt,
                                   walletclass=walletclass,
                                   fb_indices=fb_indices)
    #the sendpayment bot uses the last wallet in the list
    wallet_service = wallet_services[num_ygs]['wallet']
    jmprint("\n\nTaker wallet seed : " + wallet_services[num_ygs]['seed'])
    # for manual audit if necessary, show the maker's wallet seeds
    # also (note this audit should be automated in future, see
    # test_full_coinjoin.py in this directory)
    jmprint("\n\nMaker wallet seeds: ")
    for i in range(num_ygs):
        jmprint("Maker seed: " + wallet_services[i]['seed'])
    jmprint("\n")
    wallet_service.sync_wallet(fast=True)
    ygclass = YieldGeneratorBasic

    # As per previous note, override non-default command line settings:
    options = {}
    for x in [
            "ordertype", "txfee", "txfee_factor", "cjfee_a", "cjfee_r",
            "cjfee_factor", "minsize", "size_factor"
    ]:
        options[x] = jm_single().config.get("YIELDGENERATOR", x)
    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:
        assert False, "incorrect offertype config for yieldgenerator."

    txtype = wallet_service.get_txtype()
    if txtype == "p2wpkh":
        prefix = "sw0"
    elif txtype == "p2sh-p2wpkh":
        prefix = "sw"
    elif txtype == "p2pkh":
        prefix = ""
    else:
        assert False, "Unsupported wallet type for yieldgenerator: " + txtype

    ordertype = prefix + ordertype

    if malicious:
        if deterministic:
            ygclass = DeterministicMaliciousYieldGenerator
        else:
            ygclass = MaliciousYieldGenerator
    for i in range(num_ygs):
        cfg = [
            txfee, cjfee_a, cjfee_r, ordertype, minsize, txfee_factor,
            cjfee_factor, size_factor
        ]
        wallet_service_yg = wallet_services[i]["wallet"]

        wallet_service_yg.startService()

        yg = ygclass(wallet_service_yg, cfg)
        if i in fb_indices:
            # create a timelocked address and fund it;
            # must be done after sync, so deferred:
            wallet_service_yg.timelock_funded = False
            sync_wait_loop = task.LoopingCall(get_addr_and_fund, yg)
            sync_wait_loop.start(1.0, now=False)
        if malicious:
            yg.set_maliciousness(malicious, mtype="tx")
        clientfactory = JMClientProtocolFactory(yg, proto_type="MAKER")
        if jm_single().config.get("SNICKER", "enabled") == "true":
            snicker_r = SNICKERReceiver(wallet_service_yg)
            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
        rs = True if i == num_ygs - 1 else False
        start_reactor(jm_single().config.get("DAEMON", "daemon_host"),
                      jm_single().config.getint("DAEMON", "daemon_port"),
                      clientfactory,
                      snickerfactory=snicker_factory,
                      daemon=daemon,
                      rs=rs)
Ejemplo n.º 3
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)
Ejemplo n.º 4
0
def test_snicker_e2e(setup_snicker, nw, wallet_structures, mean_amt, sdev_amt,
                     amt, net_transfer):
    """ Test strategy:
    1. create two wallets.
    2. with wallet 1 (Receiver), create a single transaction
    tx1, from mixdepth 0 to 1.
    3. with wallet 2 (Proposer), take pubkey of all inputs from tx1, and use
    them to create snicker proposals to the non-change out of tx1,
    in base64 and place in proposals.txt.
    4. Receiver polls for proposals in the file manually (instead of twisted
    LoopingCall) and processes them.
    5. Check for valid final transaction with broadcast.
    """

    # TODO: Make this test work with native segwit wallets
    wallets = make_wallets(nw,
                           wallet_structures,
                           mean_amt,
                           sdev_amt,
                           wallet_cls=SegwitLegacyWallet)
    for w in wallets.values():
        w['wallet'].sync_wallet(fast=True)
    print(wallets)
    wallet_r = wallets[0]['wallet']
    wallet_p = wallets[1]['wallet']
    # next, create a tx from the receiver wallet
    our_destn_script = wallet_r.get_new_script(
        1, BaseWallet.ADDRESS_TYPE_INTERNAL)
    tx = direct_send(wallet_r,
                     btc.coins_to_satoshi(0.3),
                     0,
                     wallet_r.script_to_addr(our_destn_script),
                     accept_callback=dummy_accept_callback,
                     info_callback=dummy_info_callback,
                     return_transaction=True)

    assert tx, "Failed to spend from receiver wallet"
    print("Parent transaction OK. It was: ")
    print(btc.human_readable_transaction(tx))
    wallet_r.process_new_tx(tx)
    # we must identify the receiver's output we're going to use;
    # it can be destination or change, that's up to the proposer
    # to guess successfully; here we'll just choose index 0.
    txid1 = tx.GetTxid()[::-1]
    txid1_index = 0

    receiver_start_bal = sum(
        [x['value'] for x in wallet_r.get_all_utxos().values()])

    # Now create a proposal for every input index in tx1
    # (version 1 proposals mean we source keys from the/an
    # ancestor transaction)
    propose_keys = []
    for i in range(len(tx.vin)):
        # todo check access to pubkey
        sig, pub = [a for a in iter(tx.wit.vtxinwit[i].scriptWitness)]
        propose_keys.append(pub)
    # the proposer wallet needs to choose a single
    # utxo that is bigger than the output amount of tx1
    prop_m_utxos = wallet_p.get_utxos_by_mixdepth()[0]
    prop_utxo = prop_m_utxos[list(prop_m_utxos)[0]]
    # get the private key for that utxo
    priv = wallet_p.get_key_from_addr(
        wallet_p.script_to_addr(prop_utxo['script']))
    prop_input_amt = prop_utxo['value']
    # construct the arguments for the snicker proposal:
    our_input = list(prop_m_utxos)[0]  # should be (txid, index)
    their_input = (txid1, txid1_index)
    our_input_utxo = btc.CMutableTxOut(prop_utxo['value'], prop_utxo['script'])
    fee_est = estimate_tx_fee(len(tx.vin), 2)
    change_spk = wallet_p.get_new_script(0, BaseWallet.ADDRESS_TYPE_INTERNAL)

    encrypted_proposals = []

    for p in propose_keys:
        # TODO: this can be a loop over all outputs,
        # not just one guessed output, if desired.
        encrypted_proposals.append(
            wallet_p.create_snicker_proposal(our_input,
                                             their_input,
                                             our_input_utxo,
                                             tx.vout[txid1_index],
                                             net_transfer,
                                             fee_est,
                                             priv,
                                             p,
                                             prop_utxo['script'],
                                             change_spk,
                                             version_byte=1) + b"," +
            bintohex(p).encode('utf-8'))
    with open(TEST_PROPOSALS_FILE, "wb") as f:
        f.write(b"\n".join(encrypted_proposals))
    sR = SNICKERReceiver(wallet_r)
    sR.proposals_source = TEST_PROPOSALS_FILE  # avoid clashing with mainnet
    sR.poll_for_proposals()
    assert len(sR.successful_txs) == 1
    wallet_r.process_new_tx(sR.successful_txs[0])
    end_utxos = wallet_r.get_all_utxos()
    print("At end the receiver has these utxos: ", end_utxos)
    receiver_end_bal = sum([x['value'] for x in end_utxos.values()])
    assert receiver_end_bal == receiver_start_bal + net_transfer