Merge branch 'release-0.8.6'
This commit is contained in:
commit
b6e7eb3c04
@ -1,3 +1,9 @@
|
|||||||
|
version 0.8.6
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- fix JSON bugs from 0.8.5
|
||||||
|
- fix issue #61 (valesi)
|
||||||
|
|
||||||
version 0.8.5
|
version 0.8.5
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,8 @@ class RPCClient(JSONRPC):
|
|||||||
async def send_and_wait(self, method, params, timeout=None):
|
async def send_and_wait(self, method, params, timeout=None):
|
||||||
# Raise incoming buffer size - presumably connection is trusted
|
# Raise incoming buffer size - presumably connection is trusted
|
||||||
self.max_buffer_size = 5000000
|
self.max_buffer_size = 5000000
|
||||||
self.send_json_request(method, id_=method, params=params)
|
payload = self.request_payload(method, id_=method, params=params)
|
||||||
|
self.encode_and_send_payload(payload)
|
||||||
|
|
||||||
future = asyncio.ensure_future(self.messages.get())
|
future = asyncio.ensure_future(self.messages.get())
|
||||||
for f in asyncio.as_completed([future], timeout=timeout):
|
for f in asyncio.as_completed([future], timeout=timeout):
|
||||||
|
|||||||
130
lib/jsonrpc.py
130
lib/jsonrpc.py
@ -15,28 +15,6 @@ import time
|
|||||||
from lib.util import LoggedClass
|
from lib.util import LoggedClass
|
||||||
|
|
||||||
|
|
||||||
def json_response_payload(result, id_):
|
|
||||||
# We should not respond to notifications
|
|
||||||
assert id_ is not None
|
|
||||||
return {'jsonrpc': '2.0', 'result': result, 'id': id_}
|
|
||||||
|
|
||||||
def json_error_payload(message, code, id_=None):
|
|
||||||
error = {'message': message, 'code': code}
|
|
||||||
return {'jsonrpc': '2.0', 'error': error, 'id': id_}
|
|
||||||
|
|
||||||
def json_request_payload(method, id_, params=None):
|
|
||||||
payload = {'jsonrpc': '2.0', 'id': id_, 'method': method}
|
|
||||||
if params:
|
|
||||||
payload['params'] = params
|
|
||||||
return payload
|
|
||||||
|
|
||||||
def json_notification_payload(method, params=None):
|
|
||||||
return json_request_payload(method, None, params)
|
|
||||||
|
|
||||||
def json_payload_id(payload):
|
|
||||||
return payload.get('id') if isinstance(payload, dict) else None
|
|
||||||
|
|
||||||
|
|
||||||
class JSONRPC(asyncio.Protocol, LoggedClass):
|
class JSONRPC(asyncio.Protocol, LoggedClass):
|
||||||
'''Manages a JSONRPC connection.
|
'''Manages a JSONRPC connection.
|
||||||
|
|
||||||
@ -56,7 +34,6 @@ class JSONRPC(asyncio.Protocol, LoggedClass):
|
|||||||
handle_notification() and handle_response() should not return
|
handle_notification() and handle_response() should not return
|
||||||
anything or raise any exceptions. All three functions have
|
anything or raise any exceptions. All three functions have
|
||||||
default "ignore" implementations supplied by this class.
|
default "ignore" implementations supplied by this class.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# See http://www.jsonrpc.org/specification
|
# See http://www.jsonrpc.org/specification
|
||||||
@ -79,6 +56,31 @@ class JSONRPC(asyncio.Protocol, LoggedClass):
|
|||||||
class LargeRequestError(Exception):
|
class LargeRequestError(Exception):
|
||||||
'''Raised if a large request was prevented from being sent.'''
|
'''Raised if a large request was prevented from being sent.'''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def request_payload(cls, method, id_, params=None):
|
||||||
|
payload = {'jsonrpc': '2.0', 'id': id_, 'method': method}
|
||||||
|
if params:
|
||||||
|
payload['params'] = params
|
||||||
|
return payload
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def response_payload(cls, result, id_):
|
||||||
|
# We should not respond to notifications
|
||||||
|
assert id_ is not None
|
||||||
|
return {'jsonrpc': '2.0', 'result': result, 'id': id_}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def notification_payload(cls, method, params=None):
|
||||||
|
return cls.request_payload(method, None, params)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def error_payload(cls, message, code, id_=None):
|
||||||
|
error = {'message': message, 'code': code}
|
||||||
|
return {'jsonrpc': '2.0', 'error': error, 'id': id_}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def payload_id(cls, payload):
|
||||||
|
return payload.get('id') if isinstance(payload, dict) else None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -182,14 +184,14 @@ class JSONRPC(asyncio.Protocol, LoggedClass):
|
|||||||
message = message.decode()
|
message = message.decode()
|
||||||
except UnicodeDecodeError as e:
|
except UnicodeDecodeError as e:
|
||||||
msg = 'cannot decode binary bytes: {}'.format(e)
|
msg = 'cannot decode binary bytes: {}'.format(e)
|
||||||
self.send_json_error(msg, self.INVALID_REQUEST)
|
self.send_json_error(msg, self.PARSE_ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = json.loads(message)
|
message = json.loads(message)
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
msg = 'cannot decode JSON: {}'.format(e)
|
msg = 'cannot decode JSON: {}'.format(e)
|
||||||
self.send_json_error(msg, self.INVALID_REQUEST)
|
self.send_json_error(msg, self.PARSE_ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
'''Queue the request for asynchronous handling.'''
|
'''Queue the request for asynchronous handling.'''
|
||||||
@ -199,53 +201,53 @@ class JSONRPC(asyncio.Protocol, LoggedClass):
|
|||||||
|
|
||||||
def encode_payload(self, payload):
|
def encode_payload(self, payload):
|
||||||
try:
|
try:
|
||||||
text = (json.dumps(payload) + '\n').encode()
|
binary = json.dumps(payload).encode()
|
||||||
except TypeError:
|
except TypeError:
|
||||||
msg = 'JSON encoding failure: {}'.format(payload)
|
msg = 'JSON encoding failure: {}'.format(payload)
|
||||||
self.log_error(msg)
|
self.log_error(msg)
|
||||||
return self.json_error(msg, self.INTERNAL_ERROR,
|
return self.json_error(msg, self.INTERNAL_ERROR,
|
||||||
json_payload_id(payload))
|
self.payload_id(payload))
|
||||||
|
|
||||||
self.check_oversized_request(len(text))
|
self.check_oversized_request(len(binary))
|
||||||
if 'error' in payload:
|
|
||||||
self.error_count += 1
|
|
||||||
self.send_count += 1
|
self.send_count += 1
|
||||||
self.send_size += len(text)
|
self.send_size += len(binary)
|
||||||
self.using_bandwidth(len(text))
|
self.using_bandwidth(len(binary))
|
||||||
return text
|
return binary
|
||||||
|
|
||||||
def send_text(self, text, close):
|
def _send_bytes(self, text, close):
|
||||||
'''Send JSON text over the transport. Close it if close is True.'''
|
'''Send JSON text over the transport. Close it if close is True.'''
|
||||||
# Confirmed this happens, sometimes a lot
|
# Confirmed this happens, sometimes a lot
|
||||||
if self.transport.is_closing():
|
if self.transport.is_closing():
|
||||||
return
|
return
|
||||||
self.transport.write(text)
|
self.transport.write(text)
|
||||||
|
self.transport.write(b'\n')
|
||||||
if close:
|
if close:
|
||||||
self.transport.close()
|
self.transport.close()
|
||||||
|
|
||||||
def send_json_error(self, message, code, id_=None, close=True):
|
def send_json_error(self, message, code, id_=None, close=True):
|
||||||
'''Send a JSON error and close the connection by default.'''
|
'''Send a JSON error and close the connection by default.'''
|
||||||
self.send_text(self.json_error_text(message, code, id_), close)
|
self._send_bytes(self.json_error_bytes(message, code, id_), close)
|
||||||
|
|
||||||
def encode_and_send_payload(self, payload):
|
def encode_and_send_payload(self, payload):
|
||||||
'''Encode the payload and send it.'''
|
'''Encode the payload and send it.'''
|
||||||
self.send_text(self.encode_payload(payload), False)
|
self._send_bytes(self.encode_payload(payload), False)
|
||||||
|
|
||||||
def json_notification_text(self, method, params):
|
def json_notification_bytes(self, method, params):
|
||||||
'''Return the text of a json notification.'''
|
'''Return the bytes of a json notification.'''
|
||||||
return self.encode_payload(json_notification_payload(method, params))
|
return self.encode_payload(self.notification_payload(method, params))
|
||||||
|
|
||||||
def json_request_text(self, method, id_, params=None):
|
def json_request_bytes(self, method, id_, params=None):
|
||||||
'''Return the text of a JSON request.'''
|
'''Return the bytes of a JSON request.'''
|
||||||
return self.encode_payload(json_request_payload(method, params))
|
return self.encode_payload(self.request_payload(method, id_, params))
|
||||||
|
|
||||||
def json_response_text(self, result, id_):
|
def json_response_bytes(self, result, id_):
|
||||||
'''Return the text of a JSON response.'''
|
'''Return the bytes of a JSON response.'''
|
||||||
return self.encode_payload(json_response_payload(result, id_))
|
return self.encode_payload(self.response_payload(result, id_))
|
||||||
|
|
||||||
def json_error_text(self, message, code, id_=None):
|
def json_error_bytes(self, message, code, id_=None):
|
||||||
'''Return the text of a JSON error.'''
|
'''Return the bytes of a JSON error.'''
|
||||||
return self.encode_payload(json_error_payload(message, code, id_))
|
self.error_count += 1
|
||||||
|
return self.encode_payload(self.error_payload(message, code, id_))
|
||||||
|
|
||||||
async def handle_message(self, payload):
|
async def handle_message(self, payload):
|
||||||
'''Asynchronously handle a JSON request or response.
|
'''Asynchronously handle a JSON request or response.
|
||||||
@ -254,21 +256,21 @@ class JSONRPC(asyncio.Protocol, LoggedClass):
|
|||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
if isinstance(payload, list):
|
if isinstance(payload, list):
|
||||||
text = await self.process_json_batch(payload)
|
binary = await self.process_json_batch(payload)
|
||||||
else:
|
else:
|
||||||
text = await self.process_single_json(payload)
|
binary = await self.process_single_json(payload)
|
||||||
except self.RPCError as e:
|
except self.RPCError as e:
|
||||||
text = self.json_error_text(e.msg, e.code,
|
binary = self.json_error_bytes(e.msg, e.code,
|
||||||
json_payload_id(payload))
|
self.payload_id(payload))
|
||||||
|
|
||||||
if text:
|
if binary:
|
||||||
self.send_text(text, self.error_count > 10)
|
self._send_bytes(binary, self.error_count > 10)
|
||||||
|
|
||||||
async def process_json_batch(self, batch):
|
async def process_json_batch(self, batch):
|
||||||
'''Return the text response to a JSON batch request.'''
|
'''Return the text response to a JSON batch request.'''
|
||||||
# Batches must have at least one request.
|
# Batches must have at least one request.
|
||||||
if not batch:
|
if not batch:
|
||||||
return self.json_error_text('empty batch', self.INVALID_REQUEST)
|
return self.json_error_bytes('empty batch', self.INVALID_REQUEST)
|
||||||
|
|
||||||
# PYTHON 3.6: use asynchronous comprehensions when supported
|
# PYTHON 3.6: use asynchronous comprehensions when supported
|
||||||
parts = []
|
parts = []
|
||||||
@ -280,8 +282,8 @@ class JSONRPC(asyncio.Protocol, LoggedClass):
|
|||||||
total_len += len(part) + 2
|
total_len += len(part) + 2
|
||||||
self.check_oversized_request(total_len)
|
self.check_oversized_request(total_len)
|
||||||
if parts:
|
if parts:
|
||||||
return '{' + ', '.join(parts) + '}'
|
return b'[' + b', '.join(parts) + b']'
|
||||||
return ''
|
return b''
|
||||||
|
|
||||||
async def process_single_json(self, payload):
|
async def process_single_json(self, payload):
|
||||||
'''Return the JSON result of a single JSON request, response or
|
'''Return the JSON result of a single JSON request, response or
|
||||||
@ -300,16 +302,16 @@ class JSONRPC(asyncio.Protocol, LoggedClass):
|
|||||||
await asyncio.sleep(secs)
|
await asyncio.sleep(secs)
|
||||||
|
|
||||||
if not isinstance(payload, dict):
|
if not isinstance(payload, dict):
|
||||||
return self.json_error_text('request must be a dict',
|
return self.json_error_bytes('request must be a dict',
|
||||||
self.INVALID_REQUEST)
|
self.INVALID_REQUEST)
|
||||||
|
|
||||||
if not 'id' in payload:
|
if not 'id' in payload:
|
||||||
return await self.process_json_notification(payload)
|
return await self.process_json_notification(payload)
|
||||||
|
|
||||||
id_ = payload['id']
|
id_ = payload['id']
|
||||||
if not isinstance(id_, self.ID_TYPES):
|
if not isinstance(id_, self.ID_TYPES):
|
||||||
return self.json_error_text('invalid id: {}'.format(id_),
|
return self.json_error_bytes('invalid id: {}'.format(id_),
|
||||||
self.INVALID_REQUEST)
|
self.INVALID_REQUEST)
|
||||||
|
|
||||||
if 'method' in payload:
|
if 'method' in payload:
|
||||||
return await self.process_json_request(payload)
|
return await self.process_json_request(payload)
|
||||||
@ -338,12 +340,12 @@ class JSONRPC(asyncio.Protocol, LoggedClass):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
await self.handle_notification(method, params)
|
await self.handle_notification(method, params)
|
||||||
return ''
|
return b''
|
||||||
|
|
||||||
async def process_json_request(self, payload):
|
async def process_json_request(self, payload):
|
||||||
method, params = self.method_and_params(payload)
|
method, params = self.method_and_params(payload)
|
||||||
result = await self.handle_request(method, params)
|
result = await self.handle_request(method, params)
|
||||||
return self.json_response_text(result, payload['id'])
|
return self.json_response_bytes(result, payload['id'])
|
||||||
|
|
||||||
async def process_json_response(self, payload):
|
async def process_json_response(self, payload):
|
||||||
# Only one of result and error should exist; we go with 'error'
|
# Only one of result and error should exist; we go with 'error'
|
||||||
@ -352,7 +354,7 @@ class JSONRPC(asyncio.Protocol, LoggedClass):
|
|||||||
await self.handle_response(None, payload['error'], payload['id'])
|
await self.handle_response(None, payload['error'], payload['id'])
|
||||||
elif 'result' in payload:
|
elif 'result' in payload:
|
||||||
await self.handle_response(payload['result'], None, payload['id'])
|
await self.handle_response(payload['result'], None, payload['id'])
|
||||||
return ''
|
return b''
|
||||||
|
|
||||||
def check_oversized_request(self, total_len):
|
def check_oversized_request(self, total_len):
|
||||||
if total_len > max(1000, self.max_send):
|
if total_len > max(1000, self.max_send):
|
||||||
|
|||||||
@ -20,7 +20,7 @@ from functools import partial
|
|||||||
import pylru
|
import pylru
|
||||||
|
|
||||||
from lib.hash import sha256, double_sha256, hash_to_str, hex_str_to_hash
|
from lib.hash import sha256, double_sha256, hash_to_str, hex_str_to_hash
|
||||||
from lib.jsonrpc import JSONRPC, json_notification_payload
|
from lib.jsonrpc import JSONRPC
|
||||||
from lib.tx import Deserializer
|
from lib.tx import Deserializer
|
||||||
import lib.util as util
|
import lib.util as util
|
||||||
from server.block_processor import BlockProcessor
|
from server.block_processor import BlockProcessor
|
||||||
@ -687,14 +687,14 @@ class ElectrumX(Session):
|
|||||||
if self.subscribe_headers:
|
if self.subscribe_headers:
|
||||||
key = 'headers_payload'
|
key = 'headers_payload'
|
||||||
if key not in cache:
|
if key not in cache:
|
||||||
cache[key] = json_notification_payload(
|
cache[key] = self.notification_payload(
|
||||||
'blockchain.headers.subscribe',
|
'blockchain.headers.subscribe',
|
||||||
(self.electrum_header(height), ),
|
(self.electrum_header(height), ),
|
||||||
)
|
)
|
||||||
self.encode_and_send_payload(cache[key])
|
self.encode_and_send_payload(cache[key])
|
||||||
|
|
||||||
if self.subscribe_height:
|
if self.subscribe_height:
|
||||||
payload = json_notification_payload(
|
payload = self.notification_payload(
|
||||||
'blockchain.numblocks.subscribe',
|
'blockchain.numblocks.subscribe',
|
||||||
(height, ),
|
(height, ),
|
||||||
)
|
)
|
||||||
@ -705,7 +705,7 @@ class ElectrumX(Session):
|
|||||||
for hash168 in matches:
|
for hash168 in matches:
|
||||||
address = hash168_to_address(hash168)
|
address = hash168_to_address(hash168)
|
||||||
status = await self.address_status(hash168)
|
status = await self.address_status(hash168)
|
||||||
payload = json_notification_payload(
|
payload = self.notification_payload(
|
||||||
'blockchain.address.subscribe', (address, status))
|
'blockchain.address.subscribe', (address, status))
|
||||||
self.encode_and_send_payload(payload)
|
self.encode_and_send_payload(payload)
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
VERSION = "ElectrumX 0.8.5"
|
VERSION = "ElectrumX 0.8.6"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user