/cryptos/electrumx_client/rpc.py

https://github.com/primal100/pybitcointools · Python · 144 lines · 114 code · 21 blank · 9 comment · 24 complexity · c92ca89bec33426a60f716d497f5ab04 MD5 · raw file

  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (c) 2016, Neil Booth
  4. #
  5. # All rights reserved.
  6. #
  7. # See the file "LICENCE" for information about the copyright
  8. # and warranty status of this software.
  9. '''Script to send RPC commands to a running ElectrumX server.'''
  10. import asyncio
  11. import json
  12. import random
  13. import os
  14. from functools import partial
  15. from .jsonrpc import JSONSession, JSONRPCv2
  16. class RPCClient(JSONSession):
  17. def __init__(self):
  18. super().__init__(version=JSONRPCv2)
  19. self.max_send = 0
  20. self.max_buffer_size = 5*10**6
  21. self.result = {}
  22. async def wait_for_response(self, id_):
  23. from datetime import datetime
  24. now = datetime.now()
  25. await self.items_events[id_].wait()
  26. print(id_, "event raised:", datetime.now() - now)
  27. await self.process_pending_items()
  28. self.items_events[id_].clear()
  29. del self.items_events[id_]
  30. return self.result.pop(id_)
  31. def send_rpc_request(self, method, params):
  32. handler = partial(self.handle_response, method, params)
  33. return self.send_request(handler, method, params)
  34. def handle_response(self, method, params, id_, data, error):
  35. self.result[id_] = {'data': data, 'error': error, 'method': method, 'params': params}
  36. def read_json(filename, default):
  37. path = os.path.join(os.path.dirname(__file__), 'servers', filename)
  38. try:
  39. with open(path, 'r') as f:
  40. r = json.loads(f.read())
  41. except:
  42. r = default
  43. return r
  44. class ElectrumXClient(RPCClient):
  45. def __init__(self, server_file="bitcoin.json", servers=(), host=None, port=50001, timeout=15, max_servers=5, loop=None):
  46. super().__init__()
  47. if loop:
  48. self.loop = loop
  49. else:
  50. self.loop = asyncio.get_event_loop()
  51. self.timeout = timeout
  52. self.failed_hosts = []
  53. self.max_servers = max_servers
  54. if servers:
  55. self.servers = servers
  56. else:
  57. self.servers = read_json(server_file, {})
  58. self.host = host
  59. self.port = port
  60. self.rpc_client = None
  61. if not self.host:
  62. self.host, self.port = self.choose_random_server()
  63. self.connect_to_server()
  64. def choose_random_server(self):
  65. host = random.choice(list(self.servers.keys()))
  66. try:
  67. return host, self.servers[host]['t']
  68. except KeyError:
  69. del self.servers[host]
  70. return self.choose_random_server()
  71. def connect_to_server(self):
  72. print(self.host, self.port)
  73. try:
  74. coro = self.loop.create_connection(RPCClient, self.host, self.port)
  75. transport, self.rpc_client = self.loop.run_until_complete(coro)
  76. except OSError:
  77. self.change_server()
  78. def change_server(self):
  79. if self.rpc_client:
  80. self.rpc_client.close()
  81. if self.host not in self.failed_hosts:
  82. self.failed_hosts.append(self.host)
  83. if len(self.failed_hosts) >= self.max_servers:
  84. raise Exception("Attempted to connect to %s servers but failed" % len(self.failed_hosts))
  85. while self.host in self.failed_hosts:
  86. self.host, self.port = self.choose_random_server()
  87. self.connect_to_server()
  88. def rpc_multiple_send_and_wait(self, requests):
  89. from datetime import datetime
  90. coroutines = []
  91. for request in requests:
  92. method, params = request
  93. try:
  94. now = datetime.now()
  95. id_ = self.rpc_client.send_rpc_request(method, params)
  96. print(id_, "Request sent:", datetime.now() - now)
  97. try:
  98. coro = self.rpc_client.wait_for_response(id_)
  99. coroutines.append(asyncio.wait_for(coro, self.timeout))
  100. except asyncio.TimeoutError:
  101. self.change_server()
  102. return self.rpc_multiple_send_and_wait(requests)
  103. except OSError:
  104. self.change_server()
  105. return self.rpc_multiple_send_and_wait(requests)
  106. now = datetime.now()
  107. values = self.loop.run_until_complete(asyncio.gather(*coroutines))
  108. print("Values gathered:", datetime.now() - now)
  109. self.failed_hosts = []
  110. return values
  111. def unspent(self, *addrs):
  112. requests = [("blockchain.address.listunspent", [addr]) for addr in addrs]
  113. results = self.rpc_multiple_send_and_wait(requests)
  114. unspents = []
  115. for i, result in enumerate(results):
  116. if result['error']:
  117. raise Exception(result['error'])
  118. unspent_for_addr = result['data']
  119. addr = result['params'][0]
  120. for u in unspent_for_addr:
  121. u['address'] = addr
  122. unspents.append(u)
  123. return unspents