From ae41fb1b724099796f9c409adcf3f770011ebfd5 Mon Sep 17 00:00:00 2001 From: Kevin F Date: Fri, 21 Apr 2023 09:08:55 -0500 Subject: [PATCH] Adding logic to create blocks over RPC while allowing client to do VDF compute --- static-data/official-plugins/rpc/main.py | 2 +- .../rpc/rpc/blocks/__init__.py | 32 +++++++- .../rpc/test_insert_block.py | 81 +++++++++++++++++++ 3 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 tests/default-plugin-tests/rpc/test_insert_block.py diff --git a/static-data/official-plugins/rpc/main.py b/static-data/official-plugins/rpc/main.py index f8066ace..e2578215 100644 --- a/static-data/official-plugins/rpc/main.py +++ b/static-data/official-plugins/rpc/main.py @@ -89,7 +89,7 @@ class OnionrRPC(object): @cherrypy.expose def rpc(self): - # Basic RPC, intended for small amounts of work + # Basic RPC, intended for work that won't block very long # Use /queue_rpc for large workloads like creating blocks # and getting results with /get_rpc_result?id= # Dispatcher is dictionary {: callable} diff --git a/static-data/official-plugins/rpc/rpc/blocks/__init__.py b/static-data/official-plugins/rpc/rpc/blocks/__init__.py index e842714e..d9bde71f 100644 --- a/static-data/official-plugins/rpc/rpc/blocks/__init__.py +++ b/static-data/official-plugins/rpc/rpc/blocks/__init__.py @@ -1,10 +1,13 @@ from secrets import randbits import base64 +from time import time from typing import Union -import ujson from onionrblocks import Block +import kasten +from kasten.generator import pack as kasten_pack + import onionrblocks from jsonrpc import dispatcher @@ -67,6 +70,30 @@ def create_and_insert_block( return bl +@dispatcher.add_method +def prepare_block_for_vdf(block_data: 'base64', block_type, ttl: int, metadata: dict): + # This allows for untrusted clients to create blocks, they just have to compute the VDF + metadata['ttl'] = ttl + kasten_packed = kasten_pack.pack(block_data, block_type, metadata, int(time())) + kasten_obj = kasten.Kasten('', kasten_packed, kasten.generator.KastenBaseGenerator, auto_check_generator=False) + return { + 'raw': base64.b64encode(kasten_obj).decode('utf-8'), + 'rounds_needed': onionrblocks.blockcreator.anonvdf.AnonVDFGenerator.get_rounds_for_ttl_seconds(ttl, len(kasten_packed)) + } + +@dispatcher.add_method +def assemble_and_insert_block( + kasten_packed: 'base64', vdf_result: 'base64') -> str: + bl = onionrblocks.Block( + base64.b64decode(vdf_result), + base64.b64decode(kasten_packed), auto_verify=True) + insert_block(bl) + return { + 'id': bl.id, + 'raw': base64.b64encode(bl.raw).decode('utf-8') + } + + # As per dandelion++ spec the edge should be the same. # We keep it the same for each daemon life time. queue_to_use = randbits(1) @@ -74,8 +101,9 @@ queue_to_use = randbits(1) @dispatcher.add_method def insert_block(block: Union[dict, Block]): + # Accepts dict because json and accepts block because other functions use it if isinstance(block, dict): block = Block( - block['id'], base64.b64decode(block['raw']), auto_verify=False) + block['id'], base64.b64decode(block['raw']), auto_verify=True) gossip_block_queues[queue_to_use].put_nowait(block) return "ok" diff --git a/tests/default-plugin-tests/rpc/test_insert_block.py b/tests/default-plugin-tests/rpc/test_insert_block.py new file mode 100644 index 00000000..f4df881d --- /dev/null +++ b/tests/default-plugin-tests/rpc/test_insert_block.py @@ -0,0 +1,81 @@ +import os, uuid +import base64 +import secrets + +import time +from nacl import signing + +import kasten + +TEST_DIR = 'testdata/%s-%s' % (str(uuid.uuid4())[:6], os.path.basename(__file__)) + '/' +print("Test directory:", TEST_DIR) +os.environ["ONIONR_HOME"] = TEST_DIR + +import unittest +import sys + +sys.path.append('static-data/official-plugins/rpc/rpc') +sys.path.append("src/") + +import queue + +import onionrblocks + +from gossip import blockqueues + + +import blocks + +class MockQueue: + def __init__(self): + self.data = [] + def get_nowait(self): + return self.data.pop(0) + + def put_nowait(self, data): + print("putting", data) + self.data.append(data) + return True + + + +class RPCInsertBlockTest(unittest.TestCase): + + def test_insert_block_dict_valid(self): + bl = onionrblocks.create_anonvdf_block(b'test', 'test', 3600) + insert_data = { + 'id': bl.id, + 'raw': base64.b64encode(bl.raw).decode('utf-8') + } + assert blocks.insert_block(insert_data) == "ok" + try: + blockqueues.gossip_block_queues[0].get_nowait() + except queue.Empty: + pass + else: + return + bl = blockqueues.gossip_block_queues[1].get_nowait() + + def test_insert_block_dict_invalid(self): + bl = onionrblocks.create_anonvdf_block(b'test', 'test', 3600) + insert_data = { + 'id': secrets.token_hex(len(bl.id)), + 'raw': base64.b64encode(bl.raw).decode('utf-8') + } + try: + blocks.insert_block(insert_data) + except kasten.exceptions.InvalidID: + pass + try: + blockqueues.gossip_block_queues[0].get_nowait() + except queue.Empty: + pass + else: + raise AssertionError("Block was inserted") + try: + blockqueues.gossip_block_queues[1].get_nowait() + except queue.Empty: + pass + else: + raise AssertionError("Block was inserted") +unittest.main()