|
@@ -9,7 +9,7 @@ from .proof_of_work import DIFFICULTY_BLOCK_INTERVAL, DIFFICULTY_TARGET_TIMEDELT
|
|
|
|
|
|
|
|
class Blockchain:
|
|
class Blockchain:
|
|
|
"""
|
|
"""
|
|
|
- A block chain: a ordrered list of blocks.
|
|
|
|
|
|
|
+ A block chain: a ordered list of valid blocks.
|
|
|
|
|
|
|
|
:ivar blocks: The blocks in this chain, oldest first.
|
|
:ivar blocks: The blocks in this chain, oldest first.
|
|
|
:vartype blocks: List[Block]
|
|
:vartype blocks: List[Block]
|
|
@@ -29,13 +29,19 @@ class Blockchain:
|
|
|
self.unspent_coins = {}
|
|
self.unspent_coins = {}
|
|
|
|
|
|
|
|
def try_append(self, block: 'Block') -> 'Optional[Blockchain]':
|
|
def try_append(self, block: 'Block') -> 'Optional[Blockchain]':
|
|
|
|
|
+ """
|
|
|
|
|
+ If `block` is valid on top of this chain, returns a new block chain including that block.
|
|
|
|
|
+ Otherwise, it returns `None`.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ if not block.verify(self):
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
unspent_coins = self.unspent_coins.copy()
|
|
unspent_coins = self.unspent_coins.copy()
|
|
|
|
|
|
|
|
for t in block.transactions:
|
|
for t in block.transactions:
|
|
|
for inp in t.inputs:
|
|
for inp in t.inputs:
|
|
|
- if inp not in unspent_coins:
|
|
|
|
|
- logging.warning("Aborting computation of unspent transactions because a transaction spent an unavailable coin.")
|
|
|
|
|
- return None
|
|
|
|
|
|
|
+ assert inp in unspent_coins, "Aborting computation of unspent transactions because a transaction spent an unavailable coin."
|
|
|
del unspent_coins[inp]
|
|
del unspent_coins[inp]
|
|
|
for i, target in enumerate(t.targets):
|
|
for i, target in enumerate(t.targets):
|
|
|
unspent_coins[TransactionInput(t.get_hash(), i)] = target
|
|
unspent_coins[TransactionInput(t.get_hash(), i)] = target
|
|
@@ -46,42 +52,8 @@ class Blockchain:
|
|
|
chain.block_indices = self.block_indices.copy()
|
|
chain.block_indices = self.block_indices.copy()
|
|
|
chain.block_indices[block.hash] = len(self.blocks)
|
|
chain.block_indices[block.hash] = len(self.blocks)
|
|
|
|
|
|
|
|
- if not block.verify(chain):
|
|
|
|
|
- return None
|
|
|
|
|
-
|
|
|
|
|
return chain
|
|
return chain
|
|
|
|
|
|
|
|
- def get_transaction_by_hash(self, hash_val: bytes) -> 'Optional[Transaction]':
|
|
|
|
|
- """ Returns a transaction from its hash, or None. """
|
|
|
|
|
- # TODO: build a hash table with this info
|
|
|
|
|
- for block in self.blocks[::-1]:
|
|
|
|
|
- for trans in block.transactions:
|
|
|
|
|
- if trans.get_hash() == hash_val:
|
|
|
|
|
- return trans
|
|
|
|
|
- return None
|
|
|
|
|
-
|
|
|
|
|
- def is_coin_still_valid(self, transaction_input: 'TransactionInput',
|
|
|
|
|
- prev_block: 'Block'=None) -> bool:
|
|
|
|
|
- """
|
|
|
|
|
- Validates that the coins that were sent in the transaction identified
|
|
|
|
|
- by `transaction_hash_val` to the nth receiver (n=output_idx) have not been
|
|
|
|
|
- spent before the given block.
|
|
|
|
|
-
|
|
|
|
|
- :param transaction_input: The coin to check.
|
|
|
|
|
- :param prev_block: The youngest block in this block chain that should be considered for
|
|
|
|
|
- the validation.
|
|
|
|
|
- """
|
|
|
|
|
- if prev_block is None or prev_block is self.head:
|
|
|
|
|
- return transaction_input in self.unspent_coins
|
|
|
|
|
-
|
|
|
|
|
- idx = self.block_indices[prev_block.hash]
|
|
|
|
|
- assert self.blocks[idx] is prev_block
|
|
|
|
|
- for block in self.blocks[idx::-1]:
|
|
|
|
|
- for trans in block.transactions:
|
|
|
|
|
- if transaction_input in trans.inputs:
|
|
|
|
|
- return False
|
|
|
|
|
- return True
|
|
|
|
|
-
|
|
|
|
|
def get_block_by_hash(self, hash_val: bytes) -> 'Optional[Block]':
|
|
def get_block_by_hash(self, hash_val: bytes) -> 'Optional[Block]':
|
|
|
""" Returns a block by its hash value, or None if it cannot be found. """
|
|
""" Returns a block by its hash value, or None if it cannot be found. """
|
|
|
idx = self.block_indices.get(hash_val)
|
|
idx = self.block_indices.get(hash_val)
|
|
@@ -89,17 +61,6 @@ class Blockchain:
|
|
|
return None
|
|
return None
|
|
|
return self.blocks[idx]
|
|
return self.blocks[idx]
|
|
|
|
|
|
|
|
- def verify_all_transactions(self) -> bool:
|
|
|
|
|
- """ Verify the transactions in all blocks in this chain. """
|
|
|
|
|
- for block in self.blocks:
|
|
|
|
|
- if not block.verify_transactions(self):
|
|
|
|
|
- return False
|
|
|
|
|
- return True
|
|
|
|
|
-
|
|
|
|
|
- def verify_all(self) -> bool:
|
|
|
|
|
- """ Verify all blocks in this block chain. """
|
|
|
|
|
- return all(block.verify(self) for block in self.blocks)
|
|
|
|
|
-
|
|
|
|
|
@property
|
|
@property
|
|
|
def head(self):
|
|
def head(self):
|
|
|
"""
|
|
"""
|
|
@@ -109,21 +70,18 @@ class Blockchain:
|
|
|
"""
|
|
"""
|
|
|
return self.blocks[-1]
|
|
return self.blocks[-1]
|
|
|
|
|
|
|
|
- def compute_difficulty(self, prev_block: 'Block'=None) -> int:
|
|
|
|
|
- """ Compute the desired difficulty for the block after `prev_block` (defaults to `head`). """
|
|
|
|
|
|
|
+ def compute_difficulty_next_block(self) -> int:
|
|
|
|
|
+ """ Compute the desired difficulty for the block following this chain's `head`. """
|
|
|
target_timedelta = Fraction(int(DIFFICULTY_TARGET_TIMEDELTA.total_seconds() * 1000 * 1000))
|
|
target_timedelta = Fraction(int(DIFFICULTY_TARGET_TIMEDELTA.total_seconds() * 1000 * 1000))
|
|
|
|
|
|
|
|
- if prev_block is None:
|
|
|
|
|
- prev_block = self.head
|
|
|
|
|
-
|
|
|
|
|
- block_idx = self.block_indices[prev_block.hash] + 1
|
|
|
|
|
|
|
+ block_idx = len(self.blocks)
|
|
|
if block_idx % DIFFICULTY_BLOCK_INTERVAL != 0:
|
|
if block_idx % DIFFICULTY_BLOCK_INTERVAL != 0:
|
|
|
- return prev_block.difficulty
|
|
|
|
|
|
|
+ return self.head.difficulty
|
|
|
|
|
|
|
|
- duration = prev_block.time - self.blocks[block_idx - DIFFICULTY_BLOCK_INTERVAL].time
|
|
|
|
|
|
|
+ duration = self.head.time - self.blocks[block_idx - DIFFICULTY_BLOCK_INTERVAL].time
|
|
|
duration = Fraction(int(duration.total_seconds() * 1000 * 1000))
|
|
duration = Fraction(int(duration.total_seconds() * 1000 * 1000))
|
|
|
|
|
|
|
|
- prev_difficulty = Fraction(prev_block.difficulty)
|
|
|
|
|
|
|
+ prev_difficulty = Fraction(self.head.difficulty)
|
|
|
hash_rate = prev_difficulty * DIFFICULTY_BLOCK_INTERVAL / duration
|
|
hash_rate = prev_difficulty * DIFFICULTY_BLOCK_INTERVAL / duration
|
|
|
|
|
|
|
|
new_difficulty = hash_rate * target_timedelta / DIFFICULTY_BLOCK_INTERVAL
|
|
new_difficulty = hash_rate * target_timedelta / DIFFICULTY_BLOCK_INTERVAL
|
|
@@ -135,15 +93,11 @@ class Blockchain:
|
|
|
|
|
|
|
|
return int(new_difficulty)
|
|
return int(new_difficulty)
|
|
|
|
|
|
|
|
- def compute_blockreward(self, prev_block: 'Block'=None) -> int:
|
|
|
|
|
- """ Compute the block reward that is expected for the block following `prev_block`. """
|
|
|
|
|
- # TODO: remove prev_block param
|
|
|
|
|
- if prev_block is None:
|
|
|
|
|
- prev_block = self.head
|
|
|
|
|
- assert prev_block is not None
|
|
|
|
|
|
|
+ def compute_blockreward_next_block(self) -> int:
|
|
|
|
|
+ """ Compute the block reward that is expected for the block following this chain's `head`. """
|
|
|
reward = 1000
|
|
reward = 1000
|
|
|
- l = self.block_indices[prev_block.hash]
|
|
|
|
|
- while l > 0:
|
|
|
|
|
|
|
+ l = len(self.blocks) - 1
|
|
|
|
|
+ while l > 0 and reward > 0:
|
|
|
l = l - 10000
|
|
l = l - 10000
|
|
|
reward = reward // 2
|
|
reward = reward // 2
|
|
|
return reward
|
|
return reward
|