node.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import copy
  2. import utils
  3. from block import Block
  4. from blockchain import Blockchain
  5. from vote import Vote
  6. class Node:
  7. ''' This class represents a protocol node.
  8. Each node is numbered and has a secret-public keys pair, to sign messages.
  9. Nodes hold a set of Blockchains(some of which are not notarized)
  10. and a set of unconfirmed pending transactions.
  11. All nodes have syncronized clocks, using GST approach. '''
  12. def __init__(self, id, clock, password, init_block):
  13. self.id = id
  14. self.clock = clock # Clock syncronization to be implemented.
  15. self.password = password
  16. self.private_key, self.public_key = utils.generate_keys(self.password)
  17. self.canonical_blockchain = Blockchain(init_block)
  18. self.node_blockchains = []
  19. self.unconfirmed_transactions = []
  20. def __repr__(self):
  21. return "Node=[id={0}]".format(self.id)
  22. def output(self):
  23. ''' A nodes output is the finalized (canonical) blockchain they hold. '''
  24. return self.canonical_blockchain
  25. def receive_transaction(self, transaction):
  26. ''' Node retreives a transaction and append it to the unconfirmed transactions list.
  27. Additional validity rules must be defined by the protocol for its blockchain data structure. '''
  28. self.unconfirmed_transactions.append(transaction)
  29. def broadcast_transaction(self, nodes, transaction):
  30. ''' Node broadcast a transaction to provided nodes list. '''
  31. for node in nodes:
  32. node.receive_transaction(transaction)
  33. def find_longest_notarized_chain(self):
  34. ''' Finds the longest fully notarized blockchain the node holds.'''
  35. longest_notarized_chain = self.canonical_blockchain
  36. length = 0
  37. for blockchain in self.node_blockchains:
  38. if blockchain.is_notarized() and len(blockchain.blocks) > length:
  39. longest_notarized_chain = blockchain
  40. length = len(blockchain.blocks)
  41. return longest_notarized_chain
  42. def propose_block(self, epoch, nodes):
  43. ''' Node generates a block for that epoch, containing all uncorfirmed transactions.
  44. Block extends the longest notarized blockchain the node holds.
  45. Node signs the block, and broadcasts it to rest nodes. '''
  46. longest_notarized_chain = self.find_longest_notarized_chain()
  47. proposed_block = copy.deepcopy(Block(
  48. hash(longest_notarized_chain.blocks[-1]), epoch, self.unconfirmed_transactions))
  49. signed_proposed_block = copy.deepcopy(
  50. utils.sign_message(
  51. self.password,
  52. self.private_key,
  53. proposed_block))
  54. for node in nodes:
  55. node.receive_proposed_block(self.public_key, copy.deepcopy(
  56. proposed_block), copy.deepcopy(signed_proposed_block), nodes)
  57. def find_extended_blockchain(self, block):
  58. ''' For a provided block, node searches for any blockchain that it extends.
  59. If a fork blockchain is not found, block is tested against the canonical blockchain. '''
  60. for blockchain in self.node_blockchains:
  61. if block.h == hash(
  62. blockchain.blocks[-1]) and block.e > blockchain.blocks[-1].e:
  63. return blockchain
  64. if block.h == hash(
  65. self.canonical_blockchain.blocks[-1]) and block.e > self.canonical_blockchain.blocks[-1].e:
  66. return self.canonical_blockchain
  67. return None
  68. def find_block(self, vote_block):
  69. ''' Node searches it the blockchains it holds for provided block. '''
  70. for blockchain in self.node_blockchains:
  71. for block in reversed(blockchain.blocks):
  72. if vote_block == block:
  73. return block
  74. for block in reversed(self.canonical_blockchain.blocks):
  75. if vote_block == block:
  76. return block
  77. return None
  78. def extends_notarized_blockchain(self, blockchain):
  79. ''' Node verifies if provided blockchain is notarized excluding the last block. '''
  80. for block in blockchain.blocks[:-1]:
  81. if not block.notarized:
  82. return False
  83. return True
  84. def vote_block(self, block, nodes):
  85. ''' Given a block, node finds which blockchain it extends.
  86. If block extends the canonical blockchain, a new fork blockchain is created.
  87. Node votes on the block, only if it extends the longest notarized chain it has seen. '''
  88. blockchain = self.find_extended_blockchain(block)
  89. if not blockchain or blockchain is self.canonical_blockchain:
  90. blockchain = Blockchain(copy.deepcopy(block))
  91. self.node_blockchains.append(blockchain)
  92. else:
  93. blockchain.add_block(copy.deepcopy(block))
  94. if self.extends_notarized_blockchain(blockchain):
  95. signed_block = utils.sign_message(
  96. self.password, self.private_key, block)
  97. vote = Vote(signed_block, block, self.id)
  98. for node in nodes:
  99. node.receive_vote(self.public_key, vote, nodes)
  100. def receive_proposed_block(
  101. self,
  102. leader_public_key,
  103. round_block,
  104. signed_round_block,
  105. nodes):
  106. ''' Node receives the proposed block, verifies its sender(epoch leader), and proceeds with voting on it. '''
  107. assert(
  108. utils.verify_signature(
  109. leader_public_key,
  110. round_block,
  111. signed_round_block))
  112. self.vote_block(round_block, nodes)
  113. def check_blockchain_finalization(self, block):
  114. ''' For the provided block, node checks if the blockchain it extends can be finalized.
  115. Consensus finalization logic: If node has observed the notarization of 3 consecutive
  116. blocks in a fork chain, it finalizes (appends to canonical blockchain) all blocks up to the middle block.
  117. When fork chain blocks are finalized, rest fork chains not starting by those blocks are removed. '''
  118. if block in self.canonical_blockchain.blocks:
  119. blockchain = self.canonical_blockchain
  120. else:
  121. for node_blockchain in self.node_blockchains:
  122. if block in node_blockchain.blocks:
  123. blockchain = node_blockchain
  124. if blockchain and len(blockchain) > 2:
  125. if blockchain.blocks[-3].notarized and blockchain.blocks[-2].notarized:
  126. for block in blockchain.blocks[:-1]:
  127. block.finalized = True
  128. self.canonical_blockchain.blocks.append(block)
  129. for node_blockchain in self.node_blockchains:
  130. if node_blockchain.blocks[-len(blockchain.blocks[:-1]):] != blockchain.blocks[:-1]:
  131. self.node_blockchains.remove(node_blockchain)
  132. else:
  133. del node_blockchain[-len(blockchain.blocks[:-1]):]
  134. def receive_vote(self, node_public_key, vote, nodes):
  135. ''' Node receives a vote for a block.
  136. First, sender is verified using their public key.
  137. Block is searched in nodes blockchains.
  138. If the vote wasn't received before, it is appended to block votes list.
  139. When a node sees 2n/3 votes for a block it notarizes it.
  140. When a block gets notarized, the transactions it contains are removed from
  141. nodes unconfirmed transactions list.
  142. Finally, we check if the notarization of the block can finalize parent blocks
  143. in its blockchain. '''
  144. assert(utils.verify_signature(node_public_key, vote.block, vote.vote))
  145. vote_block = self.find_block(vote.block)
  146. if not vote_block:
  147. self.vote_block(copy.deepcopy(vote.block), nodes)
  148. return
  149. if vote not in vote_block.votes:
  150. vote_block.votes.append(vote)
  151. if not vote_block.notarized and len(vote_block.votes) > (2 * len(nodes) / 3):
  152. vote_block.notarized = True
  153. for transaction in vote_block.txs:
  154. if transaction in self.unconfirmed_transactions:
  155. self.unconfirmed_transactions.remove(transaction)
  156. self.check_blockchain_finalization(vote_block)