block.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. """ Definitions of blocks, and the genesis block. """
  2. from datetime import datetime
  3. from binascii import hexlify, unhexlify
  4. import json
  5. import logging
  6. import src.utils as utils
  7. from .config import *
  8. from .merkle import merkle_tree
  9. from .crypto import get_hasher
  10. __all__ = ['Block']
  11. class Block:
  12. """
  13. A block: a container for all the data associated with a block.
  14. To figure out whether the block is valid on top of a block chain, there are a few `verify`
  15. methods. Without calling these, you must assume the block was crafted maliciously.
  16. :ivar hash: The hash value of this block.
  17. :vartype hash: bytes
  18. :ivar id: The ID of this block. Genesis-Block has the ID '0'.
  19. :vartype id: int
  20. :ivar prev_block_hash: The hash of the previous block.
  21. :vartype prev_block_hash: bytes
  22. :ivar merkle_root_hash: The hash of the merkle tree root of the transactions in this block.
  23. :vartype merkle_root_hash: bytes
  24. :ivar time: The time when this block was created.
  25. :vartype time: datetime
  26. :ivar nonce: The nonce in this block that was required to achieve the proof of work.
  27. :vartype nonce: int
  28. :ivar height: The height (accumulated target) of this block.
  29. :vartype height: int
  30. :ivar received_time: The time when we received this block.
  31. :vartype received_time: datetime
  32. :ivar target: The target of this block.
  33. :vartype target: int
  34. :ivar transactions: The list of transactions in this block.
  35. :vartype transactions: List[Transaction]
  36. """
  37. # TODO: Check if "id" is really needed. Should be the same as "height".
  38. def __init__(self, prev_block_hash, time, nonce, height, received_time, target, transactions,
  39. merkle_root_hash=None, id=None):
  40. self.id = id
  41. self.prev_block_hash = prev_block_hash
  42. self.merkle_root_hash = merkle_root_hash
  43. self.time = time
  44. self.nonce = nonce
  45. self.height = height
  46. self.received_time = received_time
  47. self.target = target
  48. self.transactions = transactions
  49. self._hash = self._get_hash()
  50. @property
  51. def hash(self):
  52. return self._hash
  53. @hash.setter
  54. def hash(self, value):
  55. self._hash = value
  56. def to_json_compatible(self):
  57. """ Returns a JSON-serializable representation of this object. """
  58. val = {}
  59. val['id'] = self.id
  60. val['hash'] = hexlify(self._hash).decode()
  61. val['prev_block_hash'] = hexlify(self.prev_block_hash).decode()
  62. val['merkle_root_hash'] = hexlify(self.merkle_root_hash).decode()
  63. val['time'] = self.time.strftime("%Y-%m-%dT%H:%M:%S.%f UTC")
  64. val['nonce'] = self.nonce
  65. val['height'] = self.height
  66. val['target'] = self.target
  67. val['transactions'] = [t.to_json_compatible() for t in self.transactions]
  68. return val
  69. @classmethod
  70. def from_json_compatible(cls, val):
  71. """ Create a new block from its JSON-serializable representation. """
  72. from .transaction import Transaction
  73. return cls(unhexlify(val['prev_block_hash']),
  74. datetime.strptime(val['time'], "%Y-%m-%dT%H:%M:%S.%f UTC"),
  75. int(val['nonce']),
  76. int(val['height']),
  77. datetime.utcnow(),
  78. int(val['target']),
  79. [Transaction.from_json_compatible(t) for t in list(val['transactions'])],
  80. unhexlify(val['merkle_root_hash']),
  81. int(val['id']))
  82. @classmethod
  83. def create(cls, chain_difficulty: int, prev_block: 'Block', transactions: list, ts=None):
  84. """
  85. Create a new block for a certain blockchain, containing certain transactions.
  86. """
  87. tree = merkle_tree(transactions)
  88. difficulty = chain_difficulty
  89. id = prev_block.height + 1
  90. if ts is None:
  91. ts = datetime.utcnow()
  92. if ts <= prev_block.time:
  93. ts = prev_block.time + timedelta(microseconds=1)
  94. return Block(prev_block.hash, ts, 0, prev_block.height + 1,
  95. None, difficulty, transactions, tree.get_hash(), id)
  96. def __str__(self):
  97. return json.dumps(self.to_json_compatible(), indent=4)
  98. def get_partial_hash(self):
  99. """
  100. Computes a hash over the contents of this block, except for the nonce. The proof of
  101. work can use this partial hash to efficiently try different nonces. Other uses should
  102. use `hash` to get the complete hash.
  103. """
  104. hasher = get_hasher()
  105. hasher.update(self.prev_block_hash)
  106. hasher.update(self.merkle_root_hash)
  107. hasher.update(self.time.strftime("%Y-%m-%dT%H:%M:%S.%f UTC").encode())
  108. hasher.update(utils.int_to_bytes(self.target))
  109. return hasher
  110. def finish_hash(self, hasher):
  111. """
  112. Finishes the hash in `hasher` with the nonce in this block. The proof of
  113. work can use this function to efficiently try different nonces. Other uses should
  114. use `hash` to get the complete hash in one step.
  115. """
  116. hasher.update(utils.int_to_bytes(self.nonce))
  117. return hasher.digest()
  118. def _get_hash(self):
  119. """ Compute the hash of the header data. This is not necessarily the received hash value for this block! """
  120. hasher = self.get_partial_hash()
  121. return self.finish_hash(hasher)
  122. def verify_merkle(self):
  123. """ Verify that the merkle root hash is correct for the transactions in this block. """
  124. return merkle_tree(self.transactions).get_hash() == self.merkle_root_hash
  125. def verify_proof_of_work(self):
  126. """ Verify the proof of work on a block. """
  127. return int.from_bytes(self._hash, 'big') < self.target
  128. def verify_difficulty(self):
  129. """ Verifies that the hash value is correct and fulfills its target promise. """
  130. if not self.verify_proof_of_work():
  131. logging.warning("block does not satisfy proof of work")
  132. return False
  133. return True
  134. def verify_prev_block(self, prev_block: 'Block', chain_target: int):
  135. """ Verifies that the previous block pointer points to the head of the given blockchain and target and height are correct. """
  136. if prev_block.hash != self.prev_block_hash:
  137. logging.warning("Previous block is not head of the block chain.")
  138. return False
  139. if self.target != chain_target:
  140. logging.warning("Block has wrong target.")
  141. return False
  142. if prev_block.height + 1 != self.height:
  143. logging.warning("Block has wrong height.")
  144. return False
  145. return True
  146. def verify_block_transactions(self, unspent_coins: dict, reward: int):
  147. """ Verifies that all transaction in this block are valid in the given blockchain. """
  148. mining_rewards = []
  149. all_inputs = []
  150. for t in self.transactions:
  151. all_inputs += t.inputs
  152. if t.inputs[0].is_coinbase:
  153. if len(mining_rewards) > 1:
  154. logging.warning("block has more than one coinbase transaction")
  155. return False
  156. mining_rewards.append(t)
  157. if not t.validate_tx(unspent_coins):
  158. return False
  159. fees = sum(t.get_transaction_fee(unspent_coins) for t in self.transactions)
  160. actual_reward_and_fees = sum(t.amount for t in mining_rewards[0].targets)
  161. if actual_reward_and_fees > reward + fees:
  162. warn = "{} is different than specified({})".format(actual_reward_and_fees, reward+fees)
  163. logging.error(warn)
  164. return False
  165. if not self._verify_input_consistency(all_inputs):
  166. return False
  167. return True
  168. def _verify_input_consistency(self, tx_inputs: 'List[TransactionInputs]'):
  169. """"Verify that all the transactions in the transaction list are not spending from a same input transaction and index"""
  170. tx_inp = [(i.transaction_hash, i.output_idx) for i in tx_inputs]
  171. return len(tx_inp) == len(set(tx_inp))
  172. def verify_time(self, head_time: datetime):
  173. """
  174. Verifies that blocks are not from far in the future, but a bit younger
  175. than the head of `chain`.
  176. """
  177. if self.time - timedelta(hours=2) > datetime.utcnow():
  178. logging.warning("discarding block because it is from the far future")
  179. return False
  180. if self.time <= head_time:
  181. logging.warning("discarding block because it is younger than its predecessor")
  182. return False
  183. return True
  184. def verify(self, prev_block: 'Block', chain_difficulty: int, unspent_coins: dict, chain_indices: dict, reward: int):
  185. """
  186. Verifies that this block contains only valid data and can be applied on top of the block
  187. chain `chain`.
  188. """
  189. assert self._hash not in chain_indices
  190. if self.height == 0:
  191. logging.warning("only the genesis block may have height=0")
  192. return False
  193. return self.verify_difficulty() and self.verify_merkle() and self.verify_prev_block(prev_block,
  194. chain_difficulty) \
  195. and self.verify_time(prev_block
  196. .time) and self.verify_block_transactions(unspent_coins, reward)