def setUp(self): self.n = 2 #create n new random wallets. #put coins into the first mixdepth for each #The amount is 1btc + 300,000 satoshis, to account #for a 0.2% fee for 1 counterparty + a large tx fee. #(but not large enough to handle the bad wallet) wallet_structures = [[1, 0, 0, 0, 0]] * (self.n) self.wallets = make_wallets(self.n, wallet_structures=wallet_structures, mean_amt=1.00300000) #the sender is wallet (n), i.e. index wallets[n-1] #we need a counterparty with a huge set of utxos. bad_wallet_struct = [[1, 0, 0, 0, 0]] self.wallets.update( make_wallets(1, wallet_structures=bad_wallet_struct, mean_amt=0.01, start_index=2)) #having created the bad wallet, add lots of utxos to #the same mixdepth print 'creating a crazy amount of utxos in one wallet...' r_addr = self.wallets[2]['wallet'].get_external_addr(0) for i in range(60): jm_single().bc_interface.grab_coins(r_addr, 0.02) time.sleep(1) #for sweep, create a yg wallet with enough for the mix #of the bad wallet above (acting as sender) self.wallets.update( make_wallets(1, wallet_structures=[[1, 0, 0, 0, 0]], mean_amt=3, start_index=3))
def setUp(self): self.n = 2 #create n new random wallets. #put coins into the first mixdepth for each #The amount is 1btc + 300,000 satoshis, to account #for a 0.2% fee for 1 counterparty + a large tx fee. #(but not large enough to handle the bad wallet) wallet_structures = [[1, 0, 0, 0, 0]] * (self.n) self.wallets = make_wallets(self.n, wallet_structures=wallet_structures, mean_amt=1.00300000) #the sender is wallet (n), i.e. index wallets[n-1] #we need a counterparty with a huge set of utxos. bad_wallet_struct = [[1, 0, 0, 0, 0]] self.wallets.update(make_wallets(1, wallet_structures=bad_wallet_struct, mean_amt=0.01, start_index=2)) #having created the bad wallet, add lots of utxos to #the same mixdepth print 'creating a crazy amount of utxos in one wallet...' r_addr = self.wallets[2]['wallet'].get_external_addr(0) for i in range(60): jm_single().bc_interface.grab_coins(r_addr, 0.02) time.sleep(1) #for sweep, create a yg wallet with enough for the mix #of the bad wallet above (acting as sender) self.wallets.update(make_wallets(1, wallet_structures=[[1, 0, 0, 0, 0]], mean_amt=3, start_index=3))
def test_simple_payjoin(monkeypatch, tmpdir, setup_cj, wallet_cls, wallet_structures, mean_amt): def raise_exit(i): raise Exception("sys.exit called") monkeypatch.setattr(sys, 'exit', raise_exit) wallet_services = [] wallet_services.append(make_wallets_to_list(make_wallets( 1, wallet_structures=[wallet_structures[0]], mean_amt=mean_amt, wallet_cls=wallet_cls[0]))[0]) wallet_services.append(make_wallets_to_list(make_wallets( 1, wallet_structures=[wallet_structures[1]], mean_amt=mean_amt, wallet_cls=wallet_cls[1]))[0]) jm_single().bc_interface.tickchain() sync_wallets(wallet_services) # For accounting purposes, record the balances # at the start. msb = getbals(wallet_services[0], 0) tsb = getbals(wallet_services[1], 0) cj_amount = int(1.1 * 10**8) maker = P2EPMaker(wallet_services[0], 0, cj_amount) destaddr = maker.destination_addr monkeypatch.setattr(maker, 'user_check', dummy_user_check) # TODO use this to sanity check behaviour # in presence of the rest of the joinmarket orderbook. orderbook = create_orderbook([maker]) assert len(orderbook) == 1 # mixdepth, amount, counterparties, dest_addr, waittime; # in payjoin we only pay attention to the first two entries. schedule = [(0, cj_amount, 1, destaddr, 0)] taker = create_taker(wallet_services[-1], schedule, monkeypatch) monkeypatch.setattr(taker, 'user_check', dummy_user_check) init_data = taker.initialize(orderbook) # the P2EPTaker.initialize() returns: # (True, self.cjamount, "p2ep", "p2ep", {self.p2ep_receiver_nick:{}}) assert init_data[0], "taker.initialize error" active_orders = init_data[4] assert len(active_orders.keys()) == 1 response = taker.receive_utxos(list(active_orders.keys())) assert response[0], "taker receive_utxos error" # test for validity of signed fallback transaction; requires 0.17; # note that we count this as an implicit test of fallback mode. res = jm_single().bc_interface.rpc('testmempoolaccept', [[response[2]]]) assert res[0]["allowed"], "Proposed transaction was rejected from mempool." maker_response = maker.on_tx_received("faketaker", response[2]) if not maker_response[0]: print("maker on_tx_received failed, reason: ", maker_response[1]) assert False taker_response = taker.on_tx_received("fakemaker", maker_response[2]) if not taker_response[1] == "OK": print("Failure in taker on_tx_received, reason: ", taker_response[1]) assert False # Although the above OK is proof that a transaction went through, # it doesn't prove it was a good transaction! Here do balance checks: assert final_checks(wallet_services, cj_amount, taker.total_txfee, tsb, msb)
def do_test_payment(self, wc1, wc2, amt=1.1): wallet_structures = [self.wallet_structure] * 2 wallet_cls = (wc1, wc2) self.wallet_services = [] self.wallet_services.append(make_wallets_to_list(make_wallets( 1, wallet_structures=[wallet_structures[0]], mean_amt=self.mean_amt, wallet_cls=wallet_cls[0]))[0]) self.wallet_services.append(make_wallets_to_list(make_wallets( 1, wallet_structures=[wallet_structures[1]], mean_amt=self.mean_amt, wallet_cls=wallet_cls[1]))[0]) jm_single().bc_interface.tickchain() sync_wallets(self.wallet_services) # For accounting purposes, record the balances # at the start. self.rsb = getbals(self.wallet_services[0], 0) self.ssb = getbals(self.wallet_services[1], 0) self.cj_amount = int(amt * 10**8) def cbStopListening(): return self.port.stopListening() b78rm = JMBIP78ReceiverManager(self.wallet_services[0], 0, self.cj_amount, 47083) resource = DummyBIP78ReceiverResource(jmprint, cbStopListening, b78rm) self.site = Site(resource) self.site.displayTracebacks = False # NB The connectivity aspects of the onion-based BIP78 setup # are time heavy. This server is TCP only. self.port = reactor.listenTCP(47083, self.site) self.addCleanup(cbStopListening) # setup of spender bip78_btc_amount = amount_to_btc(amount_to_sat(self.cj_amount)) bip78_uri = encode_bip21_uri(str(b78rm.receiving_address), {"amount": bip78_btc_amount, "pj": b"http://127.0.0.1:47083"}, safe=":/") self.manager = parse_payjoin_setup(bip78_uri, self.wallet_services[1], 0) self.manager.mode = "testing" success, msg = make_payment_psbt(self.manager) assert success, msg params = make_payjoin_request_params(self.manager) # avoiding backend daemon (testing only jmclient code here), # we send the http request manually: serv = b"http://127.0.0.1:47083" agent = get_nontor_agent() body = BytesProducer(self.manager.initial_psbt.to_base64().encode("utf-8")) url_parts = list(wrapped_urlparse(serv)) url_parts[4] = urlencode(params).encode("utf-8") destination_url = urlparse.urlunparse(url_parts) d = agent.request(b"POST", destination_url, Headers({"Content-Type": ["text/plain"]}), bodyProducer=body) d.addCallback(bip78_receiver_response, self.manager) return d
def test_coinjoin_mixed_maker_addresses(monkeypatch, tmpdir, setup_cj, wallet_cls, wallet_cls_sec): set_commitment_file(str(tmpdir.join('commitments.json'))) MAKER_NUM = 2 wallet_services = make_wallets_to_list( make_wallets(MAKER_NUM + 1, wallet_structures=[[1, 0, 0, 0, 0]] * MAKER_NUM + [[3, 0, 0, 0, 0]], mean_amt=1, wallet_cls=wallet_cls)) wallet_services_sec = make_wallets_to_list( make_wallets(MAKER_NUM, wallet_structures=[[1, 0, 0, 0, 0]] * MAKER_NUM, mean_amt=1, wallet_cls=wallet_cls_sec)) for i in range(MAKER_NUM): wif = wallet_services_sec[i].get_wif(0, False, 0) wallet_services[i].wallet.import_private_key( 0, wif, key_type=wallet_services_sec[i].wallet.TYPE) jm_single().bc_interface.tickchain() jm_single().bc_interface.tickchain() sync_wallets(wallet_services, fast=False) makers = [ YieldGeneratorBasic(wallet_services[i], [0, 2000, 0, 'swabsoffer', 10**7]) for i in range(MAKER_NUM) ] orderbook = create_orderbook(makers) cj_amount = int(1.1 * 10**8) # mixdepth, amount, counterparties, dest_addr, waittime, rounding schedule = [(0, cj_amount, MAKER_NUM, 'INTERNAL', 0, NO_ROUNDING)] taker = create_taker(wallet_services[-1], schedule, monkeypatch) active_orders, maker_data = init_coinjoin(taker, makers, orderbook, cj_amount) txdata = taker.receive_utxos(maker_data) assert txdata[0], "taker.receive_utxos error" taker_final_result = do_tx_signing(taker, makers, active_orders, txdata) assert taker_final_result is not False assert taker.on_finished_callback.status is not False
def test_direct_send(setup_regtest, wallet_structures, mean_amt, mixdepth, amount, valid): log = get_log() wallets = make_wallets(1, wallet_structures=wallet_structures, mean_amt=mean_amt) wallet = wallets[0]['wallet'] sync_wallet(wallet) destaddr = btc.privkey_to_address( os.urandom(32), #TODO deterministic-ise from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg if not valid: with pytest.raises(Exception) as e_info: sendpayment.direct_send(wallet, amount, mixdepth, destaddr, answeryes=True) else: sendpayment.direct_send(wallet, amount, mixdepth, destaddr, answeryes=True)
def create_wallet_for_sync(wallet_file, password, wallet_structure, a): #Prepare a testnet wallet file for this wallet password_key = bitcoin.bin_dbl_sha256(password) #We need a distinct seed for each run so as not to step over each other; #make it through a deterministic hash seedh = bitcoin.sha256("".join([str(x) for x in a]))[:32] encrypted_seed = encryptData(password_key, seedh.decode('hex')) timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S") walletfilejson = { 'creator': 'joinmarket project', 'creation_time': timestamp, 'encrypted_seed': encrypted_seed.encode('hex'), 'network': get_network() } walletfile = json.dumps(walletfilejson) if not os.path.exists('wallets'): os.makedirs('wallets') with open(os.path.join('wallets', wallet_file), "wb") as f: f.write(walletfile) #The call to Wallet() in make_wallets should now find the file #and read from it: return make_wallets(1, [wallet_structure], fixed_seeds=[wallet_file], test_wallet=True, passwords=[password])[0]['wallet']
def setUp(self): load_test_config() self.clean_out_wallet_files() jm_single().bc_interface.tick_forward_chain_interval = 5 jm_single().bc_interface.simulate_blocks() # a client connnection object which is often but not always # instantiated: self.client_connector = None # start the daemon; note we are using tcp connections # to avoid storing certs in the test env. # TODO change that. self.daemon = JMWalletDaemonT(self.dport, self.wss_port, tls=False) self.daemon.auth_disabled = False # because we sync and start the wallet service manually here # (and don't use wallet files yet), we won't have set a wallet name, # so we set it here: self.daemon.wallet_name = self.get_wallet_file_name(1) r, s = self.daemon.startService() self.listener_rpc = r self.listener_ws = s wallet_structures = [self.wallet_structure] * 2 # note: to test fidelity bond wallets we should add the argument # `wallet_cls=SegwitWalletFidelityBonds` here, but it slows the # test down from 9 seconds to 1 minute 40s, which is too slow # to be acceptable. TODO: add a test with FB by speeding up # the sync for test, by some means or other. self.daemon.services["wallet"] = make_wallets_to_list( make_wallets(1, wallet_structures=[wallet_structures[0]], mean_amt=self.mean_amt, wallet_cls=SegwitWalletFidelityBonds))[0] jm_single().bc_interface.tickchain() sync_wallets([self.daemon.services["wallet"]]) # dummy tx example to force a notification event: self.test_tx = CTransaction.deserialize(hextobin(test_tx_hex_1))
def test_spend_p2wpkh(setup_tx_creation): #make 3 p2wpkh outputs from 3 privs privs = [struct.pack(b'B', x) * 32 + b'\x01' for x in range(1, 4)] pubs = [bitcoin.privkey_to_pubkey(priv) for priv in privs] scriptPubKeys = [bitcoin.pubkey_to_p2wpkh_script(pub) for pub in pubs] addresses = [str(bitcoin.CCoinAddress.from_scriptPubKey( spk)) for spk in scriptPubKeys] #pay into it wallet_service = make_wallets(1, [[3, 0, 0, 0, 0]], 3)[0]['wallet'] wallet_service.sync_wallet(fast=True) amount = 35000000 p2wpkh_ins = [] for i, addr in enumerate(addresses): ins_full = wallet_service.select_utxos(0, amount) txid = make_sign_and_push(ins_full, wallet_service, amount, output_addr=addr) assert txid p2wpkh_ins.append((txid, 0)) txhex = jm_single().bc_interface.get_transaction(txid) #wait for mining jm_single().bc_interface.tick_forward_chain(1) #random output address output_addr = wallet_service.get_internal_addr(1) amount2 = amount*3 - 50000 outs = [{'value': amount2, 'address': output_addr}] tx = bitcoin.mktx(p2wpkh_ins, outs) for i, priv in enumerate(privs): # sign each of 3 inputs; note that bitcoin.sign # automatically validates each signature it creates. sig, msg = bitcoin.sign(tx, i, priv, amount=amount, native="p2wpkh") if not sig: assert False, msg txid = jm_single().bc_interface.pushtx(tx.serialize()) assert txid
def test_verify_tx_input(setup_tx_creation): priv = b"\xaa" * 32 + b"\x01" pub = bitcoin.privkey_to_pubkey(priv) script = bitcoin.pubkey_to_p2sh_p2wpkh_script(pub) addr = str(bitcoin.CCoinAddress.from_scriptPubKey(script)) wallet_service = make_wallets(1, [[2, 0, 0, 0, 0]], 1)[0]['wallet'] wallet_service.sync_wallet(fast=True) insfull = wallet_service.select_utxos(0, 110000000) outs = [{"address": addr, "value": 1000000}] ins = list(insfull.keys()) tx = bitcoin.mktx(ins, outs) scripts = {0: (insfull[ins[0]]["script"], bitcoin.coins_to_satoshi(1))} success, msg = wallet_service.sign_tx(tx, scripts) assert success, msg # testing Joinmarket's ability to verify transaction inputs # of others: pretend we don't have a wallet owning the transaction, # and instead verify an input using the (sig, pub, scriptCode) data # that is sent by counterparties: cScrWit = tx.wit.vtxinwit[0].scriptWitness sig = cScrWit.stack[0] pub = cScrWit.stack[1] scriptSig = tx.vin[0].scriptSig tx2 = bitcoin.mktx(ins, outs) res = bitcoin.verify_tx_input(tx2, 0, scriptSig, bitcoin.pubkey_to_p2wpkh_script(pub), amount=bitcoin.coins_to_satoshi(1), witness=bitcoin.CScriptWitness([sig, pub])) assert res
def test_utxo_selection(setup_wallets, nw, wallet_structures, mean_amt, sdev_amt, amount): """Check that all the utxo selection algorithms work with a random variety of wallet contents. """ wallets = make_wallets(nw, wallet_structures, mean_amt, sdev_amt) for w in wallets.values(): jm_single().bc_interface.wallet_synced = False sync_wallet(w['wallet']) for k, w in enumerate(wallets.values()): for algo in [select_gradual, select_greedy, select_greediest, None]: wallet = w['wallet'] if algo: wallet.utxo_selector = algo if k == 0: with pytest.raises(Exception) as e_info: selected = wallet.select_utxos(1, amount) else: selected = wallet.select_utxos(1, amount) algostr = algo.__name__ if algo else "default" print 'selected these for algo ' + algostr + ':' print selected #basic check: #does this algo actually generate sufficient coins? total_selected = sum([x['value'] for x in selected.values()]) assert total_selected > amount, "Selection algo: " + algo + \ "failed to select sufficient coins, total: " + \ str(total_selected) + ", should be: " + str(amount)
def test_wallet_sync_from_scratch(setup_wallets, wallet_structure, wallet_file, password, ic): """Simulate a scenario in which we use a new bitcoind, thusly: generate a new wallet and simply pretend that it has an existing index_cache. This will force import of all addresses up to the index_cache values. """ setup_import(mainnet=False) wallet = make_wallets(1,[wallet_structure], fixed_seeds=[wallet_file], test_wallet=True, passwords=[password])[0]['wallet'] sync_count = 0 jm_single().bc_interface.wallet_synced = False wallet.index_cache = ic while not jm_single().bc_interface.wallet_synced: wallet.index = [] for i in range(5): wallet.index.append([0, 0]) jm_single().bc_interface.sync_wallet(wallet) sync_count += 1 #avoid infinite loop assert sync_count < 10 log.debug("Tried " + str(sync_count) + " times") #after #586 we expect to ALWAYS succeed within 2 rounds assert sync_count == 2 #for each external branch, the new index may be higher than #the original index_cache if there was a higher used address expected_wallet_index = [] for i, val in enumerate(wallet_structure): if val > wallet.index_cache[i][0]: expected_wallet_index.append([val, wallet.index_cache[i][1]]) else: expected_wallet_index.append([wallet.index_cache[i][0], wallet.index_cache[i][1]]) assert wallet.index == expected_wallet_index
def test_junk_messages(setup_messaging): #start a yg bot just to receive messages wallets = make_wallets(1, wallet_structures=[[1, 0, 0, 0, 0]], mean_amt=1) wallet = wallets[0]['wallet'] ygp = local_command([python_cmd, yg_cmd,\ str(wallets[0]['seed'])], bg=True) #time.sleep(90) #start a raw IRCMessageChannel instance in a thread; #then call send_* on it with various errant messages mc = DummyMC("irc_ping_test") mc.register_orderbookwatch_callbacks(on_order_seen=on_order_seen) mc.register_taker_callbacks(on_pubkey=on_pubkey) RawIRCThread(mc).start() time.sleep(1) mc._IRCMessageChannel__pubmsg("!orderbook") time.sleep(1) mc._IRCMessageChannel__pubmsg("!orderbook!orderbook") time.sleep(1) mc._IRCMessageChannel__pubmsg("junk and crap" * 20) time.sleep(5) #try: with pytest.raises(CJPeerError) as e_info: mc.send_error(yg_name, "fly you fools!") #except CJPeerError: # print "CJPeerError raised" # pass time.sleep(5) mc.shutdown() ygp.send_signal(signal.SIGINT) ygp.wait()
def test_start_ygs(setup_ygrunner, num_ygs, wallet_structures, mean_amt): """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) print wallet.unspent yigen_procs = [] for i in range(num_ygs): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) try: while True: time.sleep(20) print 'waiting' finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait()
def test_utxo_selection(setup_wallets, nw, wallet_structures, mean_amt, sdev_amt, amount): """Check that all the utxo selection algorithms work with a random variety of wallet contents. """ wallets = make_wallets(nw, wallet_structures, mean_amt, sdev_amt) for w in wallets.values(): jm_single().bc_interface.wallet_synced = False sync_wallet(w['wallet']) for k, w in enumerate(wallets.values()): for algo in [select_gradual, select_greedy, select_greediest, None]: wallet = w['wallet'] if algo: wallet.utxo_selector = algo if k == 0: with pytest.raises(Exception) as e_info: selected = wallet.select_utxos(1, amount) else: selected = wallet.select_utxos(1, amount) algostr = algo.__name__ if algo else "default" print 'selected these for algo ' + algostr + ':' print selected #basic check: #does this algo actually generate sufficient coins? total_selected = sum([x['value'] for x in selected.values()]) assert total_selected > amount, "Selection algo: " + str(algo) + \ "failed to select sufficient coins, total: " + \ str(total_selected) + ", should be: " + str(amount)
def test_spend_p2sh_utxos(setup_tx_creation): #make a multisig address from 3 privs privs = [chr(x) * 32 + '\x01' for x in range(1, 4)] pubs = [btc.privkey_to_pubkey(binascii.hexlify(priv)) for priv in privs] script = btc.mk_multisig_script(pubs, 2) msig_addr = btc.scriptaddr(script, magicbyte=196) #pay into it wallet = make_wallets(1, [[2, 0, 0, 0, 1]], 3)[0]['wallet'] jm_single().bc_interface.sync_wallet(wallet) amount = 350000000 ins_full = wallet.select_utxos(0, amount) txid = make_sign_and_push(ins_full, wallet, amount, output_addr=msig_addr) assert txid #wait for mining time.sleep(4) #spend out; the input can be constructed from the txid of previous msig_in = txid + ":0" ins = [msig_in] #random output address and change addr output_addr = wallet.get_new_addr(1, 1) amount2 = amount - 50000 outs = [{'value': amount2, 'address': output_addr}] tx = btc.mktx(ins, outs) sigs = [] for priv in privs[:2]: sigs.append(btc.multisign(tx, 0, script, binascii.hexlify(priv))) tx = btc.apply_multisignatures(tx, 0, script, sigs) txid = jm_single().bc_interface.pushtx(tx) assert txid
def test_create_sighash_txs(setup_tx_creation): #non-standard hash codes: for sighash in [ bitcoin.SIGHASH_ANYONECANPAY + bitcoin.SIGHASH_SINGLE, bitcoin.SIGHASH_NONE, bitcoin.SIGHASH_SINGLE ]: wallet = make_wallets(1, [[2, 0, 0, 0, 1]], 3)[0]['wallet'] sync_wallet(wallet) amount = 350000000 ins_full = wallet.select_utxos(0, amount) print "using hashcode: " + str(sighash) txid = make_sign_and_push(ins_full, wallet, amount, hashcode=sighash) assert txid #Create an invalid sighash single (too many inputs) extra = wallet.select_utxos(4, 100000000) #just a few more inputs ins_full.update(extra) with pytest.raises(Exception) as e_info: txid = make_sign_and_push(ins_full, wallet, amount, hashcode=bitcoin.SIGHASH_SINGLE) #trigger insufficient funds with pytest.raises(Exception) as e_info: fake_utxos = wallet.select_utxos(4, 1000000000)
def make_tx_add_notify(): wallet_dict = make_wallets(1, [[1, 0, 0, 0, 0]], mean_amt=4, sdev_amt=0)[0] amount = 250000000 txfee = 10000 wallet = wallet_dict['wallet'] sync_wallet(wallet) inputs = wallet.select_utxos(0, amount) ins = inputs.keys() input_value = sum([i['value'] for i in inputs.values()]) output_addr = wallet.get_new_addr(1, 0) change_addr = wallet.get_new_addr(0, 1) outs = [{ 'value': amount, 'address': output_addr }, { 'value': input_value - amount - txfee, 'address': change_addr }] tx = btc.mktx(ins, outs) de_tx = btc.deserialize(tx) for index, ins in enumerate(de_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) addr = inputs[utxo]['address'] priv = wallet.get_key_from_addr(addr) tx = btc.sign(tx, index, priv) unconfirm_called[0] = confirm_called[0] = False timeout_unconfirm_called[0] = timeout_confirm_called[0] = False jm_single().bc_interface.add_tx_notify(btc.deserialize(tx), unconfirm_callback, confirm_callback, output_addr, timeout_callback) return tx
def runcase(alice_class, carol_class, fail_alice_state=None, fail_carol_state=None): options_server = Options() wallets = make_wallets(num_alices + 1, wallet_structures=wallet_structures, mean_amt=funding_amount) args_server = ["dummy"] test_data_server = (wallets[num_alices]['seed'], args_server, options_server, False, None, carol_class, None, fail_carol_state) carol_bbmb = main_cs(test_data_server) options_alice = Options() options_alice.serve = False alices = [] for i in range(num_alices): args_alice = ["dummy", amounts[i]] if dest_addr: args_alice.append(dest_addr) test_data_alice = (wallets[i]['seed'], args_alice, options_alice, False, alice_class, None, fail_alice_state, None) alices.append(main_cs(test_data_alice)) l = task.LoopingCall(miner) reactor.callWhenRunning(start_mining, l) reactor.run() return (alices, carol_bbmb, wallets[num_alices]['wallet'])
def test_simple_coinjoin(monkeypatch, tmpdir, setup_cj, wallet_cls): def raise_exit(i): raise Exception("sys.exit called") monkeypatch.setattr(sys, 'exit', raise_exit) set_commitment_file(str(tmpdir.join('commitments.json'))) MAKER_NUM = 3 wallets = make_wallets_to_list(make_wallets( MAKER_NUM + 1, wallet_structures=[[4, 0, 0, 0, 0]] * (MAKER_NUM + 1), mean_amt=1, wallet_cls=wallet_cls)) jm_single().bc_interface.tickchain() sync_wallets(wallets) makers = [YieldGeneratorBasic( wallets[i], [0, 2000, 0, 'swabsoffer', 10**7]) for i in range(MAKER_NUM)] orderbook = create_orderbook(makers) assert len(orderbook) == MAKER_NUM cj_amount = int(1.1 * 10**8) # mixdepth, amount, counterparties, dest_addr, waittime schedule = [(0, cj_amount, MAKER_NUM, 'INTERNAL', 0)] taker = create_taker(wallets[-1], schedule, monkeypatch) active_orders, maker_data = init_coinjoin(taker, makers, orderbook, cj_amount) txdata = taker.receive_utxos(maker_data) assert txdata[0], "taker.receive_utxos error" taker_final_result = do_tx_signing(taker, makers, active_orders, txdata) assert taker_final_result is not False assert taker.on_finished_callback.status is not False
def test_junk_messages(setup_messaging): #start a yg bot just to receive messages wallets = make_wallets(1, wallet_structures=[[1,0,0,0,0]], mean_amt=1) wallet = wallets[0]['wallet'] ygp = local_command([python_cmd, yg_cmd,\ str(wallets[0]['seed'])], bg=True) #time.sleep(90) #start a raw IRCMessageChannel instance in a thread; #then call send_* on it with various errant messages mc = DummyMC("irc_ping_test") mc.register_orderbookwatch_callbacks(on_order_seen=on_order_seen) mc.register_taker_callbacks(on_pubkey=on_pubkey) RawIRCThread(mc).start() time.sleep(1) mc._IRCMessageChannel__pubmsg("!orderbook") time.sleep(1) mc._IRCMessageChannel__pubmsg("!orderbook!orderbook") time.sleep(1) mc._IRCMessageChannel__pubmsg("junk and crap"*20) time.sleep(5) #try: with pytest.raises(CJPeerError) as e_info: mc.send_error(yg_name, "fly you fools!") #except CJPeerError: # print "CJPeerError raised" # pass time.sleep(5) mc.shutdown() ygp.send_signal(signal.SIGINT) ygp.wait()
def make_tx_add_notify(): wallet_dict = make_wallets(1, [[1, 0, 0, 0, 0]], mean_amt=4, sdev_amt=0)[0] amount = 250000000 txfee = 10000 wallet = wallet_dict['wallet'] sync_wallet(wallet) inputs = wallet.select_utxos(0, amount) ins = inputs.keys() input_value = sum([i['value'] for i in inputs.values()]) output_addr = wallet.get_new_addr(1, 0) change_addr = wallet.get_new_addr(0, 1) outs = [{'value': amount, 'address': output_addr}, {'value': input_value - amount - txfee, 'address': change_addr}] tx = btc.mktx(ins, outs) de_tx = btc.deserialize(tx) for index, ins in enumerate(de_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) addr = inputs[utxo]['address'] priv = wallet.get_key_from_addr(addr) tx = btc.sign(tx, index, priv) unconfirm_called[0] = confirm_called[0] = False timeout_unconfirm_called[0] = timeout_confirm_called[0] = False jm_single().bc_interface.add_tx_notify( btc.deserialize(tx), unconfirm_callback, confirm_callback, output_addr, timeout_callback) return tx
def test_coinjoin_mixdepth_wrap_taker(monkeypatch, tmpdir, setup_cj): def raise_exit(i): raise Exception("sys.exit called") monkeypatch.setattr(sys, 'exit', raise_exit) set_commitment_file(str(tmpdir.join('commitments.json'))) MAKER_NUM = 3 wallet_services = make_wallets_to_list( make_wallets(MAKER_NUM + 1, wallet_structures=[[4, 0, 0, 0, 0]] * MAKER_NUM + [[0, 0, 0, 0, 3]], mean_amt=1)) for wallet_service in wallet_services: assert wallet_service.max_mixdepth == 4 jm_single().bc_interface.tickchain() jm_single().bc_interface.tickchain() sync_wallets(wallet_services) cj_fee = 2000 makers = [ YieldGeneratorBasic( wallet_services[i], [0, cj_fee, 0, absoffer_type_map[SegwitWallet], 10**7]) for i in range(MAKER_NUM) ] create_orders(makers) orderbook = create_orderbook(makers) assert len(orderbook) == MAKER_NUM cj_amount = int(1.1 * 10**8) # mixdepth, amount, counterparties, dest_addr, waittime, rounding schedule = [(4, cj_amount, MAKER_NUM, 'INTERNAL', 0, NO_ROUNDING)] taker = create_taker(wallet_services[-1], schedule, monkeypatch) active_orders, maker_data = init_coinjoin(taker, makers, orderbook, cj_amount) txdata = taker.receive_utxos(maker_data) assert txdata[0], "taker.receive_utxos error" taker_final_result = do_tx_signing(taker, makers, active_orders, txdata) assert taker_final_result is not False tx = btc.CMutableTransaction.deserialize(hextobin(txdata[2])) wallet_service = wallet_services[-1] # TODO change for new tx monitoring: wallet_service.remove_old_utxos(tx) wallet_service.add_new_utxos(tx) balances = wallet_service.get_balance_by_mixdepth() assert balances[0] == cj_amount # <= because of tx fee assert balances[4] <= 3 * 10**8 - cj_amount - (cj_fee * MAKER_NUM)
def test_donation_address(setup_donations, tx_type, tx_id, tx_hex, address): wallets = make_wallets(1, wallet_structures=[[1,1,1,0,0]], mean_amt=0.5) wallet = wallets[0]['wallet'] priv, addr = donation_address(tx_hex, wallet) print addr #just a check that it doesn't throw sign_donation_tx(tx_hex, 0, priv)
def setUp(self): self.n = 2 #create n+1 new random wallets. #put 10 coins into the first receive address #to allow that bot to start. wallet_structures = [[1, 0, 0, 0, 0]] * 3 self.wallets = commontest.make_wallets( 3, wallet_structures=wallet_structures, mean_amt=10)
def setUp(self): self.n = 2 #create n+1 new random wallets. #put 10 coins into the first receive address #to allow that bot to start. wallet_structures = [[1,0,0,0,0]]*3 self.wallets = commontest.make_wallets(3, wallet_structures=wallet_structures, mean_amt=10)
def setUp(self): #create 2 new random wallets. #put 10 coins into the first receive address #to allow that bot to start. self.wallets = make_wallets( 2, wallet_structures=[[1, 0, 0, 0, 0], [1, 0, 0, 0, 0]], mean_amt=10)
def setUp(self): #create 2 new random wallets. #put 10 coins into the first receive address #to allow that bot to start. self.wallets = commontest.make_wallets( 2, wallet_structures=[[1, 0, 0, 0, 0], [1, 0, 0, 0, 0]], mean_amt=10)
def test_coinjoin_mixdepth_wrap_maker(monkeypatch, tmpdir, setup_cj): def raise_exit(i): raise Exception("sys.exit called") monkeypatch.setattr(sys, 'exit', raise_exit) set_commitment_file(str(tmpdir.join('commitments.json'))) MAKER_NUM = 2 wallet_services = make_wallets_to_list( make_wallets(MAKER_NUM + 1, wallet_structures=[[0, 0, 0, 0, 4]] * MAKER_NUM + [[3, 0, 0, 0, 0]], mean_amt=1)) for wallet_service in wallet_services: assert wallet_service.max_mixdepth == 4 jm_single().bc_interface.tickchain() jm_single().bc_interface.tickchain() sync_wallets(wallet_services) cj_fee = 2000 makers = [ YieldGeneratorBasic(wallet_services[i], [0, cj_fee, 0, 'swabsoffer', 10**7]) for i in range(MAKER_NUM) ] orderbook = create_orderbook(makers) assert len(orderbook) == MAKER_NUM cj_amount = int(1.1 * 10**8) # mixdepth, amount, counterparties, dest_addr, waittime, rounding schedule = [(0, cj_amount, MAKER_NUM, 'INTERNAL', 0, NO_ROUNDING)] taker = create_taker(wallet_services[-1], schedule, monkeypatch) active_orders, maker_data = init_coinjoin(taker, makers, orderbook, cj_amount) txdata = taker.receive_utxos(maker_data) assert txdata[0], "taker.receive_utxos error" taker_final_result = do_tx_signing(taker, makers, active_orders, txdata) assert taker_final_result is not False tx = btc.deserialize(txdata[2]) binarize_tx(tx) for i in range(MAKER_NUM): wallet_service = wallet_services[i] # TODO as above re: monitoring wallet_service.remove_old_utxos_(tx) wallet_service.add_new_utxos_(tx, b'\x00' * 32) # fake txid balances = wallet_service.get_balance_by_mixdepth() assert balances[0] == cj_amount assert balances[4] == 4 * 10**8 - cj_amount + cj_fee
def test_donation_address(setup_donations, tx_type, tx_id, tx_hex, address): wallets = make_wallets(1, wallet_structures=[[1, 1, 1, 0, 0]], mean_amt=0.5) wallet = wallets[0]['wallet'] priv, addr = donation_address(tx_hex, wallet) print addr #just a check that it doesn't throw sign_donation_tx(tx_hex, 0, priv)
def test_spend_p2wsh(setup_tx_creation): #make 2 x 2 of 2multisig outputs; will need 4 privs privs = [struct.pack(b'B', x) * 32 + b'\x01' for x in range(1, 5)] privs = [binascii.hexlify(priv).decode('ascii') for priv in privs] pubs = [bitcoin.privkey_to_pubkey(priv) for priv in privs] redeemScripts = [ bitcoin.mk_multisig_script(pubs[i:i + 2], 2) for i in [0, 2] ] scriptPubKeys = [ bitcoin.pubkeys_to_p2wsh_script(pubs[i:i + 2]) for i in [0, 2] ] addresses = [ bitcoin.pubkeys_to_p2wsh_address(pubs[i:i + 2]) for i in [0, 2] ] #pay into it wallet_service = make_wallets(1, [[3, 0, 0, 0, 0]], 3)[0]['wallet'] wallet_service.sync_wallet(fast=True) amount = 35000000 p2wsh_ins = [] for addr in addresses: ins_full = wallet_service.select_utxos(0, amount) txid = make_sign_and_push(ins_full, wallet_service, amount, output_addr=addr) assert txid p2wsh_ins.append(txid + ":0") #wait for mining time.sleep(1) #random output address and change addr output_addr = wallet_service.get_internal_addr(1) amount2 = amount * 2 - 50000 outs = [{'value': amount2, 'address': output_addr}] tx = bitcoin.mktx(p2wsh_ins, outs) sigs = [] for i in range(2): sigs = [] for priv in privs[i * 2:i * 2 + 2]: # sign input j with each of 2 keys sig = bitcoin.multisign(tx, i, redeemScripts[i], priv, amount=amount) sigs.append(sig) # check that verify_tx_input correctly validates; assert bitcoin.verify_tx_input(tx, i, scriptPubKeys[i], sig, bitcoin.privkey_to_pubkey(priv), scriptCode=redeemScripts[i], amount=amount) tx = bitcoin.apply_p2wsh_multisignatures(tx, i, redeemScripts[i], sigs) txid = jm_single().bc_interface.pushtx(tx) assert txid
def test_spend_p2wpkh(setup_tx_creation): #make 3 p2wpkh outputs from 3 privs privs = [struct.pack(b'B', x) * 32 + b'\x01' for x in range(1, 4)] pubs = [ bitcoin.privkey_to_pubkey(binascii.hexlify(priv).decode('ascii')) for priv in privs ] scriptPubKeys = [bitcoin.pubkey_to_p2wpkh_script(pub) for pub in pubs] addresses = [bitcoin.pubkey_to_p2wpkh_address(pub) for pub in pubs] #pay into it wallet_service = make_wallets(1, [[3, 0, 0, 0, 0]], 3)[0]['wallet'] wallet_service.sync_wallet(fast=True) amount = 35000000 p2wpkh_ins = [] for addr in addresses: ins_full = wallet_service.select_utxos(0, amount) txid = make_sign_and_push(ins_full, wallet_service, amount, output_addr=addr) assert txid p2wpkh_ins.append(txid + ":0") #wait for mining time.sleep(1) #random output address output_addr = wallet_service.get_internal_addr(1) amount2 = amount * 3 - 50000 outs = [{'value': amount2, 'address': output_addr}] tx = bitcoin.mktx(p2wpkh_ins, outs) sigs = [] for i, priv in enumerate(privs): # sign each of 3 inputs tx = bitcoin.p2wpkh_sign(tx, i, binascii.hexlify(priv), amount, native=True) # check that verify_tx_input correctly validates; # to do this, we need to extract the signature and get the scriptCode # of this pubkey scriptCode = bitcoin.pubkey_to_p2pkh_script(pubs[i]) witness = bitcoin.deserialize(tx)['ins'][i]['txinwitness'] assert len(witness) == 2 assert witness[1] == pubs[i] sig = witness[0] assert bitcoin.verify_tx_input(tx, i, scriptPubKeys[i], sig, pubs[i], scriptCode=scriptCode, amount=amount) txid = jm_single().bc_interface.pushtx(tx) assert txid
def test_create_and_sign_psbt_with_legacy(setup_psbt_wallet): """ The purpose of this test is to check that we can create and then partially sign a PSBT where we own one input and the other input is of legacy p2pkh type. """ wallet_service = make_wallets(1, [[1, 0, 0, 0, 0]], 1)[0]['wallet'] wallet_service.sync_wallet(fast=True) utxos = wallet_service.select_utxos(0, bitcoin.coins_to_satoshi(0.5)) assert len(utxos) == 1 # create a legacy address and make a payment into it legacy_addr = bitcoin.CCoinAddress.from_scriptPubKey( bitcoin.pubkey_to_p2pkh_script(bitcoin.privkey_to_pubkey(b"\x01" * 33))) tx = direct_send(wallet_service, bitcoin.coins_to_satoshi(0.3), 0, str(legacy_addr), accept_callback=dummy_accept_callback, info_callback=dummy_info_callback, return_transaction=True) assert tx # this time we will have one utxo worth <~ 0.7 my_utxos = wallet_service.select_utxos(0, bitcoin.coins_to_satoshi(0.5)) assert len(my_utxos) == 1 # find the outpoint for the legacy address we're spending n = -1 for i, t in enumerate(tx.vout): if bitcoin.CCoinAddress.from_scriptPubKey( t.scriptPubKey) == legacy_addr: n = i assert n > -1 utxos = copy.deepcopy(my_utxos) utxos[(tx.GetTxid()[::-1], n)] = { "script": legacy_addr.to_scriptPubKey(), "value": bitcoin.coins_to_satoshi(0.3) } outs = [{ "value": bitcoin.coins_to_satoshi(0.998), "address": wallet_service.get_addr(0, 0, 0) }] tx2 = bitcoin.mktx(list(utxos.keys()), outs) spent_outs = wallet_service.witness_utxos_to_psbt_utxos(my_utxos) spent_outs.append(tx) new_psbt = wallet_service.create_psbt_from_tx(tx2, spent_outs, force_witness_utxo=False) signed_psbt_and_signresult, err = wallet_service.sign_psbt( new_psbt.serialize(), with_sign_result=True) assert err is None signresult, signed_psbt = signed_psbt_and_signresult assert signresult.num_inputs_signed == 1 assert signresult.num_inputs_final == 1 assert not signresult.is_final
def test_junk_messages(setup_messaging): #start a yg bot just to receive messages wallets = make_wallets(1, wallet_structures=[[1, 0, 0, 0, 0]], mean_amt=1) wallet = wallets[0]['wallet'] ygp = local_command([python_cmd, yg_cmd,\ str(wallets[0]['seed'])], bg=True) #time.sleep(90) #start a raw IRCMessageChannel instance in a thread; #then call send_* on it with various errant messages mc = DummyMC("irc_ping_test") mc.register_orderbookwatch_callbacks(on_order_seen=on_order_seen) mc.register_taker_callbacks(on_pubkey=on_pubkey) RawIRCThread(mc).start() time.sleep(1) mc.request_orderbook() time.sleep(1) #now try directly mc.pubmsg("!orderbook") time.sleep(1) #should be ignored; can we check? mc.pubmsg("!orderbook!orderbook") time.sleep(1) #assuming MAX_PRIVMSG_LEN is not something crazy #big like 550, this should fail with pytest.raises(AssertionError) as e_info: mc.pubmsg("junk and crap" * 40) time.sleep(1) #assuming MAX_PRIVMSG_LEN is not something crazy #small like 180, this should succeed mc.pubmsg("junk and crap" * 15) time.sleep(2) #try a long order announcement in public #because we don't want to build a real orderbook, #call the underlying IRC announce function. #TODO: how to test that the sent format was correct? mc._announce_orders(["!abc def gh 0001"] * 30, None) time.sleep(5) #send a fill with an invalid pubkey to the existing yg; #this should trigger a NaclError but should NOT kill it. mc._IRCMessageChannel__privmsg(yg_name, "fill", "0 10000000 abcdef") time.sleep(1) #try: with pytest.raises(CJPeerError) as e_info: mc.send_error(yg_name, "fly you fools!") #except CJPeerError: # print "CJPeerError raised" # pass time.sleep(5) mc.shutdown() ygp.send_signal(signal.SIGINT) ygp.wait()
def test_absurd_fees(setup_tx_creation): """Test triggering of ValueError exception if the transaction fees calculated from the blockchain interface exceed the limit set in the config. """ jm_single().bc_interface.absurd_fees = True #pay into it wallet = make_wallets(1, [[2, 0, 0, 0, 1]], 3)[0]['wallet'] jm_single().bc_interface.sync_wallet(wallet) amount = 350000000 ins_full = wallet.select_utxos(0, amount) with pytest.raises(ValueError) as e_info: txid = make_sign_and_push(ins_full, wallet, amount, estimate_fee=True)
def test_create_sighash_txs(setup_tx_creation): #non-standard hash codes: for sighash in [bitcoin.SIGHASH_ANYONECANPAY + bitcoin.SIGHASH_SINGLE, bitcoin.SIGHASH_NONE, bitcoin.SIGHASH_SINGLE]: wallet_service = make_wallets(1, [[2, 0, 0, 0, 1]], 3)[0]['wallet'] wallet_service.sync_wallet(fast=True) amount = 350000000 ins_full = wallet_service.select_utxos(0, amount) txid = make_sign_and_push(ins_full, wallet_service, amount, hashcode=sighash) assert txid #trigger insufficient funds with pytest.raises(Exception) as e_info: fake_utxos = wallet_service.select_utxos(4, 1000000000)
def test_all_same_priv(setup_tx_creation): #recipient priv = "aa" * 32 + "01" addr = bitcoin.privkey_to_address(priv, magicbyte=get_p2pk_vbyte()) wallet = make_wallets(1, [[1, 0, 0, 0, 0]], 1)[0]['wallet'] #make another utxo on the same address addrinwallet = wallet.get_addr(0, 0, 0) jm_single().bc_interface.grab_coins(addrinwallet, 1) sync_wallet(wallet) insfull = wallet.select_utxos(0, 110000000) outs = [{"address": addr, "value": 1000000}] ins = insfull.keys() tx = bitcoin.mktx(ins, outs) tx = bitcoin.signall(tx, wallet.get_key_from_addr(addrinwallet))
def test_verify_tx_input(setup_tx_creation, signall, mktxlist): priv = "aa" * 32 + "01" addr = bitcoin.privkey_to_address(priv, magicbyte=get_p2pk_vbyte()) wallet = make_wallets(1, [[2, 0, 0, 0, 0]], 1)[0]['wallet'] sync_wallet(wallet) insfull = wallet.select_utxos(0, 110000000) print(insfull) if not mktxlist: outs = [{"address": addr, "value": 1000000}] ins = insfull.keys() tx = bitcoin.mktx(ins, outs) else: out1 = addr + ":1000000" ins0, ins1 = insfull.keys() print("INS0 is: " + str(ins0)) print("INS1 is: " + str(ins1)) tx = bitcoin.mktx(ins0, ins1, out1) desertx = bitcoin.deserialize(tx) print(desertx) if signall: privdict = {} for index, ins in enumerate(desertx['ins']): utxo = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) ad = insfull[utxo]['address'] priv = wallet.get_key_from_addr(ad) privdict[utxo] = priv tx = bitcoin.signall(tx, privdict) else: for index, ins in enumerate(desertx['ins']): utxo = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) ad = insfull[utxo]['address'] priv = wallet.get_key_from_addr(ad) if index % 2: tx = binascii.unhexlify(tx) tx = bitcoin.sign(tx, index, priv) if index % 2: tx = binascii.hexlify(tx) desertx2 = bitcoin.deserialize(tx) print(desertx2) sig, pub = bitcoin.deserialize_script(desertx2['ins'][0]['script']) print(sig, pub) pubscript = bitcoin.address_to_script( bitcoin.pubkey_to_address(pub, magicbyte=get_p2pk_vbyte())) sig = binascii.unhexlify(sig) pub = binascii.unhexlify(pub) sig_good = bitcoin.verify_tx_input(tx, 0, pubscript, sig, pub) assert sig_good
def test_create_p2sh_output_tx(setup_tx_creation, nw, wallet_structures, mean_amt, sdev_amt, amount, pubs, k): wallets = make_wallets(nw, wallet_structures, mean_amt, sdev_amt) for w in wallets.values(): jm_single().bc_interface.sync_wallet(w['wallet']) for k, w in enumerate(wallets.values()): wallet = w['wallet'] ins_full = wallet.select_utxos(0, amount) script = btc.mk_multisig_script(pubs, k) #try the alternative argument passing pubs.append(k) script2 = btc.mk_multisig_script(*pubs) assert script2 == script output_addr = btc.scriptaddr(script, magicbyte=196) txid = make_sign_and_push(ins_full, wallet, amount, output_addr=output_addr) assert txid
def test_create_sighash_txs(setup_tx_creation): #non-standard hash codes: for sighash in [btc.SIGHASH_ANYONECANPAY + btc.SIGHASH_SINGLE, btc.SIGHASH_NONE, btc.SIGHASH_SINGLE]: wallet = make_wallets(1, [[2, 0, 0, 0, 1]], 3)[0]['wallet'] jm_single().bc_interface.sync_wallet(wallet) amount = 350000000 ins_full = wallet.select_utxos(0, amount) print "using hashcode: " + str(sighash) txid = make_sign_and_push(ins_full, wallet, amount, hashcode=sighash) assert txid #Create an invalid sighash single (too many inputs) extra = wallet.select_utxos(4, 100000000) #just a few more inputs ins_full.update(extra) with pytest.raises(Exception) as e_info: txid = make_sign_and_push(ins_full, wallet, amount, hashcode=btc.SIGHASH_SINGLE) #trigger insufficient funds with pytest.raises(Exception) as e_info: fake_utxos = wallet.select_utxos(4, 1000000000)
def test_donation_address(setup_donations, amount): wallets = make_wallets(1, wallet_structures=[[1,1,1,0,0]], mean_amt=0.5) wallet = wallets[0]['wallet'] jm_single().bc_interface.sync_wallet(wallet) #make a rdp from a simple privkey rdp_priv = "\x01"*32 reusable_donation_pubkey = binascii.hexlify(secp256k1.PrivateKey( privkey=rdp_priv, raw=True, ctx=btc.ctx).pubkey.serialize()) dest_addr, sign_k = donation_address(reusable_donation_pubkey) print dest_addr jm_single().bc_interface.rpc('importaddress', [dest_addr, '', False]) ins_full = wallet.unspent total = sum(x['value'] for x in ins_full.values()) ins = ins_full.keys() output_addr = wallet.get_new_addr(1, 1) fee_est = 10000 outs = [{'value': amount, 'address': dest_addr}, {'value': total - amount - fee_est, 'address': output_addr}] tx = btc.mktx(ins, outs) de_tx = btc.deserialize(tx) for index, ins in enumerate(de_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) addr = ins_full[utxo]['address'] priv = wallet.get_key_from_addr(addr) priv = binascii.unhexlify(priv) usenonce = binascii.unhexlify(sign_k) if index == 0 else None if index == 0: log.debug("Applying rdp to input: " + str(ins)) tx = btc.sign(tx, index, priv, usenonce=usenonce) #pushtx returns False on any error push_succeed = jm_single().bc_interface.pushtx(tx) if push_succeed: log.debug(btc.txhash(tx)) else: assert False #Role of receiver: regenerate the destination private key, #and address, from the nonce of the first input; check it has #received the coins. detx = btc.deserialize(tx) first_utxo_script = detx['ins'][0]['script'] sig, pub = btc.deserialize_script(first_utxo_script) log.debug(sig) sig = binascii.unhexlify(sig) kGlen = ord(sig[3]) kG = sig[4:4+kGlen] log.debug(binascii.hexlify(kG)) if kG[0] == "\x00": kG = kG[1:] #H(rdp private key * K) + rdp should be ==> dest addr #Open issue: re-introduce recovery without ECC shenanigans #Just cheat by trying both signs for pubkey coerced_kG_1 = "02" + binascii.hexlify(kG) coerced_kG_2 = "03" + binascii.hexlify(kG) for coerc in [coerced_kG_1, coerced_kG_2]: c = btc.sha256(btc.multiply(binascii.hexlify(rdp_priv), coerc, True)) pub_check = btc.add_pubkeys([reusable_donation_pubkey, btc.privtopub(c+'01', True)], True) addr_check = btc.pubtoaddr(pub_check, get_p2pk_vbyte()) log.debug("Found checked address: " + addr_check) if addr_check == dest_addr: time.sleep(3) received = jm_single().bc_interface.get_received_by_addr( [dest_addr], None)['data'][0]['balance'] assert received == amount return assert False
def test_external_commitment_used(setup_podle): tries = jm_single().config.getint("POLICY","taker_utxo_retries") #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 50000000 wallets = make_wallets(3, wallet_structures=[[1,0,0,0,0],[1,0,0,0,0],[1,1,0,0,0]], mean_amt=1) #the sendpayment bot uses the last wallet in the list wallet = wallets[2]['wallet'] yigen_procs = [] for i in range(2): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(10) destaddr = btc.privkey_to_address( binascii.hexlify(os.urandom(32)), magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #add all utxo in mixdepth 0 to 'used' list of commitments, utxos = wallet.get_utxos_by_mixdepth()[0] for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) podle = btc.PoDLE(u, priv) for i in range(tries): #loop because we want to use up all retries of this utxo commitment = podle.generate_podle(i)['commit'] btc.update_commitments(commitment=commitment) #create a new utxo, notionally from an external source; to make life a little #easier we'll pay to another mixdepth, but this is OK because #taker does not source from here currently, only from the utxos chosen #for the transaction, not the whole wallet. So we can treat it as if #external (don't access its privkey). utxos = wallet.get_utxos_by_mixdepth()[1] ecs = {} for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) ecs[u] = {} ecs[u]['reveal']={} for j in range(tries): P, P2, s, e, commit = generate_single_podle_sig( binascii.unhexlify(priv), j) if 'P' not in ecs[u]: ecs[u]['P'] = P ecs[u]['reveal'][j] = {'P2':P2, 's':s, 'e':e} btc.update_commitments(external_to_add=ecs) #Now the conditions described above hold. We do a normal single #sendpayment. taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, 2, 5000, 3, 0, True, weighted_order_choose) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] assert received == amount, "sendpayment failed - coins not arrived, " +\ "received: " + str(received) #Cleanup - remove the external commitments added btc.update_commitments(external_to_remove=ecs)
def test_tx_commitments_used(setup_podle, consume_tx, age_required, cmt_age): tries = jm_single().config.getint("POLICY","taker_utxo_retries") #remember and reset at the end taker_utxo_age = jm_single().config.getint("POLICY", "taker_utxo_age") jm_single().config.set("POLICY", "taker_utxo_age", str(age_required)) #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 0 wallets = make_wallets(3, wallet_structures=[[1,2,1,0,0],[1,2,0,0,0],[2,2,1,0,0]], mean_amt=1) #the sendpayment bot uses the last wallet in the list wallet = wallets[2]['wallet'] #make_wallets calls grab_coins which mines 1 block per individual payout, #so the age of the coins depends on where they are in that list. The sendpayment #is the last wallet in the list, and we choose the non-tx utxos which are in #mixdepth 1 and 2 (2 and 1 utxos in each respectively). We filter for those #that have sufficient age, so to get 1 which is old enough, it will be the oldest, #which will have an age of 2 + 1 (the first utxo spent to that wallet). #So if we need an age of 6, we need to mine 3 more blocks. blocks_reqd = cmt_age - 3 jm_single().bc_interface.tick_forward_chain(blocks_reqd) yigen_procs = [] for i in range(2): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) time.sleep(5) destaddr = btc.privkey_to_address( binascii.hexlify(os.urandom(32)), magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) log.debug("Here is the whole wallet: \n" + str(wallet.unspent)) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) if consume_tx: #add all utxo in mixdepth 0 to 'used' list of commitments, utxos = wallet.get_utxos_by_mixdepth()[0] for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) podle = btc.PoDLE(u, priv) for i in range(tries): #loop because we want to use up all retries of this utxo commitment = podle.generate_podle(i)['commit'] btc.update_commitments(commitment=commitment) #Now test a sendpayment from mixdepth 0 with all the depth 0 utxos #used up, so that the other utxos in the wallet get used. taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, 2, 5000, 3, 0, True, weighted_order_choose) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] jm_single().config.set("POLICY", "taker_utxo_age", str(taker_utxo_age)) if cmt_age < age_required: assert received == 0, "Coins arrived but shouldn't" else: assert received != 0, "sendpayment failed - coins not arrived, " +\ "received: " + str(received)
def test_tumbler(setup_tumbler, num_ygs, wallet_structures, mean_amt, sdev_amt, yg_excess): """Test of tumbler code, with yield generators in background. """ log = get_log() options = Options() options.mixdepthsrc = 0 options.mixdepthcount = 4 options.minmakercount = 2 options.makercountrange = (num_ygs, 0) options.maxcjfee = (0.01, 10000) options.txfee = 5000 options.addrcount = 3 options.donateamount = 0.5 options.txcountparams = (4, 1) options.mintxcount = 1 options.amountpower = 100 options.timelambda = 0.2 options.waittime = 10 options.mincjamount = 1000000 options.liquiditywait = 5 options.maxbroadcasts = 4 options.maxcreatetx = 9 options = vars(options) wallets = make_wallets(num_ygs + 1, wallet_structures=wallet_structures, mean_amt=mean_amt, sdev_amt=sdev_amt) #need to make sure that at least some ygs have substantially #more coins for last stages of sweep/spend in tumble: for i in range(num_ygs): jm_single().bc_interface.grab_coins( wallets[i]['wallet'].get_external_addr(0), yg_excess) #the tumbler bot uses the last wallet in the list wallet = wallets[num_ygs]['wallet'] yigen_procs = [] for i in range(num_ygs): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(20) destaddrs = [] for i in range(3): if btc.secp_present: destaddr = btc.privkey_to_address( os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) else: destaddr = btc.privkey_to_address( os.urandom(32), magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg destaddrs.append(destaddr) tx_list = tumbler.generate_tumbler_tx(destaddrs, options) pprint(tx_list) if options['addrcount'] + 1 > options['mixdepthcount']: print('not enough mixing depths to pay to all destination addresses, ' 'increasing mixdepthcount') options['mixdepthcount'] = options['addrcount'] + 1 tx_list2 = copy.deepcopy(tx_list) tx_dict = {} for tx in tx_list2: srcmixdepth = tx['srcmixdepth'] tx.pop('srcmixdepth') if srcmixdepth not in tx_dict: tx_dict[srcmixdepth] = [] tx_dict[srcmixdepth].append(tx) dbg_tx_list = [] for srcmixdepth, txlist in tx_dict.iteritems(): dbg_tx_list.append({'srcmixdepth': srcmixdepth, 'tx': txlist}) log.debug('tumbler transaction list') pprint(dbg_tx_list) total_wait = sum([tx['wait'] for tx in tx_list]) print('creates ' + str(len(tx_list)) + ' transactions in total') print('waits in total for ' + str(len(tx_list)) + ' blocks and ' + str( total_wait) + ' minutes') total_block_and_wait = len(tx_list) * 10 + total_wait print('estimated time taken ' + str(total_block_and_wait) + ' minutes or ' + str(round(total_block_and_wait / 60.0, 2)) + ' hours') jm_single().nickname = random_nick() log.debug('starting tumbler') jm_single().bc_interface.sync_wallet(wallet) jm_single().bc_interface.pushtx_failure_prob = 0.4 irc = IRCMessageChannel(jm_single().nickname) tumbler_bot = tumbler.Tumbler(irc, wallet, tx_list, options) try: log.debug('starting irc') irc.run() except: log.debug('CRASHING, DUMPING EVERYTHING') debug_dump_object(wallet, ['addr_cache', 'keys', 'wallet_name', 'seed']) debug_dump_object(tumbler_bot) import traceback log.debug(traceback.format_exc()) finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] assert received != 0 """TODO: figure out a sensible assertion check for the destination
def test_failed_sendpayment(setup_podle, num_ygs, wallet_structures, mean_amt, mixdepth, sending_amt): """Test of initiating joins, but failing to complete, to see commitment usage. YGs in background as per test_regtest. Use sweeps to avoid recover_from_nonrespondants without intruding into sendpayment code. """ makercount = num_ygs answeryes = True txfee = 5000 waittime = 3 #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 0 wallets = make_wallets(makercount + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list wallet = wallets[makercount]['wallet'] yigen_procs = [] for i in range(makercount): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(20) destaddr = btc.privkey_to_address( os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg #TODO paramatetrize this as a test variable chooseOrdersFunc = weighted_order_choose log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #Allow taker more retries than makers allow, so as to trigger #blacklist failure case jm_single().config.set("POLICY", "taker_utxo_retries", "4") #override ioauth receipt with a dummy do-nothing callback: def on_ioauth(*args): log.debug("Taker received: " + ','.join([str(x) for x in args])) class DummySendPayment(sendpayment.SendPayment): def __init__(self, msgchan, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc, on_ioauth): self.on_ioauth = on_ioauth self.podle_fails = 0 self.podle_allowed_fails = 3 #arbitrary; but do it more than once self.retries = 0 super(DummySendPayment, self).__init__(msgchan, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) def on_welcome(self): Taker.on_welcome(self) DummyPaymentThread(self).start() class DummyPaymentThread(sendpayment.PaymentThread): def finishcallback(self, coinjointx): #Don't ignore makers and just re-start self.taker.retries += 1 if self.taker.podle_fails == self.taker.podle_allowed_fails: self.taker.msgchan.shutdown() return self.create_tx() def create_tx(self): try: super(DummyPaymentThread, self).create_tx() except btc.PoDLEError: log.debug("Got one commit failure, continuing") self.taker.podle_fails += 1 taker = DummySendPayment(mcc, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc, on_ioauth) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #We should have been able to try (tur -1) + podle_allowed_fails times assert taker.retries == jm_single().config.getint( "POLICY", "taker_utxo_retries") + taker.podle_allowed_fails #wait for block generation time.sleep(2) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] #Sanity check no transaction succeeded assert received == 0
def test_sendpayment(setup_regtest, num_ygs, wallet_structures, mean_amt, mixdepth, sending_amt, ygcfs, fails, donate, rpcwallet): """Test of sendpayment code, with yield generators in background. """ log = get_log() makercount = num_ygs answeryes = True txfee = 5000 waittime = 5 amount = sending_amt wallets = make_wallets(makercount + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list if not rpcwallet: wallet = wallets[makercount]['wallet'] else: wallet = BitcoinCoreWallet(fromaccount="") yigen_procs = [] if ygcfs: assert makercount == len(ygcfs) for i in range(makercount): if ygcfs: #back up default config, overwrite before start os.rename("joinmarket.cfg", "joinmarket.cfg.bak") shutil.copy2(ygcfs[i], "joinmarket.cfg") ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) if ygcfs: #Note: in case of using multiple configs, #the starting config is what is used by sendpayment os.rename("joinmarket.cfg.bak", "joinmarket.cfg") #A significant delay is needed to wait for the yield generators to sync time.sleep(20) if donate: destaddr = None else: destaddr = btc.privkey_to_address( os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg #TODO paramatetrize this as a test variable chooseOrdersFunc = weighted_order_choose log.debug('starting sendpayment') sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #hack fix for #356 if multiple orders per counterparty #removed for now. #if amount==0: makercount=2 taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, makercount-2, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) try: log.debug('starting message channels') mcc.run(failures=fails) finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) if not donate: received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] if amount != 0: assert received == amount, "sendpayment failed - coins not arrived, " +\ "received: " + str(received) #TODO: how to check success for sweep case? else: assert received != 0
def test_wallet_sync_with_fast(setup_wallets, num_txs, fake_count, wallet_structure, amount, wallet_file, password): setup_import(mainnet=False) wallet = make_wallets(1,[wallet_structure], fixed_seeds=[wallet_file], test_wallet=True, passwords=[password])[0]['wallet'] sync_count = 0 jm_single().bc_interface.wallet_synced = False while not jm_single().bc_interface.wallet_synced: sync_wallet(wallet) sync_count += 1 #avoid infinite loop assert sync_count < 10 log.debug("Tried " + str(sync_count) + " times") assert jm_single().bc_interface.wallet_synced assert not jm_single().bc_interface.fast_sync_called #do some transactions with the wallet, then close, then resync for i in range(num_txs): do_tx(wallet, amount) log.debug("After doing a tx, index is now: " + str(wallet.index)) #simulate a spammer requesting a bunch of transactions. This #mimics what happens in CoinJoinOrder.__init__() for j in range(fake_count): #Note that as in a real script run, #the initial call to sync_wallet will #have set wallet_synced to True, so these will #trigger actual imports. cj_addr = wallet.get_internal_addr(0) change_addr = wallet.get_internal_addr(0) wallet.update_cache_index() log.debug("After doing a spam, index is now: " + str(wallet.index)) assert wallet.index[0][1] == num_txs+fake_count*2*num_txs #Attempt re-sync, simulating a script restart. jm_single().bc_interface.wallet_synced = False sync_count = 0 #Probably should be fixed in main code: #wallet.index_cache is only assigned in Wallet.__init__(), #meaning a second sync in the same script, after some transactions, #will not know about the latest index_cache value (see is_index_ahead_of_cache), #whereas a real re-sync will involve reading the cache from disk. #Hence, simulation of the fact that the cache index will #be read from the file on restart: wallet.index_cache = wallet.index while not jm_single().bc_interface.wallet_synced: #Wallet.__init__() resets index to zero. wallet.index = [] for i in range(5): wallet.index.append([0, 0]) #Wallet.__init__() also updates the cache index #from file, but we can reuse from the above pre-loop setting, #since nothing else in sync will overwrite the cache. #for regtest add_watchonly_addresses does not exit(), so can #just repeat as many times as possible. This might #be usable for non-test code (i.e. no need to restart the #script over and over again)? sync_count += 1 log.debug("TRYING SYNC NUMBER: " + str(sync_count)) sync_wallet(wallet, fast=True) assert jm_single().bc_interface.fast_sync_called #avoid infinite loop on failure. assert sync_count < 10 #Wallet should recognize index_cache on fast sync, so should not need to #run sync process more than once. assert sync_count == 1 #validate the wallet index values after sync for i, ws in enumerate(wallet_structure): assert wallet.index[i][0] == ws #spends into external only #Same number as above; note it includes the spammer's extras. assert wallet.index[0][1] == num_txs+fake_count*2*num_txs assert wallet.index[1][1] == num_txs #one change per transaction for i in range(2,5): assert wallet.index[i][1] == 0 #unused #Now try to do more transactions as sanity check. do_tx(wallet, 50000000)
def test_sendpayment(setup_regtest, num_ygs, wallet_structures, mean_amt, mixdepth, sending_amt): """Test of sendpayment code, with yield generators in background. """ log = get_log() makercount = num_ygs answeryes = True txfee = 5000 waittime = 5 amount = sending_amt wallets = make_wallets(makercount + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list wallet = wallets[makercount]['wallet'] yigen_procs = [] for i in range(makercount): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(20) if btc.secp_present: destaddr = btc.privkey_to_address( os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) else: destaddr = btc.privkey_to_address( os.urandom(32), magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg #TODO paramatetrize this as a test variable chooseOrdersFunc = weighted_order_choose jm_single().nickname = random_nick() log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 irc = IRCMessageChannel(jm_single().nickname) taker = sendpayment.SendPayment(irc, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) try: log.debug('starting irc') irc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] if amount != 0: assert received == amount, "sendpayment failed - coins not arrived, " +\ "received: " + str(received) #TODO: how to check success for sweep case? else: assert received != 0