stakeholder.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. from copy import deepcopy
  2. import time
  3. import numpy as np
  4. from ouroboros.block import Block, GensisBlock, EmptyBlock
  5. from ouroboros.blockchain import Blockchain
  6. from ouroboros.epoch import Epoch
  7. from ouroboros.vrf import verify, VRF
  8. from ouroboros.utils import *
  9. from ouroboros.logger import Logger
  10. from ouroboros.consts import *
  11. from ouroboros.data import Data, Transaction, Item
  12. '''
  13. \class Stakeholder
  14. '''
  15. class Stakeholder(object):
  16. def __init__(self, epoch_length, passwd='password'):
  17. #TODO (fix) remove redundant variables reley on environment
  18. self.passwd=passwd
  19. self.stake=1
  20. self.epoch_length=epoch_length
  21. self.vrf = VRF(self.passwd)
  22. #verification keys
  23. self.__vrf_pk = self.vrf.pk
  24. self.__vrf_sk = self.vrf.sk
  25. self.__vrf_base = self.vrf.g
  26. #signature keys
  27. sig_sk, sig_pk = generate_sig_keys(self.passwd)
  28. self.sig_sk = sig_sk
  29. self.sig_pk = sig_pk
  30. #
  31. self.current_block = None
  32. self.current_epoch = None
  33. self.am_corrupt=False
  34. #
  35. self.blockchain=None
  36. #
  37. self.data = Data()
  38. #verifiable fingerprint for a stakeholder taking advantage of public sig, vrf
  39. self.id = sign_message(self.passwd, self.sig_sk, str(self.vrf_pk))
  40. def receive_tx(self, tx):
  41. #TODO validate trx
  42. self.data.append(tx)
  43. def broadcast_tx(self, tx):
  44. self.data.append(tx)
  45. self.env.broadcast_tx(tx)
  46. @property
  47. def vrf_pk(self):
  48. return self.__vrf_pk
  49. @property
  50. def vrf_base(self):
  51. return self.__vrf_base
  52. @property
  53. def probability_leader_election(self):
  54. return 1 - np.pow(1 - self.env.slot_coef, self.stake)
  55. '''
  56. true random oracle from the blockchain
  57. it's simulate by a hash function that is modeled as a random oracle. This hash function
  58. is applied to the concatenation of VRF values that are inserted into each block, using values from
  59. all blocks up to and including the middle ~ 8k slots of an epoch that lasts approximately 24k slots
  60. in entirety
  61. '''
  62. def random_oracle(self):
  63. pass
  64. def __repr__(self):
  65. buff=''
  66. if self.env.is_current_leader(self.id):
  67. buff = f"\tleader {self.id} with stake:{self.stake}\nsig_pk: {self.sig_pk}"
  68. elif self.env.is_current_endorser(self.id):
  69. buff = f"\tendorser {self.id} with stake:{self.stake}\nsig_pk: {self.sig_pk}"
  70. else:
  71. buff = f"\thonest committee memeber {self.id} with stake:{self.stake}\nsig_pk: {self.sig_pk}"
  72. return buff
  73. def __call__(self, env):
  74. self.env=env
  75. self.log = Logger(self, self.env.genesis_time)
  76. self.blockchain = Blockchain(self.epoch_length, self.env.genesis_time)
  77. #self.beacon = TrustedBeacon(self, self.vrf, self.epoch_length, self.env.genesis_time)
  78. #self.current_slot_uid = self.beacon.slot
  79. @property
  80. def epoch_index(self):
  81. return round(self.current_slot_uid/self.epoch_length)
  82. def end_slot(self):
  83. # start new transactions
  84. self.data = Data()
  85. def add_epoch(self):
  86. self.blockchain.append(self.current_epoch)
  87. self.update_stake()
  88. def new_epoch(self, current_epoch):
  89. if self.current_epoch!=None:
  90. self.add_epoch()
  91. self.current_epoch = current_epoch
  92. def new_slot(self, slot, sigma, proof):
  93. self.log.highlight("<new_slot> start")
  94. vrf_pk = self.env.prev_leader_vrf_pk()
  95. vrf_g = self.env.prev_leader_vrf_g()
  96. self.log.highlight(f"verifying slot leader with pk: {str(vrf_pk)}, : {str(vrf_g)}")
  97. self.log.highlight(f"verifying slot {slot}\nsigma {sigma}\nproof {proof}\npk {vrf_pk} \nbase {vrf_g}")
  98. if not verify(slot, sigma, proof, vrf_pk, vrf_g):
  99. #TODO the leader is corrupted, action to be taken against the corrupt stakeholder
  100. #in this case this slot is empty
  101. self.log.warn(f"<new_slot> leader verification fails")
  102. self.current_block=EmptyBlock(self.env.genesis_time)
  103. self.current_epoch.add_block(self.current_block)
  104. return
  105. self.current_slot_uid = slot
  106. if self.current_slot_uid%self.epoch_length!=0:
  107. prev_blk = self.blockchain[-1] if len(self.blockchain)>0 else EmptyBlock(self.env.genesis_time)
  108. self.current_block=Block(prev_blk, self.data, self.current_slot_uid, self.env.genesis_time)
  109. self.current_epoch.add_block(self.current_block)
  110. if self.env.is_current_leader(self.id):
  111. self.log.highlight(f"{str(self)} is broadcasting block")
  112. self.broadcast_block()
  113. elif self.env.is_current_endorser(self.id):
  114. self.log.highlight(f"{str(self)} is endorsing block")
  115. self.endorse_block()
  116. def update_stake(self):
  117. if len(self.blockchain)==0:
  118. return
  119. epoch = self.blockchain[-1]
  120. pall = epoch.coffee()
  121. leader_cnt=0
  122. endorser_cnt=0
  123. for blk in epoch:
  124. if blk.leader_id==self.id:
  125. leader_cnt+=1
  126. elif blk.endorser_id==self.id:
  127. endorser_cnt+=1
  128. self.stake += (self.env.beta * (endorser_cnt/self.env.endorser_len) + \
  129. (1-self.env.beta) * (leader_cnt/self.env.epoch_length)) * pall
  130. def set_corrupt(self):
  131. self.am_corrupt=False
  132. '''
  133. only leader can broadcast block
  134. '''
  135. def broadcast_block(self):
  136. if not self.env.is_current_leader(self.id):
  137. return
  138. self.current_block.set_leader(self.id)
  139. self.log.highlight("broadcasting block")
  140. assert self.env.is_current_leader(self.id) and self.current_block is not None
  141. signed_block=None
  142. #TODO should wait for l slot until block is endorsed
  143. endorsing_cnt=10
  144. #TODO (rev)
  145. while not self.current_block.endorsed or self.blockchain[self.current_slot_uid]:
  146. time.sleep(1)
  147. self.log.info("...waiting for endorsment..")
  148. endorsing_cnt-=1
  149. '''
  150. if not self.current_block.endorsed:
  151. self.log.warn("failure endorsing the block...")
  152. self.current_block = EmptyBlock(self.env.genesis_time)
  153. '''
  154. signed_block = sign_message(self.passwd, self.sig_sk, self.current_block)
  155. self.current_block.set_signature(signed_block)
  156. self.env.broadcast_block(self.current_block)
  157. @property
  158. def current_slot(self):
  159. return self.env.beacon.current_slot
  160. '''
  161. only endorser can broadcast block
  162. '''
  163. def endorse_block(self):
  164. assert self.env.is_current_endorser(self.id)
  165. 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]}'
  166. #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}'
  167. self.current_block.set_endorser(self.id)
  168. self.log.info(f"endorsing block for current_leader_id: {self.env.current_leader_id}")
  169. if not self.env.is_current_endorser(self.id):
  170. self.log.warn("not endorser")
  171. return
  172. assert self.current_block is not None
  173. sig = sign_message(self.passwd, self.sig_sk, self.current_block)
  174. self.log.highlight(f'block to be endorsed {str(self.current_block)}')
  175. self.log.highlight(f'block to be endorsed has slot_uid: {self.current_slot_uid}')
  176. self.log.highlight(f'block to be endorsed has sig_pk: {str(self.sig_pk)}')
  177. self.env.endorse_block(sig, self.current_slot_uid)
  178. def __get_blk(self, blk_uid):
  179. assert(blk_uid>=0)
  180. stashed=True
  181. cur_blk = self.current_block
  182. if blk_uid < len(self.blockchain):
  183. #TODO this assumes synced blockchain
  184. cur_blk = self.blockchain[blk_uid]
  185. self.log.warn(f"current block from blockchain: {(cur_blk)}")
  186. stashed=False
  187. self.log.info(f"current block : {str(cur_blk)}\tblock uid: {blk_uid}\tstashed: {stashed}")
  188. if cur_blk is None:
  189. self.log.warn(f"blk uid {blk_uid}, blockchain length: {len(self.blockchain)}")
  190. self.log.warn(f"requested block is None\nblk_uid: {blk_uid}, blockchain: {self.blockchain}")
  191. 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}')
  192. while cur_blk is None:
  193. self.log.info("waiting for start of slot/epoch...")
  194. time.sleep(1)
  195. return cur_blk, stashed
  196. def receive_block(self, blk, endorser_sig):
  197. self.log.highlight("receiving block")
  198. cur_blk, stashed = self.__get_blk(blk.slot)
  199. #TODO to consider deley should retrive leader_pk of corresponding blk_uid
  200. self.log.highlight(f'receiving block {str(cur_blk)}')
  201. self.log.highlight(f'receiving block has slot_uid: {self.current_slot_uid}')
  202. self.log.highlight(f'receiving block has sig_pk: {self.env.current_endorser_sig_pk}')
  203. blk_verified = verify_signature(self.env.current_leader_sig_pk, cur_blk, blk.signature)
  204. self.log.info("endorser sig_pk {self.env.current_endorser_sig_pk}, cur_blk: {cur_blk}, endorser_sig: {endorser_sig}")
  205. blk_edrs_verified = verify_signature(self.env.current_endorser_sig_pk, cur_blk, endorser_sig)
  206. if blk_verified and blk_edrs_verified:
  207. if stashed:
  208. self.current_epoch.add_block(cur_blk)
  209. else:
  210. if not blk_verified:
  211. self.log.warn("block verification failed")
  212. elif not blk_edrs_verified:
  213. self.log.warn("block endorsing verification failed")
  214. self.env.corrupt_blk()
  215. def confirm_endorsing(self, endorser_sig, blk_uid, slot):
  216. self.log.highlight(f"confirming block with epoch slot id {blk_uid}")
  217. confirmed = False
  218. cur_blk, _ = self.__get_blk(blk_uid)
  219. self.log.highlight(f'confirming endorsed block {str(cur_blk)}')
  220. self.log.highlight(f'confirming endorsed has slot: {slot} epoch slot_uid: {self.current_slot_uid}')
  221. self.log.highlight(f'confirming endorsed has sig_pk: {self.env.current_endorser_sig_pk}')
  222. endorser_sig_pk = self.env.endorser_sig_pk(self.env.beacon.slot)
  223. self.log.highlight(f'confirming endorsed sig pk: {endorser_sig_pk}')
  224. if verify_signature(endorser_sig_pk, cur_blk, endorser_sig):
  225. if self.current_slot_uid==self.env.current_slot:
  226. self.log.highlight("set current block as endorsed")
  227. self.current_block.set_endorser(self.env.current_endorser_uid)
  228. self.current_block.set_endorsed()
  229. else:
  230. self.log.highlight(f"set delayed blockchain block with uid: {blk_uid} as endorsed")
  231. self.blockchain[blk_uid].set_endorser(self.env.current_endorser_uid)
  232. self.blockchain[blk_uid].set_endorsed()
  233. confirmed=True
  234. else:
  235. self.log.warn(f"confirmed enderser signature failure for pk: {str(endorser_sig_pk)} on block {str(cur_blk)} of signature {str(endorser_sig)}")
  236. confirmed=False
  237. return confirmed