stakeholder.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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.vrf import verify, VRF
  7. from ouroboros.utils import *
  8. from ouroboros.logger import Logger
  9. from ouroboros.consts import *
  10. from ouroboros.data import Data, Transaction, Item
  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=1
  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.current_epoch = None
  32. self.am_corrupt=False
  33. #
  34. self.blockchain=None
  35. #
  36. self.data = Data()
  37. #verifiable fingerprint for a stakeholder taking advantage of public sig, vrf
  38. self.id = sign_message(self.passwd, self.sig_sk, str(self.vrf_pk))
  39. def receive_tx(self, tx):
  40. #TODO validate trx
  41. self.data.append(tx)
  42. def broadcast_tx(self, tx):
  43. self.data.append(tx)
  44. self.env.broadcast_tx(tx)
  45. @property
  46. def vrf_pk(self):
  47. return self.__vrf_pk
  48. @property
  49. def vrf_base(self):
  50. return self.__vrf_base
  51. def __repr__(self):
  52. buff=''
  53. if self.env.is_current_leader(self.id):
  54. buff = f"\tleader {self.id} with stake:{self.stake}\nsig_pk: {self.sig_pk}"
  55. elif self.env.is_current_endorser(self.id):
  56. buff = f"\tendorser {self.id} with stake:{self.stake}\nsig_pk: {self.sig_pk}"
  57. else:
  58. buff = f"\thonest committee memeber {self.id} with stake:{self.stake}\nsig_pk: {self.sig_pk}"
  59. return buff
  60. def __call__(self, env):
  61. self.env=env
  62. self.log = Logger(self, self.env.genesis_time)
  63. self.blockchain = Blockchain(self.epoch_length, self.env.genesis_time)
  64. #self.beacon = TrustedBeacon(self, self.vrf, self.epoch_length, self.env.genesis_time)
  65. #self.current_slot_uid = self.beacon.slot
  66. @property
  67. def epoch_index(self):
  68. return round(self.current_slot_uid/self.epoch_length)
  69. def end_slot(self):
  70. # start new transactions
  71. self.data = Data()
  72. def add_epoch(self):
  73. self.blockchain.append(self.current_epoch)
  74. self.update_stake()
  75. def new_epoch(self, current_epoch):
  76. if self.current_epoch!=None:
  77. self.add_epoch()
  78. self.current_epoch = current_epoch
  79. def new_slot(self, slot, sigma, proof):
  80. self.log.highlight("<new_slot> start")
  81. vrf_pk = self.env.prev_leader_vrf_pk()
  82. vrf_g = self.env.prev_leader_vrf_g()
  83. self.log.highlight(f"verifying slot leader with pk: {str(vrf_pk)}, : {str(vrf_g)}")
  84. self.log.highlight(f"verifying slot {slot}\nsigma {sigma}\nproof {proof}\npk {vrf_pk} \nbase {vrf_g}")
  85. if not verify(slot, sigma, proof, vrf_pk, vrf_g):
  86. #TODO the leader is corrupted, action to be taken against the corrupt stakeholder
  87. #in this case this slot is empty
  88. self.log.warn(f"<new_slot> leader verification fails")
  89. self.current_block=EmptyBlock(self.env.genesis_time)
  90. self.current_epoch.add_block(self.current_block)
  91. return
  92. self.current_slot_uid = slot
  93. if self.current_slot_uid%self.epoch_length!=0:
  94. prev_blk = self.blockchain[-1] if len(self.blockchain)>0 else EmptyBlock(self.env.genesis_time)
  95. self.current_block=Block(prev_blk, self.data, self.current_slot_uid, self.env.genesis_time)
  96. self.current_epoch.add_block(self.current_block)
  97. if self.env.is_current_leader(self.id):
  98. self.log.highlight(f"{str(self)} is broadcasting block")
  99. self.broadcast_block()
  100. elif self.env.is_current_endorser(self.id):
  101. self.log.highlight(f"{str(self)} is endorsing block")
  102. self.endorse_block()
  103. def update_stake(self):
  104. if len(self.blockchain)==0:
  105. return
  106. epoch = self.blockchain[-1]
  107. pall = epoch.coffee()
  108. leader_cnt=0
  109. endorser_cnt=0
  110. for blk in epoch:
  111. if blk.leader_id==self.id:
  112. leader_cnt+=1
  113. elif blk.endorser_id==self.id:
  114. endorser_cnt+=1
  115. self.stake += (self.env.beta * (endorser_cnt/self.env.endorser_len) + \
  116. (1-self.env.beta) * (leader_cnt/self.env.epoch_length)) * pall
  117. def set_corrupt(self):
  118. self.am_corrupt=False
  119. '''
  120. only leader can broadcast block
  121. '''
  122. def broadcast_block(self):
  123. if not self.env.is_current_leader(self.id):
  124. return
  125. self.current_block.set_leader(self.id)
  126. self.log.highlight("broadcasting block")
  127. assert self.env.is_current_leader(self.id) and self.current_block is not None
  128. signed_block=None
  129. #TODO should wait for l slot until block is endorsed
  130. endorsing_cnt=10
  131. #TODO (rev)
  132. while not self.current_block.endorsed or self.blockchain[self.current_slot_uid]:
  133. time.sleep(1)
  134. self.log.info("...waiting for endorsment..")
  135. endorsing_cnt-=1
  136. '''
  137. if not self.current_block.endorsed:
  138. self.log.warn("failure endorsing the block...")
  139. self.current_block = EmptyBlock(self.env.genesis_time)
  140. '''
  141. signed_block = sign_message(self.passwd, self.sig_sk, self.current_block)
  142. self.env.broadcast_block(self.current_block, signed_block, self.current_slot_uid)
  143. @property
  144. def current_slot(self):
  145. return self.env.beacon.current_slot
  146. '''
  147. only endorser can broadcast block
  148. '''
  149. def endorse_block(self):
  150. assert self.env.is_current_endorser(self.id)
  151. assert self.env.endorser_sig_pk(self.env.beacon.slot) == self.sig_pk, f' assertion failed for beacon slot {self.env.beacon.slot}, current_slot {self.current_slot}, lhs: {self.env.endorser_sig_pk(self.env.beacon.slot)},\nrhs: {self.sig_pk}\nleader\endorser ids {self.env.slot_committee[self.env.beacon.slot][0]}/{self.env.slot_committee[self.env.beacon.slot][1]}'
  152. #assert self.env.endorser_sig_pk(self.current_slot) == self.sig_pk, f'lsh: {self.env.endorser_sig_pk(self.current_slot)},\nrhs: {self.sig_pk}'
  153. self.current_block.set_endorser(self.id)
  154. self.log.info(f"endorsing block for current_leader_id: {self.env.current_leader_id}")
  155. if not self.env.is_current_endorser(self.id):
  156. self.log.warn("not endorser")
  157. return
  158. assert self.current_block is not None
  159. sig = sign_message(self.passwd, self.sig_sk, self.current_block)
  160. self.log.highlight(f'block to be endorsed {str(self.current_block)}')
  161. self.log.highlight(f'block to be endorsed has slot_uid: {self.current_slot_uid}')
  162. self.log.highlight(f'block to be endorsed has sig_pk: {str(self.sig_pk)}')
  163. self.env.endorse_block(sig, self.current_slot_uid)
  164. def __get_blk(self, blk_uid):
  165. assert(blk_uid>=0)
  166. stashed=True
  167. cur_blk = self.current_block
  168. if blk_uid < len(self.blockchain):
  169. #TODO this assumes synced blockchain
  170. cur_blk = self.blockchain[blk_uid]
  171. self.log.warn(f"current block from blockchain: {(cur_blk)}")
  172. stashed=False
  173. self.log.info(f"current block : {str(cur_blk)}\tblock uid: {blk_uid}\tstashed: {stashed}")
  174. if cur_blk is None:
  175. self.log.warn(f"blk uid {blk_uid}, blockchain length: {len(self.blockchain)}")
  176. self.log.warn(f"requested block is None\nblk_uid: {blk_uid}, blockchain: {self.blockchain}")
  177. 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}')
  178. while cur_blk is None:
  179. self.log.info("waiting for start of slot/epoch...")
  180. time.sleep(1)
  181. return cur_blk, stashed
  182. def receive_block(self, signed_block, endorser_sig, blk_uid):
  183. self.log.highlight("receiving block")
  184. cur_blk, stashed = self.__get_blk(blk_uid)
  185. #TODO to consider deley should retrive leader_pk of corresponding blk_uid
  186. self.log.highlight(f'receiving block {str(cur_blk)}')
  187. self.log.highlight(f'receiving block has slot_uid: {self.current_slot_uid}')
  188. self.log.highlight(f'receiving block has sig_pk: {self.env.current_endorser_sig_pk}')
  189. blk_verified = verify_signature(self.env.current_leader_sig_pk, cur_blk, signed_block)
  190. self.log.info("endorser sig_pk {self.env.current_endorser_sig_pk}, cur_blk: {cur_blk}, endorser_sig: {endorser_sig}")
  191. blk_edrs_verified = verify_signature(self.env.current_endorser_sig_pk, cur_blk, endorser_sig)
  192. if blk_verified and blk_edrs_verified:
  193. if stashed:
  194. self.current_epoch.add_block(cur_blk)
  195. else:
  196. if not blk_verified:
  197. self.log.warn("block verification failed")
  198. elif not blk_edrs_verified:
  199. self.log.warn("block endorsing verification failed")
  200. self.env.corrupt_blk()
  201. def confirm_endorsing(self, endorser_sig, blk_uid, slot):
  202. self.log.highlight(f"confirming block with epoch slot id {blk_uid}")
  203. confirmed = False
  204. cur_blk, _ = self.__get_blk(blk_uid)
  205. self.log.highlight(f'confirming endorsed block {str(cur_blk)}')
  206. self.log.highlight(f'confirming endorsed has slot: {slot} epoch slot_uid: {self.current_slot_uid}')
  207. self.log.highlight(f'confirming endorsed has sig_pk: {self.env.current_endorser_sig_pk}')
  208. endorser_sig_pk = self.env.endorser_sig_pk(self.env.beacon.slot)
  209. self.log.highlight(f'confirming endorsed sig pk: {endorser_sig_pk}')
  210. if verify_signature(endorser_sig_pk, cur_blk, endorser_sig):
  211. if self.current_slot_uid==self.env.current_slot:
  212. self.log.highlight("set current block as endorsed")
  213. self.current_block.set_endorser(self.env.current_endorser_uid)
  214. self.current_block.set_endorsed()
  215. else:
  216. self.log.highlight(f"set delayed blockchain block with uid: {blk_uid} as endorsed")
  217. self.blockchain[blk_uid].set_endorser(self.env.current_endorser_uid)
  218. self.blockchain[blk_uid].set_endorsed()
  219. confirmed=True
  220. else:
  221. self.log.warn(f"confirmed enderser signature failure for pk: {str(endorser_sig_pk)} on block {str(cur_blk)} of signature {str(endorser_sig)}")
  222. confirmed=False
  223. return confirmed