def test_start_ygs(setup_ygrunner, num_ygs, wallet_structures, mean_amt, malicious): """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 """ wallets = make_wallets(num_ygs + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list wallet = wallets[num_ygs]['wallet'] print("Seed : " + wallets[num_ygs]['seed']) #useful to see the utxos on screen sometimes sync_wallet(wallet, fast=True) print(wallet.unspent) txfee = 1000 cjfee_a = 4200 cjfee_r = '0.001' ordertype = 'swreloffer' minsize = 100000 ygclass = MaliciousYieldGenerator if malicious else YieldGeneratorBasic for i in range(num_ygs): cfg = [txfee, cjfee_a, cjfee_r, ordertype, minsize] sync_wallet(wallets[i]["wallet"], fast=True) yg = ygclass(wallets[i]["wallet"], cfg) if malicious: yg.set_maliciousness(malicious) clientfactory = JMClientProtocolFactory(yg, proto_type="MAKER") 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, daemon=daemon, rs=rs)
def test_start_payjoin_server(setup_payjoin_server): # set up the wallet that the server owns, and the wallet for # the sender too (print the seed): if jm_single().config.get("POLICY", "native") == "true": walletclass = SegwitWallet else: walletclass = SegwitLegacyWallet wallet_services = make_wallets(2, wallet_structures=[[1, 3, 0, 0, 0]] * 2, mean_amt=2, walletclass=walletclass) #the server bot uses the first wallet, the sender the second server_wallet_service = wallet_services[0]['wallet'] jmprint("\n\nTaker wallet seed : " + wallet_services[1]['seed']) jmprint("\n") server_wallet_service.sync_wallet(fast=True) site = Site(PayjoinServer(server_wallet_service)) # TODO for now, just sticking with TLS test as non-encrypted # is unlikely to be used, but add that option. reactor.listenSSL(8080, site, contextFactory=get_ssl_context()) #endpoint = endpoints.TCP4ServerEndpoint(reactor, 8080) #endpoint.listen(site) reactor.run()
def test_start_ygs(setup_ygrunner, num_ygs, wallet_structures, 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) #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) txfee = 1000 cjfee_a = 4200 cjfee_r = '0.001' ordertype = 'swreloffer' minsize = 100000 ygclass = YieldGeneratorBasic if malicious: if deterministic: ygclass = DeterministicMaliciousYieldGenerator else: ygclass = MaliciousYieldGenerator for i in range(num_ygs): cfg = [txfee, cjfee_a, cjfee_r, ordertype, minsize] wallet_service_yg = wallet_services[i]["wallet"] wallet_service_yg.startService() yg = ygclass(wallet_service_yg, cfg) if malicious: yg.set_maliciousness(malicious, mtype="tx") clientfactory = JMClientProtocolFactory(yg, proto_type="MAKER") 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, daemon=daemon, rs=rs)
def test_spend_p2sh_p2wpkh_multi(setup_segwit, wallet_structure, in_amt, amount, segwit_amt, segwit_ins, o_ins): """Creates a wallet from which non-segwit inputs/ outputs can be created, constructs one or more p2wpkh in p2sh spendable utxos (by paying into the corresponding address) and tests spending them in combination. wallet_structure is in accordance with commontest.make_wallets, see docs there in_amt is the amount to pay into each address into the wallet (non-segwit adds) amount (in satoshis) is how much we will pay to the output address segwit_amt in BTC is the amount we will fund each new segwit address with segwit_ins is a list of input indices (where to place the funding segwit utxos) other_ins is a list of input indices (where to place the funding non-sw utxos) """ wallet = make_wallets(1, wallet_structure, in_amt, walletclass=Wallet)[0]['wallet'] jm_single().bc_interface.sync_wallet(wallet) other_ins = {} ctr = 0 for k, v in wallet.unspent.iteritems(): #only extract as many non-segwit utxos as we need; #doesn't matter which they are if ctr == len(o_ins): break other_ins[k] = (v["value"], wallet.get_key_from_addr(v["address"]), o_ins[ctr]) ctr += 1 ins_sw = {} for i in range(len(segwit_ins)): #build segwit ins from "deterministic-random" keys; #intended to be the same for each run with the same parameters seed = json.dumps( [i, wallet_structure, in_amt, amount, segwit_ins, other_ins]) priv = btc.sha256(seed) + "01" pub = btc.privtopub(priv) #magicbyte is testnet p2sh addr1 = btc.pubkey_to_p2sh_p2wpkh_address(pub, magicbyte=196) print "got address for p2shp2wpkh: " + addr1 txid = jm_single().bc_interface.grab_coins(addr1, segwit_amt) #TODO - int cast, fix? ins_sw[get_utxo_from_txid(txid, addr1)] = (int(segwit_amt * 100000000), priv, segwit_ins[i]) #make_sign_and_push will sanity check the received amount is correct txid = make_sign_and_push(ins_sw, wallet, amount, other_ins) #will always be False if it didn't push. assert txid
def test_spend_p2sh_p2wpkh_multi(setup_segwit, wallet_structure, in_amt, amount, segwit_amt, segwit_ins, o_ins): """Creates a wallet from which non-segwit inputs/ outputs can be created, constructs one or more p2wpkh in p2sh spendable utxos (by paying into the corresponding address) and tests spending them in combination. wallet_structure is in accordance with commontest.make_wallets, see docs there in_amt is the amount to pay into each address into the wallet (non-segwit adds) amount (in satoshis) is how much we will pay to the output address segwit_amt in BTC is the amount we will fund each new segwit address with segwit_ins is a list of input indices (where to place the funding segwit utxos) other_ins is a list of input indices (where to place the funding non-sw utxos) """ MIXDEPTH = 0 # set up wallets and inputs nsw_wallet = make_wallets(1, wallet_structure, in_amt, walletclass=LegacyWallet)[0]['wallet'] jm_single().bc_interface.sync_wallet(nsw_wallet, fast=True) sw_wallet = make_wallets(1, [[len(segwit_ins), 0, 0, 0, 0]], segwit_amt)[0]['wallet'] jm_single().bc_interface.sync_wallet(sw_wallet, fast=True) nsw_utxos = nsw_wallet.get_utxos_by_mixdepth_()[MIXDEPTH] sw_utxos = sw_wallet.get_utxos_by_mixdepth_()[MIXDEPTH] assert len(o_ins) <= len(nsw_utxos), "sync failed" assert len(segwit_ins) <= len(sw_utxos), "sync failed" total_amt_in_sat = 0 nsw_ins = {} for nsw_in_index in o_ins: total_amt_in_sat += in_amt * 10**8 nsw_ins[nsw_in_index] = nsw_utxos.popitem() sw_ins = {} for sw_in_index in segwit_ins: total_amt_in_sat += int(segwit_amt * 10**8) sw_ins[sw_in_index] = sw_utxos.popitem() all_ins = {} all_ins.update(nsw_ins) all_ins.update(sw_ins) # sanity checks assert len(all_ins) == len(nsw_ins) + len(sw_ins), \ "test broken, duplicate index" for k in all_ins: assert 0 <= k < len(all_ins), "test broken, missing input index" # FIXME: encoding mess, mktx should accept binary input formats tx_ins = [] for i, (txid, data) in sorted(all_ins.items(), key=lambda x: x[0]): tx_ins.append('{}:{}'.format(binascii.hexlify(txid[0]), txid[1])) # create outputs FEE = 50000 assert FEE < total_amt_in_sat - amount, "test broken, not enough funds" cj_script = nsw_wallet.get_new_script(MIXDEPTH + 1, True) change_script = nsw_wallet.get_new_script(MIXDEPTH, True) change_amt = total_amt_in_sat - amount - FEE tx_outs = [ {'script': binascii.hexlify(cj_script), 'value': amount}, {'script': binascii.hexlify(change_script), 'value': change_amt}] tx = btc.deserialize(btc.mktx(tx_ins, tx_outs)) binarize_tx(tx) # import new addresses to bitcoind jm_single().bc_interface.import_addresses( [nsw_wallet.script_to_addr(x) for x in [cj_script, change_script]], jm_single().bc_interface.get_wallet_name(nsw_wallet)) # sign tx scripts = {} for nsw_in_index in o_ins: inp = nsw_ins[nsw_in_index][1] scripts[nsw_in_index] = (inp['script'], inp['value']) nsw_wallet.sign_tx(tx, scripts) scripts = {} for sw_in_index in segwit_ins: inp = sw_ins[sw_in_index][1] scripts[sw_in_index] = (inp['script'], inp['value']) sw_wallet.sign_tx(tx, scripts) print(tx) # push and verify txid = jm_single().bc_interface.pushtx(binascii.hexlify(btc.serialize(tx))) assert txid balances = jm_single().bc_interface.get_received_by_addr( [nsw_wallet.script_to_addr(cj_script), nsw_wallet.script_to_addr(change_script)], None)['data'] assert balances[0]['balance'] == amount assert balances[1]['balance'] == change_amt
def test_start_ygs(setup_ygrunner, num_ygs, wallet_structures, 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) #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 malicious: yg.set_maliciousness(malicious, mtype="tx") clientfactory = JMClientProtocolFactory(yg, proto_type="MAKER") 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, daemon=daemon, rs=rs)
def test_cj(setup_full_coinjoin, num_ygs, wallet_structures, mean_amt, malicious, deterministic): """Starts by setting up wallets for maker and taker bots; then, instantiates a single taker with the final wallet. The remaining wallets are used to set up YieldGenerators (basic form). All the wallets are given coins according to the rules of make_wallets, using the parameters for the values. The final start_reactor call is the only one that actually starts the reactor; the others only set up protocol instances. Inline are custom callbacks for the Taker, and these are basically copies of those in the `sendpayment.py` script for now, but they could be customized later for testing. The Taker's schedule is a single coinjoin, using basically random values, again this could be easily edited or parametrized if we feel like it. """ # Set up some wallets, for the ygs and 1 sp. wallets = make_wallets(num_ygs + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list wallet = wallets[num_ygs]['wallet'] sync_wallet(wallet, fast=True) # grab a dest addr from the wallet destaddr = wallet.get_external_addr(4) coinjoin_amt = 20000000 schedule = [[1, coinjoin_amt, 2, destaddr, 0.0, False]] """ The following two callback functions are as simple as possible modifications of the same in scripts/sendpayment.py """ def filter_orders_callback(orders_fees, cjamount): return True def taker_finished(res, fromtx=False, waittime=0.0, txdetails=None): def final_checks(): sync_wallet(wallet, fast=True) newbal = wallet.get_balance_by_mixdepth()[4] oldbal = wallet.get_balance_by_mixdepth()[1] # These are our check that the coinjoin succeeded assert newbal == coinjoin_amt # TODO: parametrize these; cj fees = 38K (.001 x 20M x 2 makers) # minus 1K tx fee contribution each; 600M is original balance # in mixdepth 1 assert oldbal + newbal + (40000 - 2000) + taker.total_txfee == 600000000 if fromtx == "unconfirmed": #If final entry, stop *here*, don't wait for confirmation if taker.schedule_index + 1 == len(taker.schedule): reactor.stop() final_checks() return if fromtx: # currently this test uses a schedule with only one entry assert False, "taker_finished was called with fromtx=True" reactor.stop() return else: if not res: assert False, "Did not complete successfully, shutting down" # Note that this is required in both conditional branches, # especially in testing, because it's possible to receive the # confirmed callback before the unconfirmed. reactor.stop() final_checks() # twisted logging is required for debugging: startLogging(sys.stdout) taker = Taker(wallet, schedule, order_chooser=random_under_max_order_choose, max_cj_fee=(0.1, 200), callbacks=(filter_orders_callback, None, taker_finished)) clientfactory = JMClientProtocolFactory(taker) nodaemon = jm_single().config.getint("DAEMON", "no_daemon") daemon = True if nodaemon == 1 else False start_reactor(jm_single().config.get("DAEMON", "daemon_host"), jm_single().config.getint("DAEMON", "daemon_port"), clientfactory, daemon=daemon, rs=False) txfee = 1000 cjfee_a = 4200 cjfee_r = '0.001' ordertype = 'swreloffer' minsize = 100000 ygclass = YieldGeneratorBasic # As noted above, this is not currently used but can be in future: if malicious or deterministic: raise NotImplementedError for i in range(num_ygs): cfg = [txfee, cjfee_a, cjfee_r, ordertype, minsize] sync_wallet(wallets[i]["wallet"], fast=True) yg = ygclass(wallets[i]["wallet"], cfg) if malicious: yg.set_maliciousness(malicious, mtype="tx") clientfactory = JMClientProtocolFactory(yg, proto_type="MAKER") nodaemon = jm_single().config.getint("DAEMON", "no_daemon") daemon = True if nodaemon == 1 else False # As noted above, only the final start_reactor() call will # actually start it! 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, daemon=daemon, rs=rs)
def test_start_yg_and_taker_setup(setup_onion_ygrunner): """Set up some wallets, for the ygs and 1 taker. Then start LN and the ygs in the background, then fire a startup of a wallet daemon for the taker who then makes a coinjoin payment. """ if jm_single().config.get("POLICY", "native") == "true": walletclass = SegwitWallet else: # TODO add Legacy walletclass = SegwitLegacyWallet start_bot_num, end_bot_num = [int(x) for x in jm_single().config.get( "MESSAGING:onion", "regtest_count").split(",")] num_ygs = end_bot_num - start_bot_num # specify the number of wallets and bots of each type: wallet_services = make_wallets(num_ygs + 1, wallet_structures=[[1, 3, 0, 0, 0]] * (num_ygs + 1), mean_amt=2.0, walletclass=walletclass) #the sendpayment bot uses the last wallet in the list wallet_service = wallet_services[end_bot_num - 1]['wallet'] jmprint("\n\nTaker wallet seed : " + wallet_services[end_bot_num - 1]['seed']) # for manual audit if necessary, show the maker's wallet seeds # also (note this audit should be automated in future) jmprint("\n\nMaker wallet seeds: ") for i in range(start_bot_num, end_bot_num): jmprint("Maker seed: " + wallet_services[i - 1]['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_contribution", "txfee_contribution_factor", "cjfee_a", "cjfee_r", "cjfee_factor", "minsize", "size_factor"]: options[x] = jm_single().config.get("YIELDGENERATOR", x) ordertype = options["ordertype"] txfee_contribution = int(options["txfee_contribution"]) txfee_contribution_factor = float(options["txfee_contribution_factor"]) cjfee_factor = float(options["cjfee_factor"]) size_factor = float(options["size_factor"]) if ordertype == 'reloffer': cjfee_r = options["cjfee_r"] # minimum size is such that you always net profit at least 20% #of the miner fee minsize = max(int(1.2 * txfee_contribution / float(cjfee_r)), int(options["minsize"])) cjfee_a = None elif ordertype == 'absoffer': cjfee_a = int(options["cjfee_a"]) minsize = int(options["minsize"]) cjfee_r = None else: 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 for i in range(start_bot_num, end_bot_num): cfg = [txfee_contribution, cjfee_a, cjfee_r, ordertype, minsize, txfee_contribution_factor, cjfee_factor, size_factor] wallet_service_yg = wallet_services[i - 1]["wallet"] wallet_service_yg.startService() yg = ygclass(wallet_service_yg, cfg) clientfactory = RegtestJMClientProtocolFactory(yg, proto_type="MAKER") # This ensures that the right rpc/port config is passed into the daemon, # for this specific bot: clientfactory.i = i # This ensures that this bot knows which other bots are directory nodes: clientfactory.set_directory_nodes(directory_node_indices) nodaemon = jm_single().config.getint("DAEMON", "no_daemon") daemon = bool(nodaemon) #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, daemon=daemon, rs=False) reactor.callLater(1.0, start_test_taker, wallet_services[end_bot_num - 1]['wallet'], end_bot_num, num_ygs) reactor.run()