new protocol: the server sends serialized tx, deserialize it in the client
This commit is contained in:
214
lib/wallet.py
214
lib/wallet.py
@@ -64,7 +64,6 @@ class Wallet:
|
||||
self.addresses = config.get('addresses', []) # receiving addresses visible for user
|
||||
self.change_addresses = config.get('change_addresses', []) # addresses used as change
|
||||
self.seed = config.get('seed', '') # encrypted
|
||||
self.history = config.get('history',{})
|
||||
self.labels = config.get('labels',{}) # labels for addresses and transactions
|
||||
self.aliases = config.get('aliases', {}) # aliases for addresses
|
||||
self.authorities = config.get('authorities', {}) # trusted addresses
|
||||
@@ -73,10 +72,13 @@ class Wallet:
|
||||
self.receipts = config.get('receipts',{}) # signed URIs
|
||||
self.addressbook = config.get('contacts', []) # outgoing addresses, for payments
|
||||
self.imported_keys = config.get('imported_keys',{})
|
||||
self.history = config.get('history',{}) # address -> list(txid, height, timestamp)
|
||||
self.transactions = config.get('transactions',{}) # txid -> deserialised
|
||||
|
||||
# not saved
|
||||
self.prevout_values = {}
|
||||
self.spent_outputs = []
|
||||
self.receipt = None # next receipt
|
||||
self.tx_history = {}
|
||||
self.banner = ''
|
||||
|
||||
# spv
|
||||
@@ -91,14 +93,18 @@ class Wallet:
|
||||
self.lock = threading.Lock()
|
||||
self.tx_event = threading.Event()
|
||||
|
||||
self.update_tx_history()
|
||||
if self.seed_version != SEED_VERSION:
|
||||
raise ValueError("This wallet seed is deprecated. Please run upgrade.py for a diagnostic.")
|
||||
|
||||
for tx_hash in self.transactions.keys():
|
||||
self.update_tx_outputs(tx_hash)
|
||||
|
||||
|
||||
def init_up_to_date(self):
|
||||
self.up_to_date_event.clear()
|
||||
self.up_to_date = False
|
||||
|
||||
|
||||
def import_key(self, keypair, password):
|
||||
address, key = keypair.split(':')
|
||||
if not self.is_valid(address):
|
||||
@@ -348,8 +354,8 @@ class Wallet:
|
||||
return (len(self.change_addresses) > 1 ) or ( len(self.addresses) > self.gap_limit )
|
||||
|
||||
def fill_addressbook(self):
|
||||
for tx in self.tx_history.values():
|
||||
if tx['value']<0:
|
||||
for tx_hash, tx in self.transactions.items():
|
||||
if self.get_tx_value(tx_hash)<0:
|
||||
for i in tx['outputs']:
|
||||
if not self.is_mine(i) and i not in self.addressbook:
|
||||
self.addressbook.append(i)
|
||||
@@ -363,13 +369,51 @@ class Wallet:
|
||||
return flags
|
||||
|
||||
|
||||
def get_tx_value(self, tx_hash, addresses = None):
|
||||
# return the balance for that tx
|
||||
if addresses is None: addresses = self.all_addresses()
|
||||
v = 0
|
||||
d = self.transactions.get(tx_hash)
|
||||
if not d: return 0
|
||||
|
||||
for item in d.get('inputs'):
|
||||
addr = item.get('address')
|
||||
if addr in addresses:
|
||||
key = item['prevout_hash'] + ':%d'%item['prevout_n']
|
||||
value = self.prevout_values[ key ]
|
||||
v -= value
|
||||
|
||||
for item in d.get('outputs'):
|
||||
addr = item.get('address')
|
||||
if addr in addresses:
|
||||
value = item.get('value')
|
||||
v += value
|
||||
|
||||
return v
|
||||
|
||||
|
||||
|
||||
def update_tx_outputs(self, tx_hash):
|
||||
tx = self.transactions.get(tx_hash)
|
||||
for item in tx.get('outputs'):
|
||||
value = item.get('value')
|
||||
key = tx_hash+ ':%d'%item.get('index')
|
||||
with self.lock:
|
||||
self.prevout_values[key] = value
|
||||
|
||||
for item in tx.get('inputs'):
|
||||
if self.is_mine(item.get('address')):
|
||||
key = item['prevout_hash'] + ':%d'%item['prevout_n']
|
||||
self.spent_outputs.append(key)
|
||||
|
||||
|
||||
def get_addr_balance(self, addr):
|
||||
assert self.is_mine(addr)
|
||||
h = self.history.get(addr,[])
|
||||
c = u = 0
|
||||
for item in h:
|
||||
v = item['value']
|
||||
if item['height']:
|
||||
for tx_hash, tx_height in h:
|
||||
v = self.get_tx_value(tx_hash, [addr])
|
||||
if tx_height:
|
||||
c += v
|
||||
else:
|
||||
u += v
|
||||
@@ -399,28 +443,35 @@ class Wallet:
|
||||
if i in domain: domain.remove(i)
|
||||
|
||||
for addr in domain:
|
||||
h = self.history.get(addr)
|
||||
if h is None: continue
|
||||
for item in h:
|
||||
if item.get('raw_output_script'):
|
||||
coins.append( (addr,item))
|
||||
h = self.history.get(addr, [])
|
||||
for tx_hash, tx_height, in h:
|
||||
tx = self.transactions.get(tx_hash)
|
||||
for output in tx.get('outputs'):
|
||||
if output.get('address') != addr: continue
|
||||
key = tx_hash + ":%d" % output.get('index')
|
||||
if key in self.spent_outputs: continue
|
||||
output['tx_hash'] = tx_hash
|
||||
coins.append(output)
|
||||
|
||||
coins = sorted( coins, key = lambda x: x[1]['timestamp'] )
|
||||
#coins = sorted( coins, key = lambda x: x[1]['timestamp'] )
|
||||
|
||||
for addr in self.prioritized_addresses:
|
||||
h = self.history.get(addr)
|
||||
if h is None: continue
|
||||
for item in h:
|
||||
if item.get('raw_output_script'):
|
||||
prioritized_coins.append( (addr,item))
|
||||
h = self.history.get(addr, [])
|
||||
for tx_hash, tx_height, in h:
|
||||
for output in tx.get('outputs'):
|
||||
if output.get('address') != addr: continue
|
||||
key = tx_hash + ":%d" % output.get('index')
|
||||
if key in self.spent_outputs: continue
|
||||
output['tx_hash'] = tx_hash
|
||||
prioritized_coins.append(output)
|
||||
|
||||
prioritized_coins = sorted( prioritized_coins, key = lambda x: x[1]['timestamp'] )
|
||||
#prioritized_coins = sorted( prioritized_coins, key = lambda x: x[1]['timestamp'] )
|
||||
|
||||
inputs = []
|
||||
coins = prioritized_coins + coins
|
||||
|
||||
for c in coins:
|
||||
addr, item = c
|
||||
for item in coins:
|
||||
addr = item.get('address')
|
||||
v = item.get('value')
|
||||
total += v
|
||||
inputs.append((addr, v, item['tx_hash'], item['index'], item['raw_output_script'], None, None) )
|
||||
@@ -474,35 +525,46 @@ class Wallet:
|
||||
else:
|
||||
return s
|
||||
|
||||
|
||||
def get_status(self, address):
|
||||
with self.lock:
|
||||
h = self.history.get(address)
|
||||
if not h:
|
||||
status = None
|
||||
else:
|
||||
lastpoint = h[-1]
|
||||
status = lastpoint['block_hash']
|
||||
if status == 'mempool':
|
||||
status = status + ':%d'% len(h)
|
||||
return status
|
||||
if not h: return None
|
||||
status = ''
|
||||
for tx_hash, height in h:
|
||||
status += tx_hash + ':%d:' % height
|
||||
return hashlib.sha256( status ).digest().encode('hex')
|
||||
|
||||
|
||||
def receive_history_callback(self, addr, data):
|
||||
|
||||
def receive_tx_callback(self, tx_hash, d):
|
||||
#print "updating history for", addr
|
||||
with self.lock:
|
||||
self.history[addr] = data
|
||||
self.update_tx_history()
|
||||
self.transactions[tx_hash] = d
|
||||
|
||||
if self.verifier: self.verifier.add(tx_hash)
|
||||
self.update_tx_outputs(tx_hash)
|
||||
self.save()
|
||||
|
||||
|
||||
def receive_history_callback(self, addr, hist):
|
||||
#print "updating history for", addr
|
||||
with self.lock:
|
||||
self.history[addr] = hist
|
||||
self.save()
|
||||
|
||||
|
||||
|
||||
def get_tx_history(self):
|
||||
with self.lock:
|
||||
lines = self.tx_history.values()
|
||||
lines = self.transactions.values()
|
||||
|
||||
lines = sorted(lines, key=operator.itemgetter("timestamp"))
|
||||
return lines
|
||||
|
||||
def get_transactions_at_height(self, height):
|
||||
with self.lock:
|
||||
values = self.tx_history.values()[:]
|
||||
values = self.transactions.values()[:]
|
||||
|
||||
out = []
|
||||
for tx in values:
|
||||
@@ -510,42 +572,27 @@ class Wallet:
|
||||
out.append(tx['tx_hash'])
|
||||
return out
|
||||
|
||||
def update_tx_history(self):
|
||||
self.tx_history= {}
|
||||
for addr in self.all_addresses():
|
||||
h = self.history.get(addr)
|
||||
if h is None: continue
|
||||
for tx in h:
|
||||
tx_hash = tx['tx_hash']
|
||||
line = self.tx_history.get(tx_hash)
|
||||
if not line:
|
||||
self.tx_history[tx_hash] = copy.copy(tx)
|
||||
line = self.tx_history.get(tx_hash)
|
||||
else:
|
||||
line['value'] += tx['value']
|
||||
if line['height'] == 0:
|
||||
line['timestamp'] = 1e12
|
||||
else:
|
||||
if self.verifier: self.verifier.add(tx_hash)
|
||||
|
||||
self.update_tx_labels()
|
||||
|
||||
def update_tx_labels(self):
|
||||
for tx in self.tx_history.values():
|
||||
def get_default_label(self, tx_hash):
|
||||
tx = self.transactions.get(tx_hash)
|
||||
if tx:
|
||||
default_label = ''
|
||||
if tx['value']<0:
|
||||
for o_addr in tx['outputs']:
|
||||
if self.get_tx_value(tx_hash)<0:
|
||||
for o in tx['outputs']:
|
||||
o_addr = o.get('address')
|
||||
if not self.is_mine(o_addr):
|
||||
try:
|
||||
default_label = self.labels[o_addr]
|
||||
except KeyError:
|
||||
default_label = o_addr
|
||||
else:
|
||||
for o_addr in tx['outputs']:
|
||||
for o in tx['outputs']:
|
||||
o_addr = o.get('address')
|
||||
if self.is_mine(o_addr) and not self.is_change(o_addr):
|
||||
break
|
||||
else:
|
||||
for o_addr in tx['outputs']:
|
||||
for o in tx['outputs']:
|
||||
o_addr = o.get('address')
|
||||
if self.is_mine(o_addr):
|
||||
break
|
||||
else:
|
||||
@@ -558,7 +605,8 @@ class Wallet:
|
||||
except KeyError:
|
||||
default_label = o_addr
|
||||
|
||||
tx['default_label'] = default_label
|
||||
return default_label
|
||||
|
||||
|
||||
def mktx(self, to_address, amount, label, password, fee=None, change_addr=None, from_addr= None):
|
||||
if not self.is_valid(to_address):
|
||||
@@ -811,6 +859,7 @@ class Wallet:
|
||||
'frozen_addresses': self.frozen_addresses,
|
||||
'prioritized_addresses': self.prioritized_addresses,
|
||||
'gap_limit': self.gap_limit,
|
||||
'transactions': self.transactions,
|
||||
}
|
||||
for k, v in s.items():
|
||||
self.config.set_key(k,v)
|
||||
@@ -818,7 +867,9 @@ class Wallet:
|
||||
|
||||
def set_verifier(self, verifier):
|
||||
self.verifier = verifier
|
||||
self.update_tx_history()
|
||||
for tx_hash in self.transactions.keys():
|
||||
self.verifier.add(tx_hash)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -862,6 +913,7 @@ class WalletSynchronizer(threading.Thread):
|
||||
|
||||
|
||||
def run(self):
|
||||
requested_tx = []
|
||||
|
||||
# wait until we are connected, in case the user is not connected
|
||||
while not self.interface.is_connected:
|
||||
@@ -897,9 +949,28 @@ class WalletSynchronizer(threading.Thread):
|
||||
|
||||
elif method == 'blockchain.address.get_history':
|
||||
addr = params[0]
|
||||
self.wallet.receive_history_callback(addr, result)
|
||||
hist = []
|
||||
# in the new protocol, we will receive a list of (tx_hash, height)
|
||||
for tx in result: hist.append( (tx['tx_hash'], tx['height']) )
|
||||
# store it
|
||||
self.wallet.receive_history_callback(addr, hist)
|
||||
# request transactions that we don't have
|
||||
for tx_hash, tx_height in hist:
|
||||
if self.wallet.transactions.get(tx_hash) is None and tx_hash not in requested_tx:
|
||||
self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
|
||||
requested_tx.append(tx_hash)
|
||||
|
||||
elif method == 'blockchain.transaction.get':
|
||||
tx_hash = params[0]
|
||||
tx_height = params[1]
|
||||
header = self.wallet.verifier.read_header(tx_height)
|
||||
timestamp = header.get('timestamp')
|
||||
tx = result
|
||||
self.receive_tx(tx_hash, tx_height, timestamp, tx)
|
||||
requested_tx.remove(tx_hash)
|
||||
self.was_updated = True
|
||||
|
||||
|
||||
elif method == 'blockchain.transaction.broadcast':
|
||||
self.wallet.tx_result = result
|
||||
self.wallet.tx_event.set()
|
||||
@@ -916,3 +987,20 @@ class WalletSynchronizer(threading.Thread):
|
||||
self.was_updated = False
|
||||
|
||||
|
||||
def receive_tx(self, tx_hash, tx_height, timestamp, raw_tx):
|
||||
|
||||
assert tx_hash == hash_encode(Hash(raw_tx.decode('hex')))
|
||||
|
||||
import deserialize, BCDataStream
|
||||
|
||||
# deserialize
|
||||
vds = BCDataStream.BCDataStream()
|
||||
vds.write(raw_tx.decode('hex'))
|
||||
d = deserialize.parse_Transaction(vds)
|
||||
d['height'] = tx_height
|
||||
d['tx_hash'] = tx_hash
|
||||
d['timestamp'] = timestamp
|
||||
d['default_label'] = tx_hash
|
||||
print d
|
||||
self.wallet.receive_tx_callback(tx_hash, d)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user