Skip to content

Commit

Permalink
Send coin join output to an external cold storage wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
Wukong committed Sep 9, 2021
1 parent 7ba7791 commit 6149299
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 9 deletions.
2 changes: 1 addition & 1 deletion jmclient/jmclient/wallet_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def start_wallet_monitoring(self, syncresult):
if reactor.running:
reactor.stop()
return
jlog.info("Starting transaction monitor in walletservice")
jlog.info("Starting transaction monitor in walletservice for {}".format(self.get_wallet_name()))
self.monitor_loop = task.LoopingCall(
self.transaction_monitor)
self.monitor_loop.start(5.0)
Expand Down
2 changes: 1 addition & 1 deletion jmclient/jmclient/wallet_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1413,7 +1413,7 @@ def open_wallet(path, ask_for_password=True, password=None, read_only=False,
while True:
try:
# do not try empty password, assume unencrypted on empty password
pwd = get_password("Enter passphrase to decrypt wallet: ") or None
pwd = get_password("Enter passphrase to decrypt wallet {}: ".format(path)) or None
storage = Storage(path, password=pwd, read_only=read_only)
except StoragePasswordError:
jmprint("Wrong password, try again.", "warning")
Expand Down
80 changes: 76 additions & 4 deletions jmclient/jmclient/yieldgenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import time
import abc
import base64
from jmclient.wallet import WatchonlyMixin
from twisted.python.log import startLogging
from optparse import OptionParser
from jmbase import get_log
Expand Down Expand Up @@ -80,10 +81,16 @@ class YieldGeneratorBasic(YieldGenerator):
It will often (but not always) reannounce orders after transactions,
thus is somewhat suboptimal in giving more information to spies.
"""
def __init__(self, wallet_service, offerconfig):
def __init__(self, wallet_service, offerconfig, cswallet_service = None, csconfig = None):
# note the randomizing entries are ignored in this base class:
self.txfee, self.cjfee_a, self.cjfee_r, self.ordertype, self.minsize, \
self.txfee_factor, self.cjfee_factor, self.size_factor = offerconfig

if cswallet_service:
assert isinstance(cswallet_service, WalletService)
self.cs_min_balance, self.cs_min_txsize, self.cs_mixdepth = csconfig
self.cswallet_service = cswallet_service

super().__init__(wallet_service)

def create_my_orders(self):
Expand Down Expand Up @@ -255,13 +262,44 @@ def select_input_mixdepth(self, available, offer, amount):
available = sorted(available.items(), key=lambda entry: entry[0])
return available[0][0]

def should_use_cswallet(self, input_mixdepth, amount):
if not self.cswallet_service:
return False

if input_mixdepth != self.cs_mixdepth:
jlog.debug("input mixdepth {} dosen't match cs_mixdepth {}".format(
input_mixdepth, self.cs_mixdepth))
return False

if amount < self.cs_min_txsize:
jlog.debug("coin join size {} is less than cs_min_txsize {}".format(
amount, self.cs_min_txsize))
return False

# Check if the wallet balance can maintain the min balance after this coin join
total_wallet_balance = sum(self.wallet_service.get_balance_by_mixdepth().values())
if total_wallet_balance - amount < self.cs_min_balance:
jlog.debug("total_wallet_balance - amount {} is less than cs_min_balance {}".format(
total_wallet_balance - amount, self.cs_min_balance))
return False
else:
jlog.debug("sending coin join output to cold storage wallet {}".format(
self.cswallet_service.get_wallet_name()))
return True

def select_output_address(self, input_mixdepth, offer, amount):
"""Returns the address to which the mixed output should be sent for
an order spending from the given input mixdepth. Can return None if
there is no suitable output, in which case the order is
aborted."""
cjoutmix = (input_mixdepth + 1) % (self.wallet_service.mixdepth + 1)
return self.wallet_service.get_internal_addr(cjoutmix)
if self.should_use_cswallet(input_mixdepth, amount):
output_address = self.cswallet_service.get_internal_addr(WatchonlyMixin.WATCH_ONLY_MIXDEPTH)
jlog.info("sending coin join output to cold storage wallet {} at address {}".format(
self.cswallet_service.get_wallet_name(), output_address))
return output_address
else:
cjoutmix = (input_mixdepth + 1) % (self.wallet_service.mixdepth + 1)
return self.wallet_service.get_internal_addr(cjoutmix)


def ygmain(ygclass, nickserv_password='', gaplimit=6):
Expand Down Expand Up @@ -299,6 +337,18 @@ def ygmain(ygclass, nickserv_password='', gaplimit=6):
parser.add_option('-j', '--cjfee-factor', action='store', type='float',
dest='cjfee_factor', default=None,
help='variance around the average fee, decimal fraction')
parser.add_option('-c', '--cswallet', action='store', type='string',
dest='cswallet_name', default=None,
help='a watch only cold storage wallet to send coinjoin output to')
parser.add_option('-d', '--cs-mixdepth', action='store', type='int',
dest='cs_mixdepth', default=4,
help='only send coinjoin output to cold storage from this mixdepth')
parser.add_option('-x', '--cs-min-txsize', action='store', type='int',
dest='cs_min_txsize', default=100000000,
help='minimum coinjoin size in satoshis for sending to cold storage')
parser.add_option('-b', '--cs-min-balance', action='store', type='int',
dest='cs_min_balance', default=500000000,
help='minimum balalance in satoshis to keep locally')
parser.add_option('-p', '--password', action='store', type='string',
dest='password', default=nickserv_password,
help='irc nickserv password')
Expand Down Expand Up @@ -334,6 +384,12 @@ def ygmain(ygclass, nickserv_password='', gaplimit=6):
txfee_factor = float(options["txfee_factor"])
cjfee_factor = float(options["cjfee_factor"])
size_factor = float(options["size_factor"])

cswallet_name = options["cswallet_name"]
cs_min_balance = int(options["cs_min_balance"])
cs_min_txsize = int(options["cs_min_txsize"])
cs_mixdepth = int(options["cs_mixdepth"])

if ordertype == 'reloffer':
cjfee_r = options["cjfee_r"]
# minimum size is such that you always net profit at least 20%
Expand Down Expand Up @@ -366,6 +422,20 @@ def ygmain(ygclass, nickserv_password='', gaplimit=6):
wallet_service.sync_wallet(fast=not options["recoversync"])
wallet_service.startService()

if cswallet_name:
cswallet_path = get_wallet_path(cswallet_name, None)
cswallet = open_test_wallet_maybe(
cswallet_path, cswallet_name, 0,
wallet_password_stdin=options["wallet_password_stdin"],
gap_limit=options["gaplimit"])

cswallet_service = WalletService(cswallet)
while not cswallet_service.synced:
cswallet_service.sync_wallet(fast=not options["recoversync"])
cswallet_service.startService()
else:
cswallet_service = None

txtype = wallet_service.get_txtype()
if txtype == "p2wpkh":
prefix = "sw0"
Expand All @@ -382,7 +452,9 @@ def ygmain(ygclass, nickserv_password='', gaplimit=6):

maker = ygclass(wallet_service, [txfee, cjfee_a, cjfee_r,
ordertype, minsize, txfee_factor,
cjfee_factor, size_factor])
cjfee_factor, size_factor],
cswallet_service, [cs_min_balance, cs_min_txsize, cs_mixdepth])

jlog.info('starting yield generator')
clientfactory = JMClientProtocolFactory(maker, proto_type="MAKER")
if jm_single().config.get("SNICKER", "enabled") == "true":
Expand Down
6 changes: 3 additions & 3 deletions scripts/yg-privacyenhanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@

class YieldGeneratorPrivacyEnhanced(YieldGeneratorBasic):

def __init__(self, wallet_service, offerconfig):
super().__init__(wallet_service, offerconfig)

def __init__(self, wallet_service, offerconfig, cswallet_service = None, csconfig = None):
super().__init__(wallet_service, offerconfig, cswallet_service, csconfig)
def select_input_mixdepth(self, available, offer, amount):
"""Mixdepths are in cyclic order and we select the mixdepth to
maximize the largest interval of non-available mixdepths by choosing
Expand Down

0 comments on commit 6149299

Please sign in to comment.