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)
    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(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)

        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)

        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)

        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]['transaction_hash'], tx_hash)

        await self.wait_on_tx_confirmation(tx_hash)
    async def test_transactions_with_known_sender_token_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_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)
    async def test_get_block_confirmation(self, *, parity, ethminer, monitor):

        addr = '0x39bf9e501e61440b4b268d7b2e9aa2458dd201bb'
        val = 761751855997712

        await self.faucet(TEST_ID_ADDRESS, val * 10)
        tx = await monitor.confirmation_queue.get()

        body = {
            "from": TEST_ID_ADDRESS,
            "to": addr,
            "value": val
        }

        resp = await self.fetch("/tx/skel", method="POST", body=body)

        self.assertResponseCodeEqual(resp, 200, resp.body)

        body = json_decode(resp.body)

        tx = sign_transaction(body['tx'], TEST_ID_KEY)

        body = {
            "tx": tx
        }

        # stop ethminer breifly to ensure we get an unconfirmed transaction
        # if this isn't done, then there is a chance that the block may be
        # mined before the unconfirmed transaction is seen meaning only
        # the confirmed version will be seen.
        ethminer.pause()

        resp = await self.fetch("/tx", method="POST", body=body)

        self.assertResponseCodeEqual(resp, 200, resp.body)

        body = json_decode(resp.body)
        tx_hash = body['tx_hash']

        # wait until the transaction is confirmed
        got_unconfirmed = 0
        while True:
            tx = await monitor.confirmation_queue.get()
            if tx['hash'] == tx_hash:
                if tx['blockNumber'] is None:
                    got_unconfirmed += 1
                    # restart ethminer
                    ethminer.start()
                else:
                    break

        # make sure we got an unconfirmed tx notification
        self.assertNotEqual(got_unconfirmed, 0)
Example #8
0
    async def test_get_balance(self, *, ethminer):

        addr = '0x39bf9e501e61440b4b268d7b2e9aa2458dd201bb'
        val = 8761751855997712
        val2 = int(val / 2)

        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)
        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)
        self.assertEqual(result['params']['transaction']['hash'], tx_hash)
        self.assertIsNone(result['params']['transaction']['blockNumber'])

        # restart mining
        ethminer.start()

        result = await ws_con.read()
        self.assertIsNotNone(result)
        self.assertEqual(result['params']['transaction']['hash'], tx_hash)
        self.assertIsNotNone(result['params']['transaction']['blockNumber'])

        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_resend_old_after_overwrite(self, *, ethminer, parity, monitor):
        # make sure no blocks are confirmed for the meantime
        ethminer.pause()

        # set up pn registrations
        async with self.pool.acquire() as con:
            await con.execute("INSERT INTO notification_registrations (token_id, eth_address) VALUES ($1, $2)",
                              TEST_ID_ADDRESS, TEST_WALLET_ADDRESS)
            await con.fetch("INSERT INTO push_notification_registrations (service, registration_id, token_id) VALUES ($1, $2, $3)",
                            'gcm', TEST_GCM_ID, TEST_ID_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 monitor.pushclient.send_queue.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 monitor.pushclient.send_queue.get()
        _, pn = await monitor.pushclient.send_queue.get()

        # resend tx1 manually
        tx1_hash = await rpcclient.eth_sendRawTransaction(sign_transaction(tx1, FAUCET_PRIVATE_KEY))
        await monitor.filter_poll()
        _, pn = await monitor.pushclient.send_queue.get()
        _, pn = await monitor.pushclient.send_queue.get()

        async with self.pool.acquire() as con:
            tx1_row = await con.fetchrow("SELECT * FROM transactions WHERE transaction_hash = $1", tx1_hash)
            tx2_row = await con.fetchrow("SELECT * FROM transactions WHERE transaction_hash = $1", tx2_hash)

        self.assertEqual(tx1_row['last_status'], 'unconfirmed')
        self.assertEqual(tx2_row['last_status'], 'error')
Example #10
0
    async def sign_and_send_tx(self, from_key, tx):

        tx = sign_transaction(tx, from_key)

        body = {"tx": tx}

        resp = await self.fetch("/tx", method="POST", body=body)

        self.assertResponseCodeEqual(resp, 200, resp.body)

        body = json_decode(resp.body)
        tx_hash = body['tx_hash']
        return tx_hash
    async def test_transaction_overwrite_spam(self, *, ethminer, parity, monitor):

        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.execute("INSERT INTO notification_registrations (token_id, eth_address) VALUES ($1, $2)",
                              TEST_ID_ADDRESS, TEST_WALLET_ADDRESS)
            await con.fetch("INSERT INTO push_notification_registrations (service, registration_id, token_id) VALUES ($1, $2, $3)",
                            'gcm', TEST_GCM_ID, TEST_ID_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
        pn = await monitor.pushclient.send_queue.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
            pn = await monitor.pushclient.send_queue.get()
            pn = await monitor.pushclient.send_queue.get()

        async with self.pool.acquire() as con:
            tx1_row = await con.fetchrow("SELECT * FROM transactions WHERE transaction_hash = $1", tx1_hash)
            tx_rows = await con.fetchrow("SELECT COUNT(*) FROM transactions")
            tx_rows_error = await con.fetchrow("SELECT COUNT(*) FROM transactions WHERE last_status = 'error'")

        self.assertEqual(tx1_row['last_status'], 'error')

        self.assertEqual(tx_rows['count'], no_to_spam + 1)
        self.assertEqual(tx_rows_error['count'], no_to_spam)
Example #12
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, TEST_ID_KEY)

        tx_hash = await ws_con.call("send_transaction", {
            "tx": tx
        })

        self.assertIsNotNone(tx_hash)
    async def test_create_and_send_multiple_transactions(self):

        body = {"from": FAUCET_ADDRESS, "to": TEST_ADDRESS, "value": 10**10}

        tx_hashes = []

        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)

        for tx_hash in tx_hashes:
            await self.wait_on_tx_confirmation(tx_hash)
    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_transactions_with_known_sender_token_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)

        tx_hash = json_decode(resp.body)['tx_hash']

        async with self.pool.acquire() as con:

            row = await con.fetch(
                "SELECT * FROM transactions WHERE sender_token_id = $1",
                FAUCET_ADDRESS)

            self.assertEqual(len(row), 0)

            row = await con.fetch(
                "SELECT * FROM transactions WHERE sender_token_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_always_get_unconfirmed_push_notification(self, *, ethminer, monitor):
        """Tests that when tx's are send through our systems we always get
        an unconfirmed push notification"""

        # register for GCM PNs
        body = {
            "registration_id": TEST_GCM_ID
        }
        resp = await self.fetch_signed("/gcm/register", signing_key=TEST_ID_KEY, method="POST", body=body)
        self.assertResponseCodeEqual(resp, 204, resp.body)

        async with self.pool.acquire() as con:
            rows = await con.fetch("SELECT * FROM push_notification_registrations WHERE token_id = $1", TEST_ID_ADDRESS)
        self.assertIsNotNone(rows)
        self.assertEqual(len(rows), 1)

        # register for notifications for the test address
        body = {
            "addresses": [TEST_WALLET_ADDRESS]
        }
        resp = await self.fetch_signed("/register", signing_key=TEST_ID_KEY, method="POST", body=body)
        self.assertResponseCodeEqual(resp, 204, resp.body)

        # run this a bunch of times to see if
        # we can expose any race conditions
        for iteration in range(4):

            if iteration > 2:
                ethminer.pause()

            value = 2821181018869341261

            resp = await self.fetch("/tx/skel", method="POST", body={
                "from": FAUCET_ADDRESS,
                "to": TEST_WALLET_ADDRESS,
                "value": value
            })
            self.assertResponseCodeEqual(resp, 200, resp.body)
            body = json_decode(resp.body)
            tx = sign_transaction(body['tx'], FAUCET_PRIVATE_KEY)
            resp = await self.fetch("/tx", method="POST", body={
                "tx": tx
            })
            self.assertResponseCodeEqual(resp, 200, resp.body)
            tx_hash = json_decode(resp.body)['tx_hash']

            tx = decode_transaction(tx)
            self.assertEqual(tx_hash, data_encoder(tx.hash))

            if iteration > 2:
                await asyncio.sleep(5)
                ethminer.start()

            async with self.pool.acquire() as con:
                rows = await con.fetch("SELECT * FROM transactions WHERE nonce = $1", tx.nonce)
            self.assertEqual(len(rows), 1)

            unconfirmed_count = 0
            while True:
                token, payload = await monitor.pushclient.send_queue.get()

                self.assertEqual(token, TEST_GCM_ID)

                message = parse_sofa_message(payload['message'])

                self.assertIsInstance(message, SofaPayment)
                self.assertEqual(message['value'], hex(value))
                self.assertEqual(message['txHash'], tx_hash)

                if message['status'] == "confirmed":
                    break

                self.assertEqual(message['status'], "unconfirmed")
                unconfirmed_count += 1
            # when the tx is sent through our systems we should
            # always get one unconfirmed notification, and we
            # should never get more than one
            self.assertEqual(unconfirmed_count, 1)
    async def test_sending_transactions_and_storing_the_hash_correctly(self, *, monitor):

        """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
        }
        resp = await self.fetch_signed("/gcm/register", signing_key=TEST_ID_KEY, method="POST", body=body)
        self.assertResponseCodeEqual(resp, 204, resp.body)
        # register for notifications for the test address
        body = {
            "addresses": [TEST_ID_ADDRESS]
        }
        resp = await self.fetch_signed("/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]['transaction_hash'], tx_hash)
            if rows[0]['last_status'] is not None:
                self.assertEqual(rows[0]['last_status'], 'unconfirmed')
            self.assertIsNone(rows[0]['error'])

        await self.wait_on_tx_confirmation(tx_hash, check_db)
        while True:
            token, payload = await monitor.pushclient.send_queue.get()
            message = parse_sofa_message(payload['message'])

            self.assertIsInstance(message, SofaPayment)
            self.assertEqual(message['txHash'], tx_hash)

            if message['status'] == "confirmed":
                break