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)
Exemple #2
0
    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)
Exemple #6
0
    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)
Exemple #8
0
    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)
Exemple #9
0
    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)
Exemple #18
0
    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')
Exemple #21
0
    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)
Exemple #25
0
    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)
Exemple #29
0
    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()