diff --git a/duniterpy/api/ws2p/requests.py b/duniterpy/api/ws2p/requests.py index 55e8426f642082ee1fb4c83541347e5ba3606a18..5a335e8975f4271ede432f1ab9d4b816fa6ef28b 100644 --- a/duniterpy/api/ws2p/requests.py +++ b/duniterpy/api/ws2p/requests.py @@ -3,7 +3,7 @@ import re from typing import Optional from duniterpy.helpers import get_ws2p_challenge -from duniterpy.api.bma.blockchain import BLOCK_SCHEMA +from duniterpy.api.bma.blockchain import BLOCK_SCHEMA, BLOCKS_SCHEMA ERROR_RESPONSE_SCHEMA = { "type": "object", @@ -29,6 +29,18 @@ BLOCK_RESPONSE_SCHEMA = { "required": ["resId", "body"] } +BLOCKS_RESPONSE_SCHEMA = { + "type": "object", + "properties": { + "resId": { + "type": "string", + "pattern": "^[0-9,a-z,A-Z]{8}$" + }, + "body": BLOCKS_SCHEMA + }, + "required": ["resId", "body"] +} + def get_current(request_id: Optional[str] = None) -> str: """ @@ -73,3 +85,26 @@ def get_block(block_number: int, request_id: Optional[str] = None) -> str: } }) + +def get_blocks(from_number: int, count: int, request_id: Optional[str] = None) -> str: + """ + Return ws2p getBlocks(fromNumber, count) request as json string + + :return: + """ + + if request_id is None: + request_id = get_ws2p_challenge()[:8] + else: + if not re.fullmatch("^[0-9a-zA-Z]{8}$", request_id): + raise Exception("Invalid ws2p request unique id") + return json.dumps({ + "reqId": request_id, + "body": { + "name": "BLOCKS_CHUNK", + "params": { + "fromNumber": from_number, + "count": count + } + } + }) diff --git a/examples/request_ws2p.py b/examples/request_ws2p.py index c55107ccb8d01713446d9a75d5112dd9a9e042bc..bd18429c8a869fa56da29c9c0462e6101593d72b 100644 --- a/examples/request_ws2p.py +++ b/examples/request_ws2p.py @@ -163,6 +163,27 @@ async def main(): # if invalid, display exception on response validation print(exception) + # send ws2p request + print("Send getBlocks(360000, 2) request") + await ws.send_str(requests.get_blocks(360000, 2)) + # receive response as string + response = await ws.receive_str() + try: + # check response format + parse_text(response, requests.BLOCKS_RESPONSE_SCHEMA) + # if valid display response + print("Response: " + response) + except ValidationError as exception: + # if invalid response... + try: + # check error response format + parse_text(response, requests.ERROR_RESPONSE_SCHEMA) + # if valid, display error response + print("Error response: " + response) + except ValidationError as e: + # if invalid, display exception on response validation + print(exception) + # Close session await client.close() diff --git a/tests/api/ws2p/test_ws2p.py b/tests/api/ws2p/test_ws2p.py index 6c2c9d1fd91e5edc463852478cc3dace6846505d..d461333181bce3e6a8550c4a00c76916fb270643 100644 --- a/tests/api/ws2p/test_ws2p.py +++ b/tests/api/ws2p/test_ws2p.py @@ -5,7 +5,7 @@ import jsonschema from duniterpy.api.client import Client, parse_text from duniterpy.api.endpoint import BMAEndpoint from duniterpy.api.ws2p.network import heads, WS2P_HEADS_SCHEMA -from duniterpy.api.ws2p.requests import BLOCK_RESPONSE_SCHEMA, ERROR_RESPONSE_SCHEMA +from duniterpy.api.ws2p.requests import BLOCK_RESPONSE_SCHEMA, ERROR_RESPONSE_SCHEMA, BLOCKS_RESPONSE_SCHEMA from tests.api.webserver import WebFunctionalSetupMixin, web @@ -63,6 +63,11 @@ class TestWs2pHeads(WebFunctionalSetupMixin, unittest.TestCase): self.loop.run_until_complete(go()) + def test_error_response_validation(self): + error_response_string = """{"resId":"cfe10cc4","err":"Error message"}""" + error_response = parse_text(error_response_string, ERROR_RESPONSE_SCHEMA) + self.assertIsInstance(error_response, dict) + def test_block_response_validation(self): response_string = """{"resId":"cfe10cc4","body":{"wrong":false,"version":11,"number":367572, "currency":"g1-test","hash":"000024399D612753E59D44415CFA61F3A663919110CD2EB8D30C93F49C61E07F", @@ -79,7 +84,30 @@ class TestWs2pHeads(WebFunctionalSetupMixin, unittest.TestCase): response = parse_text(response_string, BLOCK_RESPONSE_SCHEMA) self.assertIsInstance(response, dict) - def test_error_response_validation(self): - error_response_string = """{"resId":"cfe10cc4","err":"Error message"}""" - error_response = parse_text(error_response_string, ERROR_RESPONSE_SCHEMA) - self.assertIsInstance(error_response, dict) + def test_blocks_response_validation(self): + response_string = """{"resId": "f608a5b8", "body": [ + {"identities": [], "joiners": [], "actives": [], "leavers": [], "revoked": [], "excluded": [], + "certifications": [], "transactions": [], "version": 11, "number": 360000, "currency": "g1-test", + "hash": "0001826F638E091DEDEC5E6A4D3917BC37772E16B66923818AED44182F9FBA45", + "inner_hash": "3BB4636E7EB6159CB4BF58E2D47E7AC92D75E42F72EC10D91645B7B2CFD6E84A", + "previousHash": "0000FBE5B2BA1C88984AEA6FFB16F622A870D1A9DBD2DDF040BFF8E27273B0E1", + "issuer": "3dnbnYY9i2bHMQUGyFp5GVvJ2wBkVpus31cDJA5cfRpj", + "previousIssuer": "JyTqcD4Q9aEAR2CWEpwBUAAyMCjfM6gaE5S2e8GWUuq", "dividend": null, "time": 1556123743, + "powMin": 60, "monetaryMass": 128680804, "unitbase": 1, "membersCount": 18, "issuersCount": 6, + "issuersFrame": 31, "issuersFrameVar": 0, "medianTime": 1556122058, "fork": false, "parameters": "", + "signature": "Mu+FgC5qhU1Wp7Ih2v7JYkKMM7rv6Z+I4qxUvmVTEW+BObKy87zHJ7B0PsZORPWFkETNpiNsm10COqPbnt84BA==", + "nonce": 10200000000241}, + {"identities": [], "joiners": [], "actives": [], "leavers": [], "revoked": [], "excluded": [], + "certifications": [], "transactions": [], "version": 11, "number": 360001, "currency": "g1-test", + "hash": "00035B7B452FAB7DBBE6E48DDD1060AC9A1A7096B32DD2C7763CC09A024A1597", + "inner_hash": "751DCAE2E881B5ABC0A61912033763872ED1D961C63C4A135CCB24E81DE17075", + "previousHash": "0001826F638E091DEDEC5E6A4D3917BC37772E16B66923818AED44182F9FBA45", + "issuer": "7BGpV28HzE6fyZtteuPmwHf6fHwHkQ9Ssww3Cxq82NnT", + "previousIssuer": "3dnbnYY9i2bHMQUGyFp5GVvJ2wBkVpus31cDJA5cfRpj", "dividend": null, "time": 1556123777, + "powMin": 60, "monetaryMass": 128680804, "unitbase": 1, "membersCount": 18, "issuersCount": 6, + "issuersFrame": 31, "issuersFrameVar": 0, "medianTime": 1556122255, "fork": false, "parameters": "", + "signature": "T13jarx2EopBhIDAmDLb5X6V9lvuIzrKvoh0ugWmOrUBZs0fcCqFyFew7d1TCPgkgjii/t3Bg/injOOVFrlPAQ==", + "nonce": 10400000000195}]}""" + response = parse_text(response_string, BLOCKS_RESPONSE_SCHEMA) + self.assertIsInstance(response, dict) +