blockchain.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import hashlib
  2. import json
  3. from time import time
  4. from urllib.parse import urlparse
  5. import requests
  6. class Blockchain:
  7. def __init__(self):
  8. self.current_transactions = []
  9. self.chain = []
  10. self.nodes = set()
  11. # Create the genesis block
  12. self.new_block(previous_hash=1, proof=100)
  13. @staticmethod
  14. def hash(block):
  15. """
  16. Creates a SHA-256 hash of a Block
  17. :param block: <dict> Block
  18. :return: <str>
  19. """
  20. # We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes
  21. block_string = json.dumps(block, sort_keys=True).encode()
  22. return hashlib.sha256(block_string).hexdigest()
  23. @property
  24. def last_block(self):
  25. return self.chain[-1]
  26. def proof_of_work(self, last_proof):
  27. """
  28. Simple Proof of Work Algorithm:
  29. - Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
  30. - p is the previous proof, and p' is the new proof
  31. :param last_proof: <int>
  32. :return: <int>
  33. """
  34. proof = 0
  35. while self.valid_proof(last_proof, proof) is False:
  36. proof += 1
  37. return proof
  38. @staticmethod
  39. def valid_proof(last_proof, proof):
  40. """
  41. Validates the Proof: Does hash(last_proof, proof) contain 4 leading zeroes?
  42. :param last_proof: <int> Previous Proof
  43. :param proof: <int> Current Proof
  44. :return: <bool> True if correct, False if not.
  45. """
  46. guess = f'{last_proof}{proof}'.encode()
  47. guess_hash = hashlib.sha256(guess).hexdigest()
  48. return guess_hash[:4] == "0000"
  49. def register_node(self, address):
  50. """
  51. Add a new node to the list of nodes
  52. :param address: <str> Address of node. Eg. 'http://192.168.0.5:5000'
  53. """
  54. parsed_url = urlparse(address)
  55. if parsed_url.netloc:
  56. self.nodes.add(parsed_url.netloc)
  57. elif parsed_url.path:
  58. # Accepts an URL without scheme like '192.168.0.5:5000'.
  59. self.nodes.add(parsed_url.path)
  60. else:
  61. raise ValueError('Invalid URL')
  62. def valid_chain(self, chain):
  63. """
  64. Determine if a given blockchain is valid
  65. :param chain: <list> A blockchain
  66. :return: <bool> True if valid, False if not
  67. """
  68. last_block = chain[0]
  69. current_index = 1
  70. while current_index < len(chain):
  71. block = chain[current_index]
  72. if block['previous_hash'] != self.hash(last_block):
  73. return False
  74. if not self.valid_proof(last_block['proof'], block['proof']):
  75. return False
  76. last_block = block
  77. current_index += 1
  78. return True
  79. def resolve_conflicts(self):
  80. """
  81. This is our Consensus Algorithm, it resolves conflicts
  82. by replacing our chain with the longest one in the network.
  83. :return: <bool> True if our chain was replaced, False if not
  84. """
  85. neighbours = self.nodes
  86. new_chain = None
  87. # We're only looking for chains longer than ours
  88. max_length = len(self.chain)
  89. # Grab and verify the chains from all the nodes in our network
  90. for node in neighbours:
  91. response = requests.get(f'http://{node}/chain')
  92. if response.status_code == 200:
  93. length = response.json()['length']
  94. chain = response.json()['chain']
  95. # Check if the length is longer and the chain is valid
  96. if length > max_length and self.valid_chain(chain):
  97. max_length = length
  98. new_chain = chain
  99. # Replace our chain if we discovered a new, valid chain longer than ours
  100. if new_chain:
  101. self.chain = new_chain
  102. return True
  103. return False
  104. def new_block(self, proof, previous_hash=None):
  105. """
  106. Create a new Block in the Blockchain
  107. :param proof: <int> The proof given by the Proof of Work algorithm
  108. :param previous_hash: (Optional) <str> Hash of previous Block
  109. :return: <dict> New Block
  110. """
  111. block = {
  112. 'index': len(self.chain) + 1,
  113. 'timestamp': time(),
  114. 'transactions': self.current_transactions,
  115. 'proof': proof,
  116. 'previous_hash': previous_hash or self.hash(self.chain[-1]),
  117. }
  118. # Reset the current list of transactions
  119. self.current_transactions = []
  120. self.chain.append(block)
  121. return block
  122. def new_transaction(self, sender, recipient, amount):
  123. """
  124. Creates a new transaction to go into the next mined Block
  125. :param sender: <str> Address of the Sender
  126. :param recipient: <str> Address of the Recipient
  127. :param amount: <int> Amount
  128. :return: <int> The index of the Block that will hold this transaction
  129. """
  130. self.current_transactions.append({
  131. 'sender': sender,
  132. 'recipient': recipient,
  133. 'amount': amount,
  134. })
  135. def remove_block(self, index):
  136. # Remove the block at the specified index
  137. self.chain.pop(index)
  138. # Update the previous block to point to the new block
  139. if index > 0:
  140. self.chain[index - 1]['next_block'] = self.chain[index]['next_block']
  141. # Re-encrypt the subsequent blocks
  142. for i in range(index, len(self.chain)):
  143. block = self.chain[i]
  144. previous_hash = self.hash(self.chain[i - 1])
  145. block['previous_hash'] = previous_hash
  146. block['hash'] = self.hash(block)
  147. return self.last_block['index'] + 1