From a7fb49fd75923a8e06b1f949a511518ddbe57cf0 Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Mon, 13 Feb 2017 16:04:49 +0200 Subject: [PATCH] Add direct-send to sendpayment --- scripts/sendpayment.py | 75 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/scripts/sendpayment.py b/scripts/sendpayment.py index 8c5f66d..a399a20 100644 --- a/scripts/sendpayment.py +++ b/scripts/sendpayment.py @@ -57,13 +57,15 @@ from jmclient import (Taker, load_program_config, get_schedule, choose_orders, choose_sweep_orders, cheapest_order_choose, weighted_order_choose, Wallet, BitcoinCoreWallet, sync_wallet, - RegtestBitcoinCoreInterface, estimate_tx_fee) + RegtestBitcoinCoreInterface, estimate_tx_fee, + mktx, deserialize, sign, txhash) from jmbase.support import get_log, debug_dump_object, get_password from cli_options import get_sendpayment_parser log = get_log() +#CLI specific, so relocated here (not used by tumbler) def pick_order(orders, n): #pragma: no cover print("Considered orders:") for i, o in enumerate(orders): @@ -85,6 +87,63 @@ def pick_order(orders, n): #pragma: no cover return orders[pickedOrderIndex] pickedOrderIndex = -1 +def direct_send(wallet, amount, mixdepth, destaddr, answeryes=False): + """Send coins directly from one mixdepth to one destination address; + does not need IRC. Sweep as for normal sendpayment (set amount=0). + """ + #Sanity checks; note destaddr format is carefully checked in startup + assert isinstance(mixdepth, int) + assert mixdepth >= 0 + assert isinstance(amount, int) + assert amount >=0 and amount < 10000000000 + assert isinstance(wallet, Wallet) + + from pprint import pformat + if amount == 0: + utxos = wallet.get_utxos_by_mixdepth()[mixdepth] + if utxos == {}: + log.error( + "There are no utxos in mixdepth: " + str(mixdepth) + ", quitting.") + return + total_inputs_val = sum([va['value'] for u, va in utxos.iteritems()]) + fee_est = estimate_tx_fee(len(utxos), 1) + outs = [{"address": destaddr, "value": total_inputs_val - fee_est}] + else: + initial_fee_est = estimate_tx_fee(8,2) #8 inputs to be conservative + utxos = wallet.select_utxos(mixdepth, amount + initial_fee_est) + if len(utxos) < 8: + fee_est = estimate_tx_fee(len(utxos), 2) + else: + fee_est = initial_fee_est + total_inputs_val = sum([va['value'] for u, va in utxos.iteritems()]) + changeval = total_inputs_val - fee_est - amount + outs = [{"value": amount, "address": destaddr}] + change_addr = wallet.get_internal_addr(mixdepth) + outs.append({"value": changeval, "address": change_addr}) + + #Now ready to construct transaction + log.info("Using a fee of : " + str(fee_est) + " satoshis.") + if amount != 0: + log.info("Using a change value of: " + str(changeval) + " satoshis.") + tx = mktx(utxos.keys(), outs) + stx = deserialize(tx) + for index, ins in enumerate(stx['ins']): + utxo = ins['outpoint']['hash'] + ':' + str( + ins['outpoint']['index']) + addr = utxos[utxo]['address'] + tx = sign(tx, index, wallet.get_key_from_addr(addr)) + txsigned = deserialize(tx) + log.info("Got signed transaction:\n") + log.info(tx + "\n") + log.info(pformat(txsigned)) + if not answeryes: + if raw_input('Would you like to push to the network? (y/n):')[0] != 'y': + log.info("You chose not to broadcast the transaction, quitting.") + return + jm_single().bc_interface.pushtx(tx) + txid = txhash(tx) + log.info("Transaction sent: " + txid + ", shutting down") + def main(): parser = get_sendpayment_parser() (options, args) = parser.parse_args() @@ -126,8 +185,10 @@ def main(): wallet_name = args[0] - #for testing, TODO remove - jm_single().maker_timeout_sec = 5 + #to allow testing of confirm/unconfirm callback for multiple txs + if isinstance(jm_single().bc_interface, RegtestBitcoinCoreInterface): + jm_single().bc_interface.tick_forward_chain_interval = 10 + jm_single().maker_timeout_sec = 5 chooseOrdersFunc = None if options.pickorders: @@ -171,6 +232,14 @@ def main(): wallet = BitcoinCoreWallet(fromaccount=wallet_name) sync_wallet(wallet, fast=options.fastsync) + #Note that direct send is currently only supported for command line, + #not for schedule file (in that case options.makercount is 4-6, not 0) + if options.makercount == 0: + if isinstance(wallet, BitcoinCoreWallet): + raise NotImplementedError("Direct send only supported for JM wallets") + direct_send(wallet, amount, mixdepth, destaddr, options.answeryes) + return + def filter_orders_callback(orders_fees, cjamount): orders, total_cj_fee = orders_fees log.info("Chose these orders: " +pprint.pformat(orders))