示例#1
0
 def test_get_data_from_raw_transaction_ng_failure_v(self):
     web3 = Web3(HTTPProvider('http://localhost:8584'))
     test_account = web3.eth.account.create()
     test_data = '0xa9059cbb'
     nonce = 10
     transaction = {
         'nonce': nonce,
         'gasPrice': 0,
         'gas': 100000,
         'to': web3.toChecksumAddress(os.environ['PRIVATE_CHAIN_ALIS_TOKEN_ADDRESS']),
         'value': 0,
         'data': test_data,
         'chainId': 8994
     }
     signed = web3.eth.account.sign_transaction(transaction, test_account.key)
     with self.assertRaises(ValidationError) as e:
         PrivateChainUtil.get_data_from_raw_transaction(signed.rawTransaction.hex(), format(nonce, '#x'))
     self.assertEqual(e.exception.args[0], 'v is invalid')
示例#2
0
 def test_get_data_from_raw_transaction_ng_failure_to(self):
     web3 = Web3(HTTPProvider('http://localhost:8584'))
     test_account = web3.eth.account.create()
     test_data = '0xa9059cbb'
     nonce = 10
     transaction = {
         'nonce': nonce,
         'gasPrice': 0,
         'gas': 100000,
         'to': test_account.address,
         'value': 0,
         'data': test_data,
         'chainId': 8995
     }
     signed = web3.eth.account.sign_transaction(transaction, test_account.key)
     with self.assertRaises(ValidationError) as e:
         PrivateChainUtil.get_data_from_raw_transaction(signed.rawTransaction.hex(), format(nonce, '#x'))
     self.assertEqual(e.exception.args[0], 'private_chain_alis_token_address is invalid')
示例#3
0
 def test_get_data_from_raw_transaction_ok_with_nonce_zero(self):
     web3 = Web3(HTTPProvider('http://localhost:8584'))
     test_account = web3.eth.account.create()
     test_data = '0xa9059cbb'
     nonce = 0
     transaction = {
         'nonce': nonce,
         'gasPrice': 0,
         'gas': 100000,
         'to': web3.toChecksumAddress(os.environ['PRIVATE_CHAIN_ALIS_TOKEN_ADDRESS']),
         'value': 0,
         'data': test_data,
         'chainId': 8995
     }
     signed = web3.eth.account.sign_transaction(transaction, test_account.key)
     actual = PrivateChainUtil.get_data_from_raw_transaction(signed.rawTransaction.hex(), format(nonce, '#x'))
     self.assertEqual(test_data[2:], actual)
示例#4
0
    def test_main_ok_call_validate_methods(self):
        test_tip_value = 10
        to_address = format(10, '064x')
        burn_value = int(test_tip_value / Decimal(10))
        raw_transactions = self.create_singed_transactions(
            to_address, test_tip_value, burn_value)

        with patch('me_wallet_tip.UserUtil.get_private_eth_address') as mock_get_private_eth_address, \
                patch('me_wallet_tip.UserUtil.verified_phone_and_email') as mock_verified_phone_and_email, \
                patch('me_wallet_tip.UserUtil.validate_private_eth_address') as mock_validate_private_eth_address, \
                patch('me_wallet_tip.PrivateChainUtil.validate_raw_transaction_signature') as mock_validate_signature, \
                patch('me_wallet_tip.PrivateChainUtil.validate_erc20_transfer_data') \
                as mock_validate_erc20_transfer_data:
            mock_get_private_eth_address.return_value = '0x' + to_address[24:]
            target_article_id = self.article_info_table_items[0]['article_id']

            event = {
                'body': {
                    'article_id':
                    target_article_id,
                    'tip_signed_transaction':
                    raw_transactions['tip'].rawTransaction.hex(),
                    'burn_signed_transaction':
                    raw_transactions['burn'].rawTransaction.hex()
                },
                'requestContext': {
                    'authorizer': {
                        'claims': {
                            'cognito:username': '******',
                            'custom:private_eth_address':
                            self.test_account.address,
                            'phone_number_verified': 'true',
                            'email_verified': 'true'
                        }
                    }
                }
            }
            event['body'] = json.dumps(event['body'])

            response = MeWalletTip(event, {}, self.dynamodb,
                                   cognito=None).main()
            self.assertEqual(response['statusCode'], 200)
            # verified_phone_and_email
            args, _ = mock_verified_phone_and_email.call_args
            self.assertEqual(event, args[0])
            # validate_private_eth_address
            args, _ = mock_validate_private_eth_address.call_args
            self.assertEqual(self.dynamodb, args[0])
            self.assertEqual('act_user_01', args[1])
            # validate_raw_transaction_signature
            args, _ = mock_validate_signature.call_args_list[0]
            self.assertEqual(raw_transactions['tip'].rawTransaction.hex(),
                             args[0])
            self.assertEqual(self.test_account.address, args[1])
            args, _ = mock_validate_signature.call_args_list[1]
            self.assertEqual(raw_transactions['burn'].rawTransaction.hex(),
                             args[0])
            self.assertEqual(self.test_account.address, args[1])
            # validate_erc20_transfer_data
            args, _ = mock_validate_erc20_transfer_data.call_args_list[0]
            tip_data = PrivateChainUtil.get_data_from_raw_transaction(
                raw_transactions['tip'].rawTransaction.hex(), '0x5')
            self.assertEqual(tip_data, args[0])
            self.assertEqual('0x' + to_address[24:], args[1])
            args, _ = mock_validate_erc20_transfer_data.call_args_list[1]
            burn_data = PrivateChainUtil.get_data_from_raw_transaction(
                raw_transactions['burn'].rawTransaction.hex(), '0x6')
            self.assertEqual(burn_data, args[0])
            self.assertEqual('0x' + os.environ['BURN_ADDRESS'], args[1])
示例#5
0
    def exec_main_proc(self):
        ################
        # get parameter
        ################
        sort_key = TimeUtil.generate_sort_key()
        from_user_eth_address = self.event['requestContext']['authorizer'][
            'claims']['custom:private_eth_address']
        user_id = self.event['requestContext']['authorizer']['claims'][
            'cognito:username']
        allowance = PrivateChainUtil.get_allowance(from_user_eth_address)
        transaction_count = PrivateChainUtil.get_transaction_count(
            from_user_eth_address)

        ################
        # validation
        ################
        # validate raw_transaction
        # init_approve_signed_transaction
        if int(allowance, 16) != 0:
            # allowance が設定されている場合は必須
            if self.params.get('init_approve_signed_transaction') is None:
                raise ValidationError(
                    'init_approve_signed_transaction is invalid.')
            # data
            init_approve_data = PrivateChainUtil.get_data_from_raw_transaction(
                self.params['init_approve_signed_transaction'],
                transaction_count)
            PrivateChainUtil.validate_erc20_approve_data(init_approve_data)
            if int(init_approve_data[72:], 16) != 0:
                raise ValidationError('Value of init_approve is invalid.')
            transaction_count = PrivateChainUtil.increment_transaction_count(
                transaction_count)

        # approve_signed_transaction
        approve_data = PrivateChainUtil.get_data_from_raw_transaction(
            self.params['approve_signed_transaction'], transaction_count)
        PrivateChainUtil.validate_erc20_approve_data(approve_data)
        # 日次の限度額を超えていた場合は例外
        sum_price = self.__get_token_send_value_today(user_id)
        if Decimal(os.environ['DAILY_LIMIT_TOKEN_SEND_VALUE']
                   ) < sum_price + Decimal(int(approve_data[72:], 16)):
            raise ValidationError('Token withdrawal limit has been exceeded.')
        transaction_count = PrivateChainUtil.increment_transaction_count(
            transaction_count)

        # relay_signed_transaction
        relay_data = PrivateChainUtil.get_data_from_raw_transaction(
            self.params['relay_signed_transaction'], transaction_count)
        PrivateChainUtil.validate_erc20_relay_data(relay_data)
        # approve と relay の value が同一であること
        approve_value = int(approve_data[72:], 16)
        relay_value = int(relay_data[72:], 16)
        if approve_value != relay_value:
            raise ValidationError('approve and relay values do not match.')

        #######################
        # send_raw_transaction
        #######################
        # 既に approve されている場合(allowance の戻り値が 0 ではない場合)、該当の approve を削除する(0 で更新)
        if int(allowance, 16) != 0:
            PrivateChainUtil.send_raw_transaction(
                self.params.get('init_approve_signed_transaction'))

        # approve 実施
        approve_transaction_hash = PrivateChainUtil.send_raw_transaction(
            self.params.get('approve_signed_transaction'))
        self.__create_send_info_with_approve_transaction_hash(
            sort_key, user_id, approve_transaction_hash, relay_value)

        # token_send_table への書き込み完了後に出金関連の例外が発生した場合は、token_send_table のステータスを fail に更新する
        try:
            # relay 実施
            relay_transaction_hash = PrivateChainUtil.send_raw_transaction(
                self.params.get('relay_signed_transaction'))
            self.__update_send_info_with_relay_transaction_hash(
                sort_key, user_id, relay_transaction_hash)
            # transaction の完了を確認
            is_completed = PrivateChainUtil.is_transaction_completed(
                relay_transaction_hash)
        except SendTransactionError as e:
            # ステータスを fail に更新し中断
            self.__update_send_info_with_send_status(sort_key, user_id, 'fail')
            raise e
        except ReceiptError:
            # send_value の値が残高を超えた場合や、処理最小・最大値の範囲に収まっていない場合に ReceiptError が発生するため
            # ValidationError として処理を中断する
            # ステータスを fail に更新
            self.__update_send_info_with_send_status(sort_key, user_id, 'fail')
            raise ValidationError('send_value')

        # transaction が完了していた場合、ステータスを done に更新
        if is_completed:
            self.__update_send_info_with_send_status(sort_key, user_id, 'done')

        return {
            'statusCode': 200,
            'body': json.dumps({'is_completed': is_completed})
        }
    def exec_main_proc(self):
        ################
        # get parameter
        ################
        # article info
        article_info_table = self.dynamodb.Table(
            os.environ['ARTICLE_INFO_TABLE_NAME'])
        article_info = article_info_table.get_item(
            Key={
                'article_id': self.params['article_id']
            }).get('Item')
        # eth_address
        from_user_eth_address = self.event['requestContext']['authorizer'][
            'claims']['custom:private_eth_address']
        to_user_eth_address = UserUtil.get_private_eth_address(
            self.cognito, article_info['user_id'])
        # transaction_count
        transaction_count = PrivateChainUtil.get_transaction_count(
            from_user_eth_address)

        ################
        # validation
        ################
        # does not tip same user
        if article_info['user_id'] == self.event['requestContext'][
                'authorizer']['claims']['cognito:username']:
            raise ValidationError('Can not tip to myself')

        # validate raw_transaction
        # tip
        tip_data = PrivateChainUtil.get_data_from_raw_transaction(
            self.params['tip_signed_transaction'], transaction_count)
        PrivateChainUtil.validate_erc20_transfer_data(tip_data,
                                                      to_user_eth_address)
        # burn
        transaction_count = PrivateChainUtil.increment_transaction_count(
            transaction_count)
        burn_data = PrivateChainUtil.get_data_from_raw_transaction(
            self.params['burn_signed_transaction'], transaction_count)
        PrivateChainUtil.validate_erc20_transfer_data(
            burn_data, '0x' + os.environ['BURN_ADDRESS'])

        # burn 量が正しいこと
        tip_value = int(tip_data[72:], 16)
        burn_value = int(burn_data[72:], 16)
        calc_burn_value = int(Decimal(tip_value) / Decimal(10))
        if burn_value != calc_burn_value:
            raise ValidationError('burn_value is invalid.')

        # 残高が足りていること
        if not self.__is_burnable_user(from_user_eth_address, tip_value,
                                       burn_value):
            raise ValidationError('Required at least {token} token'.format(
                token=tip_value + burn_value))

        #######################
        # send_raw_transaction
        #######################
        transaction_hash = PrivateChainUtil.send_raw_transaction(
            self.params['tip_signed_transaction'])
        burn_transaction = None
        try:
            # 投げ銭が成功した時のみバーン処理を行う
            if PrivateChainUtil.is_transaction_completed(transaction_hash):
                # バーンのトランザクション処理
                burn_transaction = PrivateChainUtil.send_raw_transaction(
                    self.params['burn_signed_transaction'])
            else:
                logging.info(
                    'Burn was not executed because tip transaction was uncompleted.'
                )
        except Exception as err:
            logging.fatal(err)
            traceback.print_exc()
        finally:
            # create tip info
            self.__create_tip_info(transaction_hash, tip_value,
                                   burn_transaction, article_info)

        return {'statusCode': 200}
示例#7
0
 def test_get_data_from_raw_transaction_ng_failure_raw_transaction(self):
     with self.assertRaises(ValidationError) as e:
         PrivateChainUtil.get_data_from_raw_transaction('0xabcdef', '0x10')
     self.assertEqual(e.exception.args[0], 'raw_transaction is invalid')
    def exec_main_proc(self):
        ################
        # get parameter
        ################
        # get article info
        article_info_table = self.dynamodb.Table(
            os.environ['ARTICLE_INFO_TABLE_NAME'])
        article_info = article_info_table.get_item(
            Key={
                'article_id': self.params['article_id']
            }).get('Item')
        # purchase article
        paid_articles_table = self.dynamodb.Table(
            os.environ['PAID_ARTICLES_TABLE_NAME'])
        paid_status_table = self.dynamodb.Table(
            os.environ['PAID_STATUS_TABLE_NAME'])
        # eth_address
        article_user_eth_address = UserUtil.get_private_eth_address(
            self.cognito, article_info['user_id'])
        user_eth_address = self.event['requestContext']['authorizer'][
            'claims']['custom:private_eth_address']
        # transaction_count
        transaction_count = PrivateChainUtil.get_transaction_count(
            user_eth_address)

        ################
        # validation
        ################
        # does not purchase same user's article
        user_id = self.event['requestContext']['authorizer']['claims'][
            'cognito:username']
        if article_info['user_id'] == user_id:
            raise ValidationError('Can not purchase own article')
        # validate raw_transaction
        # purchase
        purchase_data = PrivateChainUtil.get_data_from_raw_transaction(
            self.params['purchase_signed_transaction'], transaction_count)
        PrivateChainUtil.validate_erc20_transfer_data(
            purchase_data, article_user_eth_address)
        # burn
        transaction_count = PrivateChainUtil.increment_transaction_count(
            transaction_count)
        burn_data = PrivateChainUtil.get_data_from_raw_transaction(
            self.params['burn_signed_transaction'], transaction_count)
        PrivateChainUtil.validate_erc20_transfer_data(
            burn_data, '0x' + os.environ['BURN_ADDRESS'])

        # burn 量が正しいこと
        purchase_value = int(purchase_data[72:], 16)
        burn_value = int(burn_data[72:], 16)
        calc_burn_value = int(Decimal(purchase_value) / Decimal(9))
        if burn_value != calc_burn_value:
            raise ValidationError('burn_value is invalid.')

        # purchase_value が記事で指定されている金額に基づいた量が設定されていること
        DBUtil.validate_latest_price(self.dynamodb, self.params['article_id'],
                                     purchase_value)

        ################
        # purchase
        ################
        sort_key = TimeUtil.generate_sort_key()
        # 多重リクエストによる不必要なレコード生成を防ぐためにpaid_statusレコードを生成
        self.__create_paid_status(paid_status_table, user_id)
        # 購入のトランザクション処理
        purchase_transaction = PrivateChainUtil.send_raw_transaction(
            self.params['purchase_signed_transaction'])
        # 購入記事データを作成
        self.__create_paid_article(paid_articles_table, article_info,
                                   purchase_transaction, sort_key)
        # プライベートチェーンへのポーリングを行いトランザクションの承認状態を取得
        transaction_status = self.__polling_to_private_chain(
            purchase_transaction)
        # トランザクションの承認状態をpaid_articleとpaid_statusに格納
        self.__update_transaction_status(article_info, paid_articles_table,
                                         transaction_status, sort_key,
                                         paid_status_table, user_id)

        # 購入のトランザクションが成功した時のみバーンのトランザクションを発行する
        if transaction_status == 'done':
            try:
                # 購入に成功した場合、著者の未読通知フラグをTrueにする
                self.__update_unread_notification_manager(
                    article_info['user_id'])
                # 著者へ通知を作成
                self.__notify_author(article_info, user_id)
                # バーンのトランザクション処理
                burn_transaction = PrivateChainUtil.send_raw_transaction(
                    self.params['burn_signed_transaction'])
                # バーンのトランザクションを購入テーブルに格納
                self.__add_burn_transaction_to_paid_article(
                    burn_transaction, paid_articles_table, article_info,
                    sort_key)
            except Exception as err:
                logging.fatal(err)
                traceback.print_exc()
        # 記事購入者へは購入処理中の場合以外で通知を作成
        if transaction_status == 'done' or transaction_status == 'fail':
            self.__update_unread_notification_manager(user_id)
            self.__notify_purchaser(article_info, user_id, transaction_status)

        return {
            'statusCode': 200,
            'body': json.dumps({'status': transaction_status})
        }