From 5d6cde4b705c4f2e6c49ef7a89ae40f7bcfdf6e2 Mon Sep 17 00:00:00 2001 From: Moul <moul@moul.re> Date: Sun, 8 Dec 2019 22:19:05 +0200 Subject: [PATCH] =?UTF-8?q?[feat]=20#262:=20Add=20'verify'=20command=20to?= =?UTF-8?q?=20check=20blocks=E2=80=99=20signatures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Based on DuniterPy 0.56.0 which implements block signature verification - #183: Click progress bar - Handle BMA anti-spam protection whith an exception - Possibility to pass starting and ending block numbers to verify them - Default to 0,0(head) for the full blockchain --- silkaj/blocks.py | 97 +++++++++++++++++++++++++++++++++++++++++++++ silkaj/cli.py | 2 + silkaj/constants.py | 1 + 3 files changed, 100 insertions(+) create mode 100644 silkaj/blocks.py diff --git a/silkaj/blocks.py b/silkaj/blocks.py new file mode 100644 index 00000000..0c4e44bd --- /dev/null +++ b/silkaj/blocks.py @@ -0,0 +1,97 @@ +""" +Copyright 2016-2020 Maël Azimi <m.a@moul.re> + +Silkaj is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Silkaj 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Silkaj. If not, see <https://www.gnu.org/licenses/>. +""" + +import logging +from asyncio import sleep +from click import command, argument, INT, progressbar +from aiohttp.client_exceptions import ServerDisconnectedError + +from duniterpy.api import bma +from duniterpy.api.client import Client +from duniterpy.api.errors import DuniterError +from duniterpy.documents import Block +from duniterpy.key.verifying_key import VerifyingKey + +from silkaj.tools import message_exit, coroutine +from silkaj.network_tools import EndPoint, HeadBlock +from silkaj.constants import BMA_MAX_BLOCKS_CHUNK_SIZE + + +@command("verify", help="Verify blocks’ signatures. Range defaults to 0, 0 (head)") +@argument("from_", default=0, type=INT) +@argument("to", default=0, type=INT) +@coroutine +async def verify_blocks_signatures(from_, to): + client = Client(EndPoint().BMA_ENDPOINT) + + if to == 0: + to = (await HeadBlock().head_block)["number"] + if from_ > to: + message_exit("FROM should be lower than TO") + + invalid_blocks_signature = list() + chunks_from = range(from_, to + 1, BMA_MAX_BLOCKS_CHUNK_SIZE) + with progressbar(chunks_from, label="Processing blocks verification") as bar: + for chunk_from in bar: + chunk_size = get_chunk_size(from_, to, chunks_from, chunk_from) + logging.info( + "Processing chunk from block {} to {}".format( + chunk_from, chunk_from + chunk_size + ) + ) + chunk = await get_chunk(client, chunk_size, chunk_from) + + for block in chunk: + block = Block.from_signed_raw(block["raw"] + block["signature"] + "\n") + verify_block_signature(invalid_blocks_signature, block) + + await client.close() + display_result(from_, to, invalid_blocks_signature) + + +def get_chunk_size(from_, to, chunks_from, chunk_from): + """If not last chunk, take the maximum size + Otherwise, calculate the size for the last chunk""" + if chunk_from != chunks_from[-1]: + return BMA_MAX_BLOCKS_CHUNK_SIZE + else: + return (to + 1 - from_) % BMA_MAX_BLOCKS_CHUNK_SIZE + + +async def get_chunk(client, chunk_size, chunk_from): + try: + return await client(bma.blockchain.blocks, chunk_size, chunk_from) + except ServerDisconnectedError: + logging.info("Reach BMA anti-spam protection. Waiting two seconds") + await sleep(2) + return await client(bma.blockchain.blocks, chunk_size, chunk_from) + except DuniterError as error: + logging.error(error) + + +def verify_block_signature(invalid_blocks_signature, block): + key = VerifyingKey(block.issuer) + if not key.verify_document(block): + invalid_blocks_signature.append(block.number) + + +def display_result(from_, to, invalid_blocks_signature): + print("Within {0}-{1} range, ".format(from_, to), end="") + if invalid_blocks_signature: + print("blocks with a wrong signature:", *invalid_blocks_signature) + else: + print("no blocks with a wrong signature.") diff --git a/silkaj/cli.py b/silkaj/cli.py index 09c81a50..1e78ff03 100644 --- a/silkaj/cli.py +++ b/silkaj/cli.py @@ -33,6 +33,7 @@ from silkaj.commands import ( from silkaj.wot import received_sent_certifications, id_pubkey_correspondence from silkaj.auth import generate_auth_file from silkaj.license import license_command +from silkaj.blocks import verify_blocks_signatures from silkaj.constants import SILKAJ_VERSION @@ -95,6 +96,7 @@ cli.add_command(currency_info) cli.add_command(license_command) cli.add_command(network_info) cli.add_command(send_transaction) +cli.add_command(verify_blocks_signatures) cli.add_command(received_sent_certifications) diff --git a/silkaj/constants.py b/silkaj/constants.py index ae35b2ca..17185490 100644 --- a/silkaj/constants.py +++ b/silkaj/constants.py @@ -25,3 +25,4 @@ ASYNC_SLEEP = 0.1 SOURCES_PER_TX = 40 SUCCESS_EXIT_STATUS = 0 FAILURE_EXIT_STATUS = 1 +BMA_MAX_BLOCKS_CHUNK_SIZE = 5000 -- GitLab