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")
self.on_finished_callback(False)
return False
if not self.filter_orders_callback((self.orderbook, self.total_cj_fee),
self.cjamount):
self.on_finished_callback(False)
return False
if self.filter_orders_callback:
if not self.filter_orders_callback((self.orderbook,
self.total_cj_fee),
self.cjamount):
self.on_finished_callback(False)
return False
self.utxos = {None: self.input_utxos.keys()}
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_log, weighted_order_choose, Taker,
JMTakerClientProtocolFactory, WalletError,
start_reactor)
start_reactor, get_schedule, get_tumble_schedule)
#from joinmarket import load_program_config, get_network, Wallet, encryptData, \
# get_p2pk_vbyte, jm_single, mn_decode, mn_encode, create_wallet_file, \
# validate_address, random_nick, get_log, IRCMessageChannel, \
# weighted_order_choose, get_blockchain_interface_instance, joinmarket_alert, \
# core_alert
def satoshis_to_amt_str(x):
return str(Decimal(x)/Decimal('1e8')) + " BTC"
log = get_log()
donation_address = '1LT6rwv26bV7mgvRosoSCyGM7ttVRsYidP'
donation_address_testnet = 'mz6FQosuiNe8135XaQqWYmXsa3aD8YsqGL'
@ -74,6 +77,7 @@ config_types = {'rpc_port': int,
'usessl': bool,
'socks5': bool,
'network': bool,
'checktx': bool,
'socks5_port': int,
'maker_timeout_sec': int,
'tx_fees': int,
@ -86,6 +90,7 @@ config_types = {'rpc_port': int,
config_tips = {
'blockchain_source': 'options: blockr, bitcoin-rpc',
'network': 'one of "testnet" or "mainnet"',
'checktx': 'whether to check fees before completing transaction',
'rpc_host':
'the host for bitcoind; only used if blockchain_source is bitcoin-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',
'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',
'27183']
'27183', 'true']
if "GUI" not in jm_single().config.sections():
jm_single().config.add_section("GUI")
gui_items = jm_single().config.items("GUI")
@ -164,6 +169,42 @@ def persist_config():
with open('joinmarket.cfg', 'w') as 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):
'''Thread that runs background tasks. Callbacks are guaranteed
to happen in the context of its parent.'''
@ -653,6 +694,162 @@ class SettingsTab(QDialog):
results.append((QLabel(label), qt))
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):
@ -674,7 +871,39 @@ class SpendTab(QWidget):
self.takerInfo)
#Signal indicating Taker has finished its work
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):
vbox = QVBoxLayout(self)
@ -685,11 +914,48 @@ class SpendTab(QWidget):
sA = QScrollArea()
sA.setWidgetResizable(True)
topLayout.addWidget(sA)
iFrame = QFrame()
sA.setWidget(iFrame)
self.qtw = QTabWidget()
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.setSpacing(4)
iFrame.setLayout(innerTopLayout)
self.single_join_tab.setLayout(innerTopLayout)
donateLayout = QHBoxLayout()
self.donateCheckBox = QCheckBox()
@ -728,25 +994,25 @@ class SpendTab(QWidget):
donateLayout.addWidget(label3)
donateLayout.addStretch(1)
innerTopLayout.addLayout(donateLayout, 0, 0, 1, 2)
self.widgets = self.getSettingsWidgets()
self.widgets = getSettingsWidgets()
for i, x in enumerate(self.widgets):
innerTopLayout.addWidget(x[0], i + 1, 0)
innerTopLayout.addWidget(x[1], i + 1, 1, 1, 2)
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.setToolTip(
'You will be prompted to decide whether to accept\n' +
'the transaction after connecting, and shown the\n' +
'fees to pay; you can cancel at that point if you wish.')
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.setEnabled(False)
buttons = QHBoxLayout()
buttons.addStretch(1)
buttons.addWidget(self.startButton)
buttons.addWidget(self.abortButton)
self.abortButton.clicked.connect(self.giveUp)
innerTopLayout.addLayout(buttons, len(self.widgets) + 1, 0, 1, 2)
splitter1 = QSplitter(QtCore.Qt.Vertical)
self.textedit = QTextEdit()
@ -780,9 +1046,13 @@ class SpendTab(QWidget):
def resizeScroll(self, mini, 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
if not self.validateSettings():
if not multiple and not self.validateSettings():
return
if jm_single().config.get("BLOCKCHAIN",
"blockchain_source") == 'blockr':
@ -795,27 +1065,39 @@ class SpendTab(QWidget):
self,
"Connecting to IRC.\nView real-time log in the lower pane.",
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 ...")
jm_single().bc_interface.sync_wallet(w.wallet, fast=True)
self.destaddr = str(self.widgets[0][1].text())
#convert from bitcoins (enforced by QDoubleValidator) to satoshis
self.btc_amount_str = str(self.widgets[3][1].text())
amount = int(Decimal(self.btc_amount_str) * Decimal('1e8'))
makercount = int(self.widgets[1][1].text())
mixdepth = int(self.widgets[2][1].text())
#note 'amount' is integer, so not interpreted as fraction
#TODO allow fractional for mixdepth? would need a dialog/warning
self.taker_schedule = [(mixdepth, amount, makercount,
self.destaddr, 0)]
if not multiple:
destaddr = str(self.widgets[0][1].text())
#convert from bitcoins (enforced by QDoubleValidator) to satoshis
btc_amount_str = str(self.widgets[3][1].text())
amount = int(Decimal(btc_amount_str) * Decimal('1e8'))
makercount = int(self.widgets[1][1].text())
mixdepth = int(self.widgets[2][1].text())
#note 'amount' is integer, so not interpreted as fraction
self.taker_schedule = [(mixdepth, amount, makercount, 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_schedule,
order_chooser=weighted_order_choose,
callbacks=[self.callback_checkOffers,
callbacks=[check_offers_callback,
self.callback_takerInfo,
self.callback_takerFinished])
if ignored_makers:
@ -832,6 +1114,7 @@ class SpendTab(QWidget):
ish=False,
daemon=True))
else:
#This will re-use IRC connections in background (daemon), no restart
self.clientfactory.getClient().taker = self.taker
self.clientfactory.getClient().clientStart()
w.statusBar().showMessage("Connecting to IRC ...")
@ -839,20 +1122,26 @@ class SpendTab(QWidget):
def callback_checkOffers(self, offers_fee, cjamount):
"""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.proposed_amount = cjamount
self.jmclient_obj.emit(QtCore.SIGNAL('JMCLIENT:offers'))
#The JMClient thread must wait for user input
while not self.filter_offers_response:
time.sleep(0.1)
if self.filter_offers_response == "ACCEPT":
self.filter_offers_response = None
#The user is now committed to the transaction
self.abortButton.setEnabled(False)
return True
self.filter_offers_response = None
return False
def callback_takerInfo(self, infotype, infomsg):
if infotype == "ABORT":
#Abort signal explicitly means this transaction will not continue.
self.giveUp()
self.taker_info_type = 'warn'
elif infotype == "INFO":
self.taker_info_type = 'info'
@ -893,11 +1182,9 @@ class SpendTab(QWidget):
self.giveUp()
return
offers, total_cj_fee = self.offers_fee
total_fee_pc = 1.0 * total_cj_fee / self.proposed_amount
#reset the btc amount display string if it's a sweep:
if self.taker.cjamount == 0:
self.btc_amount_str = str(
(Decimal(self.proposed_amount) / Decimal('1e8'))) + " BTC"
total_fee_pc = 1.0 * total_cj_fee / self.taker.cjamount
#Note this will be a new value if sweep, else same as previously entered
btc_amount_str = satoshis_to_amt_str(self.taker.cjamount)
#TODO separate this out into a function
mbinfo = []
@ -912,14 +1199,14 @@ class SpendTab(QWidget):
core_alert[0] + "</font></b>")
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(" ")
mbinfo.append("Counterparties chosen:")
mbinfo.append('Name, Order id, Coinjoin fee (sat.)')
for k, o in offers.iteritems():
if o['ordertype'] == 'reloffer':
display_fee = int(self.proposed_amount *
display_fee = int(self.taker.cjamount *
float(o['cjfee'])) - int(o['txfee'])
elif o['ordertype'] == 'absoffer':
display_fee = int(o['cjfee']) - int(o['txfee'])
@ -943,7 +1230,6 @@ class SpendTab(QWidget):
title=title)
if reply == QMessageBox.Yes:
#amount is now accepted; pass control back to reactor
self.cjamount = self.proposed_amount
self.filter_offers_response = "ACCEPT"
else:
self.filter_offers_response = "REJECT"
@ -951,24 +1237,56 @@ class SpendTab(QWidget):
def takerFinished(self):
if self.taker_finished_fromtx:
#not the final finished transaction
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()
else:
#a transaction failed; just stop
self.giveUp()
else:
#the final, or a permanent failure
if not self.taker_finished_res:
log.info("Did not complete successfully, shutting down")
else:
log.info("All transactions completed correctly")
self.persistTxToHistory(self.taker.my_cj_addr,
self.taker.cjamount,
self.taker.txid)
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):
self.aborted = True
log.debug("Transaction aborted.")
self.abortButton.setEnabled(False)
self.startButton.setEnabled(True)
self.qtw.setTabEnabled(0, True)
self.qtw.setTabEnabled(1, True)
self.toggleButtons(True, False, False, False)
w.statusBar().showMessage("Transaction aborted.")
def cleanUp(self):
@ -999,25 +1317,9 @@ class SpendTab(QWidget):
else:
self.giveUp()
return
else:
w.statusBar().showMessage("Transaction completed successfully.")
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)
self.qtw.setTabEnabled(0, True)
self.qtw.setTabEnabled(1, True)
self.toggleButtons(True, False, False, False)
def validateSettings(self):
valid, errmsg = validate_address(self.widgets[0][1].text())
@ -1071,42 +1373,6 @@ class SpendTab(QWidget):
log.debug("GUI error: unrecognized button, canceling.")
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):

Loading…
Cancel
Save