diff --git a/silkaj/blocks.py b/silkaj/blocks.py new file mode 100644 index 0000000000000000000000000000000000000000..0c4e44bdb59ef4de5f7996a8f1aaf97f2fa67ea7 --- /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 09c81a504b74a6ea0e9d2f5e85f8f4c55bac7033..1e78ff03a383e5e9fccd3a55de9f3bec692b077c 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 ae35b2ca925a06183d3aa5359b14b4a1369b81fa..17185490b1d3c45d9b203bf6ad541dfbffc8fec9 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