app.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import grp
  2. from flask import Flask, request, jsonify
  3. from json import JSONEncoder
  4. import json
  5. import grpc
  6. # Here are Iroha dependencies.
  7. # Python library generally consists of 3 parts:
  8. # Iroha, IrohaCrypto and IrohaGrpc which we need to import:
  9. import os
  10. import binascii
  11. from iroha import IrohaCrypto
  12. from iroha import Iroha, IrohaGrpc
  13. from werkzeug.wrappers import response
  14. from werkzeug.exceptions import HTTPException
  15. # Here is the information about the environment and admin account information:
  16. IROHA_HOST_ADDR = os.getenv('IROHA_HOST_ADDR', '127.0.0.1')
  17. IROHA_PORT = os.getenv('IROHA_PORT', '50051')
  18. ADMIN_ACCOUNT_ID = os.getenv('ADMIN_ACCOUNT_ID', 'admin@test')
  19. ADMIN_PRIVATE_KEY = os.getenv(
  20. 'ADMIN_PRIVATE_KEY', 'f101537e319568c765b2cc89698325604991dca57b9716b58016b253506cab70')
  21. # Here we will create user keys
  22. user_private_key = IrohaCrypto.private_key()
  23. user_public_key = IrohaCrypto.derive_public_key(user_private_key)
  24. iroha = Iroha(ADMIN_ACCOUNT_ID)
  25. net = IrohaGrpc('{}:{}'.format(IROHA_HOST_ADDR, IROHA_PORT))
  26. class MyEncoder(JSONEncoder):
  27. def default(self, o):
  28. if isinstance(o, bytes):
  29. return str(o, encoding='utf-8')
  30. return o.__dict__
  31. app = Flask(__name__)
  32. app.json_encoder = MyEncoder
  33. class Asset:
  34. assetId = ""
  35. balance = 0
  36. def __init__(self, assetId = "", balance = 0):
  37. self.assetId = assetId
  38. self.balance = balance
  39. class Assets:
  40. assets = []
  41. class StatefulValidationError:
  42. name = ""
  43. description = ""
  44. solution = ""
  45. def __init__(self, name = "", description = "", solution = ""):
  46. self.name = name
  47. self.description = description
  48. self.solution = solution
  49. transferAssetsValidationErrors = [
  50. StatefulValidationError("Could not transfer asset", "Internal error happened", "Try again or contact developers"),
  51. StatefulValidationError("No such permissions", "Command's creator does not have permission to transfer asset from his account", "Grant the necessary permission"),
  52. StatefulValidationError("No such source account", "Cannot find account with such id to transfer money from", "Make sure source account id is correct"),
  53. StatefulValidationError("No such destination account", "Cannot find account with such id to transfer money to", "Make sure destination account id is correct"),
  54. StatefulValidationError("No such asset found", "Cannot find such asset", "Make sure asset name and precision are correct"),
  55. StatefulValidationError("Not enough balance", "Source account's balance is too low to perform the operation", "Add asset to account or choose lower value to subtract"),
  56. StatefulValidationError("Too much asset to transfer", "Resulting asset quantity of destination account would exceed the allowed maximum", "Make sure that the final destination value is less than 2^256 / 10^asset_precision"),
  57. StatefulValidationError("Too long description", "Too long description", "Ensure that description length matches the criteria above (or just shorten it)")
  58. ]
  59. @app.errorhandler(Exception)
  60. def handle_exception(e):
  61. # pass through HTTP errors
  62. if isinstance(e, HTTPException):
  63. return e
  64. if isinstance(e, grpc.RpcError):
  65. return {
  66. "details" : e.details(),
  67. "debug_error" : json.loads(e.debug_error_string())
  68. }, 500
  69. # now you're handling non-HTTP exceptions only
  70. return e, 500
  71. @app.route("/", methods=['GET'])
  72. def Hello():
  73. return "IROHA REST API"
  74. @app.route("/accounts/<string:accountId>/assets/", methods=['GET'])
  75. def get_account_assets(accountId): #TODO: add validation and error handling
  76. query = iroha.query('GetAccountAssets', account_id=accountId)
  77. IrohaCrypto.sign_query(query, ADMIN_PRIVATE_KEY)
  78. iroha_response = net.send_query(query)
  79. data = iroha_response.account_assets_response.account_assets
  80. response = Assets()
  81. response.assets = []
  82. if len(data) < 1:
  83. return '', 204
  84. for asset in data:
  85. response.assets.append(Asset(asset.asset_id, asset.balance))
  86. return jsonify(response)
  87. @app.route("/assets/transfer/", methods=['POST'])
  88. def transfer_assets():
  89. data = request.get_json()
  90. if data["transfers"] and len(data["transfers"]) > 0:
  91. commands = []
  92. for transfer in data["transfers"]:
  93. commands.append(
  94. iroha.command('TransferAsset', src_account_id = transfer["source"], dest_account_id = transfer["destination"],
  95. asset_id = transfer["asset"], description = transfer["description"], amount = str(transfer["amount"]))
  96. )
  97. transaction = iroha.transaction(commands)
  98. hex_hash = binascii.hexlify(IrohaCrypto.hash(transaction))
  99. IrohaCrypto.sign_transaction(transaction, ADMIN_PRIVATE_KEY)
  100. net.send_tx(transaction)
  101. #TODO speed up status reading
  102. transactionStates = []
  103. for status in net.tx_status_stream(transaction):
  104. transactionStates.append(status)
  105. if(status[0] == "STATEFUL_VALIDATION_SUCCESS"):
  106. break
  107. if(status[0] == "STATEFUL_VALIDATION_FAILED"):
  108. return jsonify({
  109. "transactionHash" : hex_hash,
  110. "error" : transferAssetsValidationErrors[status[2] - 1]
  111. }), 400
  112. #TODO send URL to newly created transaction in Content-Location header
  113. return jsonify({
  114. "transactionHash" : hex_hash,
  115. # "transactionStates" : transactionStates
  116. })