async def faucet(self, to, value, *, from_private_key=FAUCET_PRIVATE_KEY, startgas=None, gasprice=DEFAULT_GASPRICE, nonce=None, data=b"", wait_on_confirmation=True): if isinstance(from_private_key, str): from_private_key = data_decoder(from_private_key) from_address = private_key_to_address(from_private_key) ethclient = JsonRPCClient(self._app.config['ethereum']['url']) to = data_decoder(to) if len(to) not in (20, 0): raise Exception( 'Addresses must be 20 or 0 bytes long (len was {})'.format( len(to))) if nonce is None: nonce = await ethclient.eth_getTransactionCount(from_address) balance = await ethclient.eth_getBalance(from_address) if startgas is None: startgas = await ethclient.eth_estimateGas(from_address, to, data=data, nonce=nonce, value=value, gasprice=gasprice) tx = Transaction(nonce, gasprice, startgas, to, value, data, 0, 0, 0) if balance < (tx.value + (tx.startgas * tx.gasprice)): raise Exception("Faucet doesn't have enough funds") tx.sign(from_private_key) tx_encoded = data_encoder(rlp.encode(tx, Transaction)) tx_hash = await ethclient.eth_sendRawTransaction(tx_encoded) while wait_on_confirmation: resp = await ethclient.eth_getTransactionByHash(tx_hash) if resp is None or resp['blockNumber'] is None: await asyncio.sleep(0.1) else: break if to == b'': print("contract address: {}".format(data_encoder(tx.creates))) return tx_hash
async def deploy_contract(self, bytecode, *, from_private_key=FAUCET_PRIVATE_KEY, startgas=None, gasprice=DEFAULT_GASPRICE, wait_on_confirmation=True): if isinstance(from_private_key, str): from_private_key = data_decoder(from_private_key) from_address = private_key_to_address(from_private_key) ethclient = JsonRPCClient(self._app.config['ethereum']['url']) nonce = await ethclient.eth_getTransactionCount(from_address) balance = await ethclient.eth_getBalance(from_address) gasestimate = await ethclient.eth_estimateGas(from_address, '', data=bytecode, nonce=nonce, value=0, gasprice=gasprice) if startgas is None: startgas = gasestimate elif gasestimate > startgas: raise Exception( "Estimated gas usage is larger than the provided gas") tx = Transaction(nonce, gasprice, startgas, '', 0, bytecode, 0, 0, 0) if balance < (tx.value + (tx.startgas * tx.gasprice)): raise Exception("Faucet doesn't have enough funds") tx.sign(from_private_key) tx_encoded = data_encoder(rlp.encode(tx, Transaction)) tx_hash = await ethclient.eth_sendRawTransaction(tx_encoded) contract_address = data_encoder(tx.creates) while wait_on_confirmation: resp = await ethclient.eth_getTransactionByHash(tx_hash) if resp is None or resp['blockNumber'] is None: await asyncio.sleep(0.1) else: code = await ethclient.eth_getCode(contract_address) if code == '0x': raise Exception("Failed to deploy contract") break return tx_hash, contract_address
def test_encode_decode_transaction(self): sender_private_key = "0x0164f7c7399f4bb1eafeaae699ebbb12050bc6a50b2836b9ca766068a9d000c0" sender_address = "0xde3d2d9dd52ea80f7799ef4791063a5458d13913" to_address = "0x056db290f8ba3250ca64a45d16284d04bc6f5fbf" value = 10000000000 nonce = 1048576 data = b'' gasprice = DEFAULT_GASPRICE startgas = DEFAULT_STARTGAS expected_tx_hash = "0x2f321aa116146a9bc62b61c76508295f708f42d56340c9e613ebfc27e33f240c" tx1 = Transaction(nonce, gasprice, startgas, to_address, value, data) tx1.sign(data_decoder(sender_private_key)) self.assertEqual(data_encoder(tx1.hash), expected_tx_hash) # rlputx1 = rlp.encode(tx1, UnsignedTransaction) # rlpstx1 = rlp.encode(tx1, Transaction) tx1 = Transaction(nonce, gasprice, startgas, to_address, value, data) enc1 = rlp.encode(tx1, UnsignedTransaction) tx2 = rlp.decode(enc1, UnsignedTransaction) tx2.sign(data_decoder(sender_private_key)) tx3 = Transaction(tx2.nonce, tx2.gasprice, tx2.startgas, tx2.to, tx2.value, tx2.data) tx3.sign(data_decoder(sender_private_key)) self.assertEqual(data_encoder(tx3.sender), sender_address) self.assertEqual(data_encoder(tx3.hash), expected_tx_hash) self.assertEqual(data_encoder(tx2.sender), sender_address) # NOTE: this is false because tx2 still thinks it's an unsigned tx # so it doesn't include the signature variables in the tx # if this suddenly starts failing, it means the behaviour # has been modified in the library self.assertNotEqual(data_encoder(tx2.hash), expected_tx_hash)
from tornado.testing import gen_test from tokenid.app import urls from tokenservices.test.database import requires_database from tokenservices.test.base import AsyncHandlerTest from tokenservices.ethereum.utils import data_decoder TEST_PRIVATE_KEY = data_decoder( "0xe8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35") TEST_ADDRESS = "0x056db290f8ba3250ca64a45d16284d04bc6f5fbf" TEST_PAYMENT_ADDRESS = "0x444433335555ffffaaaa222211119999ffff7777" TEST_ADDRESS_2 = "0x056db290f8ba3250ca64a45d16284d04bc000000" class UserHandlerTest(AsyncHandlerTest): def get_urls(self): return urls def get_url(self, path): path = "/v1{}".format(path) return super().get_url(path) @gen_test @requires_database async def test_update_reputation(self): self._app.config['reputation'] = {'id': TEST_ADDRESS} async with self.pool.acquire() as con: await con.execute("INSERT INTO users (token_id) VALUES ($1)",
import asyncio import time from tornado.escape import json_decode, json_encode from tornado.testing import gen_test from tornado.platform.asyncio import to_asyncio_future from tokeneth.app import urls from tokeneth.test.base import EthServiceBaseTest from tokenservices.test.database import requires_database from tokenservices.test.redis import requires_redis from tokenservices.test.ethereum.parity import requires_parity, FAUCET_PRIVATE_KEY, FAUCET_ADDRESS from tokenservices.request import sign_request from tokenservices.ethereum.utils import data_decoder, data_encoder from tokenservices.ethereum.tx import sign_transaction, decode_transaction, signature_from_transaction TEST_PRIVATE_KEY = data_decoder( "0xe8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35") TEST_ADDRESS = "0x056db290f8ba3250ca64a45d16284d04bc6f5fbf" TEST_PRIVATE_KEY_2 = data_decoder( "0x0ffdb88a7a0a40831ca0b19bd31f3f6085764ef8b7db1bd6b57072e5eaea24ff") TEST_ADDRESS_2 = "0x35351b44e03ec8515664a955146bf9c6e503a381" class TransactionTest(EthServiceBaseTest): @gen_test(timeout=30) @requires_database @requires_redis @requires_parity async def test_create_and_send_transaction(self): body = {"from": FAUCET_ADDRESS, "to": TEST_ADDRESS, "value": 10**10}
async def create_transaction_skeleton(self, *, to_address, from_address, value=0, nonce=None, gas=None, gas_price=None, data=None): if not validate_address(from_address): raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_from_address', 'message': 'Invalid From Address' }) if not validate_address(to_address): raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_to_address', 'message': 'Invalid To Address' }) if value: value = parse_int(value) if value is None: raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_value', 'message': 'Invalid Value' }) # check optional arguments if nonce is None: # check cache for nonce nonce = self.redis.get("nonce:{}".format(from_address)) if nonce: nonce = int(nonce) # get the network's value too nw_nonce = await self.eth.eth_getTransactionCount(from_address) if nonce is None or nw_nonce > nonce: # if not cached, or the cached value is lower than # the network value, use the network value! nonce = nw_nonce else: nonce = parse_int(nonce) if nonce is None: raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_nonce', 'message': 'Invalid Nonce' }) if data is not None: if isinstance(data, int): data = hex(data) if isinstance(data, str): try: data = data_decoder(data) except binascii.Error: pass if not isinstance(data, bytes): raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_data', 'message': 'Invalid Data field' }) else: data = b'' if gas is None: # if there is data the default startgas value wont be enough if data: gas = await self.eth.eth_estimateGas(from_address, to_address, nonce=nonce, data=data) else: gas = DEFAULT_STARTGAS else: gas = parse_int(gas) if gas is None: raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_gas', 'message': 'Invalid Gas' }) if gas_price is None: gas_price = DEFAULT_GASPRICE else: gas_price = parse_int(gas_price) if gas_price is None: raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_gas_price', 'message': 'Invalid Gas Price' }) tx = create_transaction(nonce=nonce, gasprice=gas_price, startgas=gas, to=to_address, value=value, data=data) transaction = encode_transaction(tx) return transaction
async def send_transaction(self, *, tx, signature=None): try: tx = decode_transaction(tx) except: raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_transaction', 'message': 'Invalid Transaction' }) if is_transaction_signed(tx): if signature: tx_sig = signature_from_transaction(tx) if tx_sig != signature: raise JsonRPCInvalidParamsError( data={ 'id': 'invalid_signature', 'message': 'Invalid Signature: Signature in payload and signature of transaction do not match' }) else: if signature is None: raise JsonRPCInvalidParamsError(data={ 'id': 'missing_signature', 'message': 'Missing Signature' }) if not validate_signature(signature): raise JsonRPCInvalidParamsError( data={ 'id': 'invalid_signature', 'message': 'Invalid Signature: {}'.format('Invalid length' if len( signature) != 132 else 'Invalid hex value') }) try: signature = data_decoder(signature) except Exception: log.exception( "Unexpected error decoding valid signature: {}".format( signature)) raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_signature', 'message': 'Invalid Signature' }) add_signature_to_transaction(tx, signature) from_address = data_encoder(tx.sender) to_address = data_encoder(tx.to) # prevent spamming of transactions with the same nonce from the same sender with RedisLock(self.redis, "{}:{}".format(from_address, tx.nonce), raise_when_locked=partial(JsonRPCInvalidParamsError, data={ 'id': 'invalid_nonce', 'message': 'Nonce already used' }), ex=5): # disallow transaction overwriting for known transactions async with self.db: existing = await self.db.fetchrow( "SELECT * FROM transactions WHERE " "from_address = $1 AND nonce = $2 AND last_status != $3", from_address, tx.nonce, 'error') if existing: # debugging checks existing_tx = await self.eth.eth_getTransactionByHash( existing['transaction_hash']) raise JsonRPCInvalidParamsError(data={ 'id': 'invalid_nonce', 'message': 'Nonce already used' }) # make sure the account has enough funds for the transaction network_balance, balance = await self.get_balances( from_address, ignore_pending_recieved=True) log.info( "Attempting to send transaction\n{} -> {}\nValue: {} + {} (gas) * {} (startgas) = {}\nSender's Balance {} ({} unconfirmed)" .format(from_address, to_address, tx.value, tx.startgas, tx.gasprice, tx.value + (tx.startgas * tx.gasprice), network_balance, balance)) if balance < (tx.value + (tx.startgas * tx.gasprice)): raise JsonRPCInsufficientFundsError( data={ 'id': 'insufficient_funds', 'message': 'Insufficient Funds' }) # validate the nonce c_nonce = self.redis.get("nonce:{}".format(from_address)) if c_nonce: c_nonce = int(c_nonce) # get the network's value too nw_nonce = await self.eth.eth_getTransactionCount(from_address) if c_nonce is None or nw_nonce > c_nonce: c_nonce = nw_nonce if tx.nonce < c_nonce: raise JsonRPCInvalidParamsError( data={ 'id': 'invalid_nonce', 'message': 'Provided nonce is too low' }) if tx.nonce > c_nonce: raise JsonRPCInvalidParamsError( data={ 'id': 'invalid_nonce', 'message': 'Provided nonce is too high' }) # send the transaction to the network try: tx_encoded = encode_transaction(tx) tx_hash = await self.eth.eth_sendRawTransaction(tx_encoded) except JsonRPCError as e: log.error(e.format()) raise JsonRPCInternalError( data={ 'id': 'unexpected_error', 'message': 'An error occured communicating with the ethereum network, try again later' }) # cache nonce self.redis.set("nonce:{}".format(from_address), tx.nonce + 1) # add tx to database async with self.db: await self.db.execute( "INSERT INTO transactions " "(transaction_hash, from_address, to_address, nonce, value, estimated_gas_cost, sender_token_id) " "VALUES ($1, $2, $3, $4, $5, $6, $7)", tx_hash, from_address, to_address, tx.nonce, str(tx.value), str(tx.startgas * tx.gasprice), self.user_token_id) await self.db.commit() # if there is a block monitor, force send PNs for this without # waiting for the node to see it if hasattr(self.application, 'monitor'): txjson = transaction_to_json(tx) assert txjson['hash'] == tx_hash IOLoop.current().add_callback( self.application.monitor.send_transaction_notifications, txjson) return tx_hash
def test_valid_recovery(self): self.assertTrue(ecrecover( '{"custom":{"about":"about ","location":"location "},"timestamp":1483968938,"username":"******"}', data_decoder('0xbd5c9009cc87c6d4ebb3ef8223fc036726bc311678890890619c787aa914d3b636aee82d885c6fb668233b5cc70ab09eea7051648f989e758ee09234f5340d9100'), '0x5249dc212cd9c16f107c50b6c893952d617c011e' ))
def test_too_short_signature_comparison(self): self.assertFalse(ecrecover( '{"custom":{"about":"æ","location":""},"timestamp":1483964545,"username":"******"}', data_decoder('0x5301'), '0x5249dc212cd9c16f107c50b6c893952d617c011e' ))
def test_too_short_signature(self): self.assertEqual(ecrecover( '{"custom":{"about":"æ","location":""},"timestamp":1483964545,"username":"******"}', data_decoder('0x5301') ), None)
def test_valid_recovery_unicode(self): self.assertTrue(ecrecover( '{"custom":{"about":"æ","location":""},"timestamp":1483964545,"username":"******"}', data_decoder('0xb3c61812e1e73f1a75cc9a2f5e748099378b7af2dd8bc3c1b4f0c067e6e9a4012d0c411b77bab63708b350742d41de574add6b06a3d06a5ae10fc9c63c18405301'), '0x5249dc212cd9c16f107c50b6c893952d617c011e' ))
def do_POST(self): # TODO: figure out why read is blocking here data = self.rfile.read(len(self.rfile.peek())) data = data.decode('utf-8') data = json.loads(data) if self.path == "/v1/tx/skel": gas_price = parse_int( data['gas_price']) if 'gas_price' in data else DEFAULT_GASPRICE gas = parse_int(data['gas']) if 'gas' in data else DEFAULT_STARTGAS nonce = parse_int(data['nonce']) if 'nonce' in data else 0 if 'value' not in data or 'from' not in data or 'to' not in data: self.write_data( 400, { 'errors': [{ 'id': 'bad_arguments', 'message': 'Bad Arguments' }] }) return value = parse_int(data['value']) to_address = data['to'] from_address = data['from'] if not validate_address(to_address): self.write_data( 400, { 'errors': [{ 'id': 'invalid_to_address', 'message': 'Invalid To Address' }] }) return if not validate_address(from_address): self.write_data( 400, { 'errors': [{ 'id': 'invalid_from_address', 'message': 'Invalid From Address' }] }) return tx = create_transaction(nonce=nonce, gasprice=gas_price, startgas=gas, to=to_address, value=value) transaction = encode_transaction(tx) self.write_data( 200, { "tx_data": { "nonce": hex(nonce), "from": from_address, "to": to_address, "value": hex(value), "startGas": hex(gas), "gasPrice": hex(gas_price) }, "tx": transaction }) elif self.path == "/v1/tx": tx = decode_transaction(data['tx']) if 'signature' in data: sig = data_decoder(data['signature']) add_signature_to_transaction(tx, sig) self.write_data(200, {"tx_hash": data_encoder(tx.hash)}) else: self.write_data(404)