You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
7.7 KiB
236 lines
7.7 KiB
#!/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 threading
|
|
import Queue
|
|
|
|
import util
|
|
from network import Network
|
|
from util import print_error
|
|
from simple_config import SimpleConfig
|
|
from network import serialize_proxy, serialize_server
|
|
|
|
|
|
|
|
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 = []
|
|
self.jobs = []
|
|
# value returned by estimatefee
|
|
self.fee = None
|
|
|
|
|
|
def run(self):
|
|
while self.is_running():
|
|
for job in self.jobs:
|
|
job()
|
|
try:
|
|
response = self.pipe.get()
|
|
except util.timeout:
|
|
continue
|
|
if response is None:
|
|
break
|
|
self.process(response)
|
|
self.trigger_callback('stop')
|
|
if self.network:
|
|
self.network.stop()
|
|
self.print_error("stopped")
|
|
|
|
def process(self, response):
|
|
if self.debug:
|
|
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:
|
|
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:
|
|
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_header(self, height):
|
|
return self.synchronous_get([('network.get_header', [height])])[0]
|
|
|
|
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]
|
|
|