Skip to content

Commit

Permalink
Add one more situation where we write the cached chain data.
Browse files Browse the repository at this point in the history
bitcoinx will normally rebuild the chain state from the Genesis block every time. We
have a system for writing the chain data to disk on application shutdown. If someone
always kills the application and it does not shut down properly, this can result in
a long application lock up on start up while bitcoinx rebuilds the chain data from the
Genesis block.

This commit writes the chain data when initial synchronisation of headers to the tip on
any header server is complete, which should ensure that we capture both any processing
of outstanding unprocessed headers and any new synchronised headers on application
startup.
  • Loading branch information
rt121212121 authored and AustEcon committed Apr 4, 2023
1 parent cd44e04 commit 4de5f4c
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 5 deletions.
10 changes: 6 additions & 4 deletions electrumsv/app_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import os
import shutil
import threading
import time
from types import TracebackType
from typing import Any, Callable, cast, Coroutine, Type, TYPE_CHECKING, TypeVar

Expand Down Expand Up @@ -116,6 +117,7 @@ def __init__(self, config: SimpleConfig, gui_kind: str) -> None:
self.credentials = CredentialCache()
self.headers: Headers | None = None
self._headers_lock = threading.RLock()
self._last_headers_save: float|None = time.time()
# Not entirely sure these are worth caching, but preserving existing method for now
self.decimal_point = config.get_explicit_type(int, 'decimal_point', 8)
self.num_zeros = config.get_explicit_type(int, 'num_zeros', 0)
Expand Down Expand Up @@ -241,16 +243,16 @@ def connect_header(self, header_bytes: bytes) -> tuple[Header, Chain]:
"""
assert self.headers is not None
with self._headers_lock:
header_and_chain = cast(tuple[Header, Chain], self.headers.connect(header_bytes))
return header_and_chain
return cast(tuple[Header, Chain], self.headers.connect(header_bytes))

def _write_cached_headers_state(self) -> None:
def write_cached_headers_state(self) -> None:
"""
Raises no exception (that we care to catch, see `flush_headers_object`).
"""
with self._headers_lock:
logger.debug("Writing cached headers state")
write_cached_headers(self.headers)
self._last_headers_save = time.time()

async def _follow_longest_valid_chain(self) -> None:
"""
Expand Down Expand Up @@ -308,7 +310,7 @@ def on_stop(self) -> None:
# The headers object may not be created for command-line invocations that do not require it.
if self.headers is not None:
with self._headers_lock:
self._write_cached_headers_state()
self.write_cached_headers_state()
logger.debug("Closing headers store")
close_headers_object(self.headers)

Expand Down
9 changes: 8 additions & 1 deletion electrumsv/cached_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
When bitcoinx loads a headers file, it processes all headers on startup and builds a picture of
the chain state for those headers. What forks there are and at what heights. Unfortunately at the
time of writing this, this can take up to 40 seconds to do and is an unacceptable and perplexing
user experience. Either bitcoinx has to write the chain state to avoid recalculating it or we do.
user experience. Either bitcoinx has to persist the chain state to avoid recalculating it or we do.
It makes little difference so we do it for now.
We need to know which headers in the headers file the chain data applies to. As the headers file is
Expand All @@ -20,6 +20,13 @@
* The headers file has a leading reserved space with values like the header count. We exclude that
from the hashing to ensure that the hash is deterministic even with additional appended headers.
ElectrumSV decisions:
* We try and write out the chain data on two different events. On application shutdown and after
completing initial header synchronisation with a header server. The former is what we normally
expect, and the latter is the minimum we can possibly do to make the user experience for users
who decide to kill the process less painful.
"""

from __future__ import annotations
Expand Down
3 changes: 3 additions & 0 deletions electrumsv/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,9 @@ async def _request_and_connect_headers_at_heights_async(self, server_state: Head
server_state.synchronisation_update_event.set()
server_state.synchronisation_update_event.clear()

logger.debug("Post connect header batch state write attempt")
app_state.write_cached_headers_state()

def status(self) -> NetworkStatusDict:
return {
"blockchain_height": self.get_local_height(),
Expand Down

0 comments on commit 4de5f4c

Please sign in to comment.