# Buggy code
def sync_unspent(self):
    st = time.time()
    # block height needs to be real time for addition to our utxos:
    current_blockheight = self.bci.get_current_block_height()
    wallet_name = self.get_wallet_name()
    self.reset_utxos()

    listunspent_args = []
    if "listunspent_args" in jm_single().config.options("POLICY"):
        listunspent_args = ast.literal_eval(
            jm_single().config.get("POLICY", "listunspent_args")
        )

    unspent_list = self.bci.rpc("listunspent", listunspent_args)
    # filter on label, but note (a) in certain circumstances (in-
    # wallet transfer) it is possible for the utxo to be labeled
    # with the external label, and (b) the wallet will know if it
    # belongs or not anyway (is_known_addr):
    our_unspent_list = [
        x
        for x in unspent_list
        if (
            self.bci.is_address_labeled(x, wallet_name)
            or self.bci.is_address_labeled(x, self.EXTERNAL_WALLET_LABEL)
        )
    ]
    for utxo in our_unspent_list:
        if not self.is_known_addr(utxo["address"]):
            continue
        # The result of bitcoin core's listunspent RPC call does not have
        # a "height" field, only "confirmations".
        # But the result of scantxoutset used in no-history sync does
        # have "height".
        if "height" in utxo:
            height = utxo["height"]
        else:
            height = None
            # wallet's utxo database needs to store an absolute rather
            # than relative height measure:
            confs = int(utxo["confirmations"])
            if confs < 0:
                jlog.warning("Utxo not added, has a conflict: " + str(utxo))
                continue
            if confs >= 1:
                height = current_blockheight - confs + 1
        self._add_unspent_txo(utxo, height)
    et = time.time()
    jlog.debug("bitcoind sync_unspent took " + str((et - st)) + "sec")


# Traceback (jmclient.storage.StorageError)
2020-08-20 13:51:53,349 [DEBUG]  >>privmsg on darkscience: nick=JXXX cmd=pubkey msg=8XXX
2020-08-20 13:52:02,337 [DEBUG]  mix depths that have enough = {0: XXX}
2020-08-20 13:52:02,338 [INFO]  filling offer, mixdepth=0, amount=2XXX
2020-08-20 13:52:02,893 [INFO]  sending output to address=3XXX
Unhandled Error
Traceback (most recent call last):
File "/home/nv01/joinmarket-clientserver/jmvenv/lib/python3.7/site-packages/twisted/protocols/amp.py", line 1041, in _commandReceived
deferred = self.dispatchCommand(box)
File "/home/nv01/joinmarket-clientserver/jmvenv/lib/python3.7/site-packages/twisted/protocols/amp.py", line 1099, in dispatchCommand
return maybeDeferred(responder, box)
File "/home/nv01/joinmarket-clientserver/jmvenv/lib/python3.7/site-packages/twisted/internet/defer.py", line 151, in maybeDeferred
result = f(*args, **kw)
File "/home/nv01/joinmarket-clientserver/jmvenv/lib/python3.7/site-packages/twisted/protocols/amp.py", line 1186, in doit
return maybeDeferred(aCallable, **kw).addCallback(
--- <exception caught here> ---
File "/home/nv01/joinmarket-clientserver/jmvenv/lib/python3.7/site-packages/twisted/internet/defer.py", line 654, in _runCallbacks
current.result = callback(current.result, *args, **kw)
File "/home/nv01/joinmarket-clientserver/jmvenv/lib/python3.7/site-packages/twisted/protocols/amp.py", line 1169, in checkKnownErrors
key = error.trap(*command.allErrors)
File "/home/nv01/joinmarket-clientserver/jmvenv/lib/python3.7/site-packages/twisted/python/failure.py", line 460, in trap
self.raiseException()
File "/home/nv01/joinmarket-clientserver/jmvenv/lib/python3.7/site-packages/twisted/python/failure.py", line 488, in raiseException
raise self.value.with_traceback(self.tb)
File "/home/nv01/joinmarket-clientserver/jmvenv/lib/python3.7/site-packages/twisted/internet/defer.py", line 151, in maybeDeferred
result = f(*args, **kw)
File "/home/nv01/joinmarket-clientserver/jmclient/jmclient/client_protocol.py", line 242, in on_JM_AUTH_RECEIVED
commitment, revelation, amount, kphex)
File "/home/nv01/joinmarket-clientserver/jmbase/jmbase/support.py", line 280, in func_wrapper
return func(inst, *newargs, **kwargs)
File "/home/nv01/joinmarket-clientserver/jmclient/jmclient/maker.py", line 105, in on_auth_received
self.wallet_service.save_wallet()
File "/home/nv01/joinmarket-clientserver/jmclient/jmclient/wallet_service.py", line 769, in save_wallet
self.wallet.save()
File "/home/nv01/joinmarket-clientserver/jmclient/jmclient/wallet.py", line 1531, in save
super().save()
File "/home/nv01/joinmarket-clientserver/jmclient/jmclient/wallet.py", line 1838, in save
super().save()
File "/home/nv01/joinmarket-clientserver/jmclient/jmclient/wallet.py", line 385, in save
self._utxos.save()
File "/home/nv01/joinmarket-clientserver/jmclient/jmclient/wallet.py", line 182, in save
self.storage.save()
File "/home/nv01/joinmarket-clientserver/jmclient/jmclient/storage.py", line 134, in save
raise StorageError("Read-only storage cannot be saved.")
jmclient.storage.StorageError: Read-only storage cannot be saved.

Amp server or network failure unhandled by client application.  Dropping connection!  To avoid, add errbacks to ALL remote commands!
Traceback (most recent call last):
--- <exception caught here> ---
File "/home/nv01/joinmarket-clientserver/jmvenv/lib/python3.7/site-packages/twisted/internet/defer.py", line 654, in _runCallbacks
current.result = callback(current.result, *args, **kw)
File "/home/nv01/joinmarket-clientserver/jmdaemon/jmdaemon/daemon_protocol.py", line 114, in defaultErrback
ConnectionDone, ConnectionLost)
File "/home/nv01/joinmarket-clientserver/jmvenv/lib/python3.7/site-packages/twisted/python/failure.py", line 460, in trap
self.raiseException()
File "/home/nv01/joinmarket-clientserver/jmvenv/lib/python3.7/site-packages/twisted/python/failure.py", line 488, in raiseException
raise self.value.with_traceback(self.tb)
twisted.protocols.amp.UnknownRemoteError: Code<UNKNOWN>: Unknown Error

2020-08-20 13:52:45,357 [DEBUG]  Found a new channel, setting to: JXXX,('nXXX.onion', 6667)
2020-08-20 13:52:45,434 [DEBUG]  Nick: JXXX has left.
2020-08-20 13:53:43,052 [DEBUG]  Dynamic switch nick: JXXX
2020-08-20 13:55:25,035 [DEBUG]  JXXX has dusty minsize, capping at 2730
2020-08-20 13:55:25,155 [DEBUG]  JXXX has dusty minsize, capping at 2730
(...)

# Fixed code
def sync_unspent(self):
    st = time.time()
    # block height needs to be real time for addition to our utxos:
    current_blockheight = self.bci.get_current_block_height()
    if not current_blockheight:
        # this failure will shut down the application elsewhere, here
        # just give up:
        return
    wallet_name = self.get_wallet_name()
    self.reset_utxos()

    listunspent_args = []
    if "listunspent_args" in jm_single().config.options("POLICY"):
        listunspent_args = ast.literal_eval(
            jm_single().config.get("POLICY", "listunspent_args")
        )

    unspent_list = self.bci.rpc("listunspent", listunspent_args)
    # filter on label, but note (a) in certain circumstances (in-
    # wallet transfer) it is possible for the utxo to be labeled
    # with the external label, and (b) the wallet will know if it
    # belongs or not anyway (is_known_addr):
    our_unspent_list = [
        x
        for x in unspent_list
        if (
            self.bci.is_address_labeled(x, wallet_name)
            or self.bci.is_address_labeled(x, self.EXTERNAL_WALLET_LABEL)
        )
    ]
    for utxo in our_unspent_list:
        if not self.is_known_addr(utxo["address"]):
            continue
        # The result of bitcoin core's listunspent RPC call does not have
        # a "height" field, only "confirmations".
        # But the result of scantxoutset used in no-history sync does
        # have "height".
        if "height" in utxo:
            height = utxo["height"]
        else:
            height = None
            # wallet's utxo database needs to store an absolute rather
            # than relative height measure:
            confs = int(utxo["confirmations"])
            if confs < 0:
                jlog.warning("Utxo not added, has a conflict: " + str(utxo))
                continue
            if confs >= 1:
                height = current_blockheight - confs + 1
        self._add_unspent_txo(utxo, height)
    et = time.time()
    jlog.debug("bitcoind sync_unspent took " + str((et - st)) + "sec")