Browse Source

qml: Handle situation where no more addresses are available without

creating addresses beyond the gap limit.

- if lightning is enabled, propose to create a lightning-only invoice
- otherwise, propose to reuse an address
- never generate addresses beyond the gap limit

Implementation:
 - createDefaultRequest is removed
 - create_bitcoin_address is called whether the wallet has lightning
   or not
master
ThomasV 3 years ago
parent
commit
2836dccfbb
  1. 5
      electrum/gui/qml/components/ReceiveDialog.qml
  2. 34
      electrum/gui/qml/components/WalletMainView.qml
  3. 77
      electrum/gui/qml/qewallet.py

5
electrum/gui/qml/components/ReceiveDialog.qml

@ -356,12 +356,7 @@ ElDialog {
} }
Component.onCompleted: { Component.onCompleted: {
if (dialog.key) {
request.key = dialog.key request.key = dialog.key
} else {
// callLater to make sure any popups are on top of the dialog stacking order
Qt.callLater(createDefaultRequest)
}
} }
// hack. delay qr rendering until dialog is shown // hack. delay qr rendering until dialog is shown

34
electrum/gui/qml/components/WalletMainView.qml

@ -16,8 +16,9 @@ Item {
property var _sendDialog property var _sendDialog
property string _intentUri property string _intentUri
property bool _ignore_gaplimit: false property string _request_amount
property bool _reuse_address: false property string _request_description
property string _request_expiry
function openInvoice(key) { function openInvoice(key) {
var dialog = invoiceDialog.createObject(app, { invoice: invoiceParser, invoice_key: key }) var dialog = invoiceDialog.createObject(app, { invoice: invoiceParser, invoice_key: key })
@ -171,7 +172,7 @@ Item {
icon.source: '../../icons/tab_receive.png' icon.source: '../../icons/tab_receive.png'
text: qsTr('Receive') text: qsTr('Receive')
onClicked: { onClicked: {
var dialog = receiveDetails.createObject(mainView) var dialog = receiveDetailsDialog.createObject(mainView)
dialog.open() dialog.open()
} }
} }
@ -247,17 +248,15 @@ Item {
openRequest(key) openRequest(key)
} }
function onRequestCreateError(code, error) { function onRequestCreateError(code, error) {
if (code == 'gaplimit') { if (code == 'ln') {
var dialog = app.messageDialog.createObject(app, {text: error, yesno: true}) var dialog = app.messageDialog.createObject(app, {text: error, yesno: true})
dialog.yesClicked.connect(function() { dialog.yesClicked.connect(function() {
_ignore_gaplimit = true createRequest(true, false)
createDefaultRequest()
}) })
} else if (code == 'non-deterministic') { } else if (code == 'reuse_addr') {
var dialog = app.messageDialog.createObject(app, {text: error, yesno: true}) var dialog = app.messageDialog.createObject(app, {text: error, yesno: true})
dialog.yesClicked.connect(function() { dialog.yesClicked.connect(function() {
_reuse_address = true createRequest(false, true)
createDefaultRequest()
}) })
} else { } else {
console.log(error) console.log(error)
@ -354,24 +353,29 @@ Item {
} }
} }
function createRequest(receiveDetailsDialog) { function createRequest(lightning_only, reuse_address) {
var qamt = Config.unitsToSats(receiveDetailsDialog.amount) var qamt = Config.unitsToSats(_request_amount)
Daemon.currentWallet.createRequest(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, _ignore_gaplimit, _reuse_address) Daemon.currentWallet.createRequest(qamt, _request_description, _request_expiry, lightning_only, reuse_address)
} }
Component { Component {
id: receiveDetails
ReceiveDetailsDialog {
id: receiveDetailsDialog id: receiveDetailsDialog
ReceiveDetailsDialog {
id: _receiveDetailsDialog
width: parent.width * 0.9 width: parent.width * 0.9
anchors.centerIn: parent anchors.centerIn: parent
onAccepted: { onAccepted: {
console.log('accepted') console.log('accepted')
createRequest(receiveDetailsDialog) _request_amount = _receiveDetailsDialog.amount
_request_description = _receiveDetailsDialog.description
_request_expiry = _receiveDetailsDialog.expiry
createRequest(false, false)
} }
onRejected: { onRejected: {
console.log('rejected') console.log('rejected')
} }
onClosed: destroy()
} }
} }

77
electrum/gui/qml/qewallet.py

@ -577,39 +577,26 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
threading.Thread(target=pay_thread, daemon=True).start() threading.Thread(target=pay_thread, daemon=True).start()
def create_bitcoin_request(self, amount: int, message: str, expiration: int, *, ignore_gap: bool = False, reuse_address: bool = False) -> Optional[Tuple]: def create_bitcoin_request(self, amount: int, message: str, expiration: int, *, lightning_only: bool = False, reuse_address: bool = False) -> Optional[Tuple]:
addr = self.wallet.get_unused_address() addr = self.wallet.get_unused_address()
if addr is None: if addr is None:
if not self.wallet.is_deterministic(): # imported wallet if reuse_address:
if not reuse_address: addr = self.wallet.get_receiving_address()
elif lightning_only:
addr = None
else:
has_lightning = self.wallet.has_lightning()
msg = [ msg = [
_('No more addresses in your wallet.'), ' ', _('No more unused addresses in your wallet.'),
_('You are using a non-deterministic wallet, which cannot create new addresses.'), ' ', _('All your addresses are used by unpaid requests.'),
_('If you want to create new addresses, use a deterministic wallet instead.'), '\n\n',
_('Creating a new payment request will reuse one of your addresses and overwrite an existing request. Continue anyway?'),
] ]
self.requestCreateError.emit('non-deterministic',''.join(msg)) msg.append(_('Do you wish to create a lightning-only request?') if has_lightning else _('Do you want to reuse an address?'))
return
addr = self.wallet.get_receiving_address()
else: # deterministic wallet
if not ignore_gap:
self.requestCreateError.emit('gaplimit',_("Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\n\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\n\nCreate anyway?"))
return return
addr = self.wallet.create_new_address(False)
req_key = self.wallet.create_request(amount, message, expiration, addr) req_key = self.wallet.create_request(amount, message, expiration, addr)
self._logger.debug(f'created request with key {req_key}') self._logger.debug(f'created request with key {req_key} addr {addr}')
#try:
#self.wallet.add_payment_request(req) return req_key
#except Exception as e:
#self.logger.exception('Error adding payment request')
#self.requestCreateError.emit('fatal',_('Error adding payment request') + ':\n' + repr(e))
#else:
## TODO: check this flow. Only if alias is defined in config. OpenAlias?
#pass
##self.sign_payment_request(addr)
return req_key, addr
def _delete_expired_requests(self): def _delete_expired_requests(self):
keys = self.wallet.delete_expired_requests() keys = self.wallet.delete_expired_requests()
@ -620,17 +607,12 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
@pyqtSlot(QEAmount, str, int, bool) @pyqtSlot(QEAmount, str, int, bool)
@pyqtSlot(QEAmount, str, int, bool, bool) @pyqtSlot(QEAmount, str, int, bool, bool)
@pyqtSlot(QEAmount, str, int, bool, bool, bool) @pyqtSlot(QEAmount, str, int, bool, bool, bool)
def createRequest(self, amount: QEAmount, message: str, expiration: int, ignore_gap: bool = False, reuse_address: bool = False): def createRequest(self, amount: QEAmount, message: str, expiration: int, lightning_only: bool = False, reuse_address: bool = False):
self._delete_expired_requests() self._delete_expired_requests()
try: try:
if self.wallet.lnworker and self.wallet.lnworker.channels: key = self.create_bitcoin_request(amount.satsInt, message, expiration, lightning_only=lightning_only, reuse_address=reuse_address)
# TODO maybe show a warning if amount exceeds lnworker.num_sats_can_receive (as in kivy)
# TODO fallback address robustness
addr = self.wallet.get_unused_address()
key = self.wallet.create_request(amount.satsInt, message, expiration, addr)
else:
key, addr = self.create_bitcoin_request(amount.satsInt, message, expiration, ignore_gap=ignore_gap, reuse_address=reuse_address)
if not key: if not key:
self.requestCreateError.emit('ln' if self.wallet.has_lightning() else 'reuse_addr', ' '.join(msg))
return return
self.addressModel.setDirty() self.addressModel.setDirty()
except InvoiceError as e: except InvoiceError as e:
@ -641,33 +623,6 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
self.requestModel.add_invoice(self.wallet.get_request(key)) self.requestModel.add_invoice(self.wallet.get_request(key))
self.requestCreateSuccess.emit(key) self.requestCreateSuccess.emit(key)
@pyqtSlot()
@pyqtSlot(bool)
@pyqtSlot(bool, bool)
def createDefaultRequest(self, ignore_gap: bool = False, reuse_address: bool = False):
self._delete_expired_requests()
try:
default_expiry = self.wallet.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
if self.wallet.lnworker and self.wallet.lnworker.channels:
addr = self.wallet.get_unused_address()
# if addr is None, we ran out of addresses
if addr is None:
# TODO: remove oldest unpaid request having a fallback address and try again
pass
key = self.wallet.create_request(None, None, default_expiry, addr)
else:
req = self.create_bitcoin_request(None, None, default_expiry, ignore_gap=ignore_gap, reuse_address=reuse_address)
if not req:
return
key, addr = req
except InvoiceError as e:
self.requestCreateError.emit('fatal',_('Error creating payment request') + ':\n' + str(e))
return
assert key is not None
self.requestModel.add_invoice(self.wallet.get_request(key))
self.requestCreateSuccess.emit(key)
@pyqtSlot(str) @pyqtSlot(str)
def delete_request(self, key: str): def delete_request(self, key: str):
self._logger.debug('delete req %s' % key) self._logger.debug('delete req %s' % key)

Loading…
Cancel
Save