# Buggy code
def update_blockheight(self):
    """Can be called manually (on startup, or for tests)
    but will be called as part of main monitoring
    loop to ensure new transactions are added at
    the right height.
    Any failure of the RPC call must result in this returning
    False, otherwise return True (means self.current_blockheight
    has been correctly updated).
    """

    def critical_error():
        jlog.error("Failure to get blockheight from Bitcoin Core.")
        self.stopService()
        return False

    if self.current_blockheight:
        old_blockheight = self.current_blockheight
    else:
        old_blockheight = -1
    try:
        self.current_blockheight = self.bci.get_current_block_height()
    except Exception as e:
        # This should never happen now, as we are catching every
        # possible Exception in jsonrpc or bci.rpc:
        return critical_error()
    if not self.current_blockheight:
        return critical_error()

    # We have received a new blockheight from Core, sanity check it:
    assert isinstance(self.current_blockheight, Integral)
    assert self.current_blockheight >= 0
    if self.current_blockheight < old_blockheight:
        jlog.warn("Bitcoin Core is reporting a lower blockheight, possibly a reorg.")
    return True


# 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 update_blockheight(self):
    """Can be called manually (on startup, or for tests)
    but will be called as part of main monitoring
    loop to ensure new transactions are added at
    the right height.
    Any failure of the RPC call must result in this returning
    False, otherwise return True (means self.current_blockheight
    has been correctly updated).
    """

    def critical_error():
        jlog.error("Critical error updating blockheight.")
        # this cleanup (a) closes the wallet, removing the lock
        # and (b) signals to clients that the service is no longer
        # in a running state, both of which can be useful
        # post reactor shutdown.
        self.stopService()
        stop_reactor()
        return False

    if self.current_blockheight:
        old_blockheight = self.current_blockheight
    else:
        old_blockheight = -1
    try:
        self.current_blockheight = self.bci.get_current_block_height()
    except Exception as e:
        # This should never happen now, as we are catching every
        # possible Exception in jsonrpc or bci.rpc:
        return critical_error()
    if not self.current_blockheight:
        return critical_error()

    # We have received a new blockheight from Core, sanity check it:
    assert isinstance(self.current_blockheight, Integral)
    assert self.current_blockheight >= 0
    if self.current_blockheight < old_blockheight:
        jlog.warn("Bitcoin Core is reporting a lower blockheight, possibly a reorg.")
    return True