2020-02-15 21:52:24 +02:00
|
|
|
#!/usr/bin/env python3
|
2016-11-22 20:32:41 +02:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
A sample implementation of a single coinjoin script,
|
|
|
|
|
adapted from `sendpayment.py` in Joinmarket-Org/joinmarket.
|
2017-02-21 15:53:58 +02:00
|
|
|
For notes, see scripts/README.md; in particular, note the use
|
|
|
|
|
of "schedules" with the -S flag.
|
2016-11-22 20:32:41 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import sys
|
2016-11-23 17:20:27 +02:00
|
|
|
from twisted.internet import reactor
|
2016-12-07 21:01:27 +02:00
|
|
|
import pprint
|
2016-11-22 20:32:41 +02:00
|
|
|
|
BIP78 receiver over a Tor hidden service.
This commit implements a command line script and a GUI
dialog to receive a payment using the BIP78 protocol,
by setting up an ephemeral hidden service.
It also deprecates the pre-existing inter-Joinmarket
protocol for payjoin payments, since we now have
both sending and receiving support for BIP78. Thus,
much code in Maker, Taker and client-daemon protocol
is removed, as is some documentation in docs/PAYJOIN.md.
Also the script `sendpayment.py` is altered to support
only the BIP78 variant.
The test in jmclient/test/test_payjoin now implements
BIP78 over a TCP connection, while the custom tests in
test/payjoinserver.py can support hidden service based
tests, but the latter is not included in the test suite
and may not always work (it is only for manual
investigations).
The following features of BIP78 are supported:
minfeerate
additionalfeeoutputindex - but *only* for single
change output transactions
maxadditionalfeecontribution
The receiver does not have nor request payment
output substitution.
Utxo selection is no longer sophisticated, instead
we only choose a single utxo to keep the size
increase of the transaction minimal. Thus UIH is
not addressed at the moment.
Errors returned are in line with BIP78.
Sequence numbers are checked by receiver, and
kept identical if uniform, otherwise respected.
Receiver uses transaction monitor to shut down
when the payment is seen.
The workflow is almost entirely implemented in
jmclient/payjoin.py and the command line script
is in scripts/receive-payjoin.py. The setup, including
configuration changes for Tor, are documented in
docs/PAYJOIN.md, including a user guide video linked.
2020-09-06 12:20:32 +01:00
|
|
|
from jmclient import Taker, load_program_config, get_schedule,\
|
2020-04-03 20:04:17 +01:00
|
|
|
JMClientProtocolFactory, start_reactor, validate_address, is_burn_destination, \
|
|
|
|
|
jm_single, estimate_tx_fee, direct_send, WalletService,\
|
2019-12-19 17:36:29 +00:00
|
|
|
open_test_wallet_maybe, get_wallet_path, NO_ROUNDING, \
|
2020-05-04 01:13:30 +01:00
|
|
|
get_sendpayment_parser, get_max_cj_fee_values, check_regtest, \
|
2021-04-19 18:04:01 +00:00
|
|
|
parse_payjoin_setup, send_payjoin, general_custom_change_warning, \
|
|
|
|
|
nonwallet_custom_change_warning, sweep_custom_change_warning, \
|
2021-11-03 12:04:18 +02:00
|
|
|
EngineError, check_and_start_tor
|
2017-09-15 17:11:06 +02:00
|
|
|
from twisted.python.log import startLogging
|
2021-04-14 15:54:18 +03:00
|
|
|
from jmbase.support import get_log, jmprint, \
|
2024-02-22 20:28:18 +02:00
|
|
|
EXIT_FAILURE, EXIT_ARGERROR, cli_prompt_user_yesno
|
2019-11-05 15:33:23 +00:00
|
|
|
|
2019-11-08 00:14:55 +02:00
|
|
|
import jmbitcoin as btc
|
2016-11-22 21:49:37 +02:00
|
|
|
|
2016-11-22 20:32:41 +02:00
|
|
|
log = get_log()
|
|
|
|
|
|
2017-02-13 16:04:49 +02:00
|
|
|
#CLI specific, so relocated here (not used by tumbler)
|
2016-12-04 21:42:14 +02:00
|
|
|
def pick_order(orders, n): #pragma: no cover
|
2019-01-08 18:59:07 +01:00
|
|
|
jmprint("Considered orders:", "info")
|
2016-12-04 21:42:14 +02:00
|
|
|
for i, o in enumerate(orders):
|
2021-09-25 22:04:40 +03:00
|
|
|
jmprint(" %2d. %20s, CJ fee: %6s, tx fee: %6d, FB value: %f" %
|
|
|
|
|
(i, o[0]['counterparty'], str(o[0]['cjfee']), o[0]['txfee'],
|
|
|
|
|
o[0]['fidelity_bond_value']), "info")
|
2016-12-04 21:42:14 +02:00
|
|
|
pickedOrderIndex = -1
|
|
|
|
|
if i == 0:
|
2019-01-08 18:59:07 +01:00
|
|
|
jmprint("Only one possible pick, picking it.", "info")
|
2016-12-04 21:42:14 +02:00
|
|
|
return orders[0]
|
|
|
|
|
while pickedOrderIndex == -1:
|
|
|
|
|
try:
|
2018-11-29 06:46:15 +08:00
|
|
|
pickedOrderIndex = int(input('Pick an order between 0 and ' +
|
2016-12-04 21:42:14 +02:00
|
|
|
str(i) + ': '))
|
|
|
|
|
except ValueError:
|
|
|
|
|
pickedOrderIndex = -1
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if 0 <= pickedOrderIndex < len(orders):
|
|
|
|
|
return orders[pickedOrderIndex]
|
|
|
|
|
pickedOrderIndex = -1
|
|
|
|
|
|
2016-11-22 20:32:41 +02:00
|
|
|
def main():
|
2016-12-27 17:01:57 +02:00
|
|
|
parser = get_sendpayment_parser()
|
2016-11-22 20:32:41 +02:00
|
|
|
(options, args) = parser.parse_args()
|
2019-12-19 17:36:29 +00:00
|
|
|
load_program_config(config_path=options.datadir)
|
BIP78 receiver over a Tor hidden service.
This commit implements a command line script and a GUI
dialog to receive a payment using the BIP78 protocol,
by setting up an ephemeral hidden service.
It also deprecates the pre-existing inter-Joinmarket
protocol for payjoin payments, since we now have
both sending and receiving support for BIP78. Thus,
much code in Maker, Taker and client-daemon protocol
is removed, as is some documentation in docs/PAYJOIN.md.
Also the script `sendpayment.py` is altered to support
only the BIP78 variant.
The test in jmclient/test/test_payjoin now implements
BIP78 over a TCP connection, while the custom tests in
test/payjoinserver.py can support hidden service based
tests, but the latter is not included in the test suite
and may not always work (it is only for manual
investigations).
The following features of BIP78 are supported:
minfeerate
additionalfeeoutputindex - but *only* for single
change output transactions
maxadditionalfeecontribution
The receiver does not have nor request payment
output substitution.
Utxo selection is no longer sophisticated, instead
we only choose a single utxo to keep the size
increase of the transaction minimal. Thus UIH is
not addressed at the moment.
Errors returned are in line with BIP78.
Sequence numbers are checked by receiver, and
kept identical if uniform, otherwise respected.
Receiver uses transaction monitor to shut down
when the payment is seen.
The workflow is almost entirely implemented in
jmclient/payjoin.py and the command line script
is in scripts/receive-payjoin.py. The setup, including
configuration changes for Tor, are documented in
docs/PAYJOIN.md, including a user guide video linked.
2020-09-06 12:20:32 +01:00
|
|
|
|
|
|
|
|
if options.schedule == '':
|
2020-04-23 17:10:31 +03:00
|
|
|
if ((len(args) < 2) or
|
|
|
|
|
(btc.is_bip21_uri(args[1]) and len(args) != 2) or
|
|
|
|
|
(not btc.is_bip21_uri(args[1]) and len(args) != 3)):
|
|
|
|
|
parser.error("Joinmarket sendpayment (coinjoin) needs arguments:"
|
|
|
|
|
" wallet, amount, destination address or wallet, bitcoin_uri.")
|
|
|
|
|
sys.exit(EXIT_ARGERROR)
|
2016-11-25 15:49:18 +02:00
|
|
|
|
2021-11-03 12:04:18 +02:00
|
|
|
check_and_start_tor()
|
|
|
|
|
|
2016-11-25 15:49:18 +02:00
|
|
|
#without schedule file option, use the arguments to create a schedule
|
|
|
|
|
#of a single transaction
|
|
|
|
|
sweeping = False
|
2020-07-10 12:32:42 +01:00
|
|
|
bip78url = None
|
2016-11-25 15:49:18 +02:00
|
|
|
if options.schedule == '':
|
2020-04-23 17:10:31 +03:00
|
|
|
if btc.is_bip21_uri(args[1]):
|
|
|
|
|
parsed = btc.decode_bip21_uri(args[1])
|
|
|
|
|
try:
|
|
|
|
|
amount = parsed['amount']
|
|
|
|
|
except KeyError:
|
|
|
|
|
parser.error("Given BIP21 URI does not contain amount.")
|
|
|
|
|
sys.exit(EXIT_ARGERROR)
|
|
|
|
|
destaddr = parsed['address']
|
BIP78 receiver over a Tor hidden service.
This commit implements a command line script and a GUI
dialog to receive a payment using the BIP78 protocol,
by setting up an ephemeral hidden service.
It also deprecates the pre-existing inter-Joinmarket
protocol for payjoin payments, since we now have
both sending and receiving support for BIP78. Thus,
much code in Maker, Taker and client-daemon protocol
is removed, as is some documentation in docs/PAYJOIN.md.
Also the script `sendpayment.py` is altered to support
only the BIP78 variant.
The test in jmclient/test/test_payjoin now implements
BIP78 over a TCP connection, while the custom tests in
test/payjoinserver.py can support hidden service based
tests, but the latter is not included in the test suite
and may not always work (it is only for manual
investigations).
The following features of BIP78 are supported:
minfeerate
additionalfeeoutputindex - but *only* for single
change output transactions
maxadditionalfeecontribution
The receiver does not have nor request payment
output substitution.
Utxo selection is no longer sophisticated, instead
we only choose a single utxo to keep the size
increase of the transaction minimal. Thus UIH is
not addressed at the moment.
Errors returned are in line with BIP78.
Sequence numbers are checked by receiver, and
kept identical if uniform, otherwise respected.
Receiver uses transaction monitor to shut down
when the payment is seen.
The workflow is almost entirely implemented in
jmclient/payjoin.py and the command line script
is in scripts/receive-payjoin.py. The setup, including
configuration changes for Tor, are documented in
docs/PAYJOIN.md, including a user guide video linked.
2020-09-06 12:20:32 +01:00
|
|
|
if "pj" in parsed:
|
2020-05-04 01:13:30 +01:00
|
|
|
# note that this is a URL; its validity
|
|
|
|
|
# checking is deferred to twisted.web.client.Agent
|
2020-07-10 12:32:42 +01:00
|
|
|
bip78url = parsed["pj"]
|
2020-05-06 21:04:28 +01:00
|
|
|
# setting makercount only for fee sanity check.
|
|
|
|
|
# note we ignore any user setting and enforce N=0,
|
|
|
|
|
# as this is a flag in the code for a non-JM coinjoin;
|
2020-07-10 12:32:42 +01:00
|
|
|
# for the fee sanity check, note that BIP78 currently
|
|
|
|
|
# will only allow small fee changes, so N=0 won't
|
2020-05-06 21:04:28 +01:00
|
|
|
# be very inaccurate.
|
|
|
|
|
jmprint("Attempting to pay via payjoin.", "info")
|
|
|
|
|
options.makercount = 0
|
2020-04-23 17:10:31 +03:00
|
|
|
else:
|
|
|
|
|
amount = btc.amount_to_sat(args[1])
|
|
|
|
|
if amount == 0:
|
|
|
|
|
sweeping = True
|
|
|
|
|
destaddr = args[2]
|
2016-11-25 15:49:18 +02:00
|
|
|
mixdepth = options.mixdepth
|
2022-09-25 15:08:24 +03:00
|
|
|
if len(args) > 2 and btc.is_bip21_uri(args[2]):
|
2022-07-08 17:21:43 +03:00
|
|
|
parsed = btc.decode_bip21_uri(args[2])
|
|
|
|
|
if 'amount' in parsed:
|
|
|
|
|
parser.error("Specify amount as a separate argument or amount in BIP21 URI, not both.")
|
|
|
|
|
sys.exit(EXIT_ARGERROR)
|
|
|
|
|
destaddr = parsed['address']
|
2016-11-25 15:49:18 +02:00
|
|
|
addr_valid, errormsg = validate_address(destaddr)
|
2020-04-03 20:04:17 +01:00
|
|
|
command_to_burn = (is_burn_destination(destaddr) and sweeping and
|
BIP78 receiver over a Tor hidden service.
This commit implements a command line script and a GUI
dialog to receive a payment using the BIP78 protocol,
by setting up an ephemeral hidden service.
It also deprecates the pre-existing inter-Joinmarket
protocol for payjoin payments, since we now have
both sending and receiving support for BIP78. Thus,
much code in Maker, Taker and client-daemon protocol
is removed, as is some documentation in docs/PAYJOIN.md.
Also the script `sendpayment.py` is altered to support
only the BIP78 variant.
The test in jmclient/test/test_payjoin now implements
BIP78 over a TCP connection, while the custom tests in
test/payjoinserver.py can support hidden service based
tests, but the latter is not included in the test suite
and may not always work (it is only for manual
investigations).
The following features of BIP78 are supported:
minfeerate
additionalfeeoutputindex - but *only* for single
change output transactions
maxadditionalfeecontribution
The receiver does not have nor request payment
output substitution.
Utxo selection is no longer sophisticated, instead
we only choose a single utxo to keep the size
increase of the transaction minimal. Thus UIH is
not addressed at the moment.
Errors returned are in line with BIP78.
Sequence numbers are checked by receiver, and
kept identical if uniform, otherwise respected.
Receiver uses transaction monitor to shut down
when the payment is seen.
The workflow is almost entirely implemented in
jmclient/payjoin.py and the command line script
is in scripts/receive-payjoin.py. The setup, including
configuration changes for Tor, are documented in
docs/PAYJOIN.md, including a user guide video linked.
2020-09-06 12:20:32 +01:00
|
|
|
options.makercount == 0)
|
2020-04-03 20:04:17 +01:00
|
|
|
if not addr_valid and not command_to_burn:
|
2019-01-08 18:59:07 +01:00
|
|
|
jmprint('ERROR: Address invalid. ' + errormsg, "error")
|
2020-04-03 20:04:17 +01:00
|
|
|
if is_burn_destination(destaddr):
|
|
|
|
|
jmprint("The required options for burning coins are zero makers"
|
BIP78 receiver over a Tor hidden service.
This commit implements a command line script and a GUI
dialog to receive a payment using the BIP78 protocol,
by setting up an ephemeral hidden service.
It also deprecates the pre-existing inter-Joinmarket
protocol for payjoin payments, since we now have
both sending and receiving support for BIP78. Thus,
much code in Maker, Taker and client-daemon protocol
is removed, as is some documentation in docs/PAYJOIN.md.
Also the script `sendpayment.py` is altered to support
only the BIP78 variant.
The test in jmclient/test/test_payjoin now implements
BIP78 over a TCP connection, while the custom tests in
test/payjoinserver.py can support hidden service based
tests, but the latter is not included in the test suite
and may not always work (it is only for manual
investigations).
The following features of BIP78 are supported:
minfeerate
additionalfeeoutputindex - but *only* for single
change output transactions
maxadditionalfeecontribution
The receiver does not have nor request payment
output substitution.
Utxo selection is no longer sophisticated, instead
we only choose a single utxo to keep the size
increase of the transaction minimal. Thus UIH is
not addressed at the moment.
Errors returned are in line with BIP78.
Sequence numbers are checked by receiver, and
kept identical if uniform, otherwise respected.
Receiver uses transaction monitor to shut down
when the payment is seen.
The workflow is almost entirely implemented in
jmclient/payjoin.py and the command line script
is in scripts/receive-payjoin.py. The setup, including
configuration changes for Tor, are documented in
docs/PAYJOIN.md, including a user guide video linked.
2020-09-06 12:20:32 +01:00
|
|
|
+ " (-N 0), sweeping (amount = 0) and not using BIP78 Payjoin", "info")
|
2019-11-05 15:33:23 +00:00
|
|
|
sys.exit(EXIT_ARGERROR)
|
2022-09-28 17:20:29 +03:00
|
|
|
if sweeping == False and options.makercount > 0 and amount < jm_single().DUST_THRESHOLD:
|
2020-02-29 19:04:58 +02:00
|
|
|
jmprint('ERROR: Amount ' + btc.amount_to_str(amount) +
|
|
|
|
|
' is below dust threshold ' +
|
2021-07-29 12:42:44 +01:00
|
|
|
btc.amount_to_str(jm_single().DUST_THRESHOLD) + '.', "error")
|
2020-02-29 19:04:58 +02:00
|
|
|
sys.exit(EXIT_ARGERROR)
|
2020-03-29 00:58:32 +02:00
|
|
|
if (options.makercount != 0 and
|
|
|
|
|
options.makercount < jm_single().config.getint(
|
|
|
|
|
"POLICY", "minimum_makers")):
|
|
|
|
|
jmprint('ERROR: Maker count ' + str(options.makercount) +
|
|
|
|
|
' below minimum_makers (' + str(jm_single().config.getint(
|
|
|
|
|
"POLICY", "minimum_makers")) + ') in joinmarket.cfg.',
|
|
|
|
|
"error")
|
|
|
|
|
sys.exit(EXIT_ARGERROR)
|
2017-02-10 14:42:45 +02:00
|
|
|
schedule = [[options.mixdepth, amount, options.makercount,
|
2019-10-03 17:43:58 +01:00
|
|
|
destaddr, 0.0, NO_ROUNDING, 0]]
|
2016-11-25 15:49:18 +02:00
|
|
|
else:
|
2021-01-13 16:47:41 +00:00
|
|
|
if len(args) > 1:
|
|
|
|
|
parser.error("Schedule files are not compatible with "
|
|
|
|
|
"payment destination/amount arguments.")
|
2020-05-04 01:13:30 +01:00
|
|
|
sys.exit(EXIT_ARGERROR)
|
2016-11-25 15:49:18 +02:00
|
|
|
result, schedule = get_schedule(options.schedule)
|
|
|
|
|
if not result:
|
2019-01-08 18:59:07 +01:00
|
|
|
log.error("Failed to load schedule file, quitting. Check the syntax.")
|
|
|
|
|
log.error("Error was: " + str(schedule))
|
2019-11-05 15:33:23 +00:00
|
|
|
sys.exit(EXIT_FAILURE)
|
2016-11-25 15:49:18 +02:00
|
|
|
mixdepth = 0
|
|
|
|
|
for s in schedule:
|
|
|
|
|
if s[1] == 0:
|
|
|
|
|
sweeping = True
|
|
|
|
|
#only used for checking the maximum mixdepth required
|
|
|
|
|
mixdepth = max([mixdepth, s[0]])
|
|
|
|
|
|
2016-11-22 20:32:41 +02:00
|
|
|
wallet_name = args[0]
|
|
|
|
|
|
2018-12-24 15:30:01 +01:00
|
|
|
check_regtest()
|
2016-11-22 20:32:41 +02:00
|
|
|
|
|
|
|
|
if options.pickorders:
|
|
|
|
|
chooseOrdersFunc = pick_order
|
2016-11-25 15:49:18 +02:00
|
|
|
if sweeping:
|
2019-01-08 18:59:07 +01:00
|
|
|
jmprint('WARNING: You may have to pick offers multiple times', "warning")
|
|
|
|
|
jmprint('WARNING: due to manual offer picking while sweeping', "warning")
|
2018-11-01 23:44:54 +01:00
|
|
|
else:
|
|
|
|
|
chooseOrdersFunc = options.order_choose_fn
|
2016-11-22 20:32:41 +02:00
|
|
|
|
2019-08-21 01:12:21 +02:00
|
|
|
# If tx_fees are set manually by CLI argument, override joinmarket.cfg:
|
|
|
|
|
if int(options.txfee) > 0:
|
2024-01-17 16:04:22 +02:00
|
|
|
if jm_single().bc_interface.fee_per_kb_has_been_manually_set(
|
|
|
|
|
options.txfee):
|
|
|
|
|
absurd_fee = jm_single().config.getint("POLICY",
|
|
|
|
|
"absurd_fee_per_kb")
|
|
|
|
|
tx_fees_factor = jm_single().config.getfloat("POLICY",
|
|
|
|
|
"tx_fees_factor")
|
|
|
|
|
max_potential_txfee = int(max(options.txfee,
|
|
|
|
|
options.txfee * float(1 + tx_fees_factor)))
|
|
|
|
|
if max_potential_txfee > absurd_fee:
|
|
|
|
|
jmprint(
|
|
|
|
|
"WARNING: Manually specified Bitcoin transaction fee "
|
|
|
|
|
f"{btc.fee_per_kb_to_str(options.txfee)} can be "
|
|
|
|
|
"randomized up to "
|
|
|
|
|
f"{btc.fee_per_kb_to_str(max_potential_txfee)}, "
|
|
|
|
|
"above absurd value "
|
|
|
|
|
f"{btc.fee_per_kb_to_str(absurd_fee)}.",
|
|
|
|
|
"warning")
|
2024-02-22 20:28:18 +02:00
|
|
|
if not cli_prompt_user_yesno("Still continue?"):
|
2024-01-17 16:04:22 +02:00
|
|
|
sys.exit("Aborted by user.")
|
|
|
|
|
jm_single().config.set("POLICY", "absurd_fee_per_kb",
|
|
|
|
|
str(max_potential_txfee))
|
2019-08-21 01:12:21 +02:00
|
|
|
jm_single().config.set("POLICY", "tx_fees", str(options.txfee))
|
|
|
|
|
|
2019-02-11 01:02:05 +02:00
|
|
|
maxcjfee = (1, float('inf'))
|
BIP78 receiver over a Tor hidden service.
This commit implements a command line script and a GUI
dialog to receive a payment using the BIP78 protocol,
by setting up an ephemeral hidden service.
It also deprecates the pre-existing inter-Joinmarket
protocol for payjoin payments, since we now have
both sending and receiving support for BIP78. Thus,
much code in Maker, Taker and client-daemon protocol
is removed, as is some documentation in docs/PAYJOIN.md.
Also the script `sendpayment.py` is altered to support
only the BIP78 variant.
The test in jmclient/test/test_payjoin now implements
BIP78 over a TCP connection, while the custom tests in
test/payjoinserver.py can support hidden service based
tests, but the latter is not included in the test suite
and may not always work (it is only for manual
investigations).
The following features of BIP78 are supported:
minfeerate
additionalfeeoutputindex - but *only* for single
change output transactions
maxadditionalfeecontribution
The receiver does not have nor request payment
output substitution.
Utxo selection is no longer sophisticated, instead
we only choose a single utxo to keep the size
increase of the transaction minimal. Thus UIH is
not addressed at the moment.
Errors returned are in line with BIP78.
Sequence numbers are checked by receiver, and
kept identical if uniform, otherwise respected.
Receiver uses transaction monitor to shut down
when the payment is seen.
The workflow is almost entirely implemented in
jmclient/payjoin.py and the command line script
is in scripts/receive-payjoin.py. The setup, including
configuration changes for Tor, are documented in
docs/PAYJOIN.md, including a user guide video linked.
2020-09-06 12:20:32 +01:00
|
|
|
if not options.pickorders and options.makercount != 0:
|
2018-11-01 23:44:54 +01:00
|
|
|
maxcjfee = get_max_cj_fee_values(jm_single().config, options)
|
|
|
|
|
log.info("Using maximum coinjoin fee limits per maker of {:.4%}, {} "
|
2019-11-08 00:14:55 +02:00
|
|
|
"".format(maxcjfee[0], btc.amount_to_str(maxcjfee[1])))
|
2018-11-01 23:44:54 +01:00
|
|
|
|
2020-05-06 21:04:28 +01:00
|
|
|
log.info('starting sendpayment')
|
2016-11-25 15:49:18 +02:00
|
|
|
|
2018-10-28 17:28:59 +01:00
|
|
|
max_mix_depth = max([mixdepth, options.amtmixdepths - 1])
|
|
|
|
|
|
|
|
|
|
wallet_path = get_wallet_path(wallet_name, None)
|
|
|
|
|
wallet = open_test_wallet_maybe(
|
2019-10-31 20:00:20 +00:00
|
|
|
wallet_path, wallet_name, max_mix_depth,
|
|
|
|
|
wallet_password_stdin=options.wallet_password_stdin,
|
|
|
|
|
gap_limit=options.gaplimit)
|
2019-06-20 18:11:56 +02:00
|
|
|
wallet_service = WalletService(wallet)
|
2020-10-07 16:05:37 +02:00
|
|
|
if wallet_service.rpc_error:
|
|
|
|
|
sys.exit(EXIT_FAILURE)
|
2019-06-20 18:11:56 +02:00
|
|
|
# in this script, we need the wallet synced before
|
|
|
|
|
# logic processing for some paths, so do it now:
|
|
|
|
|
while not wallet_service.synced:
|
|
|
|
|
wallet_service.sync_wallet(fast=not options.recoversync)
|
|
|
|
|
# the sync call here will now be a no-op:
|
|
|
|
|
wallet_service.startService()
|
2017-11-09 18:58:41 +01:00
|
|
|
|
2020-11-30 16:43:50 +00:00
|
|
|
# Dynamically estimate a realistic fee, for coinjoins.
|
2020-08-03 20:30:54 -04:00
|
|
|
# At this point we do not know even the number of our own inputs, so
|
|
|
|
|
# we guess conservatively with 2 inputs and 2 outputs each.
|
2020-11-30 16:43:50 +00:00
|
|
|
if options.makercount != 0:
|
|
|
|
|
fee_per_cp_guess = estimate_tx_fee(2, 2,
|
|
|
|
|
txtype=wallet_service.get_txtype())
|
2020-12-01 16:25:45 +00:00
|
|
|
log.debug("Estimated miner/tx fee for each cj participant: " +
|
|
|
|
|
btc.amount_to_str(fee_per_cp_guess))
|
2019-08-20 22:32:59 +02:00
|
|
|
|
|
|
|
|
# From the estimated tx fees, check if the expected amount is a
|
2019-11-18 17:17:46 +00:00
|
|
|
# significant value compared the the cj amount; currently enabled
|
|
|
|
|
# only for single join (the predominant, non-advanced case)
|
2020-11-30 16:43:50 +00:00
|
|
|
if options.schedule == '' and options.makercount != 0:
|
2019-11-18 17:17:46 +00:00
|
|
|
total_cj_amount = amount
|
2019-08-21 17:24:30 +02:00
|
|
|
if total_cj_amount == 0:
|
2019-11-18 17:17:46 +00:00
|
|
|
total_cj_amount = wallet_service.get_balance_by_mixdepth()[options.mixdepth]
|
|
|
|
|
if total_cj_amount == 0:
|
|
|
|
|
raise ValueError("No confirmed coins in the selected mixdepth. Quitting")
|
2020-05-11 11:22:11 +01:00
|
|
|
exp_tx_fees_ratio = ((1 + options.makercount) * fee_per_cp_guess) / total_cj_amount
|
2019-11-18 17:17:46 +00:00
|
|
|
if exp_tx_fees_ratio > 0.05:
|
|
|
|
|
jmprint('WARNING: Expected bitcoin network miner fees for this coinjoin'
|
|
|
|
|
' amount are roughly {:.1%}'.format(exp_tx_fees_ratio), "warning")
|
2024-02-22 20:28:18 +02:00
|
|
|
print('You might want to modify your tx_fee settings in joinmarket.cfg.')
|
|
|
|
|
if not cli_prompt_user_yesno('Still continue?'):
|
2019-11-18 17:17:46 +00:00
|
|
|
sys.exit('Aborted by user.')
|
|
|
|
|
else:
|
|
|
|
|
log.info("Estimated miner/tx fees for this coinjoin amount: {:.1%}"
|
|
|
|
|
.format(exp_tx_fees_ratio))
|
2019-08-20 22:32:59 +02:00
|
|
|
|
2021-04-19 18:04:01 +00:00
|
|
|
custom_change = None
|
|
|
|
|
if options.customchange != '':
|
|
|
|
|
addr_valid, errormsg = validate_address(options.customchange)
|
|
|
|
|
if not addr_valid:
|
|
|
|
|
parser.error(
|
|
|
|
|
"The custom change address provided is not valid\n{}".format(
|
|
|
|
|
errormsg))
|
|
|
|
|
sys.exit(EXIT_ARGERROR)
|
|
|
|
|
custom_change = options.customchange
|
|
|
|
|
if destaddr and custom_change == destaddr:
|
|
|
|
|
parser.error("The custom change address cannot be the same as the "
|
|
|
|
|
"destination address.")
|
|
|
|
|
sys.exit(EXIT_ARGERROR)
|
|
|
|
|
if sweeping:
|
|
|
|
|
parser.error(sweep_custom_change_warning)
|
|
|
|
|
sys.exit(EXIT_ARGERROR)
|
|
|
|
|
if bip78url:
|
|
|
|
|
parser.error("Custom change is not currently supported "
|
|
|
|
|
"with Payjoin. Please retry without a custom change address.")
|
|
|
|
|
sys.exit(EXIT_ARGERROR)
|
|
|
|
|
if options.makercount > 0:
|
2024-02-22 20:28:18 +02:00
|
|
|
if not options.answeryes and \
|
|
|
|
|
not cli_prompt_user_yesno(general_custom_change_warning):
|
2021-04-19 18:04:01 +00:00
|
|
|
sys.exit(EXIT_ARGERROR)
|
|
|
|
|
engine_recognized = True
|
|
|
|
|
try:
|
2022-02-16 21:47:19 +02:00
|
|
|
change_addr_type = wallet_service.get_outtype(custom_change)
|
2021-04-19 18:04:01 +00:00
|
|
|
except EngineError:
|
|
|
|
|
engine_recognized = False
|
|
|
|
|
if (not engine_recognized) or (
|
2022-02-16 21:47:19 +02:00
|
|
|
change_addr_type != wallet_service.get_txtype()):
|
2024-02-22 20:28:18 +02:00
|
|
|
if not options.answeryes and \
|
|
|
|
|
not cli_prompt_user_yesno(nonwallet_custom_change_warning):
|
2021-04-19 18:04:01 +00:00
|
|
|
sys.exit(EXIT_ARGERROR)
|
|
|
|
|
|
BIP78 receiver over a Tor hidden service.
This commit implements a command line script and a GUI
dialog to receive a payment using the BIP78 protocol,
by setting up an ephemeral hidden service.
It also deprecates the pre-existing inter-Joinmarket
protocol for payjoin payments, since we now have
both sending and receiving support for BIP78. Thus,
much code in Maker, Taker and client-daemon protocol
is removed, as is some documentation in docs/PAYJOIN.md.
Also the script `sendpayment.py` is altered to support
only the BIP78 variant.
The test in jmclient/test/test_payjoin now implements
BIP78 over a TCP connection, while the custom tests in
test/payjoinserver.py can support hidden service based
tests, but the latter is not included in the test suite
and may not always work (it is only for manual
investigations).
The following features of BIP78 are supported:
minfeerate
additionalfeeoutputindex - but *only* for single
change output transactions
maxadditionalfeecontribution
The receiver does not have nor request payment
output substitution.
Utxo selection is no longer sophisticated, instead
we only choose a single utxo to keep the size
increase of the transaction minimal. Thus UIH is
not addressed at the moment.
Errors returned are in line with BIP78.
Sequence numbers are checked by receiver, and
kept identical if uniform, otherwise respected.
Receiver uses transaction monitor to shut down
when the payment is seen.
The workflow is almost entirely implemented in
jmclient/payjoin.py and the command line script
is in scripts/receive-payjoin.py. The setup, including
configuration changes for Tor, are documented in
docs/PAYJOIN.md, including a user guide video linked.
2020-09-06 12:20:32 +01:00
|
|
|
if options.makercount == 0 and not bip78url:
|
2024-02-24 02:05:50 +02:00
|
|
|
tx = direct_send(wallet_service, mixdepth,
|
|
|
|
|
[(destaddr, amount)],
|
2023-04-01 16:58:16 +03:00
|
|
|
options.answeryes,
|
|
|
|
|
with_final_psbt=options.with_psbt,
|
2023-03-03 08:39:51 +02:00
|
|
|
optin_rbf=not options.no_rbf,
|
2023-04-01 16:58:16 +03:00
|
|
|
custom_change_addr=custom_change,
|
|
|
|
|
change_label=options.changelabel)
|
2020-04-23 19:50:45 +01:00
|
|
|
if options.with_psbt:
|
|
|
|
|
log.info("This PSBT is fully signed and can be sent externally for "
|
|
|
|
|
"broadcasting:")
|
|
|
|
|
log.info(tx.to_base64())
|
2017-02-13 16:04:49 +02:00
|
|
|
return
|
|
|
|
|
|
2017-11-09 18:58:41 +01:00
|
|
|
if wallet.get_txtype() == 'p2pkh':
|
2019-01-08 18:59:07 +01:00
|
|
|
jmprint("Only direct sends (use -N 0) are supported for "
|
|
|
|
|
"legacy (non-segwit) wallets.", "error")
|
2019-11-05 15:33:23 +00:00
|
|
|
sys.exit(EXIT_ARGERROR)
|
2017-11-09 18:58:41 +01:00
|
|
|
|
2016-12-03 17:15:50 +02:00
|
|
|
def filter_orders_callback(orders_fees, cjamount):
|
|
|
|
|
orders, total_cj_fee = orders_fees
|
2016-12-07 21:01:27 +02:00
|
|
|
log.info("Chose these orders: " +pprint.pformat(orders))
|
|
|
|
|
log.info('total cj fee = ' + str(total_cj_fee))
|
2016-12-03 17:15:50 +02:00
|
|
|
total_fee_pc = 1.0 * total_cj_fee / cjamount
|
2016-12-07 21:01:27 +02:00
|
|
|
log.info('total coinjoin fee = ' + str(float('%.3g' % (
|
2016-12-03 17:15:50 +02:00
|
|
|
100.0 * total_fee_pc))) + '%')
|
|
|
|
|
WARNING_THRESHOLD = 0.02 # 2%
|
|
|
|
|
if total_fee_pc > WARNING_THRESHOLD:
|
2016-12-07 21:01:27 +02:00
|
|
|
log.info('\n'.join(['=' * 60] * 3))
|
|
|
|
|
log.info('WARNING ' * 6)
|
|
|
|
|
log.info('\n'.join(['=' * 60] * 1))
|
|
|
|
|
log.info('OFFERED COINJOIN FEE IS UNUSUALLY HIGH. DOUBLE/TRIPLE CHECK.')
|
|
|
|
|
log.info('\n'.join(['=' * 60] * 1))
|
|
|
|
|
log.info('WARNING ' * 6)
|
|
|
|
|
log.info('\n'.join(['=' * 60] * 3))
|
2016-12-03 17:15:50 +02:00
|
|
|
if not options.answeryes:
|
2024-02-22 20:28:18 +02:00
|
|
|
if not cli_prompt_user_yesno('Send with these orders?'):
|
2016-12-03 17:15:50 +02:00
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
2017-02-11 21:09:08 +02:00
|
|
|
def taker_finished(res, fromtx=False, waittime=0.0, txdetails=None):
|
2017-02-13 15:39:45 +02:00
|
|
|
if fromtx == "unconfirmed":
|
|
|
|
|
#If final entry, stop *here*, don't wait for confirmation
|
|
|
|
|
if taker.schedule_index + 1 == len(taker.schedule):
|
|
|
|
|
reactor.stop()
|
|
|
|
|
return
|
2016-11-23 21:48:20 +02:00
|
|
|
if fromtx:
|
|
|
|
|
if res:
|
2017-02-11 21:09:08 +02:00
|
|
|
txd, txid = txdetails
|
2017-02-08 22:39:57 +02:00
|
|
|
reactor.callLater(waittime*60,
|
|
|
|
|
clientfactory.getClient().clientStart)
|
2016-11-23 21:48:20 +02:00
|
|
|
else:
|
2018-07-08 23:22:16 +03:00
|
|
|
#a transaction failed; we'll try to repeat without the
|
2018-07-22 16:13:37 +02:00
|
|
|
#troublemakers.
|
|
|
|
|
#If this error condition is reached from Phase 1 processing,
|
|
|
|
|
#and there are less than minimum_makers honest responses, we
|
|
|
|
|
#just give up (note that in tumbler we tweak and retry, but
|
|
|
|
|
#for sendpayment the user is "online" and so can manually
|
|
|
|
|
#try again).
|
|
|
|
|
#However if the error is in Phase 2 and we have minimum_makers
|
|
|
|
|
#or more responses, we do try to restart with the honest set, here.
|
|
|
|
|
if taker.latest_tx is None:
|
|
|
|
|
#can only happen with < minimum_makers; see above.
|
|
|
|
|
log.info("A transaction failed but there are insufficient "
|
|
|
|
|
"honest respondants to continue; giving up.")
|
|
|
|
|
reactor.stop()
|
|
|
|
|
return
|
|
|
|
|
#This is Phase 2; do we have enough to try again?
|
2018-07-08 23:22:16 +03:00
|
|
|
taker.add_honest_makers(list(set(
|
|
|
|
|
taker.maker_utxo_data.keys()).symmetric_difference(
|
|
|
|
|
set(taker.nonrespondants))))
|
2018-07-22 16:13:37 +02:00
|
|
|
if len(taker.honest_makers) < jm_single().config.getint(
|
|
|
|
|
"POLICY", "minimum_makers"):
|
|
|
|
|
log.info("Too few makers responded honestly; "
|
2018-07-08 23:22:16 +03:00
|
|
|
"giving up this attempt.")
|
|
|
|
|
reactor.stop()
|
|
|
|
|
return
|
2019-01-08 18:59:07 +01:00
|
|
|
jmprint("We failed to complete the transaction. The following "
|
2019-01-24 14:18:10 +01:00
|
|
|
"makers responded honestly: " + str(taker.honest_makers) +\
|
2019-01-08 18:59:07 +01:00
|
|
|
", so we will retry with them.", "warning")
|
2018-07-22 16:13:37 +02:00
|
|
|
#Now we have to set the specific group we want to use, and hopefully
|
|
|
|
|
#they will respond again as they showed honesty last time.
|
2018-07-08 23:22:16 +03:00
|
|
|
#we must reset the number of counterparties, as well as fix who they
|
|
|
|
|
#are; this is because the number is used to e.g. calculate fees.
|
|
|
|
|
#cleanest way is to reset the number in the schedule before restart.
|
|
|
|
|
taker.schedule[taker.schedule_index][2] = len(taker.honest_makers)
|
|
|
|
|
log.info("Retrying with: " + str(taker.schedule[
|
|
|
|
|
taker.schedule_index][2]) + " counterparties.")
|
|
|
|
|
#rewind to try again (index is incremented in Taker.initialize())
|
|
|
|
|
taker.schedule_index -= 1
|
|
|
|
|
taker.set_honest_only(True)
|
|
|
|
|
reactor.callLater(5.0, clientfactory.getClient().clientStart)
|
2016-11-23 17:20:27 +02:00
|
|
|
else:
|
2016-11-23 21:48:20 +02:00
|
|
|
if not res:
|
|
|
|
|
log.info("Did not complete successfully, shutting down")
|
2017-02-13 15:39:45 +02:00
|
|
|
#Should usually be unreachable, unless conf received out of order;
|
|
|
|
|
#because we should stop on 'unconfirmed' for last (see above)
|
2016-11-24 00:35:54 +02:00
|
|
|
else:
|
|
|
|
|
log.info("All transactions completed correctly")
|
2016-11-23 17:20:27 +02:00
|
|
|
reactor.stop()
|
2016-11-22 20:32:41 +02:00
|
|
|
|
2021-02-20 16:54:06 +00:00
|
|
|
nodaemon = jm_single().config.getint("DAEMON", "no_daemon")
|
|
|
|
|
daemon = True if nodaemon == 1 else False
|
|
|
|
|
dhost = jm_single().config.get("DAEMON", "daemon_host")
|
|
|
|
|
dport = jm_single().config.getint("DAEMON", "daemon_port")
|
BIP78 receiver over a Tor hidden service.
This commit implements a command line script and a GUI
dialog to receive a payment using the BIP78 protocol,
by setting up an ephemeral hidden service.
It also deprecates the pre-existing inter-Joinmarket
protocol for payjoin payments, since we now have
both sending and receiving support for BIP78. Thus,
much code in Maker, Taker and client-daemon protocol
is removed, as is some documentation in docs/PAYJOIN.md.
Also the script `sendpayment.py` is altered to support
only the BIP78 variant.
The test in jmclient/test/test_payjoin now implements
BIP78 over a TCP connection, while the custom tests in
test/payjoinserver.py can support hidden service based
tests, but the latter is not included in the test suite
and may not always work (it is only for manual
investigations).
The following features of BIP78 are supported:
minfeerate
additionalfeeoutputindex - but *only* for single
change output transactions
maxadditionalfeecontribution
The receiver does not have nor request payment
output substitution.
Utxo selection is no longer sophisticated, instead
we only choose a single utxo to keep the size
increase of the transaction minimal. Thus UIH is
not addressed at the moment.
Errors returned are in line with BIP78.
Sequence numbers are checked by receiver, and
kept identical if uniform, otherwise respected.
Receiver uses transaction monitor to shut down
when the payment is seen.
The workflow is almost entirely implemented in
jmclient/payjoin.py and the command line script
is in scripts/receive-payjoin.py. The setup, including
configuration changes for Tor, are documented in
docs/PAYJOIN.md, including a user guide video linked.
2020-09-06 12:20:32 +01:00
|
|
|
if bip78url:
|
2020-05-04 01:13:30 +01:00
|
|
|
# TODO sanity check wallet type is segwit
|
|
|
|
|
manager = parse_payjoin_setup(args[1], wallet_service, options.mixdepth)
|
2020-08-19 22:08:11 +03:00
|
|
|
reactor.callWhenRunning(send_payjoin, manager)
|
2021-02-20 16:54:06 +00:00
|
|
|
# JM is default, so must be switched off explicitly in this call:
|
|
|
|
|
start_reactor(dhost, dport, bip78=True, jm_coinjoin=False, daemon=daemon)
|
2020-05-04 01:13:30 +01:00
|
|
|
return
|
|
|
|
|
|
2018-12-24 15:30:01 +01:00
|
|
|
else:
|
2019-06-20 18:11:56 +02:00
|
|
|
taker = Taker(wallet_service,
|
2018-12-24 15:30:01 +01:00
|
|
|
schedule,
|
|
|
|
|
order_chooser=chooseOrdersFunc,
|
|
|
|
|
max_cj_fee=maxcjfee,
|
2021-04-19 18:04:01 +00:00
|
|
|
callbacks=(filter_orders_callback, None, taker_finished),
|
2023-04-01 16:58:16 +03:00
|
|
|
custom_change_address=custom_change,
|
|
|
|
|
change_label=options.changelabel)
|
2017-07-18 14:37:13 +03:00
|
|
|
clientfactory = JMClientProtocolFactory(taker)
|
2021-02-20 16:54:06 +00:00
|
|
|
|
2020-11-11 04:33:41 +02:00
|
|
|
if jm_single().config.get("BLOCKCHAIN", "network") == "regtest":
|
2017-09-15 17:11:06 +02:00
|
|
|
startLogging(sys.stdout)
|
2021-02-20 16:54:06 +00:00
|
|
|
start_reactor(dhost, dport, clientfactory, daemon=daemon)
|
2016-11-22 20:32:41 +02:00
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|
2019-01-08 18:59:07 +01:00
|
|
|
jmprint('done', "success")
|