Merge the network and network_proxy
This commit is contained in:
parent
4d6a0f29ee
commit
2d05e7d891
28
electrum
28
electrum
|
@ -77,7 +77,7 @@ if is_bundle or is_local or is_android:
|
||||||
|
|
||||||
|
|
||||||
from electrum import util
|
from electrum import util
|
||||||
from electrum import SimpleConfig, Network, Wallet, WalletStorage, NetworkProxy
|
from electrum import SimpleConfig, Network, Wallet, WalletStorage
|
||||||
from electrum.util import print_msg, print_error, print_stderr, print_json, set_verbosity, InvalidPassword
|
from electrum.util import print_msg, print_error, print_stderr, print_json, set_verbosity, InvalidPassword
|
||||||
from electrum.plugins import init_plugins, run_hook, always_hook
|
from electrum.plugins import init_plugins, run_hook, always_hook
|
||||||
from electrum.commands import get_parser, known_commands, Commands, config_variables
|
from electrum.commands import get_parser, known_commands, Commands, config_variables
|
||||||
|
@ -97,12 +97,12 @@ def prompt_password(prompt, confirm=True):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def init_gui(config, network_proxy):
|
def init_gui(config, network):
|
||||||
gui_name = config.get('gui', 'qt')
|
gui_name = config.get('gui', 'qt')
|
||||||
if gui_name in ['lite', 'classic']:
|
if gui_name in ['lite', 'classic']:
|
||||||
gui_name = 'qt'
|
gui_name = 'qt'
|
||||||
gui = __import__('electrum_gui.' + gui_name, fromlist=['electrum_gui'])
|
gui = __import__('electrum_gui.' + gui_name, fromlist=['electrum_gui'])
|
||||||
gui = gui.ElectrumGui(config, network_proxy)
|
gui = gui.ElectrumGui(config, network)
|
||||||
return gui
|
return gui
|
||||||
|
|
||||||
|
|
||||||
|
@ -157,8 +157,7 @@ def init_cmdline(config):
|
||||||
wallet = Wallet.from_seed(seed, password, storage)
|
wallet = Wallet.from_seed(seed, password, storage)
|
||||||
|
|
||||||
if not config.get('offline'):
|
if not config.get('offline'):
|
||||||
s = get_daemon(config, False)
|
network = Network(config)
|
||||||
network = NetworkProxy(s, config)
|
|
||||||
network.start()
|
network.start()
|
||||||
wallet.start_threads(network)
|
wallet.start_threads(network)
|
||||||
print_msg("Recovering wallet...")
|
print_msg("Recovering wallet...")
|
||||||
|
@ -332,13 +331,12 @@ class ClientThread(util.DaemonThread):
|
||||||
|
|
||||||
class NetworkServer(util.DaemonThread):
|
class NetworkServer(util.DaemonThread):
|
||||||
|
|
||||||
def __init__(self, config, network_proxy):
|
def __init__(self, config, network):
|
||||||
util.DaemonThread.__init__(self)
|
util.DaemonThread.__init__(self)
|
||||||
self.debug = False
|
self.debug = False
|
||||||
self.config = config
|
self.config = config
|
||||||
self.pipe = util.QueuePipe()
|
self.pipe = util.QueuePipe()
|
||||||
self.network_proxy = network_proxy
|
self.network = network
|
||||||
self.network = self.network_proxy.network
|
|
||||||
self.lock = threading.RLock()
|
self.lock = threading.RLock()
|
||||||
# each GUI is a client of the daemon
|
# each GUI is a client of the daemon
|
||||||
self.clients = []
|
self.clients = []
|
||||||
|
@ -516,11 +514,11 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
# daemon is not running
|
# daemon is not running
|
||||||
if cmd_name == 'gui':
|
if cmd_name == 'gui':
|
||||||
network_proxy = NetworkProxy(None, config)
|
network = Network(config)
|
||||||
network_proxy.start()
|
network.start()
|
||||||
server = NetworkServer(config, network_proxy)
|
server = NetworkServer(config, network)
|
||||||
server.start()
|
server.start()
|
||||||
server.gui = init_gui(config, network_proxy)
|
server.gui = init_gui(config, network)
|
||||||
server.gui.main()
|
server.gui.main()
|
||||||
elif cmd_name == 'daemon':
|
elif cmd_name == 'daemon':
|
||||||
subcommand = config.get('subcommand')
|
subcommand = config.get('subcommand')
|
||||||
|
@ -530,9 +528,9 @@ if __name__ == '__main__':
|
||||||
elif subcommand == 'start':
|
elif subcommand == 'start':
|
||||||
p = os.fork()
|
p = os.fork()
|
||||||
if p == 0:
|
if p == 0:
|
||||||
network_proxy = NetworkProxy(None, config)
|
network = Network(config)
|
||||||
network_proxy.start()
|
network.start()
|
||||||
server = NetworkServer(config, network_proxy)
|
server = NetworkServer(config, network)
|
||||||
if config.get('websocket_server'):
|
if config.get('websocket_server'):
|
||||||
import websockets
|
import websockets
|
||||||
websockets.WebSocketServer(config, server).start()
|
websockets.WebSocketServer(config, server).start()
|
||||||
|
|
|
@ -2357,7 +2357,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
|
txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
|
||||||
if ok and txid:
|
if ok and txid:
|
||||||
try:
|
try:
|
||||||
r = self.network.synchronous_get([('blockchain.transaction.get',[str(txid)])])[0]
|
r = self.network.synchronous_get(('blockchain.transaction.get',[str(txid)]))
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
self.show_message(str(e))
|
self.show_message(str(e))
|
||||||
return
|
return
|
||||||
|
|
|
@ -11,4 +11,3 @@ import transaction
|
||||||
from transaction import Transaction
|
from transaction import Transaction
|
||||||
from plugins import BasePlugin
|
from plugins import BasePlugin
|
||||||
from commands import Commands, known_commands
|
from commands import Commands, known_commands
|
||||||
from network_proxy import NetworkProxy
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ class Commands:
|
||||||
"""Return the transaction history of any address. Note: This is a
|
"""Return the transaction history of any address. Note: This is a
|
||||||
walletless server query, results are not checked by SPV.
|
walletless server query, results are not checked by SPV.
|
||||||
"""
|
"""
|
||||||
return self.network.synchronous_get([('blockchain.address.get_history', [address])])[0]
|
return self.network.synchronous_get(('blockchain.address.get_history', [address]))
|
||||||
|
|
||||||
@command('nw')
|
@command('nw')
|
||||||
def listunspent(self):
|
def listunspent(self):
|
||||||
|
@ -165,16 +165,15 @@ class Commands:
|
||||||
"""Returns the UTXO list of any address. Note: This
|
"""Returns the UTXO list of any address. Note: This
|
||||||
is a walletless server query, results are not checked by SPV.
|
is a walletless server query, results are not checked by SPV.
|
||||||
"""
|
"""
|
||||||
return self.network.synchronous_get([('blockchain.address.listunspent', [address])])[0]
|
return self.network.synchronous_get(('blockchain.address.listunspent', [address]))
|
||||||
|
|
||||||
@command('n')
|
@command('n')
|
||||||
def getutxoaddress(self, txid, pos):
|
def getutxoaddress(self, txid, pos):
|
||||||
"""Get the address of a UTXO. Note: This is a walletless server query, results are
|
"""Get the address of a UTXO. Note: This is a walletless server query, results are
|
||||||
not checked by SPV.
|
not checked by SPV.
|
||||||
"""
|
"""
|
||||||
r = self.network.synchronous_get([('blockchain.utxo.get_address', [txid, pos])])
|
r = self.network.synchronous_get(('blockchain.utxo.get_address', [txid, pos]))
|
||||||
if r:
|
return {'address': r}
|
||||||
return {'address':r[0]}
|
|
||||||
|
|
||||||
@command('wp')
|
@command('wp')
|
||||||
def createrawtx(self, inputs, outputs, unsigned=False):
|
def createrawtx(self, inputs, outputs, unsigned=False):
|
||||||
|
@ -219,7 +218,7 @@ class Commands:
|
||||||
def broadcast(self, tx):
|
def broadcast(self, tx):
|
||||||
"""Broadcast a transaction to the network. """
|
"""Broadcast a transaction to the network. """
|
||||||
t = Transaction(tx)
|
t = Transaction(tx)
|
||||||
return self.network.synchronous_get([('blockchain.transaction.broadcast', [str(t)])])[0]
|
return self.network.synchronous_get(('blockchain.transaction.broadcast', [str(t)]))
|
||||||
|
|
||||||
@command('')
|
@command('')
|
||||||
def createmultisig(self, num, pubkeys):
|
def createmultisig(self, num, pubkeys):
|
||||||
|
@ -287,7 +286,7 @@ class Commands:
|
||||||
"""Return the balance of any address. Note: This is a walletless
|
"""Return the balance of any address. Note: This is a walletless
|
||||||
server query, results are not checked by SPV.
|
server query, results are not checked by SPV.
|
||||||
"""
|
"""
|
||||||
out = self.network.synchronous_get([('blockchain.address.get_balance', [address])])[0]
|
out = self.network.synchronous_get(('blockchain.address.get_balance', [address]))
|
||||||
out["confirmed"] = str(Decimal(out["confirmed"])/COIN)
|
out["confirmed"] = str(Decimal(out["confirmed"])/COIN)
|
||||||
out["unconfirmed"] = str(Decimal(out["unconfirmed"])/COIN)
|
out["unconfirmed"] = str(Decimal(out["unconfirmed"])/COIN)
|
||||||
return out
|
return out
|
||||||
|
@ -295,7 +294,7 @@ class Commands:
|
||||||
@command('n')
|
@command('n')
|
||||||
def getproof(self, address):
|
def getproof(self, address):
|
||||||
"""Get Merkle branch of an address in the UTXO set"""
|
"""Get Merkle branch of an address in the UTXO set"""
|
||||||
p = self.network.synchronous_get([('blockchain.address.get_proof', [address])])[0]
|
p = self.network.synchronous_get(('blockchain.address.get_proof', [address]))
|
||||||
out = []
|
out = []
|
||||||
for i,s in p:
|
for i,s in p:
|
||||||
out.append(i)
|
out.append(i)
|
||||||
|
@ -305,7 +304,7 @@ class Commands:
|
||||||
def getmerkle(self, txid, height):
|
def getmerkle(self, txid, height):
|
||||||
"""Get Merkle branch of a transaction included in a block. Electrum
|
"""Get Merkle branch of a transaction included in a block. Electrum
|
||||||
uses this to verify transactions (Simple Payment Verification)."""
|
uses this to verify transactions (Simple Payment Verification)."""
|
||||||
return self.network.synchronous_get([('blockchain.transaction.get_merkle', [txid, int(height)])])[0]
|
return self.network.synchronous_get(('blockchain.transaction.get_merkle', [txid, int(height)]))
|
||||||
|
|
||||||
@command('n')
|
@command('n')
|
||||||
def getservers(self):
|
def getservers(self):
|
||||||
|
@ -522,7 +521,7 @@ class Commands:
|
||||||
"""Retrieve a transaction. """
|
"""Retrieve a transaction. """
|
||||||
tx = self.wallet.transactions.get(txid) if self.wallet else None
|
tx = self.wallet.transactions.get(txid) if self.wallet else None
|
||||||
if tx is None and self.network:
|
if tx is None and self.network:
|
||||||
raw = self.network.synchronous_get([('blockchain.transaction.get', [txid])])[0]
|
raw = self.network.synchronous_get(('blockchain.transaction.get', [txid]))
|
||||||
if raw:
|
if raw:
|
||||||
tx = Transaction(raw)
|
tx = Transaction(raw)
|
||||||
else:
|
else:
|
||||||
|
|
131
lib/network.py
131
lib/network.py
|
@ -5,7 +5,8 @@ import sys
|
||||||
import random
|
import random
|
||||||
import select
|
import select
|
||||||
import traceback
|
import traceback
|
||||||
from collections import deque
|
from collections import defaultdict, deque
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
import socks
|
import socks
|
||||||
import socket
|
import socket
|
||||||
|
@ -129,20 +130,19 @@ class Network(util.DaemonThread):
|
||||||
|
|
||||||
Our external API:
|
Our external API:
|
||||||
|
|
||||||
- Member functions get_header(), get_parameters(), get_status_value(),
|
- Member functions get_header(), get_interfaces(), get_local_height(),
|
||||||
new_blockchain_height(), set_parameters(), start(),
|
get_parameters(), get_server_height(), get_status_value(),
|
||||||
|
is_connected(), new_blockchain_height(), set_parameters(), start(),
|
||||||
stop()
|
stop()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, pipe, config=None):
|
def __init__(self, config=None):
|
||||||
if config is None:
|
if config is None:
|
||||||
config = {} # Do not use mutables as default values!
|
config = {} # Do not use mutables as default values!
|
||||||
util.DaemonThread.__init__(self)
|
util.DaemonThread.__init__(self)
|
||||||
self.config = SimpleConfig(config) if type(config) == type({}) else config
|
self.config = SimpleConfig(config) if type(config) == type({}) else config
|
||||||
self.num_server = 8 if not self.config.get('oneserver') else 0
|
self.num_server = 8 if not self.config.get('oneserver') else 0
|
||||||
self.blockchain = Blockchain(self.config, self)
|
self.blockchain = Blockchain(self.config, self)
|
||||||
self.requests_queue = pipe.send_queue
|
|
||||||
self.response_queue = pipe.get_queue
|
|
||||||
# A deque of interface header requests, processed left-to-right
|
# A deque of interface header requests, processed left-to-right
|
||||||
self.bc_requests = deque()
|
self.bc_requests = deque()
|
||||||
# Server for addresses and transactions
|
# Server for addresses and transactions
|
||||||
|
@ -155,6 +155,10 @@ class Network(util.DaemonThread):
|
||||||
if not self.default_server:
|
if not self.default_server:
|
||||||
self.default_server = pick_random_server()
|
self.default_server = pick_random_server()
|
||||||
|
|
||||||
|
self.lock = Lock()
|
||||||
|
self.pending_sends = []
|
||||||
|
self.message_id = 0
|
||||||
|
self.debug = False
|
||||||
self.irc_servers = {} # returned by interface (list from irc)
|
self.irc_servers = {} # returned by interface (list from irc)
|
||||||
self.recent_servers = self.read_recent_servers()
|
self.recent_servers = self.read_recent_servers()
|
||||||
|
|
||||||
|
@ -163,6 +167,8 @@ class Network(util.DaemonThread):
|
||||||
self.heights = {}
|
self.heights = {}
|
||||||
self.merkle_roots = {}
|
self.merkle_roots = {}
|
||||||
self.utxo_roots = {}
|
self.utxo_roots = {}
|
||||||
|
self.subscriptions = defaultdict(list)
|
||||||
|
self.callbacks = defaultdict(list)
|
||||||
|
|
||||||
dir_path = os.path.join( self.config.path, 'certs')
|
dir_path = os.path.join( self.config.path, 'certs')
|
||||||
if not os.path.exists(dir_path):
|
if not os.path.exists(dir_path):
|
||||||
|
@ -188,6 +194,15 @@ class Network(util.DaemonThread):
|
||||||
self.start_network(deserialize_server(self.default_server)[2],
|
self.start_network(deserialize_server(self.default_server)[2],
|
||||||
deserialize_proxy(self.config.get('proxy')))
|
deserialize_proxy(self.config.get('proxy')))
|
||||||
|
|
||||||
|
def register_callback(self, event, callback):
|
||||||
|
with self.lock:
|
||||||
|
self.callbacks[event].append(callback)
|
||||||
|
|
||||||
|
def trigger_callback(self, event, params=()):
|
||||||
|
with self.lock:
|
||||||
|
callbacks = self.callbacks[event][:]
|
||||||
|
[callback(*params) for callback in callbacks]
|
||||||
|
|
||||||
def read_recent_servers(self):
|
def read_recent_servers(self):
|
||||||
if not self.config.path:
|
if not self.config.path:
|
||||||
return []
|
return []
|
||||||
|
@ -231,6 +246,12 @@ class Network(util.DaemonThread):
|
||||||
def is_connected(self):
|
def is_connected(self):
|
||||||
return self.interface is not None
|
return self.interface is not None
|
||||||
|
|
||||||
|
def is_connecting(self):
|
||||||
|
return self.connection_status == 'connecting'
|
||||||
|
|
||||||
|
def is_up_to_date(self):
|
||||||
|
return self.unanswered_requests == {}
|
||||||
|
|
||||||
def queue_request(self, method, params):
|
def queue_request(self, method, params):
|
||||||
self.interface.queue_request({'method': method, 'params': params})
|
self.interface.queue_request({'method': method, 'params': params})
|
||||||
|
|
||||||
|
@ -263,7 +284,10 @@ class Network(util.DaemonThread):
|
||||||
|
|
||||||
def notify(self, key):
|
def notify(self, key):
|
||||||
value = self.get_status_value(key)
|
value = self.get_status_value(key)
|
||||||
self.response_queue.put({'method':'network.status', 'params':[key, value]})
|
if key in ['status', 'updated']:
|
||||||
|
self.trigger_callback(key)
|
||||||
|
else:
|
||||||
|
self.trigger_callback(key, (value,))
|
||||||
|
|
||||||
def get_parameters(self):
|
def get_parameters(self):
|
||||||
host, port, protocol = deserialize_server(self.default_server)
|
host, port, protocol = deserialize_server(self.default_server)
|
||||||
|
@ -337,8 +361,16 @@ class Network(util.DaemonThread):
|
||||||
self.socket_queue = Queue.Queue()
|
self.socket_queue = Queue.Queue()
|
||||||
|
|
||||||
def set_parameters(self, host, port, protocol, proxy, auto_connect):
|
def set_parameters(self, host, port, protocol, proxy, auto_connect):
|
||||||
self.auto_connect = auto_connect
|
proxy_str = serialize_proxy(proxy)
|
||||||
server = serialize_server(host, port, protocol)
|
server = serialize_server(host, port, protocol)
|
||||||
|
self.config.set_key('auto_connect', auto_connect, False)
|
||||||
|
self.config.set_key("proxy", proxy_str, False)
|
||||||
|
self.config.set_key("server", server, True)
|
||||||
|
# abort if changes were not allowed by config
|
||||||
|
if self.config.get('server') != server_str or self.config.get('proxy') != proxy_str:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.auto_connect = auto_connect
|
||||||
if self.proxy != proxy or self.protocol != protocol:
|
if self.proxy != proxy or self.protocol != protocol:
|
||||||
# Restart the network defaulting to the given server
|
# Restart the network defaulting to the given server
|
||||||
self.stop_network()
|
self.stop_network()
|
||||||
|
@ -405,7 +437,9 @@ class Network(util.DaemonThread):
|
||||||
self.switch_lagging_interface(i.server)
|
self.switch_lagging_interface(i.server)
|
||||||
self.notify('updated')
|
self.notify('updated')
|
||||||
|
|
||||||
def process_response(self, interface, response):
|
def process_response(self, interface, response, callback):
|
||||||
|
if self.debug:
|
||||||
|
self.print_error("<--", response)
|
||||||
error = response.get('error')
|
error = response.get('error')
|
||||||
result = response.get('result')
|
result = response.get('result')
|
||||||
method = response.get('method')
|
method = response.get('method')
|
||||||
|
@ -437,8 +471,19 @@ class Network(util.DaemonThread):
|
||||||
# Cache address subscription results
|
# Cache address subscription results
|
||||||
if method == 'blockchain.address.subscribe' and error is None:
|
if method == 'blockchain.address.subscribe' and error is None:
|
||||||
addr = response['params'][0]
|
addr = response['params'][0]
|
||||||
self.addr_responses[addr] = result
|
self.addr_responses[addr] = response
|
||||||
self.response_queue.put(response)
|
if callback is None:
|
||||||
|
params = response['params']
|
||||||
|
with self.lock:
|
||||||
|
for k,v in self.subscriptions.items():
|
||||||
|
if (method, params) in v:
|
||||||
|
callback = k
|
||||||
|
break
|
||||||
|
if callback is None:
|
||||||
|
self.print_error("received unexpected notification",
|
||||||
|
method, params)
|
||||||
|
else:
|
||||||
|
callback(response)
|
||||||
|
|
||||||
def process_responses(self, interface):
|
def process_responses(self, interface):
|
||||||
notifications, responses = interface.get_responses()
|
notifications, responses = interface.get_responses()
|
||||||
|
@ -449,12 +494,14 @@ class Network(util.DaemonThread):
|
||||||
if client_id is not None:
|
if client_id is not None:
|
||||||
if interface != self.interface:
|
if interface != self.interface:
|
||||||
continue
|
continue
|
||||||
self.unanswered_requests.pop(client_id)
|
_req, callback = self.unanswered_requests.pop(client_id)
|
||||||
|
else:
|
||||||
|
callback = None
|
||||||
# Copy the request method and params to the response
|
# Copy the request method and params to the response
|
||||||
response['method'] = request.get('method')
|
response['method'] = request.get('method')
|
||||||
response['params'] = request.get('params')
|
response['params'] = request.get('params')
|
||||||
response['id'] = client_id
|
response['id'] = client_id
|
||||||
self.process_response(interface, response)
|
self.process_response(interface, response, callback)
|
||||||
|
|
||||||
for response in notifications:
|
for response in notifications:
|
||||||
if not response: # Closed remotely
|
if not response: # Closed remotely
|
||||||
|
@ -466,16 +513,42 @@ class Network(util.DaemonThread):
|
||||||
response['result'] = response['params'][0]
|
response['result'] = response['params'][0]
|
||||||
response['params'] = []
|
response['params'] = []
|
||||||
elif method == 'blockchain.address.subscribe':
|
elif method == 'blockchain.address.subscribe':
|
||||||
params = response['params']
|
|
||||||
response['params'] = [params[0]] # addr
|
response['params'] = [params[0]] # addr
|
||||||
response['result'] = params[1]
|
response['result'] = params[1]
|
||||||
self.process_response(interface, response)
|
self.process_response(interface, response, None)
|
||||||
|
|
||||||
def handle_incoming_requests(self):
|
def send(self, messages, callback):
|
||||||
while not self.requests_queue.empty():
|
'''Messages is a list of (method, value) tuples'''
|
||||||
self.process_request(self.requests_queue.get())
|
with self.lock:
|
||||||
|
self.pending_sends.append((messages, callback))
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_pending_sends(self):
|
||||||
|
sends = self.pending_sends
|
||||||
|
self.pending_sends = []
|
||||||
|
|
||||||
|
for messages, callback in sends:
|
||||||
|
subs = filter(lambda (m,v): m.endswith('.subscribe'), messages)
|
||||||
|
with self.lock:
|
||||||
|
for sub in subs:
|
||||||
|
if sub not in self.subscriptions[callback]:
|
||||||
|
self.subscriptions[callback].append(sub)
|
||||||
|
_id = self.message_id
|
||||||
|
self.message_id += len(messages)
|
||||||
|
|
||||||
|
unsent = []
|
||||||
|
for message in messages:
|
||||||
|
method, params = message
|
||||||
|
request = {'id': _id, 'method': method, 'params': params}
|
||||||
|
if not self.process_request(request, callback):
|
||||||
|
unsent.append(message)
|
||||||
|
_id += 1
|
||||||
|
|
||||||
|
if unsent:
|
||||||
|
with self.lock:
|
||||||
|
self.pending_sends.append((unsent, callback))
|
||||||
|
|
||||||
|
# FIXME: inline this function
|
||||||
|
def process_request(self, request, callback):
|
||||||
'''Returns true if the request was processed.'''
|
'''Returns true if the request was processed.'''
|
||||||
method = request['method']
|
method = request['method']
|
||||||
params = request['params']
|
params = request['params']
|
||||||
|
@ -492,14 +565,14 @@ class Network(util.DaemonThread):
|
||||||
out['error'] = str(e)
|
out['error'] = str(e)
|
||||||
traceback.print_exc(file=sys.stdout)
|
traceback.print_exc(file=sys.stdout)
|
||||||
self.print_error("network error", str(e))
|
self.print_error("network error", str(e))
|
||||||
self.response_queue.put(out)
|
callback(out)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if method == 'blockchain.address.subscribe':
|
if method == 'blockchain.address.subscribe':
|
||||||
addr = params[0]
|
addr = params[0]
|
||||||
self.subscribed_addresses.add(addr)
|
self.subscribed_addresses.add(addr)
|
||||||
if addr in self.addr_responses:
|
if addr in self.addr_responses:
|
||||||
self.response_queue.put({'id':_id, 'result':self.addr_responses[addr]})
|
callback(self.addr_responses[addr])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# This request needs connectivity. If we don't have an
|
# This request needs connectivity. If we don't have an
|
||||||
|
@ -507,7 +580,9 @@ class Network(util.DaemonThread):
|
||||||
if not self.interface:
|
if not self.interface:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.unanswered_requests[_id] = request
|
if self.debug:
|
||||||
|
self.print_error("-->", request)
|
||||||
|
self.unanswered_requests[_id] = request, callback
|
||||||
self.interface.queue_request(request)
|
self.interface.queue_request(request)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -679,10 +754,12 @@ class Network(util.DaemonThread):
|
||||||
while self.is_running():
|
while self.is_running():
|
||||||
self.maintain_sockets()
|
self.maintain_sockets()
|
||||||
self.wait_on_sockets()
|
self.wait_on_sockets()
|
||||||
self.handle_incoming_requests()
|
|
||||||
self.handle_bc_requests()
|
self.handle_bc_requests()
|
||||||
|
self.run_jobs() # Synchronizer and Verifier
|
||||||
|
self.process_pending_sends()
|
||||||
|
|
||||||
self.stop_network()
|
self.stop_network()
|
||||||
|
self.trigger_callback('stop')
|
||||||
self.print_error("stopped")
|
self.print_error("stopped")
|
||||||
|
|
||||||
def on_header(self, i, header):
|
def on_header(self, i, header):
|
||||||
|
@ -706,3 +783,11 @@ class Network(util.DaemonThread):
|
||||||
|
|
||||||
def get_local_height(self):
|
def get_local_height(self):
|
||||||
return self.blockchain.height()
|
return self.blockchain.height()
|
||||||
|
|
||||||
|
def synchronous_get(self, request, timeout=100000000):
|
||||||
|
queue = Queue.Queue()
|
||||||
|
self.send([request], queue.put)
|
||||||
|
r = queue.get(True, timeout)
|
||||||
|
if r.get('error'):
|
||||||
|
raise BaseException(r.get('error'))
|
||||||
|
return r.get('result')
|
||||||
|
|
|
@ -1,235 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# Electrum - lightweight Bitcoin client
|
|
||||||
# Copyright (C) 2014 Thomas Voegtlin
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
import threading
|
|
||||||
import Queue
|
|
||||||
|
|
||||||
import util
|
|
||||||
from network import Network, serialize_proxy, serialize_server
|
|
||||||
from simple_config import SimpleConfig
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkProxy(util.DaemonThread):
|
|
||||||
|
|
||||||
def __init__(self, socket, config=None):
|
|
||||||
|
|
||||||
if config is None:
|
|
||||||
config = {} # Do not use mutables as default arguments!
|
|
||||||
util.DaemonThread.__init__(self)
|
|
||||||
self.config = SimpleConfig(config) if type(config) == type({}) else config
|
|
||||||
self.message_id = 0
|
|
||||||
self.unanswered_requests = {}
|
|
||||||
self.subscriptions = {}
|
|
||||||
self.debug = False
|
|
||||||
self.lock = threading.Lock()
|
|
||||||
self.callbacks = {}
|
|
||||||
|
|
||||||
if socket:
|
|
||||||
self.pipe = util.SocketPipe(socket)
|
|
||||||
self.network = None
|
|
||||||
else:
|
|
||||||
self.pipe = util.QueuePipe()
|
|
||||||
self.network = Network(self.pipe, config)
|
|
||||||
self.network.start()
|
|
||||||
for key in ['fee','status','banner','updated','servers','interfaces']:
|
|
||||||
value = self.network.get_status_value(key)
|
|
||||||
self.pipe.get_queue.put({'method':'network.status', 'params':[key, value]})
|
|
||||||
|
|
||||||
# status variables
|
|
||||||
self.status = 'unknown'
|
|
||||||
self.servers = {}
|
|
||||||
self.banner = ''
|
|
||||||
self.blockchain_height = 0
|
|
||||||
self.server_height = 0
|
|
||||||
self.interfaces = []
|
|
||||||
# value returned by estimatefee
|
|
||||||
self.fee = None
|
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while self.is_running():
|
|
||||||
self.run_jobs() # Synchronizer and Verifier
|
|
||||||
try:
|
|
||||||
response = self.pipe.get()
|
|
||||||
except util.timeout:
|
|
||||||
continue
|
|
||||||
if response is None:
|
|
||||||
break
|
|
||||||
# Protect against ill-formed or malicious server responses
|
|
||||||
try:
|
|
||||||
self.process(response)
|
|
||||||
except:
|
|
||||||
traceback.print_exc(file=sys.stderr)
|
|
||||||
self.trigger_callback('stop')
|
|
||||||
if self.network:
|
|
||||||
self.network.stop()
|
|
||||||
self.print_error("stopped")
|
|
||||||
|
|
||||||
def process(self, response):
|
|
||||||
if self.debug:
|
|
||||||
self.print_error("<--", response)
|
|
||||||
|
|
||||||
if response.get('method') == 'network.status':
|
|
||||||
key, value = response.get('params')
|
|
||||||
if key == 'status':
|
|
||||||
self.status = value
|
|
||||||
elif key == 'banner':
|
|
||||||
self.banner = value
|
|
||||||
elif key == 'fee':
|
|
||||||
self.fee = value
|
|
||||||
elif key == 'updated':
|
|
||||||
self.blockchain_height, self.server_height = value
|
|
||||||
elif key == 'servers':
|
|
||||||
self.servers = value
|
|
||||||
elif key == 'interfaces':
|
|
||||||
self.interfaces = value
|
|
||||||
if key in ['status', 'updated']:
|
|
||||||
self.trigger_callback(key)
|
|
||||||
else:
|
|
||||||
self.trigger_callback(key, (value,))
|
|
||||||
return
|
|
||||||
|
|
||||||
msg_id = response.get('id')
|
|
||||||
result = response.get('result')
|
|
||||||
error = response.get('error')
|
|
||||||
if msg_id is not None:
|
|
||||||
with self.lock:
|
|
||||||
method, params, callback = self.unanswered_requests.pop(msg_id)
|
|
||||||
else:
|
|
||||||
method = response.get('method')
|
|
||||||
params = response.get('params')
|
|
||||||
with self.lock:
|
|
||||||
for k,v in self.subscriptions.items():
|
|
||||||
if (method, params) in v:
|
|
||||||
callback = k
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
self.print_error("received unexpected notification",
|
|
||||||
method, params)
|
|
||||||
return
|
|
||||||
|
|
||||||
r = {'method':method, 'params':params, 'result':result,
|
|
||||||
'id':msg_id, 'error':error}
|
|
||||||
callback(r)
|
|
||||||
|
|
||||||
|
|
||||||
def send(self, messages, callback):
|
|
||||||
"""return the ids of the requests that we sent"""
|
|
||||||
|
|
||||||
# detect subscriptions
|
|
||||||
sub = []
|
|
||||||
for message in messages:
|
|
||||||
m, v = message
|
|
||||||
if m[-10:] == '.subscribe':
|
|
||||||
sub.append(message)
|
|
||||||
if sub:
|
|
||||||
with self.lock:
|
|
||||||
if self.subscriptions.get(callback) is None:
|
|
||||||
self.subscriptions[callback] = []
|
|
||||||
for message in sub:
|
|
||||||
if message not in self.subscriptions[callback]:
|
|
||||||
self.subscriptions[callback].append(message)
|
|
||||||
|
|
||||||
with self.lock:
|
|
||||||
requests = []
|
|
||||||
ids = []
|
|
||||||
for m in messages:
|
|
||||||
method, params = m
|
|
||||||
request = { 'id':self.message_id, 'method':method, 'params':params }
|
|
||||||
self.unanswered_requests[self.message_id] = method, params, callback
|
|
||||||
ids.append(self.message_id)
|
|
||||||
requests.append(request)
|
|
||||||
if self.debug:
|
|
||||||
self.print_error("-->", request)
|
|
||||||
self.message_id += 1
|
|
||||||
|
|
||||||
self.pipe.send_all(requests)
|
|
||||||
return ids
|
|
||||||
|
|
||||||
|
|
||||||
def synchronous_get(self, requests, timeout=100000000):
|
|
||||||
queue = Queue.Queue()
|
|
||||||
ids = self.send(requests, queue.put)
|
|
||||||
id2 = ids[:]
|
|
||||||
res = {}
|
|
||||||
while ids:
|
|
||||||
r = queue.get(True, timeout)
|
|
||||||
_id = r.get('id')
|
|
||||||
ids.remove(_id)
|
|
||||||
if r.get('error'):
|
|
||||||
raise BaseException(r.get('error'))
|
|
||||||
result = r.get('result')
|
|
||||||
res[_id] = r.get('result')
|
|
||||||
out = []
|
|
||||||
for _id in id2:
|
|
||||||
out.append(res[_id])
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def get_servers(self):
|
|
||||||
return self.servers
|
|
||||||
|
|
||||||
def get_interfaces(self):
|
|
||||||
return self.interfaces
|
|
||||||
|
|
||||||
def get_local_height(self):
|
|
||||||
return self.blockchain_height
|
|
||||||
|
|
||||||
def get_server_height(self):
|
|
||||||
return self.server_height
|
|
||||||
|
|
||||||
def is_connected(self):
|
|
||||||
return self.status == 'connected'
|
|
||||||
|
|
||||||
def is_connecting(self):
|
|
||||||
return self.status == 'connecting'
|
|
||||||
|
|
||||||
def is_up_to_date(self):
|
|
||||||
return self.unanswered_requests == {}
|
|
||||||
|
|
||||||
def get_parameters(self):
|
|
||||||
return self.synchronous_get([('network.get_parameters', [])])[0]
|
|
||||||
|
|
||||||
def set_parameters(self, host, port, protocol, proxy, auto_connect):
|
|
||||||
proxy_str = serialize_proxy(proxy)
|
|
||||||
server_str = serialize_server(host, port, protocol)
|
|
||||||
self.config.set_key('auto_connect', auto_connect, False)
|
|
||||||
self.config.set_key("proxy", proxy_str, False)
|
|
||||||
self.config.set_key("server", server_str, True)
|
|
||||||
# abort if changes were not allowed by config
|
|
||||||
if self.config.get('server') != server_str or self.config.get('proxy') != proxy_str:
|
|
||||||
return
|
|
||||||
|
|
||||||
return self.synchronous_get([('network.set_parameters', (host, port, protocol, proxy, auto_connect))])[0]
|
|
||||||
|
|
||||||
def stop_daemon(self):
|
|
||||||
return self.send([('daemon.stop',[])], None)
|
|
||||||
|
|
||||||
def register_callback(self, event, callback):
|
|
||||||
with self.lock:
|
|
||||||
if not self.callbacks.get(event):
|
|
||||||
self.callbacks[event] = []
|
|
||||||
self.callbacks[event].append(callback)
|
|
||||||
|
|
||||||
def trigger_callback(self, event, params=()):
|
|
||||||
with self.lock:
|
|
||||||
callbacks = self.callbacks.get(event,[])[:]
|
|
||||||
if callbacks:
|
|
||||||
[callback(*params) for callback in callbacks]
|
|
|
@ -545,7 +545,7 @@ class Transaction:
|
||||||
for privkey in privkeys:
|
for privkey in privkeys:
|
||||||
pubkey = public_key_from_private_key(privkey)
|
pubkey = public_key_from_private_key(privkey)
|
||||||
address = address_from_private_key(privkey)
|
address = address_from_private_key(privkey)
|
||||||
u = network.synchronous_get([ ('blockchain.address.listunspent',[address])])[0]
|
u = network.synchronous_get(('blockchain.address.listunspent',[address]))
|
||||||
pay_script = klass.pay_script('address', address)
|
pay_script = klass.pay_script('address', address)
|
||||||
for item in u:
|
for item in u:
|
||||||
item['scriptPubKey'] = pay_script
|
item['scriptPubKey'] = pay_script
|
||||||
|
|
|
@ -40,7 +40,7 @@ class SPV(ThreadJob):
|
||||||
if tx_hash not in self.merkle_roots and tx_height <= lh:
|
if tx_hash not in self.merkle_roots and tx_height <= lh:
|
||||||
request = ('blockchain.transaction.get_merkle',
|
request = ('blockchain.transaction.get_merkle',
|
||||||
[tx_hash, tx_height])
|
[tx_hash, tx_height])
|
||||||
if self.network.send([request], self.merkle_response):
|
self.network.send([request], self.merkle_response)
|
||||||
self.print_error('requested merkle', tx_hash)
|
self.print_error('requested merkle', tx_hash)
|
||||||
self.merkle_roots[tx_hash] = None
|
self.merkle_roots[tx_hash] = None
|
||||||
|
|
||||||
|
|
|
@ -478,7 +478,7 @@ class KeepKeyWallet(BIP32_HD_Wallet):
|
||||||
|
|
||||||
ptx = self.transactions.get(tx_hash)
|
ptx = self.transactions.get(tx_hash)
|
||||||
if ptx is None:
|
if ptx is None:
|
||||||
ptx = self.network.synchronous_get([('blockchain.transaction.get', [tx_hash])])[0]
|
ptx = self.network.synchronous_get(('blockchain.transaction.get', [tx_hash]))
|
||||||
ptx = Transaction(ptx)
|
ptx = Transaction(ptx)
|
||||||
prev_tx[tx_hash] = ptx
|
prev_tx[tx_hash] = ptx
|
||||||
|
|
||||||
|
|
|
@ -477,7 +477,7 @@ class TrezorWallet(BIP32_HD_Wallet):
|
||||||
|
|
||||||
ptx = self.transactions.get(tx_hash)
|
ptx = self.transactions.get(tx_hash)
|
||||||
if ptx is None:
|
if ptx is None:
|
||||||
ptx = self.network.synchronous_get([('blockchain.transaction.get', [tx_hash])])[0]
|
ptx = self.network.synchronous_get(('blockchain.transaction.get', [tx_hash]))
|
||||||
ptx = Transaction(ptx)
|
ptx = Transaction(ptx)
|
||||||
prev_tx[tx_hash] = ptx
|
prev_tx[tx_hash] = ptx
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@ import electrum
|
||||||
|
|
||||||
# start network
|
# start network
|
||||||
c = electrum.SimpleConfig()
|
c = electrum.SimpleConfig()
|
||||||
s = electrum.daemon.get_daemon(c,True)
|
network = electrum.Network(c)
|
||||||
network = electrum.NetworkProxy(s,c)
|
|
||||||
network.start()
|
network.start()
|
||||||
|
|
||||||
# wait until connected
|
# wait until connected
|
||||||
|
@ -26,4 +25,3 @@ network.send([('blockchain.headers.subscribe',[])], callback)
|
||||||
# 3. wait for results
|
# 3. wait for results
|
||||||
while network.is_connected():
|
while network.is_connected():
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from electrum import NetworkProxy, print_json
|
from electrum import Network, print_json
|
||||||
|
|
||||||
try:
|
try:
|
||||||
addr = sys.argv[1]
|
addr = sys.argv[1]
|
||||||
|
@ -9,8 +9,7 @@ except Exception:
|
||||||
print "usage: get_history <bitcoin_address>"
|
print "usage: get_history <bitcoin_address>"
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
n = NetworkProxy()
|
n = Network()
|
||||||
n.start(start_daemon=True)
|
n.start()
|
||||||
h = n.synchronous_get([ ('blockchain.address.get_history',[addr]) ])[0]
|
h = n.synchronous_get(('blockchain.address.get_history',[addr]))
|
||||||
print_json(h)
|
print_json(h)
|
||||||
|
|
||||||
|
|
|
@ -259,8 +259,7 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
# start network
|
# start network
|
||||||
c = electrum.SimpleConfig({'wallet_path':wallet_path})
|
c = electrum.SimpleConfig({'wallet_path':wallet_path})
|
||||||
daemon_socket = electrum.daemon.get_daemon(c, True)
|
network = electrum.Network(config)
|
||||||
network = electrum.NetworkProxy(daemon_socket, config)
|
|
||||||
network.start()
|
network.start()
|
||||||
|
|
||||||
# wait until connected
|
# wait until connected
|
||||||
|
@ -299,4 +298,3 @@ if __name__ == '__main__':
|
||||||
server.handle_request()
|
server.handle_request()
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,7 @@ except Exception:
|
||||||
|
|
||||||
# start network
|
# start network
|
||||||
c = electrum.SimpleConfig()
|
c = electrum.SimpleConfig()
|
||||||
s = electrum.daemon.get_daemon(c,True)
|
network = electrum.Network(c)
|
||||||
network = electrum.NetworkProxy(s,c)
|
|
||||||
network.start()
|
network.start()
|
||||||
|
|
||||||
# wait until connected
|
# wait until connected
|
||||||
|
@ -31,4 +30,3 @@ network.send([('blockchain.address.subscribe',[addr])], callback)
|
||||||
# 3. wait for results
|
# 3. wait for results
|
||||||
while network.is_connected():
|
while network.is_connected():
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue