Browse Source

Tumbler via wizard in -Qt working

master
Adam Gibson 9 years ago
parent
commit
841f48da8f
No known key found for this signature in database
GPG Key ID: B3AE09F1E9A3197A
  1. 10
      jmclient/jmclient/taker.py
  2. 452
      scripts/joinmarket-qt.py

10
jmclient/jmclient/taker.py

@ -194,10 +194,12 @@ class Taker(object):
"Could not find orders to complete transaction") "Could not find orders to complete transaction")
self.on_finished_callback(False) self.on_finished_callback(False)
return False return False
if not self.filter_orders_callback((self.orderbook, self.total_cj_fee), if self.filter_orders_callback:
self.cjamount): if not self.filter_orders_callback((self.orderbook,
self.on_finished_callback(False) self.total_cj_fee),
return False self.cjamount):
self.on_finished_callback(False)
return False
self.utxos = {None: self.input_utxos.keys()} self.utxos = {None: self.input_utxos.keys()}
return True return True

452
scripts/joinmarket-qt.py

@ -52,13 +52,16 @@ from jmclient import (load_program_config, get_network, Wallet,
get_p2pk_vbyte, jm_single, validate_address, get_p2pk_vbyte, jm_single, validate_address,
get_log, weighted_order_choose, Taker, get_log, weighted_order_choose, Taker,
JMTakerClientProtocolFactory, WalletError, JMTakerClientProtocolFactory, WalletError,
start_reactor) start_reactor, get_schedule, get_tumble_schedule)
#from joinmarket import load_program_config, get_network, Wallet, encryptData, \ #from joinmarket import load_program_config, get_network, Wallet, encryptData, \
# get_p2pk_vbyte, jm_single, mn_decode, mn_encode, create_wallet_file, \ # get_p2pk_vbyte, jm_single, mn_decode, mn_encode, create_wallet_file, \
# validate_address, random_nick, get_log, IRCMessageChannel, \ # validate_address, random_nick, get_log, IRCMessageChannel, \
# weighted_order_choose, get_blockchain_interface_instance, joinmarket_alert, \ # weighted_order_choose, get_blockchain_interface_instance, joinmarket_alert, \
# core_alert # core_alert
def satoshis_to_amt_str(x):
return str(Decimal(x)/Decimal('1e8')) + " BTC"
log = get_log() log = get_log()
donation_address = '1LT6rwv26bV7mgvRosoSCyGM7ttVRsYidP' donation_address = '1LT6rwv26bV7mgvRosoSCyGM7ttVRsYidP'
donation_address_testnet = 'mz6FQosuiNe8135XaQqWYmXsa3aD8YsqGL' donation_address_testnet = 'mz6FQosuiNe8135XaQqWYmXsa3aD8YsqGL'
@ -74,6 +77,7 @@ config_types = {'rpc_port': int,
'usessl': bool, 'usessl': bool,
'socks5': bool, 'socks5': bool,
'network': bool, 'network': bool,
'checktx': bool,
'socks5_port': int, 'socks5_port': int,
'maker_timeout_sec': int, 'maker_timeout_sec': int,
'tx_fees': int, 'tx_fees': int,
@ -86,6 +90,7 @@ config_types = {'rpc_port': int,
config_tips = { config_tips = {
'blockchain_source': 'options: blockr, bitcoin-rpc', 'blockchain_source': 'options: blockr, bitcoin-rpc',
'network': 'one of "testnet" or "mainnet"', 'network': 'one of "testnet" or "mainnet"',
'checktx': 'whether to check fees before completing transaction',
'rpc_host': 'rpc_host':
'the host for bitcoind; only used if blockchain_source is bitcoin-rpc', 'the host for bitcoind; only used if blockchain_source is bitcoin-rpc',
'rpc_port': 'port for connecting to bitcoind over rpc', 'rpc_port': 'port for connecting to bitcoind over rpc',
@ -143,9 +148,9 @@ def update_config_for_gui():
''' '''
gui_config_names = ['gaplimit', 'history_file', 'check_high_fee', gui_config_names = ['gaplimit', 'history_file', 'check_high_fee',
'max_mix_depth', 'txfee_default', 'order_wait_time', 'max_mix_depth', 'txfee_default', 'order_wait_time',
'daemon_port'] 'daemon_port', 'checktx']
gui_config_default_vals = ['6', 'jm-tx-history.txt', '2', '5', '5000', '30', gui_config_default_vals = ['6', 'jm-tx-history.txt', '2', '5', '5000', '30',
'27183'] '27183', 'true']
if "GUI" not in jm_single().config.sections(): if "GUI" not in jm_single().config.sections():
jm_single().config.add_section("GUI") jm_single().config.add_section("GUI")
gui_items = jm_single().config.items("GUI") gui_items = jm_single().config.items("GUI")
@ -164,6 +169,42 @@ def persist_config():
with open('joinmarket.cfg', 'w') as f: with open('joinmarket.cfg', 'w') as f:
jm_single().config.write(f) jm_single().config.write(f)
def checkAddress(parent, addr):
valid, errmsg = validate_address(str(addr))
if not valid:
JMQtMessageBox(parent,
"Bitcoin address not valid.\n" + errmsg,
mbtype='warn',
title="Error")
def getSettingsWidgets():
results = []
sN = ['Recipient address', 'Number of counterparties', 'Mixdepth',
'Amount in bitcoins (BTC)']
sH = ['The address you want to send the payment to',
'How many other parties to send to; if you enter 4\n' +
', there will be 5 participants, including you',
'The mixdepth of the wallet to send the payment from',
'The amount IN BITCOINS to send.\n' +
'If you enter 0, a SWEEP transaction\nwill be performed,' +
' spending all the coins \nin the given mixdepth.']
sT = [str, int, int, float]
#todo maxmixdepth
sMM = ['', (2, 20),
(0, jm_single().config.getint("GUI", "max_mix_depth") - 1),
(0.00000001, 100.0, 8)]
sD = ['', '3', '0', '']
for x in zip(sN, sH, sT, sD, sMM):
ql = QLabel(x[0])
ql.setToolTip(x[1])
qle = QLineEdit(x[3])
if x[2] == int:
qle.setValidator(QIntValidator(*x[4]))
if x[2] == float:
qle.setValidator(QDoubleValidator(*x[4]))
results.append((ql, qle))
return results
class TaskThread(QtCore.QThread): class TaskThread(QtCore.QThread):
'''Thread that runs background tasks. Callbacks are guaranteed '''Thread that runs background tasks. Callbacks are guaranteed
to happen in the context of its parent.''' to happen in the context of its parent.'''
@ -653,6 +694,162 @@ class SettingsTab(QDialog):
results.append((QLabel(label), qt)) results.append((QLabel(label), qt))
return results return results
""" TODO implement this option
class SchStaticPage(QWizardPage):
def __init__(self, parent):
super(SchStaticPage, self).__init__(parent)
self.setTitle("Manually create a schedule entry")
layout = QGridLayout()
wdgts = getSettingsWidgets()
for i, x in enumerate(wdgts):
layout.addWidget(x[0], i + 1, 0)
layout.addWidget(x[1], i + 1, 1, 1, 2)
wdgts[0][1].editingFinished.connect(
lambda: checkAddress(self, wdgts[0][1].text()))
self.setLayout(layout)
"""
class SchDynamicPage1(QWizardPage):
def __init__(self, parent):
super(SchDynamicPage1, self).__init__(parent)
self.setTitle("Tumble schedule generation")
self.setSubTitle("Set parameters for the sequence of transactions in the tumble.")
results = []
sN = ['Starting mixdepth', 'Average number of counterparties',
'How many mixdepths to tumble through',
'Average wait time between transactions, in seconds',
'Average number of transactions per mixdepth']
#Tooltips
sH = ["The starting mixdepth can be decided from the Wallet tab; it must "
"have coins in it, but it's OK if some coins are in other mixdepths.",
"How many other participants are in each coinjoin, on average; but "
"each individual coinjoin will have a number that's slightly varied "
"from this, randomly",
"For example, if you start at mixdepth 1 and enter 4 here, the tumble "
"will move coins from mixdepth 1 to mixdepth 5",
"This is the time waited *after* 1 confirmation has occurred, and is "
"varied randomly.",
"Will be varied randomly, with a minimum of 1 per mixdepth"]
#types
sT = [int, int, int, float, int]
#constraints
sMM = [(0, jm_single().config.getint("GUI", "max_mix_depth") - 1), (3, 20),
(1, 5), (0.00000001, 100.0, 8), (2, 10)]
sD = ['', '', '', '', '']
for x in zip(sN, sH, sT, sD, sMM):
ql = QLabel(x[0])
ql.setToolTip(x[1])
qle = QLineEdit(x[3])
if x[2] == int:
qle.setValidator(QIntValidator(*x[4]))
if x[2] == float:
qle.setValidator(QDoubleValidator(*x[4]))
results.append((ql, qle))
layout = QGridLayout()
layout.setSpacing(4)
for i, x in enumerate(results):
layout.addWidget(x[0], i + 1, 0)
layout.addWidget(x[1], i + 1, 1, 1, 2)
self.setLayout(layout)
self.registerField("mixdepthsrc*", results[0][1])
self.registerField("makercount*", results[1][1])
self.registerField("mixdepthcount*", results[2][1])
self.registerField("timelambda*", results[3][1])
self.registerField("txcountparams*", results[4][1])
class SchDynamicPage2(QWizardPage):
def __init__(self, parent):
super(SchDynamicPage2, self).__init__(parent)
self.setTitle("Tumble schedule generation 2")
self.setSubTitle("Set destination addresses for tumble.")
layout = QGridLayout()
layout.setSpacing(4)
#by default create three address fields
addrLEs = []
#for testing
testaddrs = ["mteaYsGsLCL9a4cftZFTpGEWXNwZyDt5KS",
"msFGHeut3rfJk5sKuoZNfpUq9MeVMqmido",
"mkZfBXCRPs8fCmwWLrspjCvYozDhK6Eepz"]
for i in range(3):
layout.addWidget(QLabel("Destination address: " + str(i)), i, 0)
addrLEs.append(QLineEdit(testaddrs[i]))
layout.addWidget(addrLEs[-1], i, 1, 1, 2)
#addrLEs[-1].editingFinished.connect(
# lambda: checkAddress(self, addrLEs[-1].text()))
self.registerField("destaddr"+str(i), addrLEs[-1])
self.setLayout(layout)
class SchFinishPage(QWizardPage):
def __init__(self, parent):
super(SchFinishPage, self).__init__(parent)
self.setTitle("Save your schedule")
self.setSubTitle("The schedule will be saved to this file when you click Finish")
layout = QGridLayout()
layout.setSpacing(4)
layout.addWidget(QLabel("Enter schedule name: "), 0, 0)
self.schedName = QLineEdit()
layout.addWidget(self.schedName, 0, 1, 1, 2)
self.registerField("schedfilename*", self.schedName)
self.setLayout(layout)
class SchIntroPage(QWizardPage):
def __init__(self, parent):
super(SchIntroPage, self).__init__(parent)
self.setTitle("Generate a join transaction schedule")
self.rbgroup = QButtonGroup(self)
self.r0 = QRadioButton("Define schedule manually (not yet implemented)")
self.r0.setEnabled(False)
self.r1 = QRadioButton("Generate a tumble schedule automatically")
self.rbgroup.addButton(self.r0)
self.rbgroup.addButton(self.r1)
layout = QVBoxLayout()
layout.addWidget(self.r0)
layout.addWidget(self.r1)
self.setLayout(layout)
"""
def nextId(self):
if self.rbgroup.checkedButton() == self.r0:
self.parent().staticSchedule = True
return 3
elif self.rbgroup.checkedButton() == self.r1:
self.parent().staticSchedule = False
return 1
else:
return 0
"""
class ScheduleWizard(QWizard):
def __init__(self):
super(ScheduleWizard, self).__init__()
self.setWindowTitle("Joinmarket schedule generator")
self.setPage(0, SchIntroPage(self))
self.setPage(1, SchDynamicPage1(self))
self.setPage(2, SchDynamicPage2(self))
#self.setPage(3, SchStaticPage(self))
self.setPage(3, SchFinishPage(self))
def get_schedule(self):
destaddrs = [str(x) for x in [self.field("destaddr0").toString(),
self.field("destaddr1").toString(),
self.field("destaddr2").toString()]]
opts = {}
opts['mixdepthsrc'] = int(self.field("mixdepthsrc").toString())
opts['mixdepthcount'] = int(self.field("mixdepthcount").toString())
opts['txfee'] = -1
opts['addrcount'] = 3
opts['makercountrange'] = (int(self.field("makercount").toString()), 1)
opts['minmakercount'] = 2
opts['txcountparams'] = (int(self.field("txcountparams").toString()), 1)
opts['mintxcount'] = 1
opts['amountpower'] = 100.0
opts['timelambda'] = float(self.field("timelambda").toString())
opts['waittime'] = 20
opts['mincjamount'] = 1000000
#needed for Taker to check:
jm_single().mincjamount = opts['mincjamount']
return get_tumble_schedule(opts, destaddrs)
class SpendTab(QWidget): class SpendTab(QWidget):
@ -674,7 +871,39 @@ class SpendTab(QWidget):
self.takerInfo) self.takerInfo)
#Signal indicating Taker has finished its work #Signal indicating Taker has finished its work
self.jmclient_obj.connect(self.jmclient_obj, QtCore.SIGNAL('JMCLIENT:finished'), self.jmclient_obj.connect(self.jmclient_obj, QtCore.SIGNAL('JMCLIENT:finished'),
self.takerFinished) self.takerFinished)
#will be set in 'multiple join' tab if the user chooses to run a schedule
self.loaded_schedule = None
def generateTumbleSchedule(self):
#needs a set of tumbler options and destination addresses, so needs
#a wizard
wizard = ScheduleWizard()
wizard.exec_()
self.loaded_schedule = wizard.get_schedule()
print(str(self.loaded_schedule))
self.toggleButtons(False, False, True, False)
def selectSchedule(self):
current_path = os.path.dirname(os.path.realpath(__file__))
firstarg = QFileDialog.getOpenFileName(self,
'Choose Schedule File',
directory=current_path)
#TODO validate the schedule
log.debug('Looking for schedule in: ' + firstarg)
if not firstarg:
return
res, schedule = get_schedule(firstarg)
if not res:
JMQtMessageBox(self, "Not a valid JM schedule file", mbtype='crit',
title='Error')
else:
w.statusBar().showMessage("Schedule loaded OK.")
self.sch_label2.setText(os.path.basename(str(firstarg)))
self.schedule_set_button.setEnabled(True)
self.toggleButtons(False, False, True, False)
self.loaded_schedule = schedule
def initUI(self): def initUI(self):
vbox = QVBoxLayout(self) vbox = QVBoxLayout(self)
@ -685,11 +914,48 @@ class SpendTab(QWidget):
sA = QScrollArea() sA = QScrollArea()
sA.setWidgetResizable(True) sA.setWidgetResizable(True)
topLayout.addWidget(sA) topLayout.addWidget(sA)
iFrame = QFrame() self.qtw = QTabWidget()
sA.setWidget(iFrame) sA.setWidget(self.qtw)
self.single_join_tab = QWidget()
self.schedule_tab = QWidget()
self.qtw.addTab(self.single_join_tab, "Single Join")
self.qtw.addTab(self.schedule_tab, "Multiple Join")
#construct layout for scheduler
sch_layout = QGridLayout()
sch_layout.setSpacing(4)
self.schedule_tab.setLayout(sch_layout)
current_schedule_layout = QHBoxLayout()
sch_label1=QLabel("Current schedule: ")
sch_label1.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.sch_label2 = QLabel("None")
current_schedule_layout.addWidget(sch_label1)
current_schedule_layout.addWidget(self.sch_label2)
sch_layout.addLayout(current_schedule_layout, 0, 0, 1, 2)
self.schedule_set_button = QPushButton('Choose schedule file')
self.schedule_set_button.clicked.connect(self.selectSchedule)
self.schedule_generate_button = QPushButton('Generate tumble schedule')
self.schedule_generate_button.clicked.connect(self.generateTumbleSchedule)
#TODO Is it possible to re-use buttons? (start, abort)
self.sch_startButton = QPushButton('Run schedule')
self.sch_startButton.setEnabled(False) #not runnable until schedule chosen
self.sch_startButton.clicked.connect(self.startMultiple)
self.sch_abortButton = QPushButton('Abort')
self.sch_abortButton.setEnabled(False)
self.sch_abortButton.clicked.connect(self.giveUp)
sch_buttons = QHBoxLayout()
sch_buttons.addStretch(1)
sch_buttons.addWidget(self.schedule_set_button)
sch_buttons.addWidget(self.schedule_generate_button)
sch_buttons.addWidget(self.sch_startButton)
sch_buttons.addWidget(self.sch_abortButton)
sch_layout.addLayout(sch_buttons, 1, 0, 1, 2)
innerTopLayout = QGridLayout() innerTopLayout = QGridLayout()
innerTopLayout.setSpacing(4) innerTopLayout.setSpacing(4)
iFrame.setLayout(innerTopLayout) self.single_join_tab.setLayout(innerTopLayout)
donateLayout = QHBoxLayout() donateLayout = QHBoxLayout()
self.donateCheckBox = QCheckBox() self.donateCheckBox = QCheckBox()
@ -728,25 +994,25 @@ class SpendTab(QWidget):
donateLayout.addWidget(label3) donateLayout.addWidget(label3)
donateLayout.addStretch(1) donateLayout.addStretch(1)
innerTopLayout.addLayout(donateLayout, 0, 0, 1, 2) innerTopLayout.addLayout(donateLayout, 0, 0, 1, 2)
self.widgets = self.getSettingsWidgets() self.widgets = getSettingsWidgets()
for i, x in enumerate(self.widgets): for i, x in enumerate(self.widgets):
innerTopLayout.addWidget(x[0], i + 1, 0) innerTopLayout.addWidget(x[0], i + 1, 0)
innerTopLayout.addWidget(x[1], i + 1, 1, 1, 2) innerTopLayout.addWidget(x[1], i + 1, 1, 1, 2)
self.widgets[0][1].editingFinished.connect( self.widgets[0][1].editingFinished.connect(
lambda: self.checkAddress(self.widgets[0][1].text())) lambda: checkAddress(self, self.widgets[0][1].text()))
self.startButton = QPushButton('Start') self.startButton = QPushButton('Start')
self.startButton.setToolTip( self.startButton.setToolTip(
'You will be prompted to decide whether to accept\n' + 'You will be prompted to decide whether to accept\n' +
'the transaction after connecting, and shown the\n' + 'the transaction after connecting, and shown the\n' +
'fees to pay; you can cancel at that point if you wish.') 'fees to pay; you can cancel at that point if you wish.')
self.startButton.clicked.connect(self.startSendPayment) self.startButton.clicked.connect(self.startSendPayment)
#TODO: how to make the Abort button work, at least some of the time..
self.abortButton = QPushButton('Abort') self.abortButton = QPushButton('Abort')
self.abortButton.setEnabled(False) self.abortButton.setEnabled(False)
buttons = QHBoxLayout() buttons = QHBoxLayout()
buttons.addStretch(1) buttons.addStretch(1)
buttons.addWidget(self.startButton) buttons.addWidget(self.startButton)
buttons.addWidget(self.abortButton) buttons.addWidget(self.abortButton)
self.abortButton.clicked.connect(self.giveUp)
innerTopLayout.addLayout(buttons, len(self.widgets) + 1, 0, 1, 2) innerTopLayout.addLayout(buttons, len(self.widgets) + 1, 0, 1, 2)
splitter1 = QSplitter(QtCore.Qt.Vertical) splitter1 = QSplitter(QtCore.Qt.Vertical)
self.textedit = QTextEdit() self.textedit = QTextEdit()
@ -780,9 +1046,13 @@ class SpendTab(QWidget):
def resizeScroll(self, mini, maxi): def resizeScroll(self, mini, maxi):
self.textedit.verticalScrollBar().setValue(maxi) self.textedit.verticalScrollBar().setValue(maxi)
def startSendPayment(self, ignored_makers=None): def startMultiple(self):
self.qtw.setTabEnabled(0, False)
self.startSendPayment(multiple=True)
def startSendPayment(self, ignored_makers=None, multiple=False):
self.aborted = False self.aborted = False
if not self.validateSettings(): if not multiple and not self.validateSettings():
return return
if jm_single().config.get("BLOCKCHAIN", if jm_single().config.get("BLOCKCHAIN",
"blockchain_source") == 'blockr': "blockchain_source") == 'blockr':
@ -795,27 +1065,39 @@ class SpendTab(QWidget):
self, self,
"Connecting to IRC.\nView real-time log in the lower pane.", "Connecting to IRC.\nView real-time log in the lower pane.",
title="Sendpayment") title="Sendpayment")
self.startButton.setEnabled(False)
self.abortButton.setEnabled(True)
log.debug('starting sendpayment') if multiple:
self.toggleButtons(False, False, False, True)
else:
self.toggleButtons(False, True, False, False)
log.debug('starting coinjoin(s)..')
w.statusBar().showMessage("Syncing wallet ...") w.statusBar().showMessage("Syncing wallet ...")
jm_single().bc_interface.sync_wallet(w.wallet, fast=True) jm_single().bc_interface.sync_wallet(w.wallet, fast=True)
self.destaddr = str(self.widgets[0][1].text()) if not multiple:
#convert from bitcoins (enforced by QDoubleValidator) to satoshis destaddr = str(self.widgets[0][1].text())
self.btc_amount_str = str(self.widgets[3][1].text()) #convert from bitcoins (enforced by QDoubleValidator) to satoshis
amount = int(Decimal(self.btc_amount_str) * Decimal('1e8')) btc_amount_str = str(self.widgets[3][1].text())
makercount = int(self.widgets[1][1].text()) amount = int(Decimal(btc_amount_str) * Decimal('1e8'))
mixdepth = int(self.widgets[2][1].text()) makercount = int(self.widgets[1][1].text())
#note 'amount' is integer, so not interpreted as fraction mixdepth = int(self.widgets[2][1].text())
#TODO allow fractional for mixdepth? would need a dialog/warning #note 'amount' is integer, so not interpreted as fraction
self.taker_schedule = [(mixdepth, amount, makercount, self.taker_schedule = [(mixdepth, amount, makercount, destaddr, 0)]
self.destaddr, 0)] else:
assert self.loaded_schedule
self.taker_schedule = self.loaded_schedule
#Decide whether to interrupt processing to sanity check the fees
if jm_single().config.get("GUI", "checktx") == "true":
check_offers_callback = self.callback_checkOffers
else:
check_offers_callback = None
self.taker = Taker(w.wallet, self.taker = Taker(w.wallet,
self.taker_schedule, self.taker_schedule,
order_chooser=weighted_order_choose, order_chooser=weighted_order_choose,
callbacks=[self.callback_checkOffers, callbacks=[check_offers_callback,
self.callback_takerInfo, self.callback_takerInfo,
self.callback_takerFinished]) self.callback_takerFinished])
if ignored_makers: if ignored_makers:
@ -832,6 +1114,7 @@ class SpendTab(QWidget):
ish=False, ish=False,
daemon=True)) daemon=True))
else: else:
#This will re-use IRC connections in background (daemon), no restart
self.clientfactory.getClient().taker = self.taker self.clientfactory.getClient().taker = self.taker
self.clientfactory.getClient().clientStart() self.clientfactory.getClient().clientStart()
w.statusBar().showMessage("Connecting to IRC ...") w.statusBar().showMessage("Connecting to IRC ...")
@ -839,20 +1122,26 @@ class SpendTab(QWidget):
def callback_checkOffers(self, offers_fee, cjamount): def callback_checkOffers(self, offers_fee, cjamount):
"""Receives the signal from the JMClient thread """Receives the signal from the JMClient thread
""" """
if self.aborted:
log.debug("Not processing orders, user has aborted.")
return False
self.offers_fee = offers_fee self.offers_fee = offers_fee
self.proposed_amount = cjamount
self.jmclient_obj.emit(QtCore.SIGNAL('JMCLIENT:offers')) self.jmclient_obj.emit(QtCore.SIGNAL('JMCLIENT:offers'))
#The JMClient thread must wait for user input #The JMClient thread must wait for user input
while not self.filter_offers_response: while not self.filter_offers_response:
time.sleep(0.1) time.sleep(0.1)
if self.filter_offers_response == "ACCEPT": if self.filter_offers_response == "ACCEPT":
self.filter_offers_response = None self.filter_offers_response = None
#The user is now committed to the transaction
self.abortButton.setEnabled(False)
return True return True
self.filter_offers_response = None self.filter_offers_response = None
return False return False
def callback_takerInfo(self, infotype, infomsg): def callback_takerInfo(self, infotype, infomsg):
if infotype == "ABORT": if infotype == "ABORT":
#Abort signal explicitly means this transaction will not continue.
self.giveUp()
self.taker_info_type = 'warn' self.taker_info_type = 'warn'
elif infotype == "INFO": elif infotype == "INFO":
self.taker_info_type = 'info' self.taker_info_type = 'info'
@ -893,11 +1182,9 @@ class SpendTab(QWidget):
self.giveUp() self.giveUp()
return return
offers, total_cj_fee = self.offers_fee offers, total_cj_fee = self.offers_fee
total_fee_pc = 1.0 * total_cj_fee / self.proposed_amount total_fee_pc = 1.0 * total_cj_fee / self.taker.cjamount
#reset the btc amount display string if it's a sweep: #Note this will be a new value if sweep, else same as previously entered
if self.taker.cjamount == 0: btc_amount_str = satoshis_to_amt_str(self.taker.cjamount)
self.btc_amount_str = str(
(Decimal(self.proposed_amount) / Decimal('1e8'))) + " BTC"
#TODO separate this out into a function #TODO separate this out into a function
mbinfo = [] mbinfo = []
@ -912,14 +1199,14 @@ class SpendTab(QWidget):
core_alert[0] + "</font></b>") core_alert[0] + "</font></b>")
mbinfo.append(" ") mbinfo.append(" ")
""" """
mbinfo.append("Sending amount: " + self.btc_amount_str) mbinfo.append("Sending amount: " + btc_amount_str)
mbinfo.append("to address: " + self.destaddr) mbinfo.append("to address: " + self.destaddr)
mbinfo.append(" ") mbinfo.append(" ")
mbinfo.append("Counterparties chosen:") mbinfo.append("Counterparties chosen:")
mbinfo.append('Name, Order id, Coinjoin fee (sat.)') mbinfo.append('Name, Order id, Coinjoin fee (sat.)')
for k, o in offers.iteritems(): for k, o in offers.iteritems():
if o['ordertype'] == 'reloffer': if o['ordertype'] == 'reloffer':
display_fee = int(self.proposed_amount * display_fee = int(self.taker.cjamount *
float(o['cjfee'])) - int(o['txfee']) float(o['cjfee'])) - int(o['txfee'])
elif o['ordertype'] == 'absoffer': elif o['ordertype'] == 'absoffer':
display_fee = int(o['cjfee']) - int(o['txfee']) display_fee = int(o['cjfee']) - int(o['txfee'])
@ -943,7 +1230,6 @@ class SpendTab(QWidget):
title=title) title=title)
if reply == QMessageBox.Yes: if reply == QMessageBox.Yes:
#amount is now accepted; pass control back to reactor #amount is now accepted; pass control back to reactor
self.cjamount = self.proposed_amount
self.filter_offers_response = "ACCEPT" self.filter_offers_response = "ACCEPT"
else: else:
self.filter_offers_response = "REJECT" self.filter_offers_response = "REJECT"
@ -951,24 +1237,56 @@ class SpendTab(QWidget):
def takerFinished(self): def takerFinished(self):
if self.taker_finished_fromtx: if self.taker_finished_fromtx:
#not the final finished transaction
if self.taker_finished_res: if self.taker_finished_res:
jm_single().bc_interface.sync_wallet(wallet) w.statusBar().showMessage("Transaction completed successfully.")
#notify
JMQtMessageBox(self,
"Transaction has been broadcast.\n" + "Txid: " +
str(self.taker.txid),
title="Success")
self.persistTxToHistory(self.taker.my_cj_addr,
self.taker.cjamount,
self.taker.txid)
jm_single().bc_interface.sync_wallet(w.wallet)
self.clientfactory.getClient().clientStart() self.clientfactory.getClient().clientStart()
else: else:
#a transaction failed; just stop #a transaction failed; just stop
self.giveUp() self.giveUp()
else: else:
#the final, or a permanent failure
if not self.taker_finished_res: if not self.taker_finished_res:
log.info("Did not complete successfully, shutting down") log.info("Did not complete successfully, shutting down")
else: else:
log.info("All transactions completed correctly") log.info("All transactions completed correctly")
self.persistTxToHistory(self.taker.my_cj_addr,
self.taker.cjamount,
self.taker.txid)
self.cleanUp() self.cleanUp()
def persistTxToHistory(self, addr, amt, txid):
#persist the transaction to history
with open(jm_single().config.get("GUI", "history_file"), 'ab') as f:
f.write(','.join([addr, satoshis_to_amt_str(amt), txid,
datetime.datetime.now(
).strftime("%Y/%m/%d %H:%M:%S")]))
f.write('\n') #TODO: Windows
#update the TxHistory tab
txhist = w.centralWidget().widget(3)
txhist.updateTxInfo()
def toggleButtons(self, send, abort, schsend, schabort):
self.startButton.setEnabled(send)
self.abortButton.setEnabled(abort)
self.sch_startButton.setEnabled(schsend)
self.sch_abortButton.setEnabled(schabort)
def giveUp(self): def giveUp(self):
self.aborted = True self.aborted = True
log.debug("Transaction aborted.") log.debug("Transaction aborted.")
self.abortButton.setEnabled(False) self.qtw.setTabEnabled(0, True)
self.startButton.setEnabled(True) self.qtw.setTabEnabled(1, True)
self.toggleButtons(True, False, False, False)
w.statusBar().showMessage("Transaction aborted.") w.statusBar().showMessage("Transaction aborted.")
def cleanUp(self): def cleanUp(self):
@ -999,25 +1317,9 @@ class SpendTab(QWidget):
else: else:
self.giveUp() self.giveUp()
return return
self.qtw.setTabEnabled(0, True)
else: self.qtw.setTabEnabled(1, True)
w.statusBar().showMessage("Transaction completed successfully.") self.toggleButtons(True, False, False, False)
JMQtMessageBox(self,
"Transaction has been broadcast.\n" + "Txid: " +
str(self.taker.txid),
title="Success")
#persist the transaction to history
with open(jm_single().config.get("GUI", "history_file"), 'ab') as f:
f.write(','.join([self.destaddr, self.btc_amount_str,
self.taker.txid, datetime.datetime.now(
).strftime("%Y/%m/%d %H:%M:%S")]))
f.write('\n') #TODO: Windows
#update the TxHistory tab
txhist = w.centralWidget().widget(3)
txhist.updateTxInfo()
self.startButton.setEnabled(True)
self.abortButton.setEnabled(False)
def validateSettings(self): def validateSettings(self):
valid, errmsg = validate_address(self.widgets[0][1].text()) valid, errmsg = validate_address(self.widgets[0][1].text())
@ -1071,42 +1373,6 @@ class SpendTab(QWidget):
log.debug("GUI error: unrecognized button, canceling.") log.debug("GUI error: unrecognized button, canceling.")
return True return True
def checkAddress(self, addr):
valid, errmsg = validate_address(str(addr))
if not valid:
JMQtMessageBox(self,
"Bitcoin address not valid.\n" + errmsg,
mbtype='warn',
title="Error")
def getSettingsWidgets(self):
results = []
sN = ['Recipient address', 'Number of counterparties', 'Mixdepth',
'Amount in bitcoins (BTC)']
sH = ['The address you want to send the payment to',
'How many other parties to send to; if you enter 4\n' +
', there will be 5 participants, including you',
'The mixdepth of the wallet to send the payment from',
'The amount IN BITCOINS to send.\n' +
'If you enter 0, a SWEEP transaction\nwill be performed,' +
' spending all the coins \nin the given mixdepth.']
sT = [str, int, int, float]
#todo maxmixdepth
sMM = ['', (2, 20),
(0, jm_single().config.getint("GUI", "max_mix_depth") - 1),
(0.00000001, 100.0, 8)]
sD = ['', '3', '0', '']
for x in zip(sN, sH, sT, sD, sMM):
ql = QLabel(x[0])
ql.setToolTip(x[1])
qle = QLineEdit(x[3])
if x[2] == int:
qle.setValidator(QIntValidator(*x[4]))
if x[2] == float:
qle.setValidator(QDoubleValidator(*x[4]))
results.append((ql, qle))
return results
class TxHistoryTab(QWidget): class TxHistoryTab(QWidget):

Loading…
Cancel
Save