mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-03 12:30:07 +00:00
verifier: better handle reorgs (and storage upgrade)
This commit is contained in:
parent
861640949e
commit
41e088693d
5 changed files with 52 additions and 27 deletions
|
@ -31,6 +31,7 @@ from .util import PrintError, profiler, bfh
|
|||
from .transaction import Transaction
|
||||
from .synchronizer import Synchronizer
|
||||
from .verifier import SPV
|
||||
from .blockchain import hash_header
|
||||
from .i18n import _
|
||||
|
||||
TX_HEIGHT_LOCAL = -2
|
||||
|
@ -45,6 +46,7 @@ class UnrelatedTransactionException(AddTransactionException):
|
|||
def __str__(self):
|
||||
return _("Transaction is unrelated to this wallet.")
|
||||
|
||||
|
||||
class AddressSynchronizer(PrintError):
|
||||
"""
|
||||
inherited by wallet
|
||||
|
@ -61,7 +63,7 @@ class AddressSynchronizer(PrintError):
|
|||
self.transaction_lock = threading.RLock()
|
||||
# address -> list(txid, height)
|
||||
self.history = storage.get('addr_history',{})
|
||||
# Verified transactions. txid -> (height, timestamp, block_pos). Access with self.lock.
|
||||
# Verified transactions. txid -> (height, timestamp, block_pos, block_hash). Access with self.lock.
|
||||
self.verified_tx = storage.get('verified_tx3', {})
|
||||
# Transactions pending verification. txid -> tx_height. Access with self.lock.
|
||||
self.unverified_tx = defaultdict(int)
|
||||
|
@ -434,7 +436,7 @@ class AddressSynchronizer(PrintError):
|
|||
"return position, even if the tx is unverified"
|
||||
with self.lock:
|
||||
if tx_hash in self.verified_tx:
|
||||
height, timestamp, pos = self.verified_tx[tx_hash]
|
||||
height, timestamp, pos, header_hash = self.verified_tx[tx_hash]
|
||||
return height, pos
|
||||
elif tx_hash in self.unverified_tx:
|
||||
height = self.unverified_tx[tx_hash]
|
||||
|
@ -462,7 +464,7 @@ class AddressSynchronizer(PrintError):
|
|||
history = []
|
||||
for tx_hash in tx_deltas:
|
||||
delta = tx_deltas[tx_hash]
|
||||
height, conf, timestamp = self.get_tx_height(tx_hash)
|
||||
height, conf, timestamp, header_hash = self.get_tx_height(tx_hash)
|
||||
history.append((tx_hash, height, conf, timestamp, delta))
|
||||
history.sort(key = lambda x: self.get_txpos(x[0]))
|
||||
history.reverse()
|
||||
|
@ -503,24 +505,26 @@ class AddressSynchronizer(PrintError):
|
|||
self._history_local[addr] = cur_hist
|
||||
|
||||
def add_unverified_tx(self, tx_hash, tx_height):
|
||||
if tx_height in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT) \
|
||||
and tx_hash in self.verified_tx:
|
||||
if tx_hash in self.verified_tx:
|
||||
if tx_height in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT):
|
||||
with self.lock:
|
||||
self.verified_tx.pop(tx_hash)
|
||||
if self.verifier:
|
||||
self.verifier.remove_spv_proof_for_tx(tx_hash)
|
||||
else:
|
||||
with self.lock:
|
||||
self.verified_tx.pop(tx_hash)
|
||||
# tx will be verified only if height > 0
|
||||
self.unverified_tx[tx_hash] = tx_height
|
||||
# to remove pending proof requests:
|
||||
if self.verifier:
|
||||
self.verifier.remove_spv_proof_for_tx(tx_hash)
|
||||
|
||||
# tx will be verified only if height > 0
|
||||
if tx_hash not in self.verified_tx:
|
||||
with self.lock:
|
||||
self.unverified_tx[tx_hash] = tx_height
|
||||
|
||||
def add_verified_tx(self, tx_hash, info):
|
||||
# Remove from the unverified map and add to the verified map
|
||||
with self.lock:
|
||||
self.unverified_tx.pop(tx_hash, None)
|
||||
self.verified_tx[tx_hash] = info # (tx_height, timestamp, pos)
|
||||
height, conf, timestamp = self.get_tx_height(tx_hash)
|
||||
self.verified_tx[tx_hash] = info # (tx_height, timestamp, pos, header_hash)
|
||||
height, conf, timestamp, header_hash = self.get_tx_height(tx_hash)
|
||||
self.network.trigger_callback('verified', tx_hash, height, conf, timestamp)
|
||||
|
||||
def get_unverified_txs(self):
|
||||
|
@ -533,12 +537,21 @@ class AddressSynchronizer(PrintError):
|
|||
txs = set()
|
||||
with self.lock:
|
||||
for tx_hash, item in list(self.verified_tx.items()):
|
||||
tx_height, timestamp, pos = item
|
||||
tx_height, timestamp, pos, header_hash = item
|
||||
if tx_height >= height:
|
||||
header = blockchain.read_header(tx_height)
|
||||
# fixme: use block hash, not timestamp
|
||||
if not header or header.get('timestamp') != timestamp:
|
||||
if not header or hash_header(header) != header_hash:
|
||||
self.verified_tx.pop(tx_hash, None)
|
||||
# NOTE: we should add these txns to self.unverified_tx,
|
||||
# but with what height?
|
||||
# If on the new fork after the reorg, the txn is at the
|
||||
# same height, we will not get a status update for the
|
||||
# address. If the txn is not mined or at a diff height,
|
||||
# we should get a status update. Unless we put tx into
|
||||
# unverified_tx, it will turn into local. So we put it
|
||||
# into unverified_tx with the old height, and if we get
|
||||
# a status update, that will overwrite it.
|
||||
self.unverified_tx[tx_hash] = tx_height
|
||||
txs.add(tx_hash)
|
||||
return txs
|
||||
|
||||
|
@ -547,18 +560,18 @@ class AddressSynchronizer(PrintError):
|
|||
return self.network.get_local_height() if self.network else self.storage.get('stored_height', 0)
|
||||
|
||||
def get_tx_height(self, tx_hash):
|
||||
""" Given a transaction, returns (height, conf, timestamp) """
|
||||
""" Given a transaction, returns (height, conf, timestamp, header_hash) """
|
||||
with self.lock:
|
||||
if tx_hash in self.verified_tx:
|
||||
height, timestamp, pos = self.verified_tx[tx_hash]
|
||||
height, timestamp, pos, header_hash = self.verified_tx[tx_hash]
|
||||
conf = max(self.get_local_height() - height + 1, 0)
|
||||
return height, conf, timestamp
|
||||
return height, conf, timestamp, header_hash
|
||||
elif tx_hash in self.unverified_tx:
|
||||
height = self.unverified_tx[tx_hash]
|
||||
return height, 0, None
|
||||
return height, 0, None, None
|
||||
else:
|
||||
# local transaction
|
||||
return TX_HEIGHT_LOCAL, 0, None
|
||||
return TX_HEIGHT_LOCAL, 0, None, None
|
||||
|
||||
def set_up_to_date(self, up_to_date):
|
||||
with self.lock:
|
||||
|
|
|
@ -332,7 +332,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop):
|
|||
column_title = self.headerItem().text(column)
|
||||
column_data = item.text(column)
|
||||
tx_URL = block_explorer_URL(self.config, 'tx', tx_hash)
|
||||
height, conf, timestamp = self.wallet.get_tx_height(tx_hash)
|
||||
height, conf, timestamp, header_hash = self.wallet.get_tx_height(tx_hash)
|
||||
tx = self.wallet.transactions.get(tx_hash)
|
||||
is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
|
||||
is_unconfirmed = height <= 0
|
||||
|
|
|
@ -44,7 +44,7 @@ from .keystore import bip44_derivation
|
|||
|
||||
OLD_SEED_VERSION = 4 # electrum versions < 2.0
|
||||
NEW_SEED_VERSION = 11 # electrum versions >= 2.0
|
||||
FINAL_SEED_VERSION = 17 # electrum >= 2.7 will set this to prevent
|
||||
FINAL_SEED_VERSION = 18 # electrum >= 2.7 will set this to prevent
|
||||
# old versions from overwriting new format
|
||||
|
||||
|
||||
|
@ -356,6 +356,7 @@ class WalletStorage(JsonDB):
|
|||
self.convert_version_15()
|
||||
self.convert_version_16()
|
||||
self.convert_version_17()
|
||||
self.convert_version_18()
|
||||
|
||||
self.put('seed_version', FINAL_SEED_VERSION) # just to be sure
|
||||
self.write()
|
||||
|
@ -570,6 +571,15 @@ class WalletStorage(JsonDB):
|
|||
|
||||
self.put('seed_version', 17)
|
||||
|
||||
def convert_version_18(self):
|
||||
# delete verified_tx3 as its structure changed
|
||||
if not self._is_upgrade_method_needed(17, 17):
|
||||
return
|
||||
|
||||
self.put('verified_tx3', None)
|
||||
|
||||
self.put('seed_version', 18)
|
||||
|
||||
def convert_imported(self):
|
||||
if not self._is_upgrade_method_needed(0, 13):
|
||||
return
|
||||
|
|
|
@ -26,6 +26,7 @@ from typing import Sequence, Optional
|
|||
from .util import ThreadJob, bh2u
|
||||
from .bitcoin import Hash, hash_decode, hash_encode
|
||||
from .transaction import Transaction
|
||||
from .blockchain import hash_header
|
||||
|
||||
|
||||
class MerkleVerificationFailure(Exception): pass
|
||||
|
@ -108,7 +109,8 @@ class SPV(ThreadJob):
|
|||
self.requested_merkle.remove(tx_hash)
|
||||
except KeyError: pass
|
||||
self.print_error("verified %s" % tx_hash)
|
||||
self.wallet.add_verified_tx(tx_hash, (tx_height, header.get('timestamp'), pos))
|
||||
header_hash = hash_header(header)
|
||||
self.wallet.add_verified_tx(tx_hash, (tx_height, header.get('timestamp'), pos, header_hash))
|
||||
if self.is_up_to_date() and self.wallet.is_up_to_date():
|
||||
self.wallet.save_verified_tx(write=True)
|
||||
|
||||
|
|
|
@ -318,7 +318,7 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
if tx.is_complete():
|
||||
if tx_hash in self.transactions.keys():
|
||||
label = self.get_label(tx_hash)
|
||||
height, conf, timestamp = self.get_tx_height(tx_hash)
|
||||
height, conf, timestamp, header_hash = self.get_tx_height(tx_hash)
|
||||
if height > 0:
|
||||
if conf:
|
||||
status = _("{} confirmations").format(conf)
|
||||
|
@ -839,7 +839,7 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
txid, n = txo.split(':')
|
||||
info = self.verified_tx.get(txid)
|
||||
if info:
|
||||
tx_height, timestamp, pos = info
|
||||
tx_height, timestamp, pos, header_hash = info
|
||||
conf = local_height - tx_height
|
||||
else:
|
||||
conf = 0
|
||||
|
@ -1091,7 +1091,7 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
|
||||
def price_at_timestamp(self, txid, price_func):
|
||||
"""Returns fiat price of bitcoin at the time tx got confirmed."""
|
||||
height, conf, timestamp = self.get_tx_height(txid)
|
||||
height, conf, timestamp, header_hash = self.get_tx_height(txid)
|
||||
return price_func(timestamp if timestamp else time.time())
|
||||
|
||||
def unrealized_gains(self, domain, price_func, ccy):
|
||||
|
|
Loading…
Add table
Reference in a new issue