miner.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. #!/usr/bin/env python3
  2. """ The executable that participates in the P2P network and optionally mines new blocks. """
  3. __all__ = []
  4. import argparse
  5. import json
  6. from urllib.parse import urlparse
  7. import logging
  8. logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-8s %(message)s")
  9. import flask
  10. app = flask.Flask(__name__)
  11. from src.crypto import Signing
  12. from src.protocol import Protocol
  13. from src.block import GENESIS_BLOCK
  14. from src.chainbuilder import ChainBuilder
  15. from src.mining import Miner
  16. from src.transaction import TransactionInput
  17. from src.persistence import Persistence
  18. def parse_addr_port(val):
  19. url = urlparse("//" + val)
  20. assert url.scheme == ''
  21. assert url.path == ''
  22. assert url.params == ''
  23. assert url.query == ''
  24. assert url.fragment == ''
  25. assert url.port is not None
  26. assert url.hostname is not None
  27. return (url.hostname, url.port)
  28. def rpc_server(port, chainbuilder, persist):
  29. @app.route("/network-info", methods=['GET'])
  30. def get_network_info():
  31. return json.dumps([list(peer.peer_addr)[:2] for peer in chainbuilder.protocol.peers if peer.is_connected])
  32. @app.route("/new-transaction", methods=['PUT'])
  33. def send_transaction():
  34. chainbuilder.protocol.received("transaction", flask.request.json, None, 0)
  35. # TODO: from the right thread, we want to call persist.store() here
  36. return b""
  37. @app.route("/show-balance", methods=['POST'])
  38. def show_balance():
  39. pubkeys = {Signing.from_json_compatible(pk): i for (i, pk) in enumerate(flask.request.json)}
  40. amounts = [0 for _ in pubkeys.values()]
  41. for output in chainbuilder.primary_block_chain.unspent_coins.values():
  42. if output.recipient_pk in pubkeys:
  43. amounts[pubkeys[output.recipient_pk]] += output.amount
  44. return json.dumps(amounts)
  45. @app.route("/build-transaction", methods=['POST'])
  46. def build_transaction():
  47. sender_pks = {Signing.from_json_compatible(o): i for i, o in enumerate(flask.request.json['sender-pubkeys'])}
  48. amount = flask.request.json['amount']
  49. inputs = []
  50. used_keys = []
  51. for (inp, output) in chainbuilder.primary_block_chain.unspent_coins.items():
  52. if output.recipient_pk in sender_pks:
  53. amount -= output.amount
  54. inputs.append(inp.to_json_compatible())
  55. used_keys.append(sender_pks[output.recipient_pk])
  56. if amount <= 0:
  57. break
  58. if amount > 0:
  59. inputs = []
  60. used_keys = []
  61. return json.dumps({
  62. "inputs": inputs,
  63. "remaining_amount": -amount,
  64. "key_indices": used_keys,
  65. })
  66. @app.route("/transactions", methods=['POST'])
  67. def get_transactions_for_key():
  68. key = Signing(flask.request.data)
  69. transactions = set()
  70. outputs = set()
  71. chain = chainbuilder.primary_block_chain
  72. for b in chain.blocks:
  73. for t in b.transactions:
  74. for i, target in enumerate(t.targets):
  75. if target.recipient_pk == key:
  76. transactions.add(t)
  77. outputs.add(TransactionInput(t.get_hash(), i))
  78. for b in chain.blocks:
  79. for t in b.transactions:
  80. for inp in t.inputs:
  81. if inp in outputs:
  82. transactions.add(t)
  83. return json.dumps([t.to_json_compatible() for t in transactions])
  84. app.run(port=port)
  85. def main():
  86. parser = argparse.ArgumentParser(description="Blockchain Miner.")
  87. parser.add_argument("--listen-address", default="",
  88. help="The IP address where the P2P server should bind to.")
  89. parser.add_argument("--listen-port", default=0, type=int,
  90. help="The port where the P2P server should listen. Defaults a dynamically assigned port.")
  91. parser.add_argument("--mining-pubkey", type=argparse.FileType('rb'),
  92. help="The public key where mining rewards should be sent to. No mining is performed if this is left unspecified.")
  93. parser.add_argument("--bootstrap-peer", action='append', type=parse_addr_port, default=[],
  94. help="Addresses of other P2P peers in the network.")
  95. parser.add_argument("--rpc-port", type=int, default=40203,
  96. help="The port number where the wallet can find an RPC server.")
  97. parser.add_argument("--persist-path",
  98. help="The file where data is persisted.")
  99. args = parser.parse_args()
  100. proto = Protocol(args.bootstrap_peer, GENESIS_BLOCK, args.listen_port, args.listen_address)
  101. if args.mining_pubkey is not None:
  102. pubkey = Signing(args.mining_pubkey.read())
  103. args.mining_pubkey.close()
  104. miner = Miner(proto, pubkey)
  105. miner.start_mining()
  106. chainbuilder = miner.chainbuilder
  107. else:
  108. chainbuilder = ChainBuilder(proto)
  109. if args.persist_path:
  110. persist = Persistence(args.persist_path, chainbuilder)
  111. try:
  112. persist.load()
  113. except FileNotFoundError:
  114. pass
  115. else:
  116. persist = None
  117. rpc_server(args.rpc_port, chainbuilder, persist)
  118. if __name__ == '__main__':
  119. main()