|
|
@ -17,8 +17,8 @@ |
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
|
|
|
|
|
|
|
|
import random, socket, ast |
|
|
|
import thread, threading, traceback, sys, time, json, Queue |
|
|
|
import random, socket, ast, re |
|
|
|
import threading, traceback, sys, time, json, Queue |
|
|
|
|
|
|
|
DEFAULT_TIMEOUT = 5 |
|
|
|
DEFAULT_SERVERS = ['ecdsa.org:50001:t'] # ['electrum.bitcoins.sk','ecdsa.org','electrum.novit.ro'] # list of default servers |
|
|
@ -33,8 +33,10 @@ def old_to_new(s): |
|
|
|
return s |
|
|
|
|
|
|
|
|
|
|
|
class Interface: |
|
|
|
class Interface(threading.Thread): |
|
|
|
def __init__(self, host, port): |
|
|
|
threading.Thread.__init__(self) |
|
|
|
self.daemon = True |
|
|
|
self.host = host |
|
|
|
self.port = port |
|
|
|
|
|
|
@ -98,7 +100,6 @@ class Interface: |
|
|
|
|
|
|
|
def start_session(self, addresses, version): |
|
|
|
#print "Starting new session: %s:%d"%(self.host,self.port) |
|
|
|
self.start() |
|
|
|
self.send([('server.version', [version]), ('server.banner',[]), ('blockchain.numblocks.subscribe',[]), ('server.peers.subscribe',[])]) |
|
|
|
self.subscribe(addresses) |
|
|
|
|
|
|
@ -106,13 +107,15 @@ class Interface: |
|
|
|
class PollingInterface(Interface): |
|
|
|
""" non-persistent connection. synchronous calls""" |
|
|
|
|
|
|
|
def __init__(self, host, port): |
|
|
|
Interface.__init__(self, host, port) |
|
|
|
self.session_id = None |
|
|
|
|
|
|
|
def get_history(self, address): |
|
|
|
self.send([('blockchain.address.get_history', [address] )]) |
|
|
|
|
|
|
|
def poll(self): |
|
|
|
self.send([('session.poll', [])]) |
|
|
|
|
|
|
|
pass |
|
|
|
#if is_new or wallet.remote_url: |
|
|
|
# self.was_updated = True |
|
|
|
# is_new = wallet.synchronize() |
|
|
@ -122,10 +125,12 @@ class PollingInterface(Interface): |
|
|
|
#else: |
|
|
|
# return False |
|
|
|
|
|
|
|
def poll_thread(self): |
|
|
|
def run(self): |
|
|
|
self.is_connected = True |
|
|
|
while self.is_connected: |
|
|
|
try: |
|
|
|
self.poll() |
|
|
|
if self.session_id: |
|
|
|
self.poll() |
|
|
|
time.sleep(self.poll_interval) |
|
|
|
except socket.gaierror: |
|
|
|
break |
|
|
@ -136,7 +141,7 @@ class PollingInterface(Interface): |
|
|
|
break |
|
|
|
|
|
|
|
self.is_connected = False |
|
|
|
self.responses.put(None) |
|
|
|
self.poke() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -148,7 +153,9 @@ class NativeInterface(PollingInterface): |
|
|
|
def start_session(self, addresses, version): |
|
|
|
self.send([('session.new', [ version, addresses ])] ) |
|
|
|
self.send([('server.peers.subscribe',[])]) |
|
|
|
thread.start_new_thread(self.poll_thread, ()) |
|
|
|
|
|
|
|
def poll(self): |
|
|
|
self.send([('session.poll', [])]) |
|
|
|
|
|
|
|
def send(self, messages): |
|
|
|
import time |
|
|
@ -211,13 +218,8 @@ class NativeInterface(PollingInterface): |
|
|
|
|
|
|
|
class HttpInterface(PollingInterface): |
|
|
|
|
|
|
|
def start(self): |
|
|
|
self.session_id = None |
|
|
|
thread.start_new_thread(self.poll_thread, ()) |
|
|
|
|
|
|
|
def poll(self): |
|
|
|
if self.session_id: |
|
|
|
self.send( [] ) |
|
|
|
self.send([]) |
|
|
|
|
|
|
|
def send(self, messages): |
|
|
|
import urllib2, json, time, cookielib |
|
|
@ -278,13 +280,25 @@ class HttpInterface(PollingInterface): |
|
|
|
class AsynchronousInterface(Interface): |
|
|
|
"""json-rpc over persistent TCP connection, asynchronous""" |
|
|
|
|
|
|
|
def listen_thread(self): |
|
|
|
def __init__(self, host, port): |
|
|
|
Interface.__init__(self, host, port) |
|
|
|
self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) |
|
|
|
self.s.settimeout(5) |
|
|
|
self.s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) |
|
|
|
try: |
|
|
|
self.s.connect(( self.host, self.port)) |
|
|
|
self.is_connected = True |
|
|
|
except: |
|
|
|
self.is_connected = False |
|
|
|
print "not connected" |
|
|
|
|
|
|
|
def run(self): |
|
|
|
try: |
|
|
|
out = '' |
|
|
|
while self.is_connected: |
|
|
|
try: msg = self.s.recv(1024) |
|
|
|
except socket.timeout: continue |
|
|
|
except socket.timeout: |
|
|
|
continue |
|
|
|
out += msg |
|
|
|
if msg == '': |
|
|
|
self.is_connected = False |
|
|
@ -316,31 +330,149 @@ class AsynchronousInterface(Interface): |
|
|
|
def get_history(self, addr): |
|
|
|
self.send([('blockchain.address.get_history', [addr])]) |
|
|
|
|
|
|
|
def start(self): |
|
|
|
self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) |
|
|
|
self.s.settimeout(5) |
|
|
|
self.s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) |
|
|
|
self.s.connect(( self.host, self.port)) |
|
|
|
thread.start_new_thread(self.listen_thread, ()) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WalletSynchronizer(threading.Thread): |
|
|
|
|
|
|
|
def __init__(self, wallet, loop=False): |
|
|
|
threading.Thread.__init__(self) |
|
|
|
self.daemon = True |
|
|
|
self.wallet = wallet |
|
|
|
self.loop = loop |
|
|
|
self.start_interface() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def loop_interfaces_thread(wallet): |
|
|
|
while True: |
|
|
|
def handle_response(self, r): |
|
|
|
if r is None: |
|
|
|
return |
|
|
|
|
|
|
|
method = r['method'] |
|
|
|
params = r['params'] |
|
|
|
result = r['result'] |
|
|
|
|
|
|
|
if method == 'server.banner': |
|
|
|
self.wallet.banner = result |
|
|
|
self.wallet.was_updated = True |
|
|
|
|
|
|
|
elif method == 'session.poll': |
|
|
|
# native poll |
|
|
|
blocks, changed_addresses = result |
|
|
|
if blocks == -1: raise BaseException("session not found") |
|
|
|
self.wallet.blocks = int(blocks) |
|
|
|
if changed_addresses: |
|
|
|
self.wallet.was_updated = True |
|
|
|
for addr, status in changed_addresses.items(): |
|
|
|
self.wallet.receive_status_callback(addr, status) |
|
|
|
|
|
|
|
elif method == 'server.peers.subscribe': |
|
|
|
servers = [] |
|
|
|
for item in result: |
|
|
|
s = [] |
|
|
|
host = item[1] |
|
|
|
if len(item)>2: |
|
|
|
for v in item[2]: |
|
|
|
if re.match("[thn]\d+",v): |
|
|
|
s.append(host+":"+v[1:]+":"+v[0]) |
|
|
|
#if not s: |
|
|
|
# s.append(host+":50000:n") |
|
|
|
#else: |
|
|
|
# s.append(host+":50000:n") |
|
|
|
servers = servers + s |
|
|
|
self.interface.servers = servers |
|
|
|
|
|
|
|
elif method == 'blockchain.address.subscribe': |
|
|
|
addr = params[-1] |
|
|
|
self.wallet.receive_status_callback(addr, result) |
|
|
|
|
|
|
|
elif method == 'blockchain.address.get_history': |
|
|
|
addr = params[0] |
|
|
|
self.wallet.receive_history_callback(addr, result) |
|
|
|
self.wallet.was_updated = True |
|
|
|
|
|
|
|
elif method == 'blockchain.transaction.broadcast': |
|
|
|
self.wallet.tx_result = result |
|
|
|
self.wallet.tx_event.set() |
|
|
|
|
|
|
|
elif method == 'blockchain.numblocks.subscribe': |
|
|
|
self.wallet.blocks = result |
|
|
|
|
|
|
|
elif method == 'server.version': |
|
|
|
pass |
|
|
|
|
|
|
|
else: |
|
|
|
print "unknown message:", method, params, result |
|
|
|
|
|
|
|
|
|
|
|
def start_interface(self): |
|
|
|
try: |
|
|
|
wallet.start_interface() |
|
|
|
wallet.run() |
|
|
|
except socket.error: |
|
|
|
print "socket error" |
|
|
|
wallet.interface.is_connected = False |
|
|
|
time.sleep(5) |
|
|
|
host, port, protocol = self.wallet.server.split(':') |
|
|
|
port = int(port) |
|
|
|
except: |
|
|
|
traceback.print_exc(file=sys.stdout) |
|
|
|
wallet.interface.is_connected = False |
|
|
|
time.sleep(5) |
|
|
|
continue |
|
|
|
self.wallet.pick_random_server() |
|
|
|
host, port, protocol = self.wallet.server.split(':') |
|
|
|
port = int(port) |
|
|
|
|
|
|
|
#print protocol, host, port |
|
|
|
if protocol == 'n': |
|
|
|
InterfaceClass = NativeInterface |
|
|
|
elif protocol == 't': |
|
|
|
InterfaceClass = AsynchronousInterface |
|
|
|
elif protocol == 'h': |
|
|
|
InterfaceClass = HttpInterface |
|
|
|
else: |
|
|
|
print "unknown protocol" |
|
|
|
InterfaceClass = NativeInterface |
|
|
|
|
|
|
|
self.interface = InterfaceClass(host, port) |
|
|
|
self.wallet.interface = self.interface |
|
|
|
|
|
|
|
with self.wallet.lock: |
|
|
|
self.wallet.addresses_waiting_for_status = [] |
|
|
|
self.wallet.addresses_waiting_for_history = [] |
|
|
|
addresses = self.wallet.all_addresses() |
|
|
|
version = self.wallet.electrum_version |
|
|
|
for addr in addresses: |
|
|
|
self.wallet.addresses_waiting_for_status.append(addr) |
|
|
|
|
|
|
|
try: |
|
|
|
self.interface.start() |
|
|
|
self.interface.start_session(addresses,version) |
|
|
|
except: |
|
|
|
self.interface.is_connected = False |
|
|
|
|
|
|
|
|
|
|
|
def run(self): |
|
|
|
import socket, time |
|
|
|
while True: |
|
|
|
try: |
|
|
|
while self.interface.is_connected: |
|
|
|
new_addresses = self.wallet.synchronize() |
|
|
|
if new_addresses: |
|
|
|
self.interface.subscribe(new_addresses) |
|
|
|
for addr in new_addresses: |
|
|
|
with self.wallet.lock: |
|
|
|
self.wallet.addresses_waiting_for_status.append(addr) |
|
|
|
|
|
|
|
if self.wallet.is_up_to_date(): |
|
|
|
self.wallet.up_to_date = True |
|
|
|
self.wallet.up_to_date_event.set() |
|
|
|
else: |
|
|
|
self.wallet.up_to_date = False |
|
|
|
|
|
|
|
response = self.interface.responses.get(True,100000000000) # workaround so that it can be keyboard interrupted |
|
|
|
self.handle_response(response) |
|
|
|
except socket.error: |
|
|
|
print "socket error" |
|
|
|
wallet.interface.is_connected = False |
|
|
|
|
|
|
|
if self.loop: |
|
|
|
time.sleep(5) |
|
|
|
self.start_interface() |
|
|
|
continue |
|
|
|
else: |
|
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|