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')
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')
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)
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])
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}
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}) }