blockchain.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. """ Definition of block chains. """
  2. __all__ = ['Blockchain']
  3. import logging
  4. from fractions import Fraction
  5. from typing import List, Dict, Optional
  6. from .proof_of_work import DIFFICULTY_BLOCK_INTERVAL, DIFFICULTY_TARGET_TIMEDELTA
  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):
  20. self.blocks = [GENESIS_BLOCK]
  21. assert self.blocks[0].height == 0
  22. self.block_indices = {GENESIS_BLOCK_HASH: 0}
  23. assert not GENESIS_BLOCK.transactions
  24. self.unspent_coins = {}
  25. def try_append(self, block: 'Block') -> 'Optional[Blockchain]':
  26. unspent_coins = self.unspent_coins.copy()
  27. for t in block.transactions:
  28. for inp in t.inputs:
  29. if inp not in unspent_coins:
  30. logging.warning("Aborting computation of unspent transactions because a transaction spent an unavailable coin.")
  31. return None
  32. del unspent_coins[inp]
  33. for i, target in enumerate(t.targets):
  34. unspent_coins[TransactionInput(t.get_hash(), i)] = target
  35. chain = Blockchain()
  36. chain.unspent_coins = unspent_coins
  37. chain.blocks = self.blocks + [block]
  38. chain.block_indices = self.block_indices.copy()
  39. chain.block_indices[block.hash] = len(self.blocks)
  40. if not block.verify(chain):
  41. return None
  42. return chain
  43. def get_transaction_by_hash(self, hash_val: bytes) -> 'Optional[Transaction]':
  44. """ Returns a transaction from its hash, or None. """
  45. # TODO: build a hash table with this info
  46. for block in self.blocks[::-1]:
  47. for trans in block.transactions:
  48. if trans.get_hash() == hash_val:
  49. return trans
  50. return None
  51. def is_coin_still_valid(self, transaction_input: 'TransactionInput',
  52. prev_block: 'Block'=None) -> bool:
  53. """
  54. Validates that the coins that were sent in the transaction identified
  55. by `transaction_hash_val` to the nth receiver (n=output_idx) have not been
  56. spent before the given block.
  57. :param transaction_input: The coin to check.
  58. :param prev_block: The youngest block in this block chain that should be considered for
  59. the validation.
  60. """
  61. if prev_block is None or prev_block is self.head:
  62. return transaction_input in self.unspent_coins
  63. idx = self.block_indices[prev_block.hash]
  64. assert self.blocks[idx] is prev_block
  65. for block in self.blocks[idx::-1]:
  66. for trans in block.transactions:
  67. if transaction_input in trans.inputs:
  68. return False
  69. return True
  70. def get_block_by_hash(self, hash_val: bytes) -> 'Optional[Block]':
  71. """ Returns a block by its hash value, or None if it cannot be found. """
  72. idx = self.block_indices.get(hash_val)
  73. if idx is None:
  74. return None
  75. return self.blocks[idx]
  76. def verify_all_transactions(self) -> bool:
  77. """ Verify the transactions in all blocks in this chain. """
  78. for block in self.blocks:
  79. if not block.verify_transactions(self):
  80. return False
  81. return True
  82. def verify_all(self) -> bool:
  83. """ Verify all blocks in this block chain. """
  84. return all(block.verify(self) for block in self.blocks)
  85. @property
  86. def head(self):
  87. """
  88. The head of this block chain.
  89. :rtype: Block
  90. """
  91. return self.blocks[-1]
  92. def compute_difficulty(self, prev_block: 'Block'=None) -> int:
  93. """ Compute the desired difficulty for the block after `prev_block` (defaults to `head`). """
  94. target_timedelta = Fraction(int(DIFFICULTY_TARGET_TIMEDELTA.total_seconds() * 1000 * 1000))
  95. if prev_block is None:
  96. prev_block = self.head
  97. block_idx = self.block_indices[prev_block.hash] + 1
  98. if block_idx % DIFFICULTY_BLOCK_INTERVAL != 0:
  99. return prev_block.difficulty
  100. duration = prev_block.time - self.blocks[block_idx - DIFFICULTY_BLOCK_INTERVAL].time
  101. duration = Fraction(int(duration.total_seconds() * 1000 * 1000))
  102. prev_difficulty = Fraction(prev_block.difficulty)
  103. hash_rate = prev_difficulty * DIFFICULTY_BLOCK_INTERVAL / duration
  104. new_difficulty = hash_rate * target_timedelta / DIFFICULTY_BLOCK_INTERVAL
  105. # the genesis difficulty was very easy, dropping below it means there was a pause
  106. # in mining, so let's start with a new difficulty!
  107. if new_difficulty < self.blocks[0].difficulty:
  108. new_difficulty = self.blocks[0].difficulty
  109. return int(new_difficulty)
  110. def compute_blockreward(self, prev_block: 'Block') -> int:
  111. """ Compute the block reward that is expected for the block following `prev_block`. """
  112. assert prev_block is not None
  113. reward = 1000
  114. l = self.block_indices[prev_block.hash]
  115. while l > 0:
  116. l = l - 10000
  117. reward = reward // 2
  118. return reward
  119. from .block import Block, GENESIS_BLOCK, GENESIS_BLOCK_HASH
  120. from .transaction import TransactionInput, Transaction