Beispiel #1
0
    async def test_only_apps_query(self):

        data_encoder, private_key_to_address
        users = [
            ('bob{}'.format(i), private_key_to_address(data_encoder(os.urandom(32))), False)
            for i in range(6)
        ]
        bots = [
            ('bot{}'.format(i), private_key_to_address(data_encoder(os.urandom(32))), True)
            for i in range(4)
        ]

        async with self.pool.acquire() as con:
            for args in users + bots:
                await con.execute("INSERT INTO users (username, token_id, is_app) VALUES ($1, $2, $3)", *args)

        resp = await self.fetch("/search/user?query=bo", method="GET")
        self.assertEqual(resp.code, 200)
        body = json_decode(resp.body)
        self.assertEqual(len(body['results']), 10)

        resp = await self.fetch("/search/user?query=bo&apps=false", method="GET")
        self.assertEqual(resp.code, 200)
        body = json_decode(resp.body)
        self.assertEqual(len(body['results']), 6)

        resp = await self.fetch("/search/user?query=bo&apps=true", method="GET")
        self.assertEqual(resp.code, 200)
        body = json_decode(resp.body)
        self.assertEqual(len(body['results']), 4)
Beispiel #2
0
    async def faucet(self,
                     to,
                     value,
                     *,
                     from_private_key=FAUCET_PRIVATE_KEY,
                     startgas=None,
                     gasprice=DEFAULT_GASPRICE,
                     nonce=None,
                     data=b"",
                     wait_on_confirmation=True):

        if isinstance(from_private_key, str):
            from_private_key = data_decoder(from_private_key)
        from_address = private_key_to_address(from_private_key)

        ethclient = JsonRPCClient(self._app.config['ethereum']['url'])

        to = data_decoder(to)
        if len(to) not in (20, 0):
            raise Exception(
                'Addresses must be 20 or 0 bytes long (len was {})'.format(
                    len(to)))

        if nonce is None:
            nonce = await ethclient.eth_getTransactionCount(from_address)
        balance = await ethclient.eth_getBalance(from_address)

        if startgas is None:
            startgas = await ethclient.eth_estimateGas(from_address,
                                                       to,
                                                       data=data,
                                                       nonce=nonce,
                                                       value=value,
                                                       gasprice=gasprice)

        tx = Transaction(nonce, gasprice, startgas, to, value, data, 0, 0, 0)

        if balance < (tx.value + (tx.startgas * tx.gasprice)):
            raise Exception("Faucet doesn't have enough funds")

        tx.sign(from_private_key)

        tx_encoded = data_encoder(rlp.encode(tx, Transaction))

        tx_hash = await ethclient.eth_sendRawTransaction(tx_encoded)

        while wait_on_confirmation:
            resp = await ethclient.eth_getTransactionByHash(tx_hash)
            if resp is None or resp['blockNumber'] is None:
                await asyncio.sleep(0.1)
            else:
                break

        if to == b'':
            print("contract address: {}".format(data_encoder(tx.creates)))

        return tx_hash
Beispiel #3
0
    async def deploy_contract(self,
                              bytecode,
                              *,
                              from_private_key=FAUCET_PRIVATE_KEY,
                              startgas=None,
                              gasprice=DEFAULT_GASPRICE,
                              wait_on_confirmation=True):

        if isinstance(from_private_key, str):
            from_private_key = data_decoder(from_private_key)
        from_address = private_key_to_address(from_private_key)

        ethclient = JsonRPCClient(self._app.config['ethereum']['url'])

        nonce = await ethclient.eth_getTransactionCount(from_address)
        balance = await ethclient.eth_getBalance(from_address)

        gasestimate = await ethclient.eth_estimateGas(from_address,
                                                      '',
                                                      data=bytecode,
                                                      nonce=nonce,
                                                      value=0,
                                                      gasprice=gasprice)

        if startgas is None:
            startgas = gasestimate
        elif gasestimate > startgas:
            raise Exception(
                "Estimated gas usage is larger than the provided gas")

        tx = Transaction(nonce, gasprice, startgas, '', 0, bytecode, 0, 0, 0)

        if balance < (tx.value + (tx.startgas * tx.gasprice)):
            raise Exception("Faucet doesn't have enough funds")

        tx.sign(from_private_key)

        tx_encoded = data_encoder(rlp.encode(tx, Transaction))

        tx_hash = await ethclient.eth_sendRawTransaction(tx_encoded)

        contract_address = data_encoder(tx.creates)

        while wait_on_confirmation:
            resp = await ethclient.eth_getTransactionByHash(tx_hash)
            if resp is None or resp['blockNumber'] is None:
                await asyncio.sleep(0.1)
            else:
                code = await ethclient.eth_getCode(contract_address)
                if code == '0x':
                    raise Exception("Failed to deploy contract")
                break

        return tx_hash, contract_address
    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_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 on_login(self, address):

        num = int(data_encoder(os.urandom(16))[2:], 16)
        token = b62encode(num)

        async with self.db:
            row = await self.db.fetchrow("SELECT * FROM users where token_id = $1", address)
            if row is None:
                raise JSONHTTPError(401)
            await self.db.execute("INSERT INTO auth_tokens (token, address) VALUES ($1, $2)",
                                  token, address)
            await self.db.commit()
        self.write({'auth_token': token})
    def test_encode_decode_transaction(self):

        sender_private_key = "0x0164f7c7399f4bb1eafeaae699ebbb12050bc6a50b2836b9ca766068a9d000c0"
        sender_address = "0xde3d2d9dd52ea80f7799ef4791063a5458d13913"
        to_address = "0x056db290f8ba3250ca64a45d16284d04bc6f5fbf"
        value = 10000000000
        nonce = 1048576
        data = b''
        gasprice = DEFAULT_GASPRICE
        startgas = DEFAULT_STARTGAS
        expected_tx_hash = "0x2f321aa116146a9bc62b61c76508295f708f42d56340c9e613ebfc27e33f240c"

        tx1 = Transaction(nonce, gasprice, startgas, to_address, value, data)
        tx1.sign(data_decoder(sender_private_key))

        self.assertEqual(data_encoder(tx1.hash), expected_tx_hash)

        # rlputx1 = rlp.encode(tx1, UnsignedTransaction)
        # rlpstx1 = rlp.encode(tx1, Transaction)

        tx1 = Transaction(nonce, gasprice, startgas, to_address, value, data)
        enc1 = rlp.encode(tx1, UnsignedTransaction)

        tx2 = rlp.decode(enc1, UnsignedTransaction)
        tx2.sign(data_decoder(sender_private_key))
        tx3 = Transaction(tx2.nonce, tx2.gasprice, tx2.startgas, tx2.to,
                          tx2.value, tx2.data)
        tx3.sign(data_decoder(sender_private_key))

        self.assertEqual(data_encoder(tx3.sender), sender_address)
        self.assertEqual(data_encoder(tx3.hash), expected_tx_hash)
        self.assertEqual(data_encoder(tx2.sender), sender_address)
        # NOTE: this is false because tx2 still thinks it's an unsigned tx
        # so it doesn't include the signature variables in the tx
        # if this suddenly starts failing, it means the behaviour
        # has been modified in the library
        self.assertNotEqual(data_encoder(tx2.hash), expected_tx_hash)
    async def test_missing_txs(self, *, ethminer, parity, monitor):

        values = []
        for i in range(10):
            tx = await self.get_tx_skel(FAUCET_PRIVATE_KEY, TEST_WALLET_ADDRESS, random.randint(1, 10) ** 18, i)
            tx = decode_transaction(tx)
            values.append((data_encoder(tx.hash), FAUCET_ADDRESS, data_encoder(tx.to), tx.value, -1, datetime.utcnow() - timedelta(hours=1)))

        async with self.pool.acquire() as con:
            await con.executemany(
                "INSERT INTO transactions (transaction_hash, from_address, to_address, value, nonce, created) "
                "VALUES ($1, $2, $3, $4, $5, $6)",
                values)
            count = await con.fetchrow("SELECT COUNT(*) FROM transactions WHERE (last_status != 'error' OR last_status IS NULL)")
        self.assertEqual(count['count'], 10)

        await monitor.sanity_check()

        async with self.pool.acquire() as con:
            unconf_count = await con.fetchrow("SELECT COUNT(*) FROM transactions WHERE last_status = $1 OR last_status IS NULL", 'unconfirmed')
            error_count = await con.fetchrow("SELECT COUNT(*) FROM transactions WHERE last_status = $1", 'error')

        self.assertEqual(unconf_count['count'], 0)
        self.assertEqual(error_count['count'], 10)
Beispiel #9
0
    async def send_transaction(self, *, tx, signature=None):

        try:
            tx = decode_transaction(tx)
        except:
            raise JsonRPCInvalidParamsError(data={
                'id': 'invalid_transaction',
                'message': 'Invalid Transaction'
            })

        if is_transaction_signed(tx):

            if signature:

                tx_sig = signature_from_transaction(tx)
                if tx_sig != signature:

                    raise JsonRPCInvalidParamsError(
                        data={
                            'id':
                            'invalid_signature',
                            'message':
                            'Invalid Signature: Signature in payload and signature of transaction do not match'
                        })
        else:

            if signature is None:
                raise JsonRPCInvalidParamsError(data={
                    'id': 'missing_signature',
                    'message': 'Missing Signature'
                })

            if not validate_signature(signature):
                raise JsonRPCInvalidParamsError(
                    data={
                        'id':
                        'invalid_signature',
                        'message':
                        'Invalid Signature: {}'.format('Invalid length' if len(
                            signature) != 132 else 'Invalid hex value')
                    })

            try:
                signature = data_decoder(signature)
            except Exception:
                log.exception(
                    "Unexpected error decoding valid signature: {}".format(
                        signature))
                raise JsonRPCInvalidParamsError(data={
                    'id': 'invalid_signature',
                    'message': 'Invalid Signature'
                })

            add_signature_to_transaction(tx, signature)

        from_address = data_encoder(tx.sender)
        to_address = data_encoder(tx.to)

        # prevent spamming of transactions with the same nonce from the same sender
        with RedisLock(self.redis,
                       "{}:{}".format(from_address, tx.nonce),
                       raise_when_locked=partial(JsonRPCInvalidParamsError,
                                                 data={
                                                     'id':
                                                     'invalid_nonce',
                                                     'message':
                                                     'Nonce already used'
                                                 }),
                       ex=5):

            # disallow transaction overwriting for known transactions
            async with self.db:
                existing = await self.db.fetchrow(
                    "SELECT * FROM transactions WHERE "
                    "from_address = $1 AND nonce = $2 AND last_status != $3",
                    from_address, tx.nonce, 'error')
            if existing:
                # debugging checks
                existing_tx = await self.eth.eth_getTransactionByHash(
                    existing['transaction_hash'])
                raise JsonRPCInvalidParamsError(data={
                    'id': 'invalid_nonce',
                    'message': 'Nonce already used'
                })

            # make sure the account has enough funds for the transaction
            network_balance, balance = await self.get_balances(
                from_address, ignore_pending_recieved=True)

            log.info(
                "Attempting to send transaction\n{} -> {}\nValue: {} + {} (gas) * {} (startgas) = {}\nSender's Balance {} ({} unconfirmed)"
                .format(from_address, to_address, tx.value, tx.startgas,
                        tx.gasprice, tx.value + (tx.startgas * tx.gasprice),
                        network_balance, balance))

            if balance < (tx.value + (tx.startgas * tx.gasprice)):
                raise JsonRPCInsufficientFundsError(
                    data={
                        'id': 'insufficient_funds',
                        'message': 'Insufficient Funds'
                    })

            # validate the nonce
            c_nonce = self.redis.get("nonce:{}".format(from_address))
            if c_nonce:
                c_nonce = int(c_nonce)
            # get the network's value too
            nw_nonce = await self.eth.eth_getTransactionCount(from_address)
            if c_nonce is None or nw_nonce > c_nonce:
                c_nonce = nw_nonce

            if tx.nonce < c_nonce:
                raise JsonRPCInvalidParamsError(
                    data={
                        'id': 'invalid_nonce',
                        'message': 'Provided nonce is too low'
                    })
            if tx.nonce > c_nonce:
                raise JsonRPCInvalidParamsError(
                    data={
                        'id': 'invalid_nonce',
                        'message': 'Provided nonce is too high'
                    })

            # send the transaction to the network
            try:
                tx_encoded = encode_transaction(tx)
                tx_hash = await self.eth.eth_sendRawTransaction(tx_encoded)
            except JsonRPCError as e:
                log.error(e.format())
                raise JsonRPCInternalError(
                    data={
                        'id':
                        'unexpected_error',
                        'message':
                        'An error occured communicating with the ethereum network, try again later'
                    })

            # cache nonce
            self.redis.set("nonce:{}".format(from_address), tx.nonce + 1)
            # add tx to database
            async with self.db:
                await self.db.execute(
                    "INSERT INTO transactions "
                    "(transaction_hash, from_address, to_address, nonce, value, estimated_gas_cost, sender_token_id) "
                    "VALUES ($1, $2, $3, $4, $5, $6, $7)", tx_hash,
                    from_address, to_address, tx.nonce, str(tx.value),
                    str(tx.startgas * tx.gasprice), self.user_token_id)
                await self.db.commit()

            # if there is a block monitor, force send PNs for this without
            # waiting for the node to see it
            if hasattr(self.application, 'monitor'):
                txjson = transaction_to_json(tx)
                assert txjson['hash'] == tx_hash
                IOLoop.current().add_callback(
                    self.application.monitor.send_transaction_notifications,
                    txjson)

        return tx_hash
    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
    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)
Beispiel #12
0
    def do_POST(self):

        # TODO: figure out why read is blocking here
        data = self.rfile.read(len(self.rfile.peek()))
        data = data.decode('utf-8')
        data = json.loads(data)

        if self.path == "/v1/tx/skel":

            gas_price = parse_int(
                data['gas_price']) if 'gas_price' in data else DEFAULT_GASPRICE
            gas = parse_int(data['gas']) if 'gas' in data else DEFAULT_STARTGAS
            nonce = parse_int(data['nonce']) if 'nonce' in data else 0

            if 'value' not in data or 'from' not in data or 'to' not in data:
                self.write_data(
                    400, {
                        'errors': [{
                            'id': 'bad_arguments',
                            'message': 'Bad Arguments'
                        }]
                    })
                return
            value = parse_int(data['value'])
            to_address = data['to']
            from_address = data['from']

            if not validate_address(to_address):
                self.write_data(
                    400, {
                        'errors': [{
                            'id': 'invalid_to_address',
                            'message': 'Invalid To Address'
                        }]
                    })
                return
            if not validate_address(from_address):
                self.write_data(
                    400, {
                        'errors': [{
                            'id': 'invalid_from_address',
                            'message': 'Invalid From Address'
                        }]
                    })
                return

            tx = create_transaction(nonce=nonce,
                                    gasprice=gas_price,
                                    startgas=gas,
                                    to=to_address,
                                    value=value)

            transaction = encode_transaction(tx)

            self.write_data(
                200, {
                    "tx_data": {
                        "nonce": hex(nonce),
                        "from": from_address,
                        "to": to_address,
                        "value": hex(value),
                        "startGas": hex(gas),
                        "gasPrice": hex(gas_price)
                    },
                    "tx": transaction
                })

        elif self.path == "/v1/tx":

            tx = decode_transaction(data['tx'])

            if 'signature' in data:

                sig = data_decoder(data['signature'])

                add_signature_to_transaction(tx, sig)

            self.write_data(200, {"tx_hash": data_encoder(tx.hash)})

        else:

            self.write_data(404)