stakeholder.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. from copy import deepcopy
  2. import time
  3. from ouroboros.block import Block, GensisBlock, EmptyBlock
  4. from ouroboros.blockchain import Blockchain
  5. from ouroboros.epoch import Epoch
  6. from ouroboros.beacon import TrustedBeacon
  7. from ouroboros.vrf import verify, VRF
  8. from ouroboros.utils import *
  9. from ouroboros.logger import Logger
  10. from ouroboros.consts import *
  11. '''
  12. \class Stakeholder
  13. '''
  14. class Stakeholder(object):
  15. def __init__(self, epoch_length, passwd='password'):
  16. #TODO (fix) remove redundant variables reley on environment
  17. self.passwd=passwd
  18. self.stake=0
  19. self.epoch_length=epoch_length
  20. self.vrf = VRF(self.passwd)
  21. #verification keys
  22. self.__vrf_pk = self.vrf.pk
  23. self.__vrf_sk = self.vrf.sk
  24. self.__vrf_base = self.vrf.g
  25. #signature keys
  26. sig_sk, sig_pk = generate_sig_keys(self.passwd)
  27. self.sig_sk = sig_sk
  28. self.sig_pk = sig_pk
  29. #
  30. self.current_block = None
  31. self.uncommited_tx=''
  32. self.tx=''
  33. self.current_epoch = None
  34. self.am_current_leader=False
  35. self.am_current_endorser=False
  36. self.am_corrupt=False
  37. #
  38. self.blockchain=None
  39. @property
  40. def is_leader(self):
  41. return self.am_current_leader
  42. @property
  43. def vrf_pk(self):
  44. return self.__vrf_pk
  45. @property
  46. def vrf_base(self):
  47. return self.__vrf_base
  48. def __repr__(self):
  49. buff=''
  50. if self.am_current_leader:
  51. buff = f"\tleader {(hash(self.passwd))} with stake:{self.stake}\nsig_sk: {self.sig_pk}"
  52. elif self.am_current_endorser:
  53. buff = f"\tendorser {(hash(self.passwd))} with stake:{self.stake}\nsig_sk: {self.sig_pk}"
  54. else:
  55. buff = f"\thonest committee memeber {(hash(self.passwd))} with stake:{self.stake}\nsig_sk: {self.sig_pk}"
  56. return buff
  57. def __call__(self, env):
  58. self.env=env
  59. self.log = Logger(self, self.env.genesis_time)
  60. self.blockchain = Blockchain(self.epoch_length, self.env.genesis_time)
  61. self.beacon = TrustedBeacon(self, self.vrf, self.epoch_length, self.env.genesis_time)
  62. self.current_slot_uid = self.beacon.slot
  63. def start(self):
  64. self.log.info("thread [started]")
  65. self.beacon.start()
  66. self.log.info("thread [ended]")
  67. @property
  68. def epoch_index(self):
  69. return round(self.current_slot_uid/self.epoch_length)
  70. def __gen_genesis_epoch(self):
  71. '''
  72. '''
  73. self.tx = self.env.get_genesis_data()
  74. self.tx[TX]=self.uncommited_tx
  75. self.uncommited_tx=''
  76. self.current_block=GensisBlock(self.current_block, self.tx, self.current_slot_uid, self.env.genesis_time)
  77. assert self.current_block is not None
  78. self.current_epoch=Epoch(self.current_block, self.epoch_length, self.epoch_index, self.env.genesis_time)
  79. '''
  80. it's a callback function, and called by the diffuser
  81. '''
  82. def new_epoch(self, slot, sigmas, proofs):
  83. '''
  84. #TODO implement praos
  85. for this implementation we assume synchrony,
  86. and at this point, and no delay is considered (for simplicity)
  87. '''
  88. self.log.highlight("<new_epoch> start")
  89. if self.am_current_leader:
  90. self.env.new_epoch(slot, sigmas, proofs)
  91. self.current_slot_uid = slot
  92. #kickoff gensis block
  93. # add old epoch to the ledger
  94. if self.current_slot_uid > 1 and self.current_epoch!=None and len(self.current_epoch)>0:
  95. self.blockchain.add_epoch(self.current_epoch)
  96. #if leader, you need to broadcast the block
  97. while not self.env.epoch_inited:
  98. self.log.info("pending epoch initialization")
  99. time.sleep(1)
  100. self.__gen_genesis_epoch()
  101. if self.am_current_leader:
  102. self.broadcast_block()
  103. self.end_leadership()
  104. elif self.am_current_endorser:
  105. self.endorse_block()
  106. self.end_endorsing()
  107. '''
  108. it's a callback function, and called by the diffuser
  109. '''
  110. #def new_slot(self, slot, sigma, proof):
  111. def new_slot(self, slot):
  112. '''
  113. #TODO implement praos
  114. for this implementation we assume synchrony,
  115. and at this point, and no delay is considered (for simplicity)
  116. '''
  117. self.log.highlight("<new_slot> start")
  118. self.env.new_slot(slot)
  119. '''
  120. vrf_pk = self.env.current_leader_vrf_pk
  121. vrf_g = self.env.current_leader_vrf_g
  122. if not verify(slot, sigma, proof, vrf_pk,vrf_g) :
  123. #TODO the leader is corrupted, action to be taken against the corrupt stakeholder
  124. #in this case this slot is empty
  125. self.log.warn(f"<new_slot> leader verification fails")
  126. self.current_block=EmptyBlock(self.env.genesis_time)
  127. if self.current_epoch==None:
  128. self.__gen_genesis_epoch()
  129. self.current_epoch.add_block(self.current_block)
  130. return
  131. '''
  132. if self.current_epoch==None:
  133. self.log.warn(f"<new_slot> current_epoch is None!")
  134. self.__gen_genesis_epoch()
  135. self.current_slot_uid = slot
  136. prev_blk = self.blockchain[-1] if len(self.blockchain)>0 else EmptyBlock(self.env.genesis_time)
  137. self.current_block=Block(prev_blk, self.tx, self.current_slot_uid, self.env.genesis_time)
  138. self.current_epoch.add_block(self.current_block)
  139. if self.am_current_leader:
  140. self.log.highlight(f"{str(self)} is broadcasting block")
  141. self.broadcast_block()
  142. self.end_leadership()
  143. elif self.am_current_endorser:
  144. self.log.highlight(f"{str(self)} is endorsing block")
  145. self.endorse_block()
  146. self.end_endorsing()
  147. def end_leadership(self):
  148. self.log.info(f"stakeholder:{str(self)} ending leadership for slot{self.current_slot_uid}")
  149. self.am_current_leader=False
  150. def end_endorsing(self):
  151. self.log.info(f"stakeholder:{str(self)} ending endorsing for slot{self.current_slot_uid}")
  152. self.am_current_endorser=False
  153. def set_leader(self):
  154. self.am_current_leader=True
  155. def set_endorser(self):
  156. self.am_current_endorser=True
  157. def set_corrupt(self):
  158. self.am_corrupt=False
  159. def broadcast_block(self):
  160. self.log.highlight("broadcasting block")
  161. assert self.am_current_leader and self.current_block is not None
  162. signed_block=None
  163. #TODO should wait for l slot until block is endorsed
  164. endorsing_cnt=10
  165. while not self.current_block.endorsed:
  166. time.sleep(1)
  167. self.log.info("...waiting for endorsment..")
  168. endorsing_cnt-=1
  169. if not self.current_block.endorsed:
  170. self.log.warn("failure endorsing the block...")
  171. if not self.current_block.endorsed:
  172. self.current_block = EmptyBlock(self.env.genesis_time)
  173. signed_block = sign_message(self.passwd, self.sig_sk, self.current_block)
  174. self.env.broadcast_block(signed_block, self.current_slot_uid)
  175. def endorse_block(self):
  176. if not self.am_current_endorser:
  177. return
  178. self.log.info(f"endorsing block for current_leader_id: {self.env.current_leader_id}")
  179. if not self.am_current_endorser:
  180. self.log.warn("not endorser")
  181. return
  182. assert self.current_block is not None
  183. sig = sign_message(self.passwd, self.sig_sk, self.current_block)
  184. self.log.highlight(f'block to be endorsed {str(self.current_block)}')
  185. self.log.highlight(f'block to be endorsed has slot_uid: {self.current_slot_uid}')
  186. self.log.highlight(f'block to be endorsed has sig_pk: {str(self.sig_pk)}')
  187. self.env.endorse_block(sig, self.current_slot_uid)
  188. def __get_blk(self, blk_uid):
  189. assert(blk_uid>=0)
  190. stashed=True
  191. cur_blk = self.current_block
  192. if blk_uid < len(self.blockchain):
  193. #TODO this assumes synced blockchain
  194. cur_blk = self.blockchain[blk_uid]
  195. self.log.warn(f"current block from blockchain: {(cur_blk)}")
  196. stashed=False
  197. self.log.info(f"current block : {str(cur_blk)}\tblock uid: {blk_uid}\tstashed: {stashed}")
  198. if cur_blk is None:
  199. self.log.warn(f'block is none, current block is {str(self.current_block)} and current slot {self.current_slot_uid}, current block uid {blk_uid}, env slot {self.env.current_slot}, env blk {self.env.block_id}')
  200. while cur_blk is None:
  201. self.log.info("waiting for start of slot/epoch...")
  202. time.sleep(1)
  203. return cur_blk, stashed
  204. def receive_block(self, signed_block, endorser_sig, blk_uid):
  205. self.log.highlight("receiving block")
  206. cur_blk, stashed = self.__get_blk(blk_uid)
  207. #TODO to consider deley should retrive leader_pk of corresponding blk_uid
  208. self.log.highlight(f'receiving block {str(cur_blk)}')
  209. self.log.highlight(f'receiving block has slot_uid: {self.current_slot_uid}')
  210. self.log.highlight(f'receiving block has sig_pk: {self.env.current_endorser_sig_pk}')
  211. blk_verified = verify_signature(self.env.current_leader_sig_pk, cur_blk, signed_block)
  212. self.log.info("endorser sig_pk {self.env.current_endorser_sig_pk}, cur_blk: {cur_blk}, endorser_sig: {endorser_sig}")
  213. blk_edrs_verified = verify_signature(self.env.current_endorser_sig_pk, cur_blk, endorser_sig)
  214. if blk_verified and blk_edrs_verified:
  215. if stashed:
  216. self.current_epoch.add_block(cur_blk)
  217. else:
  218. if not blk_verified:
  219. self.log.warn("block verification failed")
  220. elif not blk_edrs_verified:
  221. self.log.warn("block endorsing verification failed")
  222. self.env.corrupt_blk()
  223. def confirm_endorsing(self, endorser_sig, blk_uid, epoch_slot):
  224. self.log.highlight("receiving block")
  225. confirmed = False
  226. cur_blk, _ = self.__get_blk(blk_uid)
  227. self.log.highlight(f'confirming endorsed block {str(cur_blk)}')
  228. self.log.highlight(f'confirming endorsed has slot_uid: {self.current_slot_uid}')
  229. self.log.highlight(f'confirming endorsed has sig_pk: {self.env.current_endorser_sig_pk}')
  230. if verify_signature(self.env.endorser_sig_pk(epoch_slot), cur_blk, endorser_sig):
  231. if self.current_slot_uid==self.env.current_slot:
  232. self.current_block.set_endorsed()
  233. else:
  234. self.blockchain[blk_uid].set_endorsed()
  235. confirmed=True
  236. else:
  237. self.log.warn(f"confirmed enderser signature failure for pk: {str(self.env.current_endorser_sig_pk)} on block {str(cur_blk)} of signature {str(endorser_sig)}")
  238. confirmed=False
  239. return confirmed