Skip to content

Commit

Permalink
Merge 9134517 into 52acb7a
Browse files Browse the repository at this point in the history
  • Loading branch information
SomberNight committed Feb 1, 2018
2 parents 52acb7a + 9134517 commit 316e171
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 8 deletions.
7 changes: 5 additions & 2 deletions gui/qt/transaction_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,13 @@ def sign_done(success):
self.main_window.sign_tx(self.tx, sign_done)

def save(self):
self.wallet.add_transaction(self.tx.txid(), self.tx)
if not self.wallet.add_transaction(self.tx.txid(), self.tx):
self.show_error(_("Transaction could not be saved. It conflicts with current history."))
return
self.wallet.save_transactions(write=True)

self.main_window.history_list.update()
# need to update at least: history_list, utxo_list, address_list
self.main_window.need_update.set()

self.save_button.setDisabled(True)
self.show_message(_("Transaction saved successfully"))
Expand Down
3 changes: 2 additions & 1 deletion lib/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,8 @@ def addrequest(self, amount, memo='', expiration=None, force=False):
def addtransaction(self, tx):
""" Add a transaction to the wallet history """
tx = Transaction(tx)
self.wallet.add_transaction(tx.txid(), tx)
if not self.wallet.add_transaction(tx.txid(), tx):
return False
self.wallet.save_transactions()
return tx.txid()

Expand Down
26 changes: 25 additions & 1 deletion lib/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

OLD_SEED_VERSION = 4 # electrum versions < 2.0
NEW_SEED_VERSION = 11 # electrum versions >= 2.0
FINAL_SEED_VERSION = 16 # electrum >= 2.7 will set this to prevent
FINAL_SEED_VERSION = 17 # electrum >= 2.7 will set this to prevent
# old versions from overwriting new format


Expand Down Expand Up @@ -258,6 +258,7 @@ def upgrade(self):
self.convert_version_14()
self.convert_version_15()
self.convert_version_16()
self.convert_version_17()

self.put('seed_version', FINAL_SEED_VERSION) # just to be sure
self.write()
Expand Down Expand Up @@ -445,6 +446,29 @@ def remove_from_list(list_name):

self.put('seed_version', 16)

def convert_version_17(self):
if not self._is_upgrade_method_needed(16, 16):
return

from .transaction import Transaction
history = self.get('addr_history', {}) # address -> [[txid, height], ...]
transactions = self.get('transactions', {}) # txid -> raw_tx
spent_outpoints = {}
for addr, lst in history.items():
for txid, height in lst:
raw_tx = transactions.get(txid, None)
if raw_tx is None:
continue
tx = Transaction(raw_tx)
for txi in tx.inputs():
ser = Transaction.get_outpoint_from_txin(txi)
if ser is None:
continue
spent_outpoints[ser] = txid
self.put('spent_outpoints', spent_outpoints)

self.put('seed_version', 17)

def convert_imported(self):
# '/x' is the internal ID for imported accounts
d = self.get('accounts', {}).get('/x', {}).get('imported',{})
Expand Down
8 changes: 8 additions & 0 deletions lib/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,14 @@ def get_preimage_script(self, txin):
def serialize_outpoint(self, txin):
return bh2u(bfh(txin['prevout_hash'])[::-1]) + int_to_hex(txin['prevout_n'], 4)

@classmethod
def get_outpoint_from_txin(cls, txin):
if txin['type'] == 'coinbase':
return None
prevout_hash = txin['prevout_hash']
prevout_n = txin['prevout_n']
return prevout_hash + ':%d' % prevout_n

@classmethod
def serialize_input(self, txin, script):
# Prev hash and index
Expand Down
69 changes: 65 additions & 4 deletions lib/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def __init__(self, storage):
# wallet.up_to_date is true when the wallet is synchronized (stronger requirement)
self.up_to_date = False
self.lock = threading.Lock()
self.transaction_lock = threading.Lock()
self.transaction_lock = threading.RLock()

self.check_history()

Expand Down Expand Up @@ -234,12 +234,14 @@ def load_transactions(self):
self.txo = self.storage.get('txo', {})
self.tx_fees = self.storage.get('tx_fees', {})
self.pruned_txo = self.storage.get('pruned_txo', {})
self.spent_outpoints = self.storage.get('spent_outpoints', {})
tx_list = self.storage.get('transactions', {})
self.transactions = {}
for tx_hash, raw in tx_list.items():
tx = Transaction(raw)
self.transactions[tx_hash] = tx
if self.txi.get(tx_hash) is None and self.txo.get(tx_hash) is None and (tx_hash not in self.pruned_txo.values()):
if self.txi.get(tx_hash) is None and self.txo.get(tx_hash) is None \
and (tx_hash not in self.pruned_txo.values()):
self.print_error("removing unreferenced tx", tx_hash)
self.transactions.pop(tx_hash)

Expand All @@ -254,6 +256,7 @@ def save_transactions(self, write=False):
self.storage.put('txo', self.txo)
self.storage.put('tx_fees', self.tx_fees)
self.storage.put('pruned_txo', self.pruned_txo)
self.storage.put('spent_outpoints', self.spent_outpoints)
self.storage.put('addr_history', self.history)
if write:
self.storage.write()
Expand All @@ -264,6 +267,7 @@ def clear_history(self):
self.txo = {}
self.tx_fees = {}
self.pruned_txo = {}
self.spent_outpoints = {}
self.save_transactions()
with self.lock:
self.history = {}
Expand Down Expand Up @@ -416,7 +420,7 @@ def get_local_height(self):
return self.network.get_local_height() if self.network else self.storage.get('stored_height', 0)

def get_tx_height(self, tx_hash):
""" return the height and timestamp of a transaction. """
""" Given a transaction, returns (height, conf, timestamp) """
with self.lock:
if tx_hash in self.verified_tx:
height, timestamp, pos = self.verified_tx[tx_hash]
Expand Down Expand Up @@ -683,10 +687,54 @@ def find_pay_to_pubkey_address(self, prevout_hash, prevout_n):
self.print_error("found pay-to-pubkey address:", addr)
return addr

def get_conflicting_transactions(self, tx):
"""Returns a set of transaction hashes that are directly conflicting
with tx, i.e. they have common outpoints being spent with tx.
"""
conflicting_txns = set()
with self.transaction_lock:
for txi in tx.inputs():
ser = Transaction.get_outpoint_from_txin(txi)
if ser is None:
continue
spending_tx_hash = self.spent_outpoints.get(ser, None)
if spending_tx_hash is None:
continue
# this outpoint (ser) has already been spent, by spending_tx
if spending_tx_hash not in self.transactions:
# can't find this txn: delete and ignore it
self.spent_outpoints.pop(ser)
continue
conflicting_txns |= {spending_tx_hash}
return conflicting_txns

def add_transaction(self, tx_hash, tx):
is_coinbase = tx.inputs()[0]['type'] == 'coinbase'
related = False
with self.transaction_lock:
# Find all conflicting transactions.
# In case of a conflict,
# 1. mempool (and confirmed txns) have priority over local txns
# 2. this new txn has priority over existing ones
# When this method exits, there must NOT be any conflict, so
# either keep this txn and remove all conflicting (along with dependencies)
# or drop this txn
conflicting_txns = self.get_conflicting_transactions(tx)
if conflicting_txns:
tx_height = self.get_tx_height(tx_hash)[0]
existing_nonlocal_txn = any(self.get_tx_height(tx_hash2)[0] != TX_HEIGHT_LOCAL
for tx_hash2 in conflicting_txns)
if existing_nonlocal_txn and tx_height == TX_HEIGHT_LOCAL:
# this is a local tx that conflicts with non-local txns; drop.
return False
# keep this txn and remove all conflicting
to_remove = set()
to_remove |= conflicting_txns
for conflicting_tx_hash in conflicting_txns:
to_remove |= self.get_depending_transactions(conflicting_tx_hash)
for tx_hash2 in to_remove:
self.remove_transaction(tx_hash2)

# add inputs
self.txi[tx_hash] = d = {}
for txi in tx.inputs():
Expand All @@ -695,6 +743,7 @@ def add_transaction(self, tx_hash, tx):
prevout_hash = txi['prevout_hash']
prevout_n = txi['prevout_n']
ser = prevout_hash + ':%d'%prevout_n
self.spent_outpoints[ser] = tx_hash
if addr == "(pubkey)":
addr = self.find_pay_to_pubkey_address(prevout_hash, prevout_n)
# find value from prev output
Expand Down Expand Up @@ -740,6 +789,7 @@ def add_transaction(self, tx_hash, tx):

# save
self.transactions[tx_hash] = tx
return True

def remove_transaction(self, tx_hash):
with self.transaction_lock:
Expand All @@ -748,6 +798,17 @@ def remove_transaction(self, tx_hash):
for ser, hh in list(self.pruned_txo.items()):
if hh == tx_hash:
self.pruned_txo.pop(ser)
tx = self.transactions.get(tx_hash, None)
if tx:
# if we can find the tx, this should be faster
for txi in tx.inputs():
ser = Transaction.get_outpoint_from_txin(txi)
self.spent_outpoints.pop(ser, None)
else:
for ser, hh in list(self.spent_outpoints.items()):
if hh == tx_hash:
self.spent_outpoints.pop(ser)

# add tx to pruned_txo, and undo the txi addition
for next_tx, dd in self.txi.items():
for addr, l in list(dd.items()):
Expand All @@ -769,8 +830,8 @@ def remove_transaction(self, tx_hash):
self.print_error("tx was not in history", tx_hash)

def receive_tx_callback(self, tx_hash, tx, tx_height):
self.add_transaction(tx_hash, tx)
self.add_unverified_tx(tx_hash, tx_height)
self.add_transaction(tx_hash, tx)

def receive_history_callback(self, addr, hist, tx_fees):
with self.lock:
Expand Down

0 comments on commit 316e171

Please sign in to comment.