environment.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. import numpy as np
  2. import math
  3. import random
  4. import time
  5. from threading import Thread
  6. from ouroboros.logger import Logger
  7. from ouroboros.consts import *
  8. from ouroboros.data import GenesisItem, Data
  9. from ouroboros import utils
  10. from ouroboros.beacon import TrustedBeacon
  11. from ouroboros.block import GensisBlock
  12. from ouroboros.epoch import Epoch
  13. from ouroboros.stakeholder import Stakeholder
  14. '''
  15. \class Z is the environment,
  16. environment is ought to interfece with the network
  17. '''
  18. class Z(object):
  19. def __init__(self, stakeholdes, epoch_length, genesis_time=time.time()):
  20. self.genesis_time=genesis_time
  21. self.beacon = TrustedBeacon(epoch_length, genesis_time)
  22. self.log = Logger(self, genesis_time)
  23. self.epoch_length=epoch_length
  24. self.stakeholders = np.array(stakeholdes)
  25. self.adversary_mask=np.array([True]*len(stakeholdes))
  26. self.slot_committee = {}
  27. self.current_slot=0
  28. self.current_blk_endorser_sig=None
  29. self.epoch_inited=False
  30. self.cached_dist = []
  31. self.beta = 0.5 # endorser weight
  32. #
  33. self.l=0
  34. #a transaction is declared stable if and only if it is in a block that,
  35. # is more than k blocks deep in the ledger.
  36. self.k = self.epoch_length/2 - self.l -1
  37. self.epoch_initialized = {}
  38. #TODO (fix) replace those by query from blockchain genesis block
  39. self.rands = {}
  40. self.prev_leader_id=-1
  41. #
  42. self.current_block=None
  43. self.init()
  44. def init(self):
  45. for sh in self.stakeholders:
  46. sh(self)
  47. assert len(self.stakeholders) > 2
  48. #pick initial leader to be the first stakeholder
  49. initial_leader = self.stakeholders[0]
  50. #pick initial endorser to be the first endorser
  51. initial_endorser = self.stakeholders[1]
  52. self.current_epoch = self.beacon.epoch
  53. self.rands = self.beacon.next_epoch_seeds(initial_leader.vrf)
  54. self.current_slot = self.beacon.slot
  55. self.select_epoch_leaders()
  56. self.prev_leader_id=0
  57. self.signal()
  58. #TODO need to assign the block from the last slot in the epoch
  59. while True:
  60. if not self.beacon.slot == self.current_slot:
  61. self.current_slot = self.beacon.slot
  62. self.signal()
  63. def signal(self):
  64. ########################
  65. #TODO fix cretical
  66. ########################
  67. # run the state on a member, no static function for new_epoch, new_slot.
  68. if self.beacon.epoch_slot!=0:
  69. ############
  70. # NEW SLOT #
  71. ############
  72. y, pi = self.rands[self.current_slot]
  73. threads = []
  74. for sk in self.stakeholders:
  75. #TODO (fix) failed to synchronized current_slot 1234 for epoch length of 2 is two states
  76. # need to pass the slot with it's corresponding sigma, and proof
  77. thread = Thread(target=Stakeholder.new_slot, args=(sk, self.current_slot, y, pi))
  78. #sk.new_slot(self.current_slot, y, pi)
  79. threads.append(thread)
  80. thread.start()
  81. for th in threads:
  82. th.join()
  83. else:
  84. #############
  85. # NEW EPOCH #
  86. #############
  87. vrf = self.stakeholders[self.current_leader_id].vrf
  88. if self.beacon.epoch != self.current_epoch:
  89. self.current_epoch = self.beacon.epoch
  90. self.rands = self.beacon.next_epoch_seeds(vrf)
  91. self.select_epoch_leaders()
  92. for idx, sk in enumerate(self.stakeholders):
  93. if sk.id==id:
  94. self.prev_leader_id=idx
  95. self.cached_dist = self.get_epoch_distribution()
  96. for sk in self.stakeholders:
  97. sk.current_slot_uid=self.beacon.slot
  98. ###
  99. genesis_item = self.get_genesis_data()
  100. data = Data()
  101. data.append(genesis_item)
  102. self.current_block=GensisBlock(self.current_block, data, self.beacon.slot, self.genesis_time)
  103. assert self.current_block is not None
  104. current_epoch=Epoch(self.current_block, self.epoch_length, self.epoch, self.genesis_time)
  105. threads = []
  106. for sk in self.stakeholders:
  107. #sk.new_epoch(current_epoch)
  108. thread = Thread(target=Stakeholder.new_epoch, args=(sk, current_epoch))
  109. threads.append(thread)
  110. thread.start()
  111. for th in threads:
  112. th.join()
  113. @property
  114. def endorser_len(self):
  115. #TODO (impl)
  116. pass
  117. def __repr__(self):
  118. buff= f"envirnment of {self.length} stakholders\tcurrent leader's id: {self.current_leader_id}\tepoch_slot: {self.epoch_slot}\tendorser_id: {self.current_endorser_id}"
  119. for sh in self.stakeholders:
  120. buff+=str(sh)+"\n"
  121. return buff
  122. '''
  123. issue a coinbase for claimed reward 2(k+l) after the block
  124. '''
  125. def issue_coinbase(self):
  126. #TODO
  127. pass
  128. '''
  129. returns true if before time, false otherwise
  130. '''
  131. @property
  132. def iceage(self):
  133. return self.block_id==0
  134. @property
  135. def previous_epoch_stake_distribution(self):
  136. if self.iceage:
  137. return self.get_epoch_distribution()
  138. else:
  139. return self.cached_dist
  140. def get_epoch_distribution(self):
  141. stakes = [node.stake for node in self.stakeholders]
  142. return stakes
  143. '''
  144. return genesis data of the current epoch
  145. '''
  146. def get_genesis_data(self):
  147. #TODO implement dynaming staking
  148. distribution = self.get_epoch_distribution()
  149. genesis_data = {STAKEHOLDERS: self.stakeholders,
  150. STAKEHOLDERS_DISTRIBUTIONS: distribution,
  151. SEED: ''}
  152. return GenesisItem(genesis_data)
  153. @property
  154. def epoch_slot(self):
  155. return self.current_slot%self.epoch_length
  156. @property
  157. def current_leader_id(self):
  158. return self.slot_committee[self.current_slot][0]
  159. @property
  160. def current_stakeholder(self):
  161. self.log.info(f"getting leader of id: {self.current_leader_id}")
  162. return self.stakeholders[self.current_leader_id]
  163. @property
  164. def current_endorser_id(self):
  165. return self.slot_committee[self.current_slot][1]
  166. @property
  167. def current_endorser(self):
  168. self.log.info(f"getting endorser of id: {self.current_leader_id}")
  169. return self.stakeholders[self.current_endorser_id]
  170. @property
  171. def current_leader_vrf_pk(self):
  172. return self.stakeholders[self.current_leader_id].vrf_pk
  173. @property
  174. def current_leader_vrf_g(self):
  175. return self.stakeholders[self.current_leader_id].vrf_base
  176. '''
  177. @property
  178. def current_epoch_leader(self):
  179. return self.stakeholders[self.current_epoch_leaders[0]]
  180. @property
  181. def current_epoch_leader_vrf_pk(self):
  182. return self.current_epoch_leader.vrf_pk
  183. @property
  184. def current_epoch_leader_vrf_g(self):
  185. return self.current_epoch_leader.vrf_base
  186. '''
  187. @property
  188. def current_leader_sig_pk(self):
  189. return self.stakeholders[self.current_leader_id].sig_pk
  190. @property
  191. def current_endorser_sig_pk(self):
  192. return self.stakeholders[self.current_endorser_id].sig_pk
  193. def endorser(self, epoch_slot):
  194. assert epoch_slot >= 0 and epoch_slot < self.epoch_length
  195. return self.stakeholders[epoch_slot]
  196. def endorser_sig_pk(self, epoch_slot):
  197. return self.endorser(epoch_slot).sig_pk
  198. def endorser_vrf_pk(self, epoch_slot):
  199. return self.endorser(epoch_slot).vrf_pk
  200. #note! assumes epoch_slot lays in the current epoch
  201. def leader(self, epoch_slot):
  202. assert epoch_slot >= 0 and epoch_slot < self.epoch_length
  203. return self.stakeholders[epoch_slot]
  204. def leader_sig_pk(self, epoch_slot):
  205. return self.leader(epoch_slot).sig_pk
  206. def leader_vrf_pk(self, epoch_slot):
  207. return self.leader(epoch_slot).vrf_pk
  208. def leader_vrf_g(self, epoch_slot):
  209. return self.leader(epoch_slot).vrf_base
  210. def prev_leader_vrf_pk(self):
  211. return self.stakeholders[self.prev_leader_id].vrf_pk
  212. def prev_leader_vrf_g(self):
  213. return self.stakeholders[self.prev_leader_id].vrf_base
  214. #TODO complete
  215. def obfuscate_idx(self, i):
  216. return i
  217. #TODO complete
  218. def deobfuscate_idx(self, i):
  219. return i
  220. def corrupt(self, i):
  221. if i<0 or i>len(self.adversary_mask):
  222. return False
  223. self.adversary_mask[self.deobfuscate_idx(i)]=False
  224. return True
  225. '''
  226. return the length of all parties
  227. '''
  228. def __len__(self):
  229. return len(self.stakeholders)
  230. @property
  231. def length(self):
  232. return len(self.stakeholders)
  233. @property
  234. def honest(self):
  235. return len(self.stakeholders[self.adversary_mask])
  236. @property
  237. def epoch_stake_distribution(self):
  238. #stakes = {}
  239. ordered_stakes = [] #with the same stakeholders order
  240. for sk in self.stakeholders:
  241. #stakes[sk.id] = sk.stake
  242. ordered_stakes.append(sk.stake)
  243. return ordered_stakes
  244. @property
  245. def random(self):
  246. return utils.weighted_random(self.epoch_stake_distribution)
  247. '''
  248. since clocks are synched
  249. '''
  250. @property
  251. def epoch(self):
  252. return self.beacon.epoch
  253. def select_epoch_leaders(self):
  254. #assert len(self.sigmas)==self.epoch_length and len(self.proofs)==self.epoch_length, \
  255. #self.log.error(f"size mismatch between sigmas: {len(self.sigmas)}, proofs: {len(self.proofs)}, and epoch_length: {self.epoch_length}")
  256. for i in range(self.epoch_length):
  257. #self.log.info(f"current sigma of index {i} , epoch_length: {self.epoch_length}")
  258. slot_idx = self.current_slot + i
  259. sigma, _ = self.rands[slot_idx]
  260. assert sigma!=None, 'proof cant be None'
  261. def leader_selection_hash(sigma):
  262. Y = np.array(sigma)
  263. y_hypotenuse2 = math.ceil(np.sum(Y[1]**2+Y[2]**2))
  264. return y_hypotenuse2
  265. seed = leader_selection_hash(sigma)
  266. random.seed(seed)
  267. leader_idx=self.random
  268. endorser_idx=self.random
  269. # only select an honest leaders
  270. while leader_idx==endorser_idx or not self.adversary_mask[leader_idx] or not self.adversary_mask[endorser_idx]:
  271. leader_idx=self.random
  272. endorser_idx=self.random
  273. #TODO select the following leader for this epoch, note,
  274. # under a single condition that no one is able to predict who is next
  275. assert not leader_idx==endorser_idx
  276. #TODO move leader/endorser to a dictionary
  277. self.slot_committee[slot_idx] = (leader_idx, endorser_idx)
  278. self.epoch_initialized[str(self.epoch)] = True
  279. def broadcast_block(self, cur_block, signed_block, slot_uid):
  280. while self.current_blk_endorser_sig is None:
  281. self.log.info('pending endorsing...')
  282. time.sleep(1)
  283. #wait for it untill it gets endorsed
  284. pass
  285. self.current_block = cur_block
  286. for stakeholder in self.stakeholders:
  287. if not stakeholder.is_leader:
  288. stakeholder.receive_block(signed_block, self.current_blk_endorser_sig, slot_uid)
  289. self.print_blockchain()
  290. def is_current_leader(self, id):
  291. ldr_idx, _ = self.slot_committee[self.beacon.current_slot]
  292. return id == self.stakeholders[ldr_idx].id
  293. def is_current_endorser(self, id):
  294. _, edr_idx = self.slot_committee[self.beacon.current_slot]
  295. return id == self.stakeholders[edr_idx].id
  296. @property
  297. def block_id(self):
  298. return self.current_slot%self.epoch_length
  299. def endorse_block(self, sig, slot_uid):
  300. #TODO commit this step to handshake phases
  301. self.current_blk_endorser_sig=None
  302. self.log.info(f"endorsing block for current_leader_id: {self.current_leader_id}")
  303. confirmed = self.stakeholders[self.current_leader_id].confirm_endorsing(sig, self.block_id, self.epoch_slot)
  304. if confirmed:
  305. self.current_blk_endorser_sig=sig
  306. else:
  307. self.log.warn("unconfirmed endorsed siganture")
  308. def print_blockchain(self):
  309. for sh in self.stakeholders:
  310. bc = sh.blockchain
  311. self.log.highlight(f"<blockchain> {len(bc)} blocks: "+str(bc))
  312. def confirm_endorsing(self, sig, blk_uid):
  313. if blk_uid==self.current_slot:
  314. self.current_blk_endorser_sig = sig
  315. def corrupt_leader(self):
  316. self.corrupt(self.current_leader_id)
  317. def corrupt_endorse(self):
  318. self.corrupt(self.current_endorser_id)
  319. def corrupt_blk(self):
  320. self.log.warn(f"<corrupt_blk> at slot: {self.current_slot}")
  321. self.corrupt_leader()
  322. self.corrupt_endorse()