Add http server to signer daemon

This commit is contained in:
nolash 2021-09-06 15:47:03 +02:00
parent af7a16aa32
commit 441ace5ec4
Signed by: lash
GPG Key ID: 21D2E7BB88C2A746
5 changed files with 143 additions and 117 deletions

View File

@ -8,6 +8,11 @@ import json
import logging import logging
import argparse import argparse
from urllib.parse import urlparse from urllib.parse import urlparse
from http.server import (
HTTPServer,
BaseHTTPRequestHandler,
)
# external imports # external imports
import confini import confini
@ -72,6 +77,7 @@ logg.info('using dsn {}'.format(dsn))
logg.info('using socket {}'.format(config.get('SIGNER_SOCKET_PATH'))) logg.info('using socket {}'.format(config.get('SIGNER_SOCKET_PATH')))
re_http = r'^http' re_http = r'^http'
re_tcp = r'^tcp'
re_unix = r'^ipc' re_unix = r'^ipc'
class MissingSecretError(BaseException): class MissingSecretError(BaseException):
@ -177,7 +183,7 @@ def start_server_tcp(spec):
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
s.bind(spec) s.bind(spec)
logg.debug('created tcp socket {}'.format(spec)) logg.debug('created tcp socket {}'.format(spec))
start_server(s) start_server_socket(s)
def start_server_unix(socket_path): def start_server_unix(socket_path):
@ -196,15 +202,30 @@ def start_server_unix(socket_path):
s = socket.socket(family = socket.AF_UNIX, type = socket.SOCK_STREAM) s = socket.socket(family = socket.AF_UNIX, type = socket.SOCK_STREAM)
s.bind(socket_path) s.bind(socket_path)
logg.debug('created unix ipc socket {}'.format(socket_path)) logg.debug('created unix ipc socket {}'.format(socket_path))
start_server(s) start_server_socket(s)
def start_server(s): def start_server_http(spec):
s.listen(10) httpd = HTTPServer(spec, HTTPSignRequestHandler)
logg.debug('server started') logg.debug('starting http server {}'.format(spec))
while True: httpd.serve_forever()
(csock, caddr) = s.accept()
d = csock.recv(4096)
class SignerError(Exception):
def __init__(self, s):
super(SignerError, self).__init__(s)
self.jsonrpc_error = s
def to_jsonrpc(self):
return self.jsonrpc_error
class SignRequestHandler:
def handle_jsonrpc(self, d):
j = None j = None
try: try:
j = json.loads(d) j = json.loads(d)
@ -212,23 +233,105 @@ def start_server(s):
logg.debug('{}'.format(d.decode('utf-8'))) logg.debug('{}'.format(d.decode('utf-8')))
except Exception as e: except Exception as e:
logg.exception('input error {}'.format(e)) logg.exception('input error {}'.format(e))
csock.send(json.dumps(jsonrpc_error(None, JSONRPCParseError)).encode('utf-8')) j = json.dumps(jsonrpc_error(None, JSONRPCParseError)).encode('utf-8')
csock.close() raise SignerError(j)
continue
try: try:
(rpc_id, r) = process_input(j) (rpc_id, r) = process_input(j)
r = jsonrpc_ok(rpc_id, r) r = jsonrpc_ok(rpc_id, r)
j = json.dumps(r).encode('utf-8') j = json.dumps(r).encode('utf-8')
csock.send(j)
except ValueError as e: except ValueError as e:
# TODO: handle cases to give better error context to caller # TODO: handle cases to give better error context to caller
logg.exception('process error {}'.format(e)) logg.exception('process error {}'.format(e))
csock.send(json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8')) j = json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8')
raise SignerError(j)
except UnknownAccountError as e: except UnknownAccountError as e:
logg.exception('process unknown account error {}'.format(e)) logg.exception('process unknown account error {}'.format(e))
csock.send(json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8')) j = json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8')
raise SignerError(j)
return j
class HTTPSignRequestHandler(BaseHTTPRequestHandler, SignRequestHandler):
def do_POST(self):
if self.headers.get('Content-Type') != 'application/json':
self.send_response(400, 'me read json only')
self.end_headers()
return
try:
if 'application/json' not in self.headers.get('Accept').split(','):
self.send_response(400, 'me json only speak')
self.end_headers()
return
except AttributeError:
pass
l = self.headers.get('Content-Length')
try:
l = int(l)
except ValueError:
self.send_response(400, 'content length must be integer')
self.end_headers()
return
if l > 4096:
self.send_response(400, 'too much information')
self.end_headers()
return
if l < 0:
self.send_response(400, 'you are too negative')
self.end_headers()
return
b = b''
c = 0
while c < l:
d = self.rfile.read(l-c)
if d == None:
break
b += d
c += len(d)
if c > 4096:
self.send_response(413, 'i should slap you around for lying about your size')
self.end_headers()
return
try:
r = self.handle_jsonrpc(d)
except SignerError as e:
r = e.to_jsonrpc()
#b = json.dumps(r).encode('utf-8')
l = len(r)
self.send_response(200, 'You are the Keymaster')
self.send_header('Content-Length', str(l))
self.send_header('Cache-Control', 'no-cache')
self.send_header('Content-Type', 'application/json')
self.end_headers()
c = 0
while c < l:
n = self.wfile.write(r[c:])
c += n
def start_server_socket(s):
s.listen(10)
logg.debug('server started')
handler = SignRequestHandler()
while True:
(csock, caddr) = s.accept()
d = csock.recv(4096)
r = None
try:
r = handler.handle_jsonrpc(d)
except SignerError as e:
r = e.to_jsonrpc()
csock.send(r)
csock.close() csock.close()
s.close() s.close()
@ -255,15 +358,20 @@ def main():
try: try:
arg = json.loads(sys.argv[1]) arg = json.loads(sys.argv[1])
except: except:
logg.info('no json rpc command detected, starting socket server') logg.info('no json rpc command detected, starting socket server {}'.format(socket_url))
scheme = 'ipc' scheme = 'ipc'
if socket_url.scheme != '': if socket_url.scheme != '':
scheme = socket_url.scheme scheme = socket_url.scheme
if re.match(re_http, socket_url.scheme): if re.match(re_tcp, socket_url.scheme):
socket_spec = socket_url.netloc.split(':') socket_spec = socket_url.netloc.split(':')
host = socket_spec[0] host = socket_spec[0]
port = int(socket_spec[1]) port = int(socket_spec[1])
start_server_tcp((host, port)) start_server_tcp((host, port))
elif re.match(re_http, socket_url.scheme):
socket_spec = socket_url.netloc.split(':')
host = socket_spec[0]
port = int(socket_spec[1])
start_server_http((host, port))
else: else:
start_server_unix(socket_url.path) start_server_unix(socket_url.path)
sys.exit(0) sys.exit(0)

View File

@ -2,6 +2,7 @@ cryptography==3.2.1
pysha3==1.0.2 pysha3==1.0.2
rlp==2.0.1 rlp==2.0.1
json-rpc==1.13.0 json-rpc==1.13.0
jsonrpc-std~=0.0.1a2
confini>=0.3.6rc3,<0.5.0 confini>=0.3.6rc3,<0.5.0
coincurve==15.0.0 coincurve==15.0.0
hexathon~=0.0.1a7 hexathon~=0.0.1a7

View File

@ -33,7 +33,7 @@ f.close()
setup( setup(
name="crypto-dev-signer", name="crypto-dev-signer",
version="0.4.15a1", version="0.4.15a2",
description="A signer and keystore daemon and library for cryptocurrency software development", description="A signer and keystore daemon and library for cryptocurrency software development",
author="Louis Holbrook", author="Louis Holbrook",
author_email="dev@holbrook.no", author_email="dev@holbrook.no",

View File

@ -1,91 +0,0 @@
# standard imports
import unittest
import logging
import os
# local imports
from crypto_dev_signer.keystore.dict import DictKeystore
from crypto_dev_signer.eth.signer import ReferenceSigner
from crypto_dev_signer.helper import TxExecutor
#from crypto_dev_signer.eth.helper import EthTxExecutor
from crypto_dev_signer.encoding import to_checksum_address
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
script_dir = os.path.realpath(os.path.dirname(__file__))
class MockEthTxBackend:
def dispatcher(self, tx):
logg.debug('sender {}'.format(tx))
return os.urandom(32)
def reporter(self, tx):
logg.debug('reporter {}'.format(tx))
def verifier(self, rcpt):
logg.debug('reporter {}'.format(rcpt))
def fee_price_helper(self):
return 21
def fee_helper(self, tx):
logg.debug('fee helper tx {}'.format(tx))
return 2
def builder(self, tx):
return tx
def builder_two(self, tx):
tx['value'] = 10243
tx['to'] = to_checksum_address('0x' + os.urandom(20).hex())
tx['data'] = '0x'
if tx.get('feePrice') != None:
tx['gasPrice'] = tx['feePrice']
del tx['feePrice']
if tx.get('feeUnits') != None:
tx['gas'] = tx['feeUnits']
del tx['feeUnits']
return tx
class TestHelper(unittest.TestCase):
def setUp(self):
logg.debug('setup')
self.db = DictKeystore()
keystore_filename = 'UTC--2021-01-08T18-37-01.187235289Z--00a329c0648769a73afac7f9381e08fb43dbea72'
keystore_filepath = os.path.join(script_dir, 'testdata', keystore_filename)
self.address_hex = self.db.import_keystore_file(keystore_filepath, '')
self.signer = ReferenceSigner(self.db)
def tearDown(self):
pass
def test_helper(self):
backend = MockEthTxBackend()
executor = TxExecutor(self.address_hex, self.signer, backend.builder, backend.dispatcher, backend.reporter, 666, 13, backend.fee_helper, backend.fee_price_helper, backend.verifier)
tx_ish = {'from': self.address_hex}
executor.sign_and_send([backend.builder_two])
# def test_eth_helper(self):
# backend = MockEthTxBackend()
# w3 = web3.Web3(web3.Web3.HTTPProvider('http://localhost:8545'))
# executor = EthTxExecutor(w3, self.address_hex, self.signer, 1337)
#
# tx_ish = {'from': self.address_hex}
# #executor.sign_and_send([backend.builder, backend.builder_two])
# with self.assertRaises(ValueError):
# executor.sign_and_send([backend.builder_two])
if __name__ == '__main__':
unittest.main()

View File

@ -1,10 +1,12 @@
#!/usr/bin/python # standard imports
import unittest import unittest
import logging import logging
import copy
# external imports
from rlp import encode as rlp_encode from rlp import encode as rlp_encode
# local imports
from crypto_dev_signer.eth.signer import ReferenceSigner from crypto_dev_signer.eth.signer import ReferenceSigner
from crypto_dev_signer.eth.transaction import EIP155Transaction from crypto_dev_signer.eth.transaction import EIP155Transaction
@ -23,15 +25,22 @@ tx_ints = {
} }
tx_hexs = { tx_hexs = {
'nonce': '0x0', 'nonce': '0x',
'from': "0xEB014f8c8B418Db6b45774c326A0E64C78914dC0", 'from': "0xEB014f8c8B418Db6b45774c326A0E64C78914dC0",
'gasPrice': "0x4a817c800", 'gasPrice': "0x04a817c800",
'gas': "0x5208", 'gas': "0x5208",
'to': '0x3535353535353535353535353535353535353535', 'to': '0x3535353535353535353535353535353535353535',
'value': "0x3e8", 'value': "0x03e8",
'data': "deadbeef", 'data': "0xdeadbeef",
} }
tx_hexs_r = copy.copy(tx_hexs)
tx_hexs_r['r'] = '0x'
tx_hexs_r['s'] = '0x'
tx_hexs_r['v'] = '0x01'
del tx_hexs_r['from']
class pkGetter: class pkGetter:
def __init__(self, pk): def __init__(self, pk):
@ -68,14 +77,14 @@ class TestSign(unittest.TestCase):
t = EIP155Transaction(tx_ints, 0) t = EIP155Transaction(tx_ints, 0)
self.assertRegex(t.__class__.__name__, "Transaction") self.assertRegex(t.__class__.__name__, "Transaction")
s = t.serialize() s = t.serialize()
self.assertEqual('{}'.format(s), "{'nonce': '0x', 'gasPrice': '0x04a817c800', 'gas': '0x5208', 'to': '0x3535353535353535353535353535353535353535', 'value': '0x03e8', 'data': '0xdeadbeef', 'v': '0x01', 'r': '0x', 's': '0x'}") self.assertDictEqual(s, tx_hexs_r)
r = t.rlp_serialize() r = t.rlp_serialize()
self.assertEqual(r.hex(), 'ea808504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef018080') self.assertEqual(r.hex(), 'ea808504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef018080')
t = EIP155Transaction(tx_hexs, 0) t = EIP155Transaction(tx_hexs, 0)
self.assertRegex(t.__class__.__name__, "Transaction") self.assertRegex(t.__class__.__name__, "Transaction")
s = t.serialize() s = t.serialize()
self.assertEqual('{}'.format(s), "{'nonce': '0x', 'gasPrice': '0x04a817c800', 'gas': '0x5208', 'to': '0x3535353535353535353535353535353535353535', 'value': '0x03e8', 'data': '0xdeadbeef', 'v': '0x01', 'r': '0x', 's': '0x'}") self.assertDictEqual(s, tx_hexs_r)
r = t.rlp_serialize() r = t.rlp_serialize()
self.assertEqual(r.hex(), 'ea808504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef018080') self.assertEqual(r.hex(), 'ea808504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef018080')
@ -91,7 +100,6 @@ class TestSign(unittest.TestCase):
s = ReferenceSigner(self.pk_getter) s = ReferenceSigner(self.pk_getter)
z = s.sign_ethereum_message(tx_ints['from'], '666f6f') z = s.sign_ethereum_message(tx_ints['from'], '666f6f')
z = s.sign_ethereum_message(tx_ints['from'], b'foo') z = s.sign_ethereum_message(tx_ints['from'], b'foo')
logg.debug('zzz {}'.format(str(z)))
if __name__ == '__main__': if __name__ == '__main__':