environment.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  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.new_epoch_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. if self.beacon.epoch_slot != 0:
  63. self.new_slot_signal()
  64. else:
  65. self.new_epoch_signal()
  66. def new_slot_signal(self):
  67. ############
  68. # NEW SLOT #
  69. ############
  70. y, pi = self.rands[self.current_slot]
  71. threads = []
  72. for sk in self.stakeholders:
  73. #TODO (fix) failed to synchronized current_slot 1234 for epoch length of 2 is two states
  74. # need to pass the slot with it's corresponding sigma, and proof
  75. thread = Thread(target=Stakeholder.new_slot, args=(sk, self.current_slot, y, pi))
  76. #sk.new_slot(self.current_slot, y, pi)
  77. threads.append(thread)
  78. thread.start()
  79. for th in threads:
  80. th.join()
  81. def new_epoch_signal(self):
  82. #############
  83. # NEW EPOCH #
  84. #############
  85. vrf = self.stakeholders[self.current_leader_id].vrf
  86. if self.beacon.epoch != self.current_epoch:
  87. self.current_epoch = self.beacon.epoch
  88. self.rands = self.beacon.next_epoch_seeds(vrf)
  89. self.select_epoch_leaders()
  90. for idx, sk in enumerate(self.stakeholders):
  91. if sk.id==id:
  92. self.prev_leader_id=idx
  93. self.cached_dist = self.get_epoch_distribution()
  94. for sk in self.stakeholders:
  95. sk.current_slot_uid=self.beacon.slot
  96. ###
  97. genesis_item = self.get_genesis_data()
  98. data = Data()
  99. data.append(genesis_item)
  100. self.current_block=GensisBlock(self.current_block, data, self.beacon.slot, self.genesis_time)
  101. assert self.current_block is not None
  102. current_epoch=Epoch(self.current_block, self.epoch_length, self.epoch, self.genesis_time)
  103. threads = []
  104. for sk in self.stakeholders:
  105. #sk.new_epoch(current_epoch)
  106. thread = Thread(target=Stakeholder.new_epoch, args=(sk, current_epoch))
  107. threads.append(thread)
  108. thread.start()
  109. for th in threads:
  110. th.join()
  111. def __repr__(self):
  112. 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}"
  113. for sh in self.stakeholders:
  114. buff+=str(sh)+"\n"
  115. return buff
  116. '''
  117. issue a coinbase for claimed reward 2(k+l) after the block
  118. '''
  119. def issue_coinbase(self):
  120. #TODO
  121. pass
  122. '''
  123. returns true if before time, false otherwise
  124. '''
  125. @property
  126. def iceage(self):
  127. return self.block_id==0
  128. @property
  129. def previous_epoch_stake_distribution(self):
  130. if self.iceage:
  131. return self.get_epoch_distribution()
  132. else:
  133. return self.cached_dist
  134. def get_epoch_distribution(self):
  135. stakes = [node.stake for node in self.stakeholders]
  136. return stakes
  137. '''
  138. return genesis data of the current epoch
  139. '''
  140. def get_genesis_data(self):
  141. #TODO implement dynaming staking
  142. distribution = self.get_epoch_distribution()
  143. genesis_data = {STAKEHOLDERS: self.stakeholders,
  144. STAKEHOLDERS_DISTRIBUTIONS: distribution,
  145. SEED: ''}
  146. return GenesisItem(genesis_data)
  147. @property
  148. def epoch_slot(self):
  149. return self.current_slot%self.epoch_length
  150. @property
  151. def current_leader_id(self):
  152. return self.slot_committee[self.current_slot][0]
  153. @property
  154. def current_stakeholder(self):
  155. self.log.info(f"getting leader of id: {self.current_leader_id}")
  156. return self.stakeholders[self.current_leader_id]
  157. @property
  158. def current_endorser_id(self):
  159. return self.slot_committee[self.current_slot][1]
  160. @property
  161. def current_endorser_uid(self):
  162. return self.stakeholders[self.current_endorser_id].id
  163. @property
  164. def current_endorser(self):
  165. self.log.info(f"getting endorser of id: {self.current_leader_id}")
  166. return self.stakeholders[self.current_endorser_id]
  167. @property
  168. def current_endorser_sig_pk(self):
  169. return self.stakeholders[self.current_endorser_id].sig_pk
  170. def endorser(self, slot):
  171. return self.stakeholders[self.slot_committee[slot][1]]
  172. def endorser_sig_pk(self, slot):
  173. return self.endorser(slot).sig_pk
  174. def endorser_vrf_pk(self, slot):
  175. return self.endorser(slot).vrf_pk
  176. def is_current_endorser(self, id):
  177. _, edr_idx = self.slot_committee[self.beacon.slot]
  178. return id == self.stakeholders[edr_idx].id
  179. @property
  180. def current_leader_vrf_pk(self):
  181. return self.stakeholders[self.current_leader_id].vrf_pk
  182. @property
  183. def current_leader_vrf_g(self):
  184. return self.stakeholders[self.current_leader_id].vrf_base
  185. def is_current_leader(self, id):
  186. ldr_idx, _ = self.slot_committee[self.beacon.current_slot]
  187. return id == self.stakeholders[ldr_idx].id
  188. '''
  189. @property
  190. def current_epoch_leader(self):
  191. return self.stakeholders[self.current_epoch_leaders[0]]
  192. @property
  193. def current_epoch_leader_vrf_pk(self):
  194. return self.current_epoch_leader.vrf_pk
  195. @property
  196. def current_epoch_leader_vrf_g(self):
  197. return self.current_epoch_leader.vrf_base
  198. '''
  199. @property
  200. def current_leader_sig_pk(self):
  201. return self.stakeholders[self.current_leader_id].sig_pk
  202. #note! assumes epoch_slot lays in the current epoch
  203. def leader(self, slot):
  204. return self.stakeholders[self.slot_committee[slot][0]]
  205. '''
  206. def leader_sig_pk(self, epoch_slot):
  207. return self.leader(epoch_slot).sig_pk
  208. def leader_vrf_pk(self, epoch_slot):
  209. return self.leader(epoch_slot).vrf_pk
  210. def leader_vrf_g(self, epoch_slot):
  211. return self.leader(epoch_slot).vrf_base
  212. '''
  213. def prev_leader_vrf_pk(self):
  214. return self.stakeholders[self.prev_leader_id].vrf_pk
  215. def prev_leader_vrf_g(self):
  216. return self.stakeholders[self.prev_leader_id].vrf_base
  217. #TODO complete
  218. def obfuscate_idx(self, i):
  219. return i
  220. #TODO complete
  221. def deobfuscate_idx(self, i):
  222. return i
  223. def corrupt(self, i):
  224. if i<0 or i>len(self.adversary_mask):
  225. return False
  226. self.adversary_mask[self.deobfuscate_idx(i)]=False
  227. return True
  228. '''
  229. return the length of all parties
  230. '''
  231. def __len__(self):
  232. return len(self.stakeholders)
  233. @property
  234. def length(self):
  235. return len(self.stakeholders)
  236. @property
  237. def honest(self):
  238. return len(self.stakeholders[self.adversary_mask])
  239. @property
  240. def epoch_stake_distribution(self):
  241. #stakes = {}
  242. ordered_stakes = [] #with the same stakeholders order
  243. for sk in self.stakeholders:
  244. #stakes[sk.id] = sk.stake
  245. ordered_stakes.append(sk.stake)
  246. return ordered_stakes
  247. @property
  248. def random(self):
  249. return utils.weighted_random(self.epoch_stake_distribution)
  250. '''
  251. since clocks are synched
  252. '''
  253. @property
  254. def epoch(self):
  255. return self.beacon.epoch
  256. def select_epoch_leaders(self):
  257. #assert len(self.sigmas)==self.epoch_length and len(self.proofs)==self.epoch_length, \
  258. #self.log.error(f"size mismatch between sigmas: {len(self.sigmas)}, proofs: {len(self.proofs)}, and epoch_length: {self.epoch_length}")
  259. for i in range(self.epoch_length):
  260. #self.log.info(f"current sigma of index {i} , epoch_length: {self.epoch_length}")
  261. slot_idx = self.current_slot + i
  262. sigma, _ = self.rands[slot_idx]
  263. assert sigma!=None, 'proof cant be None'
  264. def leader_selection_hash(sigma):
  265. Y = np.array(sigma)
  266. y_hypotenuse2 = math.ceil(np.sum(Y[1]**2+Y[2]**2))
  267. return y_hypotenuse2
  268. seed = leader_selection_hash(sigma)
  269. random.seed(seed)
  270. leader_idx=self.random
  271. endorser_idx=self.random
  272. # only select an honest leaders
  273. while leader_idx==endorser_idx or not self.adversary_mask[leader_idx] or not self.adversary_mask[endorser_idx]:
  274. leader_idx=self.random
  275. endorser_idx=self.random
  276. #TODO select the following leader for this epoch, note,
  277. # under a single condition that no one is able to predict who is next
  278. assert not leader_idx==endorser_idx
  279. #TODO move leader/endorser to a dictionary
  280. self.slot_committee[slot_idx] = (leader_idx, endorser_idx)
  281. self.log.highlight(f'slot {slot_idx} has committee leader/endorser {leader_idx}/{endorser_idx}\nleader: {self.stakeholders[leader_idx]}\nendorser: {self.stakeholders[endorser_idx]}')
  282. self.epoch_initialized[str(self.epoch)] = True
  283. def broadcast_block(self, cur_block, signed_block, slot_uid):
  284. while self.current_blk_endorser_sig is None:
  285. self.log.info('pending endorsing...')
  286. time.sleep(1)
  287. #wait for it untill it gets endorsed
  288. pass
  289. self.current_block = cur_block
  290. for stakeholder in self.stakeholders:
  291. if not stakeholder.is_leader:
  292. stakeholder.receive_block(signed_block, self.current_blk_endorser_sig, slot_uid)
  293. self.print_blockchain()
  294. @property
  295. def block_id(self):
  296. return self.current_slot%self.epoch_length
  297. def endorse_block(self, sig, slot_uid):
  298. #TODO commit this step to handshake phases
  299. self.current_blk_endorser_sig=None
  300. self.log.info(f"endorsing block for current_leader_id: {self.current_leader_id}")
  301. confirmed = self.stakeholders[self.current_leader_id].confirm_endorsing(sig, self.block_id, self.current_slot)
  302. if confirmed:
  303. self.current_blk_endorser_sig=sig
  304. else:
  305. self.log.warn("unconfirmed endorsed siganture")
  306. def print_blockchain(self):
  307. for sh in self.stakeholders:
  308. bc = sh.blockchain
  309. self.log.highlight(f"<blockchain> {len(bc)} blocks: "+str(bc))
  310. '''
  311. def confirm_endorsing(self, sig, blk_uid):
  312. if blk_uid==self.current_slot:
  313. self.current_blk_endorser_sig = sig
  314. '''
  315. def corrupt_leader(self):
  316. self.corrupt(self.current_leader_id)
  317. def corrupt_endorser(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_endorser()