async def test_transaction_nonce_lock(self): """Spams transactions with the same nonce, and ensures the server rejects all but one""" no_tests = 20 txs = [] tx = await self.get_tx_skel(FAUCET_PRIVATE_KEY, TEST_ADDRESS, 10**10) dtx = decode_transaction(tx) txs.append(sign_transaction(tx, FAUCET_PRIVATE_KEY)) for i in range(11, 10 + no_tests): tx = await self.get_tx_skel(FAUCET_PRIVATE_KEY, TEST_ADDRESS, 10**i) self.assertEqual(decode_transaction(tx).nonce, dtx.nonce) txs.append(sign_transaction(tx, FAUCET_PRIVATE_KEY)) responses = await asyncio.gather(*(to_asyncio_future( self.fetch("/tx", method="POST", body={"tx": tx})) for tx in txs)) ok = 0 bad = 0 for resp in responses: if resp.code == 200: ok += 1 else: bad += 1 self.assertEqual(ok, 1) self.assertEqual(bad, no_tests - 1) # TODO: deal with lingering ioloop tasks better await asyncio.sleep(1)
async def test_send_transaction_with_unconfirmed_funds(self, *, ethminer): """Tests that someone can create a tx with unconfirmed funds""" # make sure no blocks are mined ethminer.pause() # send funds to address1 val1 = 10 ** 18 resp = await self.fetch("/tx/skel", method="POST", body={ "from": FAUCET_ADDRESS, "to": TEST_ADDRESS_1, "value": val1 # 1 eth }) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = sign_transaction(body['tx'], FAUCET_PRIVATE_KEY) resp = await self.fetch("/tx", method="POST", body={ "tx": tx }) self.assertEqual(resp.code, 200, resp.body) tx1_hash = json_decode(resp.body)['tx_hash'] # send transaction from address1 val2 = 2 * 10 ** 17 # 0.2 eth resp = await self.fetch("/tx/skel", method="POST", body={ "from": TEST_ADDRESS_1, "to": TEST_ADDRESS_2, "value": val2 }) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = sign_transaction(body['tx'], TEST_PRIVATE_KEY_1) resp = await self.fetch("/tx", method="POST", body={ "tx": tx }) self.assertEqual(resp.code, 200, resp.body) tx2_hash = json_decode(resp.body)['tx_hash'] await asyncio.sleep(1) # make sure 2nd transaction is 'queued' async with self.pool.acquire() as con: row = await con.fetchrow("SELECT * FROM transactions WHERE from_address = $1", TEST_ADDRESS_1) self.assertIsNotNone(row) self.assertEqual(row['status'], 'queued') # make sure balance adjusts for queued items resp = await self.fetch('/balance/{}'.format(TEST_ADDRESS_1)) self.assertEqual(resp.code, 200) data = json_decode(resp.body) self.assertEqual(parse_int(data['confirmed_balance']), 0) self.assertEqual(parse_int(data['unconfirmed_balance']), val1 - (val2 + (DEFAULT_STARTGAS * DEFAULT_GASPRICE))) ethminer.start() await self.wait_on_tx_confirmation(tx1_hash) await self.wait_on_tx_confirmation(tx2_hash) await asyncio.sleep(1)
async def test_send_txs_for_all_networks(self): tx = create_transaction(nonce=1048576, gasprice=20 * 10**9, startgas=21000, to="0x3535353535353535353535353535353535353535", value=10**18, data=b'') sign_transaction(tx, FAUCET_PRIVATE_KEY) resp = await self.fetch("/tx", method="POST", body={ "tx": encode_transaction(tx) }) self.assertEqual(resp.code, 200, resp.body) await self.wait_on_tx_confirmation(data_encoder(tx.hash))
async def test_unable_to_send_txs_for_other_networks(self): network_id = 1 # Test network id is 66 tx = create_transaction(nonce=9, gasprice=20 * 10**9, startgas=21000, to="0x3535353535353535353535353535353535353535", value=10**18, data=b'', network_id=network_id) sign_transaction(tx, FAUCET_PRIVATE_KEY) resp = await self.fetch("/tx", method="POST", body={ "tx": encode_transaction(tx) }) self.assertEqual(resp.code, 400, resp.body)
async def test_raw_deploy_contract(self, *, parity): """Tests that sending a raw transaction with a contract deployment works""" # contract data data = "0x6060604052341561000c57fe5b6040516102b83803806102b8833981016040528080518201919050505b806000908051906020019061003f929190610047565b505b506100ec565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061008857805160ff19168380011785556100b6565b828001600101855582156100b6579182015b828111156100b557825182559160200191906001019061009a565b5b5090506100c391906100c7565b5090565b6100e991905b808211156100e55760008160009055506001016100cd565b5090565b90565b6101bd806100fb6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063cfae32171461003b575bfe5b341561004357fe5b61004b6100d4565b604051808060200182810382528381815181526020019150805190602001908083836000831461009a575b80518252602083111561009a57602082019150602081019050602083039250610076565b505050905090810190601f1680156100c65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100dc61017d565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101725780601f1061014757610100808354040283529160200191610172565b820191906000526020600020905b81548152906001019060200180831161015557829003601f168201915b505050505090505b90565b6020604051908101604052806000815250905600a165627a7a72305820493059270656b40625319934bd6e91b0e68cf32c54c099dfc6cf540e40c91b9500290000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c68656c6c6f20776f726c64210000000000000000000000000000000000000000" resp = await self.fetch("/tx/skel", method="POST", body={ "from": FAUCET_ADDRESS, "data": data }) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = sign_transaction(body['tx'], FAUCET_PRIVATE_KEY) resp = await self.fetch("/tx", method="POST", body={"tx": tx}) self.assertEqual(resp.code, 200, resp.body) await self.wait_on_tx_confirmation(json_decode(resp.body)['tx_hash']) # test that contract txs from outside are handled correctly resp = await self.fetch_signed( "/apn/register", signing_key=FAUCET_PRIVATE_KEY, method="POST", body={"registration_id": "blahblahblah"}) resp = await self.fetch("/tx/skel", method="POST", body={ "from": FAUCET_ADDRESS, "data": data }) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = sign_transaction(body['tx'], FAUCET_PRIVATE_KEY) # deploy manually rpcclient = JsonRPCClient(parity.dsn()['url']) tx_hash = await rpcclient.eth_sendRawTransaction(tx) await self.wait_on_tx_confirmation(tx_hash) await asyncio.sleep( 5) # make sure the monitor has had a chance to process this async with self.pool.acquire() as con: rows = await con.fetch( "SELECT * FROM transactions WHERE hash = $1", tx_hash) self.assertEqual(len(rows), 1)
async def test_get_sofa_payment(self): body = { "from": FAUCET_ADDRESS, "to": TEST_ADDRESS, "value": 10 ** 10 } resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = sign_transaction(body['tx'], FAUCET_PRIVATE_KEY) resp = await self.fetch("/tx", method="POST", body={ "tx": tx }) self.assertEqual(resp.code, 200, resp.body) body = json_decode(resp.body) tx_hash = body['tx_hash'] await self.wait_on_tx_confirmation(tx_hash) resp = await self.fetch("/tx/{}?format=sofa".format(tx_hash), method="GET") self.assertEqual(resp.code, 200, resp.body) message = parse_sofa_message(resp.body.decode('utf-8')) self.assertEqual(message["txHash"], tx_hash) self.assertEqual(message["status"], "confirmed")
async def test_create_and_send_multiple_transactions(self): body = {"from": FAUCET_ADDRESS, "to": TEST_ADDRESS, "value": 10**10} tx_hashes = [] resp = await self.fetch("/balance/{}".format(FAUCET_ADDRESS)) last_balance = json_decode(resp.body)['unconfirmed_balance'] for i in range(10): resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) tx = sign_transaction( json_decode(resp.body)['tx'], FAUCET_PRIVATE_KEY) resp = await self.fetch("/tx", method="POST", body={"tx": tx}) self.assertEqual(resp.code, 200, resp.body) tx_hash = json_decode(resp.body)['tx_hash'] tx_hashes.append(tx_hash) resp = await self.fetch("/balance/{}".format(FAUCET_ADDRESS)) balance = json_decode(resp.body)['unconfirmed_balance'] # ensure the unconfirmed balance is changing with each request self.assertNotEqual(balance, last_balance) for tx_hash in tx_hashes: await self.wait_on_tx_confirmation(tx_hash)
async def test_cancel_transaction_fails_if_not_queued(self): tx = create_transaction(nonce=0, gasprice=10 ** 10, startgas=21000, to=TEST_ADDRESS, value=10 ** 18) tx = sign_transaction(tx, FAUCET_PRIVATE_KEY) tx_hash = calculate_transaction_hash(tx) from_address = FAUCET_ADDRESS to_address = TEST_ADDRESS async with self.pool.acquire() as con: await con.execute("INSERT INTO transactions (hash, from_address, to_address, nonce, value, gas, gas_price, data, v, r, s, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)", tx_hash, from_address, to_address, tx.nonce, hex(tx.value), hex(tx.startgas), hex(tx.gasprice), data_encoder(tx.data), hex(tx.v), hex(tx.r), hex(tx.s), 'unconfirmed') signature = personal_sign(FAUCET_PRIVATE_KEY, "Cancel transaction " + tx_hash) resp = await self.fetch("/tx/cancel", method="POST", body={"tx_hash": tx_hash, "signature": signature}) self.assertResponseCodeEqual(resp, 400) async with self.pool.acquire() as con: await con.execute("UPDATE transactions SET status = 'confirmed'") resp = await self.fetch("/tx/cancel", method="POST", body={"tx_hash": tx_hash, "signature": signature}) self.assertResponseCodeEqual(resp, 400) async with self.pool.acquire() as con: await con.execute("UPDATE transactions SET status = 'error'") resp = await self.fetch("/tx/cancel", method="POST", body={"tx_hash": tx_hash, "signature": signature}) self.assertResponseCodeEqual(resp, 400)
async def test_cancel_transaction(self): listener = MockTaskListener(self._app) listener.start_task_listener() tx = create_transaction(nonce=0, gasprice=10 ** 10, startgas=21000, to=TEST_ADDRESS, value=10 ** 18) tx = sign_transaction(tx, FAUCET_PRIVATE_KEY) tx_hash = calculate_transaction_hash(tx) from_address = FAUCET_ADDRESS to_address = TEST_ADDRESS async with self.pool.acquire() as con: await con.execute("INSERT INTO transactions (hash, from_address, to_address, nonce, value, gas, gas_price, data, v, r, s, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)", tx_hash, from_address, to_address, tx.nonce, hex(tx.value), hex(tx.startgas), hex(tx.gasprice), data_encoder(tx.data), hex(tx.v), hex(tx.r), hex(tx.s), 'queued') signature = personal_sign(FAUCET_PRIVATE_KEY, "Cancel transaction " + tx_hash) resp = await self.fetch("/tx/cancel", method="POST", body={"tx_hash": tx_hash, "signature": signature}) self.assertResponseCodeEqual(resp, 204) tx_id, status = await listener.get() self.assertEqual(tx_id, 1) self.assertEqual(status, 'error') await listener.stop_task_listener()
async def test_transactions_with_known_sender_toshi_id_but_invalid_signature( self): body = {"from": FAUCET_ADDRESS, "to": TEST_ADDRESS, "value": 10**10} resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) tx = sign_transaction(json_decode(resp.body)['tx'], FAUCET_PRIVATE_KEY) body = {"tx": tx} timestamp = int(time.time()) signature = sign_request(FAUCET_PRIVATE_KEY, "POST", "/v1/tx", timestamp, json_encode(body).encode('utf-8')) resp = await self.fetch_signed("/tx", method="POST", body=body, address=TEST_ADDRESS_2, signature=signature, timestamp=timestamp) self.assertEqual(resp.code, 400, resp.body) self.assertIsNotNone(resp.body) error = json_decode(resp.body) self.assertIn('errors', error) self.assertEqual(len(error['errors']), 1)
async def test_create_and_send_transaction_with_no_value_and_data(self): body = { "from": FAUCET_ADDRESS, "to": TEST_ADDRESS, "data": "0xffffffff" } resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = sign_transaction(body['tx'], FAUCET_PRIVATE_KEY) body = {"tx": tx} resp = await self.fetch("/tx", method="POST", body=body) self.assertEqual(resp.code, 200, resp.body) body = json_decode(resp.body) tx_hash = body['tx_hash'] await self.wait_on_tx_confirmation(tx_hash)
async def test_create_and_send_transaction_with_separate_sig(self): body = {"from": FAUCET_ADDRESS, "to": TEST_ADDRESS, "value": 10**10} resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = decode_transaction(body['tx']) tx = sign_transaction(tx, FAUCET_PRIVATE_KEY) sig = signature_from_transaction(tx) body = {"tx": body['tx'], "signature": data_encoder(sig)} resp = await self.fetch("/tx", method="POST", body=body) self.assertEqual(resp.code, 200, resp.body) # ensure we get a tracking events self.assertEqual((await self.next_tracking_event())[0], None) body = json_decode(resp.body) tx_hash = body['tx_hash'] async def check_db(): async with self.pool.acquire() as con: rows = await con.fetch( "SELECT * FROM transactions WHERE nonce = $1", tx.nonce) self.assertEqual(len(rows), 1) self.assertEqual(rows[0]['hash'], tx_hash) await self.wait_on_tx_confirmation(tx_hash)
async def test_resend_old_after_overwrite(self, *, ethminer, parity, monitor, push_client): # make sure no blocks are confirmed for the meantime ethminer.pause() # set up pn registrations async with self.pool.acquire() as con: await con.fetch( "INSERT INTO notification_registrations (service, registration_id, toshi_id, eth_address) VALUES ($1, $2, $3, $4)", 'gcm', TEST_GCM_ID, TEST_ID_ADDRESS, TEST_WALLET_ADDRESS) # get tx skeleton tx1 = await self.get_tx_skel(FAUCET_PRIVATE_KEY, TEST_WALLET_ADDRESS, 10**18) tx2 = await self.get_tx_skel(FAUCET_PRIVATE_KEY, TEST_WALLET_ADDRESS, 0) self.assertEqual( decode_transaction(tx1).nonce, decode_transaction(tx2).nonce) # sign and send tx1_hash = await self.sign_and_send_tx(FAUCET_PRIVATE_KEY, tx1) # wait for tx PN await push_client.get() # send tx2 manually rpcclient = JsonRPCClient(parity.dsn()['url']) tx2_hash = await rpcclient.eth_sendRawTransaction( sign_transaction(tx2, FAUCET_PRIVATE_KEY)) await monitor.filter_poll() _, pn = await push_client.get() _, pn = await push_client.get() # resend tx1 manually tx1_hash = await rpcclient.eth_sendRawTransaction( sign_transaction(tx1, FAUCET_PRIVATE_KEY)) await monitor.filter_poll() _, pn = await push_client.get() _, pn = await push_client.get() async with self.pool.acquire() as con: tx1_row = await con.fetchrow( "SELECT * FROM transactions WHERE hash = $1", tx1_hash) tx2_row = await con.fetchrow( "SELECT * FROM transactions WHERE hash = $1", tx2_hash) self.assertEqual(tx1_row['status'], 'unconfirmed') self.assertEqual(tx2_row['status'], 'error')
async def test_sending_transactions_and_storing_the_hash_correctly( self, *, push_client): """This test is born out of the fact that `UnsingedTransaction.hash` always calculates the hash of the transaction without the signature, even after `.sign` has been called on the transaction. This caused errors in what was being stored in the database and incorrectly detecting transaction overwrites. This test exposed the behaviour correctly so that it could be fixed""" # register for GCM PNs body = {"registration_id": TEST_GCM_ID, "address": TEST_ID_ADDRESS} resp = await self.fetch_signed("/gcm/register", signing_key=TEST_ID_KEY, method="POST", body=body) self.assertResponseCodeEqual(resp, 204, resp.body) body = {"from": FAUCET_ADDRESS, "to": TEST_ID_ADDRESS, "value": 10**10} resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = decode_transaction(body['tx']) tx = sign_transaction(tx, FAUCET_PRIVATE_KEY) sig = signature_from_transaction(tx) body = {"tx": body['tx'], "signature": data_encoder(sig)} resp = await self.fetch("/tx", method="POST", body=body) self.assertEqual(resp.code, 200, resp.body) body = json_decode(resp.body) tx_hash = body['tx_hash'] async def check_db(): async with self.pool.acquire() as con: rows = await con.fetch( "SELECT * FROM transactions WHERE nonce = $1", tx.nonce) self.assertEqual(len(rows), 1) self.assertEqual(rows[0]['hash'], tx_hash) if rows[0]['status'] is not None: self.assertEqual(rows[0]['status'], 'unconfirmed') self.assertIsNone(rows[0]['error']) await self.wait_on_tx_confirmation(tx_hash, check_db) while True: token, payload = await push_client.get() message = parse_sofa_message(payload['message']) self.assertIsInstance(message, SofaPayment) self.assertEqual(message['txHash'], tx_hash) if message['status'] == "confirmed": break
async def test_prevent_out_of_order_txs(self): """Spams transactions with the same nonce, and ensures the server rejects all but one""" tx1 = await self.get_tx_skel(FAUCET_PRIVATE_KEY, TEST_ADDRESS, 10 ** 10) dtx1 = decode_transaction(tx1) stx1 = sign_transaction(tx1, FAUCET_PRIVATE_KEY) tx2 = await self.get_tx_skel(FAUCET_PRIVATE_KEY, TEST_ADDRESS, 10 ** 10, dtx1.nonce + 1) stx2 = sign_transaction(tx2, FAUCET_PRIVATE_KEY) resp = await self.fetch("/tx", method="POST", body={"tx": stx2}) self.assertEqual(resp.code, 400, resp.body) resp = await self.fetch("/tx", method="POST", body={"tx": stx1}) self.assertEqual(resp.code, 200, resp.body) resp = await self.fetch("/tx", method="POST", body={"tx": stx2}) self.assertEqual(resp.code, 200, resp.body) # lets the transaction queue processing run before ending the test await asyncio.sleep(0.1)
async def test_create_and_send_transaction(self): val = 10 ** 10 body = { "from": FAUCET_ADDRESS, "to": TEST_ADDRESS, "value": val } resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = sign_transaction(body['tx'], FAUCET_PRIVATE_KEY) body = { "tx": tx } resp = await self.fetch("/tx", method="POST", body=body) self.assertEqual(resp.code, 200, resp.body) # ensure we get a tracking events self.assertEqual((await self.next_tracking_event())[0], None) body = json_decode(resp.body) tx_hash = body['tx_hash'] tx = decode_transaction(tx) self.assertEqual(tx_hash, data_encoder(tx.hash)) async with self.pool.acquire() as con: rows = await con.fetch("SELECT * FROM transactions WHERE nonce = $1", tx.nonce) self.assertEqual(len(rows), 1) # wait for a push notification await self.wait_on_tx_confirmation(tx_hash) while True: async with self.pool.acquire() as con: row = await con.fetchrow("SELECT * FROM transactions WHERE nonce = $1", tx.nonce) if row['status'] == 'confirmed': break # make sure updated field is updated self.assertGreater(row['updated'], row['created']) # make sure balance is returned correctly resp = await self.fetch('/balance/{}'.format(TEST_ADDRESS)) self.assertEqual(resp.code, 200) data = json_decode(resp.body) self.assertEqual(parse_int(data['confirmed_balance']), val) self.assertEqual(parse_int(data['unconfirmed_balance']), val)
async def sign_and_send_tx(self, from_key, tx, expected_response_code=200, wait_on_tx_confirmation=False): tx = sign_transaction(tx, from_key) return await self.send_raw_tx( tx, expected_response_code=expected_response_code, wait_on_tx_confirmation=wait_on_tx_confirmation)
async def test_get_balance(self, *, ethminer): addr = '0x39bf9e501e61440b4b268d7b2e9aa2458dd201bb' val = 8761751855997712 val2 = int(val / 2) await self.wait_on_tx_confirmation(await self.faucet(TEST_ID_ADDRESS, val)) ws_con = await self.websocket_connect(TEST_ID_KEY) result = await ws_con.call("get_balance", [TEST_ID_ADDRESS]) self.assertEqual(int(result['confirmed_balance'][2:], 16), val) self.assertEqual(int(result['unconfirmed_balance'][2:], 16), val) # make sure no blocks get mined for a bit ethminer.pause() tx = create_transaction(nonce=0x100000, gasprice=DEFAULT_GASPRICE, startgas=DEFAULT_STARTGAS, to=addr, value=val2, network_id=self.network_id) tx = sign_transaction(tx, TEST_ID_KEY) tx = encode_transaction(tx) await ws_con.call("subscribe", [addr]) tx_hash = await ws_con.call("send_transaction", {"tx": tx}) new_balance = val - (val2 + DEFAULT_STARTGAS * DEFAULT_GASPRICE) result = await ws_con.call("get_balance", [TEST_ID_ADDRESS]) self.assertEqual(int(result['confirmed_balance'][2:], 16), val) self.assertEqual(int(result['unconfirmed_balance'][2:], 16), new_balance) # check for the unconfirmed notification result = await ws_con.read() self.assertIsNotNone(result) payment = parse_sofa_message(result['params']['message']) self.assertEqual(payment['txHash'], tx_hash) self.assertEqual(payment['status'], 'unconfirmed') # restart mining ethminer.start() result = await ws_con.read() payment = parse_sofa_message(result['params']['message']) self.assertEqual(payment['txHash'], tx_hash) self.assertEqual(payment['status'], 'confirmed') result = await ws_con.call("get_balance", [TEST_ID_ADDRESS]) self.assertEqual(int(result['confirmed_balance'][2:], 16), new_balance, "int('{}', 16) != {}".format(result['confirmed_balance'], new_balance)) self.assertEqual(int(result['unconfirmed_balance'][2:], 16), new_balance, "int('{}', 16) != {}".format(result['unconfirmed_balance'], new_balance))
async def test_only_from_and_to_required(self): body = {"from": FAUCET_ADDRESS, "to": TEST_ADDRESS} resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = sign_transaction(body['tx'], FAUCET_PRIVATE_KEY) resp = await self.fetch("/tx", method="POST", body={"tx": tx}) self.assertEqual(resp.code, 200, resp.body) # ensure we get a tracking events self.assertEqual((await self.next_tracking_event())[0], None)
async def test_insufficient_funds(self): body = {"to": FAUCET_ADDRESS, "from": TEST_ADDRESS, "value": 10**18} resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = sign_transaction(body['tx'], TEST_PRIVATE_KEY) resp = await self.fetch("/tx", method="POST", body={"tx": tx}) self.assertEqual(resp.code, 400, resp.body) body = json_decode(resp.body) self.assertEqual(len(body['errors']), 1) self.assertEqual(body['errors'][0]['id'], 'insufficient_funds')
async def sign_and_send_tx(self, from_key, tx, expected_response_code=200): tx = sign_transaction(tx, from_key) body = {"tx": tx} resp = await self.fetch("/tx", method="POST", body=body) self.assertResponseCodeEqual(resp, expected_response_code, resp.body) if expected_response_code == 200: body = json_decode(resp.body) tx_hash = body['tx_hash'] return tx_hash return None
async def test_transaction_overwrite_spam(self, *, ethminer, parity, monitor, push_client): no_to_spam = 10 # make sure no blocks are confirmed ethminer.pause() # set up pn registrations async with self.pool.acquire() as con: await con.fetch( "INSERT INTO notification_registrations (service, registration_id, toshi_id, eth_address) VALUES ($1, $2, $3, $4)", 'gcm', TEST_GCM_ID, TEST_ID_ADDRESS, TEST_WALLET_ADDRESS) # send initial tx tx1 = await self.get_tx_skel(FAUCET_PRIVATE_KEY, TEST_WALLET_ADDRESS, 10**18) txs = [] for i in range(no_to_spam): tx = await self.get_tx_skel(FAUCET_PRIVATE_KEY, TEST_WALLET_ADDRESS, i) txs.append(sign_transaction(tx, FAUCET_PRIVATE_KEY)) tx1_hash = await self.sign_and_send_tx(FAUCET_PRIVATE_KEY, tx1) # wait for tx PN await push_client.get() # spam send txs manually rpcclient = JsonRPCClient(parity.dsn()['url']) for ntx in txs: await rpcclient.eth_sendRawTransaction(ntx) # force the pending transaction filter polling to # run after each new transaction is posted await monitor.filter_poll() # we expect two pns for each overwrite await push_client.get() await push_client.get() async with self.pool.acquire() as con: tx1_row = await con.fetchrow( "SELECT * FROM transactions WHERE hash = $1", tx1_hash) tx_rows = await con.fetchrow("SELECT COUNT(*) FROM transactions") tx_rows_error = await con.fetchrow( "SELECT COUNT(*) FROM transactions WHERE status = 'error'") self.assertEqual(tx1_row['status'], 'error') self.assertEqual(tx_rows['count'], no_to_spam + 1) self.assertEqual(tx_rows_error['count'], no_to_spam)
async def test_create_transaction_with_large_data(self): body = { "from": "0x0004DE837Ea93edbE51c093f45212AB22b4B35fc", "to": "0xa0c4d49fe1a00eb5ee3d85dc7a287d84d8c66699", "value": 0, "data": "0x94d9cf8f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) tx = sign_transaction(json_decode(resp.body)['tx'], FAUCET_PRIVATE_KEY) print(", ".join(["0x{:02x}".format(b) for b in data_decoder(tx)])) print(FAUCET_ADDRESS) print(", ".join(["0x{:02x}".format(b) for b in decode_transaction(tx).hash]))
async def test_create_and_send_transaction_with_custom_values(self): # try creating a skel that is invalid # (should be invalid due to amount of gas being too low) body = { "from": FAUCET_ADDRESS, "to": TEST_ADDRESS, "gas": 21000, "gasPrice": 20000000000, "data": "0xffffffff" } resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 400) # make sure valid values are fine (incresed max gas and let skel pick the nonce) body = { "from": FAUCET_ADDRESS, "to": TEST_ADDRESS, "gas": 25000, "gasPrice": 20000000000, "data": "0xffffffff" } resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = sign_transaction(body['tx'], FAUCET_PRIVATE_KEY) body = { "tx": tx } resp = await self.fetch("/tx", method="POST", body=body) self.assertEqual(resp.code, 200, resp.body) body = json_decode(resp.body) tx_hash = body['tx_hash'] await self.wait_on_tx_confirmation(tx_hash)
async def test_send_transaction(self): addr = '0x39bf9e501e61440b4b268d7b2e9aa2458dd201bb' val = 761751855997712 await self.faucet(TEST_ID_ADDRESS, val * 10) ws_con = await self.websocket_connect(TEST_ID_KEY) result = await ws_con.call("create_transaction_skeleton", { "from": TEST_ID_ADDRESS, "to": addr, "value": val }) tx = sign_transaction(result['tx'], TEST_ID_KEY) tx_hash = await ws_con.call("send_transaction", {"tx": tx}) self.assertIsNotNone(tx_hash)
async def test_transactions_with_known_sender_toshi_id(self): body = {"from": FAUCET_ADDRESS, "to": TEST_ADDRESS, "value": 10**10} resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) tx = sign_transaction(json_decode(resp.body)['tx'], FAUCET_PRIVATE_KEY) body = {"tx": tx} resp = await self.fetch_signed("/tx", signing_key=TEST_PRIVATE_KEY_2, method="POST", body=body) self.assertEqual(resp.code, 200, resp.body) # ensure we get a tracking events with a live id self.assertEqual((await self.next_tracking_event())[0], encode_id(TEST_ADDRESS_2)) tx_hash = json_decode(resp.body)['tx_hash'] async with self.pool.acquire() as con: row = await con.fetch( "SELECT * FROM transactions WHERE sender_toshi_id = $1", FAUCET_ADDRESS) self.assertEqual(len(row), 0) row = await con.fetch( "SELECT * FROM transactions WHERE sender_toshi_id = $1", TEST_ADDRESS_2) self.assertEqual(len(row), 1) self.assertEqual(row[0]['from_address'], FAUCET_ADDRESS) await self.wait_on_tx_confirmation(tx_hash)
async def test_empty_account(self): """Makes sure an account can be emptied completely""" val = 10**16 default_fees = DEFAULT_STARTGAS * DEFAULT_GASPRICE tx_hash = await self.send_tx(FAUCET_PRIVATE_KEY, TEST_ADDRESS, val) tx = await self.wait_on_tx_confirmation(tx_hash) resp = await self.fetch('/balance/{}'.format(TEST_ADDRESS)) self.assertEqual(resp.code, 200) data = json_decode(resp.body) self.assertEqual(parse_int(data['confirmed_balance']), val) self.assertEqual(parse_int(data['unconfirmed_balance']), val) resp = await self.fetch("/tx/skel", method="POST", body={ "from": TEST_ADDRESS, "to": FAUCET_ADDRESS, "value": val - default_fees }) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = sign_transaction(body['tx'], TEST_PRIVATE_KEY) resp = await self.fetch("/tx", method="POST", body={"tx": tx}) self.assertEqual(resp.code, 200, resp.body) body = json_decode(resp.body) tx_hash = body['tx_hash'] # wait for a push notification tx = await self.wait_on_tx_confirmation(tx_hash) # make sure balance is returned correctly (and is 0) resp = await self.fetch('/balance/{}'.format(TEST_ADDRESS)) self.assertEqual(resp.code, 200) data = json_decode(resp.body) self.assertEqual(parse_int(data['confirmed_balance']), 0) self.assertEqual(parse_int(data['unconfirmed_balance']), 0)
async def test_create_tx_with_no_to_address(self): body = { "from": FAUCET_ADDRESS, "data": data_encoder(b"Hello World"), "value": 0, "gas": 530000, } resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) body = json_decode(resp.body) tx = sign_transaction(body['tx'], FAUCET_PRIVATE_KEY) body = { "tx": tx } resp = await self.fetch("/tx", method="POST", body=body) self.assertEqual(resp.code, 200, resp.body) body = json_decode(resp.body) tx_hash = body['tx_hash'] tx = decode_transaction(tx) self.assertEqual(tx_hash, data_encoder(tx.hash)) async with self.pool.acquire() as con: rows = await con.fetch("SELECT * FROM transactions WHERE nonce = $1", tx.nonce) self.assertEqual(len(rows), 1) print(rows[0]) await self.wait_on_tx_confirmation(tx_hash)
async def test_tx_queue_error_propagation(self, *, ethminer, parity, push_client): """Tests that a long chain of txs depending on a single transaction propagate errors correctly""" # start 2nd parity server p2 = ParityServer(bootnodes=parity.dsn()['node']) e2 = EthMiner(jsonrpc_url=p2.dsn()['url'], debug=False) rpcclient = JsonRPCClient(p2.dsn()['url']) default_fees = DEFAULT_STARTGAS * DEFAULT_GASPRICE val = 100 * 10 ** 18 txs = [] # send funds to address1 f_tx_hash = await self.send_tx(FAUCET_PRIVATE_KEY, TEST_ADDRESS_1, val) await self.ensure_confirmed(f_tx_hash) # make sure the nodes are synchronized while True: bal = await rpcclient.eth_getBalance(TEST_ADDRESS_1) if bal == 0: await asyncio.sleep(1) else: break # make sure no blocks are mined ethminer.pause() e2.pause() addresses = [(TEST_ADDRESS_1, TEST_PRIVATE_KEY_1), (TEST_ADDRESS_2, TEST_PRIVATE_KEY_2), (TEST_ADDRESS_3, TEST_PRIVATE_KEY_3), (TEST_ADDRESS_4, TEST_PRIVATE_KEY_4), (TEST_ADDRESS_5, TEST_PRIVATE_KEY_5), (TEST_ADDRESS_6, TEST_PRIVATE_KEY_6)] async with self.pool.acquire() as con: for addr, pk in addresses: await con.fetch("INSERT INTO notification_registrations (service, registration_id, toshi_id, eth_address) VALUES ($1, $2, $3, $4)", 'gcm', "abc", addr, addr) # send a tx from outside the system first which wont be seen by # the system until after the transactions generated in the next block tx = await self.get_tx_skel(TEST_PRIVATE_KEY_1, FAUCET_ADDRESS, val - default_fees) tx = sign_transaction(tx, TEST_PRIVATE_KEY_1) await rpcclient.eth_sendRawTransaction(tx) # generate internal transactions for i in range(len(addresses) * 2): val = val - default_fees addr1, pk1 = addresses[0] addr2, pk2 = addresses[1] # send funds tx_hash = await self.send_tx(pk1, addr2, val) txs.append(tx_hash) # swap all the variables addresses = addresses[1:] + [addresses[0]] # make sure we got pns for all for i in range(len(addresses) * 2): await push_client.get() await push_client.get() # start mining again e2.start() await self.ensure_errors(*txs) # make sure we got error pns for all for i in range(len(addresses) * 2): await push_client.get() await push_client.get() # and the pn for the overwritten tx await push_client.get()
async def test_tx_overwrite(self, *, ethminer, parity, push_client): """Tests that if a transaction with the same nonce and one the system knows about is sent from outside of the system and included in the block, that the error handling picks this up correctly""" # start 2nd parity server p2 = ParityServer(bootnodes=parity.dsn()['node']) e2 = EthMiner(jsonrpc_url=p2.dsn()['url'], debug=False) rpcclient2 = JsonRPCClient(p2.dsn()['url']) addr1, pk1 = TEST_ADDRESSES[0] addr2, pk2 = TEST_ADDRESSES[1] addr3, pk3 = TEST_ADDRESSES[2] val = 1000 * 10**18 # send funds to address1 f_tx_hash = await self.send_tx(FAUCET_PRIVATE_KEY, TEST_ADDRESS_1, val) # make sure sync is done while True: data2 = await rpcclient2.eth_getTransactionByHash(f_tx_hash) if data2 and data2['blockNumber'] is not None: break await asyncio.sleep(1) # make sure no blocks are mined ethminer.pause() e2.pause() # make sure transactions are "interesting" to the monitory async with self.pool.acquire() as con: for addr, pk in TEST_ADDRESSES[:3]: await con.fetch( "INSERT INTO notification_registrations (service, registration_id, toshi_id, eth_address) VALUES ($1, $2, $3, $4)", 'gcm', "abc", addr, addr) # create two transactions with the same nonce and submit them both tx1 = await self.get_tx_skel(pk1, addr2, int(val / 3)) tx2 = await self.get_tx_skel(pk1, addr3, int(val / 3), nonce=decode_transaction(tx1).nonce) tx2 = sign_transaction(tx2, pk1) tx_hash_2 = await rpcclient2.eth_sendRawTransaction(tx2) tx_hash_1 = await self.sign_and_send_tx(pk1, tx1) # start mining again e2.start() # wait for one of the two transactions to complete try: while True: async with self.pool.acquire() as con: tx1_row = await con.fetchrow( "SELECT * FROM transactions WHERE hash = $1", tx_hash_1) tx2_row = await con.fetchrow( "SELECT * FROM transactions WHERE hash = $1", tx_hash_2) if tx2_row is not None and tx2_row['status'] == 'confirmed': # good! break if tx1_row is not None and tx1_row['status'] == 'confirmed': self.assertFail( "tx1 confirmed, expected tx1 overwrite and tx2 confirmed" ) await asyncio.sleep(1) finally: e2.stop() p2.stop()