2020-10-17 02:44:25 +02:00
# standard imports
import os
import sys
import stat
2020-08-08 11:33:15 +02:00
import socket
import json
import logging
2020-10-17 02:44:25 +02:00
import argparse
2020-08-08 11:33:15 +02:00
2020-10-17 02:44:25 +02:00
# third-party imports
import confini
2020-08-08 11:33:15 +02:00
from jsonrpc . exceptions import *
2020-10-17 02:44:25 +02:00
# local imports
2020-08-08 11:33:15 +02:00
from crypto_dev_signer . eth . signer import ReferenceSigner
from crypto_dev_signer . eth . transaction import EIP155Transaction
from crypto_dev_signer . keystore import ReferenceKeystore
2020-10-17 02:44:25 +02:00
#logging.basicConfig(level=logging.DEBUG)
2020-08-08 11:33:15 +02:00
logg = logging . getLogger ( )
2020-10-17 02:44:25 +02:00
config_dir = os . path . join ( ' /usr/local/etc/cic-eth ' )
2020-08-08 11:33:15 +02:00
db = None
signer = None
chainId = 8995
2020-10-17 02:44:25 +02:00
socket_path = ' /run/crypto-dev-signer/jsonrpc.ipc '
argparser = argparse . ArgumentParser ( )
argparser . add_argument ( ' -c ' , type = str , default = config_dir , help = ' config file ' )
2020-10-17 14:47:01 +02:00
argparser . add_argument ( ' --env-prefix ' , default = os . environ . get ( ' CONFINI_ENV_PREFIX ' ) , dest = ' env_prefix ' , type = str , help = ' environment prefix for variables to overwrite configuration ' )
2020-10-17 02:44:25 +02:00
argparser . add_argument ( ' -i ' , type = int , help = ' default chain id for EIP155 ' )
argparser . add_argument ( ' -s ' , type = str , help = ' socket path ' )
argparser . add_argument ( ' -v ' , action = ' store_true ' , help = ' be verbose ' )
argparser . add_argument ( ' -vv ' , action = ' store_true ' , help = ' be more verbose ' )
args = argparser . parse_args ( )
if args . vv :
logging . getLogger ( ) . setLevel ( logging . DEBUG )
elif args . v :
logging . getLogger ( ) . setLevel ( logging . INFO )
2020-10-17 14:47:01 +02:00
config = confini . Config ( args . c , args . env_prefix )
config . process ( )
config . censor ( ' PASSWORD ' , ' DATABASE ' )
config . censor ( ' SECRET ' , ' SIGNER ' )
logg . debug ( ' config loaded from {} : \n {} ' . format ( config_dir , config ) )
2020-10-17 02:44:25 +02:00
if args . i :
chainId = args . i
if args . s :
socket_path = args . s
elif config . get ( ' SIGNER_SOCKET_PATH ' ) :
socket_path = config . get ( ' SIGNER_SOCKET_PATH ' )
2020-08-08 11:33:15 +02:00
2020-10-17 14:47:01 +02:00
2020-10-17 11:06:52 +02:00
# connect to database
dsn = ' postgresql:// {} : {} @ {} : {} / {} ' . format (
config . get ( ' DATABASE_USER ' ) ,
config . get ( ' DATABASE_PASSWORD ' ) ,
config . get ( ' DATABASE_HOST ' ) ,
config . get ( ' DATABASE_PORT ' ) ,
config . get ( ' DATABASE_NAME ' ) ,
)
2020-08-08 11:33:15 +02:00
2020-10-17 14:47:01 +02:00
logg . info ( ' using dsn {} ' . format ( dsn ) )
logg . info ( ' using socket {} ' . format ( socket_path ) )
2020-08-08 12:27:05 +02:00
class MissingSecretError ( BaseException ) :
def __init__ ( self , message ) :
super ( MissingSecretError , self ) . __init__ ( message )
2020-08-08 11:33:15 +02:00
def personal_new_account ( p ) :
password = p
if p . __class__ . __name__ != ' str ' :
if p . __class__ . __name__ != ' list ' :
e = JSONRPCInvalidParams ( )
e . data = ' parameter must be list containing one string '
raise ValueError ( e )
logg . error ( ' foo {} ' . format ( p ) )
if len ( p ) != 1 :
e = JSONRPCInvalidParams ( )
e . data = ' parameter must be list containing one string '
raise ValueError ( e )
if p [ 0 ] . __class__ . __name__ != ' str ' :
e = JSONRPCInvalidParams ( )
e . data = ' parameter must be list containing one string '
raise ValueError ( e )
password = p [ 0 ]
r = db . new ( password )
return r
def personal_sign_transaction ( p ) :
2020-09-22 11:26:48 +02:00
t = EIP155Transaction ( p [ 0 ] , p [ 0 ] [ ' nonce ' ] , 8995 )
2020-08-08 11:33:15 +02:00
z = signer . signTransaction ( t , p [ 1 ] )
raw_signed_tx = t . rlp_serialize ( )
o = {
' raw ' : ' 0x ' + raw_signed_tx . hex ( ) ,
' tx ' : t . serialize ( ) ,
}
2020-09-22 11:26:48 +02:00
logg . debug ( ' signed {} ' . format ( o ) )
2020-08-08 11:33:15 +02:00
return o
# TODO: temporary workaround for platform, since personal_signTransaction is missing from web3.py
def eth_signTransaction ( tx ) :
return personal_sign_transaction ( [ tx , ' ' ] )
methods = {
' personal_newAccount ' : personal_new_account ,
' personal_signTransaction ' : personal_sign_transaction ,
' eth_signTransaction ' : eth_signTransaction ,
}
def jsonrpc_error ( rpc_id , err ) :
return {
' json-rpc ' : ' 2.0 ' ,
' id ' : rpc_id ,
' error ' : {
' code ' : err . CODE ,
' message ' : err . MESSAGE ,
} ,
}
def jsonrpc_ok ( rpc_id , response ) :
return {
' json-rpc ' : ' 2.0 ' ,
' id ' : rpc_id ,
' result ' : response ,
}
def is_valid_json ( j ) :
if j . get ( ' id ' ) == ' None ' :
raise ValueError ( ' id missing ' )
return True
def process_input ( j ) :
rpc_id = j [ ' id ' ]
m = j [ ' method ' ]
p = j [ ' params ' ]
return ( rpc_id , methods [ m ] ( p ) )
def start_server ( ) :
2020-10-17 02:44:25 +02:00
socket_dir = os . path . dirname ( socket_path )
try :
fi = os . stat ( socket_dir )
if not stat . S_ISDIR :
RuntimeError ( ' socket path {} is not a directory ' . format ( socket_dir ) )
except FileNotFoundError :
os . mkdir ( socket_dir )
2020-08-08 11:33:15 +02:00
try :
2020-10-17 02:44:25 +02:00
os . unlink ( socket_path )
2020-08-08 11:33:15 +02:00
except FileNotFoundError :
pass
s = socket . socket ( family = socket . AF_UNIX , type = socket . SOCK_STREAM )
2020-10-17 02:44:25 +02:00
s . bind ( socket_path )
2020-08-08 11:33:15 +02:00
s . listen ( 10 )
while True :
( csock , caddr ) = s . accept ( )
d = csock . recv ( 4096 )
j = None
try :
j = json . loads ( d )
is_valid_json ( j )
logg . debug ( ' {} ' . format ( d . decode ( ' utf-8 ' ) ) )
2020-09-22 11:26:48 +02:00
except Exception as e :
logg . error ( ' input error {} ' . format ( e ) )
2020-08-08 11:33:15 +02:00
csock . send ( json . dumps ( jsonrpc_error ( None , JSONRPCParseError ) ) . encode ( ' utf-8 ' ) )
csock . close ( )
continue
try :
( rpc_id , r ) = process_input ( j )
2020-10-17 02:44:25 +02:00
r = jsonrpc_ok ( rpc_id , r )
j = json . dumps ( r ) . encode ( ' utf-8 ' )
csock . send ( j )
except ValueError as e :
2020-08-08 11:33:15 +02:00
# TODO: handle cases to give better error context to caller
2020-09-22 11:26:48 +02:00
logg . error ( ' process error {} ' . format ( e ) )
2020-08-08 11:33:15 +02:00
csock . send ( json . dumps ( jsonrpc_error ( j [ ' id ' ] , JSONRPCServerError ) ) . encode ( ' utf-8 ' ) )
csock . close ( )
s . close ( )
2020-10-17 02:44:25 +02:00
os . unlink ( socket_path )
2020-08-08 11:33:15 +02:00
def init ( ) :
global db , signer
2020-10-17 02:44:25 +02:00
secret_hex = config . get ( ' SIGNER_SECRET ' )
if secret_hex == None :
raise MissingSecretError ( ' please provide a valid hex value for the SIGNER_SECRET configuration variable ' )
2020-08-08 12:27:05 +02:00
2020-08-08 11:33:15 +02:00
secret = bytes . fromhex ( secret_hex )
kw = {
' symmetric_key ' : secret ,
}
2020-10-17 11:06:52 +02:00
db = ReferenceKeystore ( dsn , * * kw )
2020-08-08 11:33:15 +02:00
signer = ReferenceSigner ( db )
2020-10-18 10:32:23 +02:00
#if __name__ == '__main__':
def main ( ) :
2020-08-08 11:33:15 +02:00
init ( )
arg = None
try :
arg = json . loads ( sys . argv [ 1 ] )
except :
logg . info ( ' no json rpc command detected, starting socket server ' )
start_server ( )
sys . exit ( 0 )
( rpc_id , response ) = process_input ( arg )
r = jsonrpc_ok ( rpc_id , response )
sys . stdout . write ( json . dumps ( r ) )