blockchain.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. """ Definition of block chains. """
  2. __all__ = ['Blockchain']
  3. import logging
  4. from datetime import timedelta
  5. from fractions import Fraction
  6. from typing import List, Dict, Optional
  7. class Blockchain:
  8. """
  9. A block chain: a ordrered list of blocks.
  10. :ivar blocks: The blocks in this chain, oldest first.
  11. :vartype blocks: List[Block]
  12. :ivar block_indices: A dictionary allowing efficient lookup of the index of a block in this
  13. block chain by its hash value.
  14. :vartype block_indices: Dict[bytes, int]
  15. :ivar unspent_coins: A dictionary mapping from (allowed/available) transaction inputs
  16. to the transaction output that created this coin.
  17. :vartype unspent_coins: Dict[TransactionInput, TransactionTarget]
  18. """
  19. def __init__(self, blocks: 'List[Block]'):
  20. self.blocks = blocks
  21. assert self.blocks[0].height == 0
  22. self.block_indices = {block.hash: i for (i, block) in enumerate(blocks)}
  23. self.unspent_coins = self._compute_unspent_coins()
  24. def _compute_unspent_coins(self):
  25. val = {}
  26. for b in self.blocks:
  27. for t in b.transactions:
  28. for inp in t.inputs:
  29. if inp not in val:
  30. logging.warning("Aborting computation of unspent transactions because a transaction spent an unavailable coin.")
  31. return {}
  32. del val[inp]
  33. for i, target in enumerate(t.targets):
  34. val[TransactionInput(t.get_hash(), i)] = target
  35. return val
  36. def get_transaction_by_hash(self, hash_val: bytes) -> 'Optional[Transaction]':
  37. """ Returns a transaction from its hash, or None. """
  38. # TODO: build a hash table with this info
  39. for block in self.blocks[::-1]:
  40. for trans in block.transactions:
  41. if trans.get_hash() == hash_val:
  42. return trans
  43. return None
  44. def is_coin_still_valid(self, transaction_input: 'TransactionInput',
  45. prev_block: 'Block'=None) -> bool:
  46. """
  47. Validates that the coins that were sent in the transaction identified
  48. by `transaction_hash_val` to the nth receiver (n=output_idx) have not been
  49. spent before the given block.
  50. :param transaction_input: The coin to check.
  51. :param prev_block: The youngest block in this block chain that should be considered for
  52. the validation.
  53. """
  54. if prev_block is None or prev_block is self.head:
  55. return transaction_input in self.unspent_coins
  56. idx = self.block_indices[prev_block.hash]
  57. assert self.blocks[idx] is prev_block
  58. for block in self.blocks[idx::-1]:
  59. for trans in block.transactions:
  60. if transaction_input in trans.inputs:
  61. return False
  62. return True
  63. def get_block_by_hash(self, hash_val: bytes) -> 'Optional[Block]':
  64. """ Returns a block by its hash value, or None if it cannot be found. """
  65. idx = self.block_indices.get(hash_val)
  66. if idx is None:
  67. return None
  68. return self.blocks[idx]
  69. def verify_all_transactions(self) -> bool:
  70. """ Verify the transactions in all blocks in this chain. """
  71. for block in self.blocks:
  72. if not block.verify_transactions(self):
  73. return False
  74. return True
  75. def verify_all(self) -> bool:
  76. """ Verify all blocks in this block chain. """
  77. return all(block.verify(self) for block in self.blocks)
  78. @property
  79. def head(self):
  80. """
  81. The head of this block chain.
  82. :rtype: Block
  83. """
  84. return self.blocks[-1]
  85. def compute_difficulty(self, prev_block: 'Block'=None) -> int:
  86. """ Compute the desired difficulty for the block after `prev_block` (defaults to `head`). """
  87. BLOCK_INTERVAL = 120
  88. BLOCK_TARGET_TIMEDELTA = Fraction(int(timedelta(minutes=1).total_seconds() * 1000 * 1000))
  89. if prev_block is None:
  90. prev_block = self.head
  91. block_idx = self.block_indices[prev_block.hash] + 1
  92. if block_idx % BLOCK_INTERVAL != 0:
  93. return prev_block.difficulty
  94. duration = prev_block.time - self.blocks[block_idx - BLOCK_INTERVAL].time
  95. duration = Fraction(int(duration.total_seconds() * 1000 * 1000))
  96. prev_difficulty = Fraction(prev_block.difficulty)
  97. hash_rate = prev_difficulty * BLOCK_INTERVAL / duration
  98. new_difficulty = hash_rate * BLOCK_TARGET_TIMEDELTA / BLOCK_INTERVAL
  99. if new_difficulty < self.blocks[0].difficulty:
  100. new_difficulty = self.blocks[0].difficulty
  101. return int(new_difficulty)
  102. def compute_blockreward(self, prev_block: 'Block') -> int:
  103. """ Compute the block reward that is expected for the block following `prev_block`. """
  104. assert prev_block is not None
  105. reward = 1000
  106. l = self.block_indices[prev_block.hash]
  107. while l > 0:
  108. l = l - 10000
  109. reward = reward // 2
  110. return reward
  111. from .block import Block
  112. from .transaction import TransactionInput, Transaction