Adding logic to create blocks over RPC while allowing client to do VDF compute

This commit is contained in:
Kevin F 2023-04-21 09:08:55 -05:00
parent 8e5a5854e5
commit ae41fb1b72
3 changed files with 112 additions and 3 deletions

View File

@ -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=<id>
# Dispatcher is dictionary {<method_name>: callable}

View File

@ -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"

View File

@ -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()