|
@ -278,12 +278,10 @@ class Abstract_Wallet(PrintError): |
|
|
@profiler |
|
|
@profiler |
|
|
def build_spent_outpoints(self): |
|
|
def build_spent_outpoints(self): |
|
|
self.spent_outpoints = {} |
|
|
self.spent_outpoints = {} |
|
|
for txid, tx in self.transactions.items(): |
|
|
for txid, items in self.txi.items(): |
|
|
for txi in tx.inputs(): |
|
|
for addr, l in items.items(): |
|
|
ser = Transaction.get_outpoint_from_txin(txi) |
|
|
for ser, v in l: |
|
|
if ser is None: |
|
|
self.spent_outpoints[ser] = txid |
|
|
continue |
|
|
|
|
|
self.spent_outpoints[ser] = txid |
|
|
|
|
|
|
|
|
|
|
|
@profiler |
|
|
@profiler |
|
|
def check_history(self): |
|
|
def check_history(self): |
|
@ -709,7 +707,12 @@ class Abstract_Wallet(PrintError): |
|
|
h.append((tx_hash, tx_height)) |
|
|
h.append((tx_hash, tx_height)) |
|
|
return h |
|
|
return h |
|
|
|
|
|
|
|
|
def find_pay_to_pubkey_address(self, prevout_hash, prevout_n): |
|
|
def get_txin_address(self, txi): |
|
|
|
|
|
addr = txi.get('address') |
|
|
|
|
|
if addr != "(pubkey)": |
|
|
|
|
|
return addr |
|
|
|
|
|
prevout_hash = txi.get('prevout_hash') |
|
|
|
|
|
prevout_n = txi.get('prevout_n') |
|
|
dd = self.txo.get(prevout_hash, {}) |
|
|
dd = self.txo.get(prevout_hash, {}) |
|
|
for addr, l in dd.items(): |
|
|
for addr, l in dd.items(): |
|
|
for n, v, is_cb in l: |
|
|
for n, v, is_cb in l: |
|
@ -717,6 +720,16 @@ class Abstract_Wallet(PrintError): |
|
|
self.print_error("found pay-to-pubkey address:", addr) |
|
|
self.print_error("found pay-to-pubkey address:", addr) |
|
|
return addr |
|
|
return addr |
|
|
|
|
|
|
|
|
|
|
|
def get_txout_address(self, txo): |
|
|
|
|
|
_type, x, v = txo |
|
|
|
|
|
if _type == TYPE_ADDRESS: |
|
|
|
|
|
addr = x |
|
|
|
|
|
elif _type == TYPE_PUBKEY: |
|
|
|
|
|
addr = bitcoin.public_key_to_p2pkh(bfh(x)) |
|
|
|
|
|
else: |
|
|
|
|
|
addr = None |
|
|
|
|
|
return addr |
|
|
|
|
|
|
|
|
def get_conflicting_transactions(self, tx): |
|
|
def get_conflicting_transactions(self, tx): |
|
|
"""Returns a set of transaction hashes from the wallet history that are |
|
|
"""Returns a set of transaction hashes from the wallet history that are |
|
|
directly conflicting with tx, i.e. they have common outpoints being |
|
|
directly conflicting with tx, i.e. they have common outpoints being |
|
@ -737,11 +750,20 @@ class Abstract_Wallet(PrintError): |
|
|
return conflicting_txns |
|
|
return conflicting_txns |
|
|
|
|
|
|
|
|
def add_transaction(self, tx_hash, tx): |
|
|
def add_transaction(self, tx_hash, tx): |
|
|
if tx in self.transactions: |
|
|
|
|
|
return True |
|
|
|
|
|
is_coinbase = tx.inputs()[0]['type'] == 'coinbase' |
|
|
|
|
|
related = False |
|
|
|
|
|
with self.transaction_lock: |
|
|
with self.transaction_lock: |
|
|
|
|
|
if tx in self.transactions: |
|
|
|
|
|
return True |
|
|
|
|
|
is_coinbase = tx.inputs()[0]['type'] == 'coinbase' |
|
|
|
|
|
tx_height = self.get_tx_height(tx_hash)[0] |
|
|
|
|
|
is_mine = any([self.is_mine(txin['address']) for txin in tx.inputs()]) |
|
|
|
|
|
# do not save if tx is local and not mine |
|
|
|
|
|
if tx_height == TX_HEIGHT_LOCAL and not is_mine: |
|
|
|
|
|
# FIXME the test here should be for "not all is_mine"; cannot detect conflict in some cases |
|
|
|
|
|
return False |
|
|
|
|
|
# raise exception if unrelated to wallet |
|
|
|
|
|
is_for_me = any([self.is_mine(self.get_txout_address(txo)) for txo in tx.outputs()]) |
|
|
|
|
|
if not is_mine and not is_for_me: |
|
|
|
|
|
raise UnrelatedTransactionException() |
|
|
# Find all conflicting transactions. |
|
|
# Find all conflicting transactions. |
|
|
# In case of a conflict, |
|
|
# In case of a conflict, |
|
|
# 1. confirmed > mempool > local |
|
|
# 1. confirmed > mempool > local |
|
@ -751,7 +773,6 @@ class Abstract_Wallet(PrintError): |
|
|
# or drop this txn |
|
|
# or drop this txn |
|
|
conflicting_txns = self.get_conflicting_transactions(tx) |
|
|
conflicting_txns = self.get_conflicting_transactions(tx) |
|
|
if conflicting_txns: |
|
|
if conflicting_txns: |
|
|
tx_height = self.get_tx_height(tx_hash)[0] |
|
|
|
|
|
existing_mempool_txn = any( |
|
|
existing_mempool_txn = any( |
|
|
self.get_tx_height(tx_hash2)[0] in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT) |
|
|
self.get_tx_height(tx_hash2)[0] in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT) |
|
|
for tx_hash2 in conflicting_txns) |
|
|
for tx_hash2 in conflicting_txns) |
|
@ -771,21 +792,17 @@ class Abstract_Wallet(PrintError): |
|
|
to_remove |= self.get_depending_transactions(conflicting_tx_hash) |
|
|
to_remove |= self.get_depending_transactions(conflicting_tx_hash) |
|
|
for tx_hash2 in to_remove: |
|
|
for tx_hash2 in to_remove: |
|
|
self.remove_transaction(tx_hash2) |
|
|
self.remove_transaction(tx_hash2) |
|
|
|
|
|
|
|
|
# add inputs |
|
|
# add inputs |
|
|
self.txi[tx_hash] = d = {} |
|
|
self.txi[tx_hash] = d = {} |
|
|
for txi in tx.inputs(): |
|
|
for txi in tx.inputs(): |
|
|
addr = txi.get('address') |
|
|
addr = self.get_txin_address(txi) |
|
|
if txi['type'] != 'coinbase': |
|
|
if txi['type'] != 'coinbase': |
|
|
prevout_hash = txi['prevout_hash'] |
|
|
prevout_hash = txi['prevout_hash'] |
|
|
prevout_n = txi['prevout_n'] |
|
|
prevout_n = txi['prevout_n'] |
|
|
ser = prevout_hash + ':%d'%prevout_n |
|
|
ser = prevout_hash + ':%d'%prevout_n |
|
|
self.spent_outpoints[ser] = tx_hash |
|
|
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 |
|
|
# find value from prev output |
|
|
if addr and self.is_mine(addr): |
|
|
if addr and self.is_mine(addr): |
|
|
related = True |
|
|
|
|
|
dd = self.txo.get(prevout_hash, {}) |
|
|
dd = self.txo.get(prevout_hash, {}) |
|
|
for n, v, is_cb in dd.get(addr, []): |
|
|
for n, v, is_cb in dd.get(addr, []): |
|
|
if n == prevout_n: |
|
|
if n == prevout_n: |
|
@ -795,20 +812,13 @@ class Abstract_Wallet(PrintError): |
|
|
break |
|
|
break |
|
|
else: |
|
|
else: |
|
|
self.pruned_txo[ser] = tx_hash |
|
|
self.pruned_txo[ser] = tx_hash |
|
|
|
|
|
|
|
|
# add outputs |
|
|
# add outputs |
|
|
self.txo[tx_hash] = d = {} |
|
|
self.txo[tx_hash] = d = {} |
|
|
for n, txo in enumerate(tx.outputs()): |
|
|
for n, txo in enumerate(tx.outputs()): |
|
|
|
|
|
v = txo[2] |
|
|
ser = tx_hash + ':%d'%n |
|
|
ser = tx_hash + ':%d'%n |
|
|
_type, x, v = txo |
|
|
addr = self.get_txout_address(txo) |
|
|
if _type == TYPE_ADDRESS: |
|
|
|
|
|
addr = x |
|
|
|
|
|
elif _type == TYPE_PUBKEY: |
|
|
|
|
|
addr = bitcoin.public_key_to_p2pkh(bfh(x)) |
|
|
|
|
|
else: |
|
|
|
|
|
addr = None |
|
|
|
|
|
if addr and self.is_mine(addr): |
|
|
if addr and self.is_mine(addr): |
|
|
related = True |
|
|
|
|
|
if d.get(addr) is None: |
|
|
if d.get(addr) is None: |
|
|
d[addr] = [] |
|
|
d[addr] = [] |
|
|
d[addr].append((n, v, is_coinbase)) |
|
|
d[addr].append((n, v, is_coinbase)) |
|
@ -820,30 +830,19 @@ class Abstract_Wallet(PrintError): |
|
|
if dd.get(addr) is None: |
|
|
if dd.get(addr) is None: |
|
|
dd[addr] = [] |
|
|
dd[addr] = [] |
|
|
dd[addr].append((ser, v)) |
|
|
dd[addr].append((ser, v)) |
|
|
|
|
|
|
|
|
if not related: |
|
|
|
|
|
raise UnrelatedTransactionException() |
|
|
|
|
|
|
|
|
|
|
|
# save |
|
|
# save |
|
|
self.transactions[tx_hash] = tx |
|
|
self.transactions[tx_hash] = tx |
|
|
return True |
|
|
return True |
|
|
|
|
|
|
|
|
def remove_transaction(self, tx_hash): |
|
|
def remove_transaction(self, tx_hash): |
|
|
def undo_spend(outpoint_to_txid_map): |
|
|
def undo_spend(outpoint_to_txid_map): |
|
|
if tx: |
|
|
for addr, l in self.txi[tx_hash].items(): |
|
|
# if we have the tx, this should often be faster |
|
|
for ser, v in l: |
|
|
for txi in tx.inputs(): |
|
|
|
|
|
ser = Transaction.get_outpoint_from_txin(txi) |
|
|
|
|
|
outpoint_to_txid_map.pop(ser, None) |
|
|
outpoint_to_txid_map.pop(ser, None) |
|
|
else: |
|
|
|
|
|
for ser, hh in list(outpoint_to_txid_map.items()): |
|
|
|
|
|
if hh == tx_hash: |
|
|
|
|
|
outpoint_to_txid_map.pop(ser) |
|
|
|
|
|
|
|
|
|
|
|
with self.transaction_lock: |
|
|
with self.transaction_lock: |
|
|
self.print_error("removing tx from history", tx_hash) |
|
|
self.print_error("removing tx from history", tx_hash) |
|
|
#tx = self.transactions.pop(tx_hash) |
|
|
self.transactions.pop(tx_hash, None) |
|
|
tx = self.transactions.get(tx_hash, None) |
|
|
|
|
|
undo_spend(self.pruned_txo) |
|
|
undo_spend(self.pruned_txo) |
|
|
undo_spend(self.spent_outpoints) |
|
|
undo_spend(self.spent_outpoints) |
|
|
|
|
|
|
|
@ -873,13 +872,17 @@ class Abstract_Wallet(PrintError): |
|
|
|
|
|
|
|
|
def receive_history_callback(self, addr, hist, tx_fees): |
|
|
def receive_history_callback(self, addr, hist, tx_fees): |
|
|
with self.lock: |
|
|
with self.lock: |
|
|
old_hist = self.history.get(addr, []) |
|
|
old_hist = self.get_address_history(addr) |
|
|
for tx_hash, height in old_hist: |
|
|
for tx_hash, height in old_hist: |
|
|
if (tx_hash, height) not in hist: |
|
|
if (tx_hash, height) not in hist: |
|
|
# make tx local |
|
|
# make tx local |
|
|
self.unverified_tx.pop(tx_hash, None) |
|
|
self.unverified_tx.pop(tx_hash, None) |
|
|
self.verified_tx.pop(tx_hash, None) |
|
|
self.verified_tx.pop(tx_hash, None) |
|
|
self.verifier.merkle_roots.pop(tx_hash, None) |
|
|
self.verifier.merkle_roots.pop(tx_hash, None) |
|
|
|
|
|
# but remove completely if not is_mine |
|
|
|
|
|
if self.txi[tx_hash] == {}: |
|
|
|
|
|
# FIXME the test here should be for "not all is_mine"; cannot detect conflict in some cases |
|
|
|
|
|
self.remove_transaction(tx_hash) |
|
|
self.history[addr] = hist |
|
|
self.history[addr] = hist |
|
|
|
|
|
|
|
|
for tx_hash, tx_height in hist: |
|
|
for tx_hash, tx_height in hist: |
|
@ -981,14 +984,9 @@ class Abstract_Wallet(PrintError): |
|
|
output_addresses = [] |
|
|
output_addresses = [] |
|
|
for x in tx.inputs(): |
|
|
for x in tx.inputs(): |
|
|
if x['type'] == 'coinbase': continue |
|
|
if x['type'] == 'coinbase': continue |
|
|
addr = x.get('address') |
|
|
addr = self.get_txin_address(x) |
|
|
if addr == None: continue |
|
|
if addr is None: |
|
|
if addr == "(pubkey)": |
|
|
continue |
|
|
prevout_hash = x.get('prevout_hash') |
|
|
|
|
|
prevout_n = x.get('prevout_n') |
|
|
|
|
|
_addr = self.wallet.find_pay_to_pubkey_address(prevout_hash, prevout_n) |
|
|
|
|
|
if _addr: |
|
|
|
|
|
addr = _addr |
|
|
|
|
|
input_addresses.append(addr) |
|
|
input_addresses.append(addr) |
|
|
for addr, v in tx.get_outputs(): |
|
|
for addr, v in tx.get_outputs(): |
|
|
output_addresses.append(addr) |
|
|
output_addresses.append(addr) |
|
|