environment.py 14 KB

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