Add http server to signer daemon
This commit is contained in:
parent
af7a16aa32
commit
441ace5ec4
@ -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)
|
||||||
|
@ -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
|
||||||
|
2
setup.py
2
setup.py
@ -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",
|
||||||
|
@ -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()
|
|
@ -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__':
|
||||||
|
Loading…
Reference in New Issue
Block a user