website.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. #!/usr/bin/env python3
  2. """
  3. The website starts the blockchain explorer, reachable as default under `localhost:80`. The blockchain explorer launches
  4. its own non-mining miner to get access to the blockchain.
  5. """
  6. import argparse
  7. import requests
  8. import binascii
  9. import miner
  10. from flask import Flask, render_template
  11. from _thread import start_new_thread
  12. from flask import abort
  13. app = Flask(__name__)
  14. sess = requests.Session()
  15. host = "http://localhost:{}/"
  16. url = ""
  17. QUERY_PARAMETER_AVERAGE_LENGTH = 10
  18. def main():
  19. """
  20. Takes arguments: `rpc-port`: The port number where the Blockchain Explorer can find an RPC server. Default is: `40203`
  21. `bootstrap-peer`: Address of other P2P peers in the network. If not supplied, no non-mining miner will be started.
  22. `listen-address`: The IP address where the P2P server should bind to. `listen-port`: The port where the P2P server should listen.
  23. Defaults a dynamically assigned port.
  24. """
  25. parser = argparse.ArgumentParser(description="Blockchain Explorer.")
  26. parser.add_argument("--rpc-port", type=int, default=40203,
  27. help="The port number where the Blockchain Explorer can find an RPC server.")
  28. parser.add_argument("--bootstrap-peer",
  29. help="Address of other P2P peers in the network.")
  30. parser.add_argument("--listen-address", default="",
  31. help="The IP address where the P2P server should bind to.")
  32. parser.add_argument("--listen-port", default=0, type=int,
  33. help="The port where the P2P server should listen. Defaults a dynamically assigned port.")
  34. args = parser.parse_args()
  35. global url
  36. url = host.format(args.rpc_port)
  37. if (args.bootstrap_peer):
  38. start_new_thread(miner.start_listener, (args.rpc_port, args.bootstrap_peer, args.listen_port, args.listen_address)) # Starts the miner.
  39. app.run(host='0.0.0.0', port=80) # Starts the flask server for the blockchain explorer
  40. else:
  41. parser.parse_args(["--help"])
  42. def append_sender_to_transaction(transaction):
  43. """ Reads the transaction inputs for the supplied transaction and adds the senders to the JSON objects. """
  44. pks = []
  45. for inp in transaction["inputs"]:
  46. res = sess.get(url + 'explorer/transaction/' + inp["transaction_hash"])
  47. output_idx = inp["output_idx"]
  48. pks.append(res.json()["targets"][output_idx]["recipient_pk"])
  49. counter = 0
  50. result = []
  51. for inp in transaction["inputs"]:
  52. dict = {"input": inp, "signature": transaction["signatures"][counter]}
  53. result.append(dict)
  54. counter += 1
  55. transaction["inp"] = result
  56. transaction["senders"] = pks
  57. @app.route("/")
  58. def index():
  59. """ Index page of the blockchain explorer. Shows statistics, last blocks and last transactions.
  60. Route: `\"/\"`. """
  61. data = {}
  62. data["blocks"] = sess.get(url + 'explorer/lastblocks/10').json()
  63. data["statistics"] = get_statistics()
  64. transactions = sess.get(url + 'explorer/lasttransactions/5').json()
  65. for transaction in transactions:
  66. append_sender_to_transaction(transaction)
  67. data["transactions"] = transactions
  68. return render_template('index.html', data=data)
  69. @app.route("/blocks")
  70. @app.route("/blocks/<int:amount>")
  71. def blocklist(amount=10):
  72. """ Lists all blocks from the blockchain. Optional takes argument as amount of blocks to just return
  73. the last amount blocks.
  74. Route: `\"/blocks/<optional: int:amount>\"`. """
  75. blocks = sess.get(url + 'explorer/lastblocks/' + str(amount)).json()
  76. if len(blocks) < amount:
  77. return render_template('blocklist.html', data=blocks)
  78. else:
  79. return render_template('blocklist.html', data=blocks, nextamount=amount * 2)
  80. @app.route("/block/<string:hash>")
  81. def block(hash):
  82. """ Lists information about the specified block.
  83. Route: `\"/block/<string:hash>\"`. """
  84. resp = sess.get(url + 'explorer/block/' + hash)
  85. if resp.status_code == 404:
  86. return render_template('not_found.html', data={'type': 'Block', 'hash': hash})
  87. resp.raise_for_status()
  88. json_obj = resp.json()
  89. for transaction in json_obj["transactions"]:
  90. append_sender_to_transaction(transaction)
  91. return render_template('block.html', data=json_obj)
  92. @app.route("/addresses/")
  93. def addresses():
  94. """ Lists all addresses found in the blockchain as sender or receiver of a transaction.
  95. Route: `\"/addresses\"`. """
  96. resp = sess.get(url + 'explorer/addresses')
  97. resp.raise_for_status()
  98. return render_template('addresses.html', data=resp.json())
  99. def try_get_json(addr):
  100. try:
  101. resp = sess.get(url + addr)
  102. except:
  103. abort(404)
  104. # return render_template('not_found.html', data={'type': 'Address', 'hash': addr})
  105. if resp.status_code == 404:
  106. abort(404)
  107. # return render_template('not_found.html', data={'type': 'Address', 'hash': addr})
  108. resp.raise_for_status()
  109. json_obj = resp.json()
  110. return json_obj
  111. @app.route("/address/<string:addr>")
  112. def address(addr):
  113. """ Lists information about the specified address. """
  114. json_obj = try_get_json('explorer/sortedtransactions/' + addr)
  115. for tr in json_obj["sent"]:
  116. append_sender_to_transaction(tr)
  117. for tr in json_obj["received"]:
  118. for target in tr["targets"]:
  119. if (target["recipient_pk"]) != addr:
  120. tr["targets"].remove(target)
  121. append_sender_to_transaction(tr)
  122. resp_credit = sess.post(url + 'explorer/show-balance', data=binascii.unhexlify(addr),
  123. headers={"Content-Type": "application/json"})
  124. resp_credit.raise_for_status()
  125. json_obj["credit"] = resp_credit.json()["credit"]
  126. json_obj["hash"] = addr
  127. return render_template('address.html', data=json_obj)
  128. @app.route("/transactions")
  129. @app.route("/transactions/<int:amount>")
  130. def transactions(amount=10):
  131. """ Lists all transactions from the blockchain. Optional takes argument as amount of transactions to just return
  132. the last amount transactions.
  133. Route: `\"/transactions/<optional: int:amount>\"`. """
  134. resp = sess.get(url + 'explorer/lasttransactions/' + str(amount))
  135. resp.raise_for_status()
  136. transactions = resp.json()
  137. for transaction in transactions:
  138. append_sender_to_transaction(transaction)
  139. if (len(transactions) < amount):
  140. return render_template('transactions.html', data_array=transactions)
  141. else:
  142. return render_template('transactions.html', data_array=transactions, nextamount=amount * 2)
  143. @app.route("/transaction/<string:hash>")
  144. def transaction(hash):
  145. """ Lists information about the specified transaction.
  146. Route: `\"/transaction/<string:hash>\"`. """
  147. resp = sess.get(url + 'explorer/transaction/' + hash)
  148. if resp.status_code == 404:
  149. return render_template('not_found.html', data={'type': 'Transaction', 'hash': hash})
  150. resp.raise_for_status()
  151. json_obj = resp.json()
  152. append_sender_to_transaction(json_obj)
  153. return render_template('transaction.html', transaction=json_obj)
  154. def get_statistics():
  155. """ Lists all calculated statistics about the blockchain and its network. """
  156. resp_blocktime = sess.get(url + 'explorer/statistics/blocktime')
  157. resp_blocktime.raise_for_status()
  158. resp_totalblocks = sess.get(url + 'explorer/statistics/totalblocks')
  159. resp_totalblocks.raise_for_status()
  160. resp_difficulty = sess.get(url + 'explorer/statistics/difficulty')
  161. resp_difficulty.raise_for_status()
  162. resp_hashrate = sess.get(url + 'explorer/statistics/hashrate?length=' + str(QUERY_PARAMETER_AVERAGE_LENGTH))
  163. resp_hashrate.raise_for_status()
  164. resp_tps = sess.get(url + 'explorer/statistics/tps?length=' + str(QUERY_PARAMETER_AVERAGE_LENGTH))
  165. resp_tps.raise_for_status()
  166. result = {"blocktime": resp_blocktime.json(), "totalblocks": resp_totalblocks.json(),
  167. "difficulty": resp_difficulty.json(), "hashrate": resp_hashrate.json(), "tps": resp_tps.json()}
  168. return result
  169. @app.route("/statistics")
  170. def statistics():
  171. """ Shows all calculated statistics about the blockchain and its network.
  172. Route: `\"/statistics\"`. """
  173. return render_template('statistics.html', data=get_statistics())
  174. @app.route("/about")
  175. def about():
  176. """ Shows the 'About'-Page.
  177. Route: `\"/about\"`. """
  178. return render_template('about.html')
  179. if __name__ == '__main__':
  180. main()