dirauth.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. #!/usr/bin/env python3
  2. import random # For simulation, not cryptography!
  3. import bisect
  4. import math
  5. import nacl.encoding
  6. import nacl.signing
  7. import network
  8. # A relay descriptor is a dict containing:
  9. # epoch: epoch id
  10. # idkey: a public identity key
  11. # onionkey: a public onion key
  12. # addr: a network address
  13. # bw: bandwidth
  14. # flags: relay flags
  15. # vrfkey: a VRF public key (Single-Pass Walking Onions only)
  16. # sig: a signature over the above by the idkey
  17. class RelayDescriptor:
  18. def __init__(self, descdict):
  19. self.descdict = descdict
  20. def __str__(self, withsig = True):
  21. res = "RelayDesc [\n"
  22. for k in ["epoch", "idkey", "onionkey", "addr", "bw", "flags",
  23. "vrfkey", "sig"]:
  24. if k in self.descdict:
  25. if k == "idkey" or k == "onionkey":
  26. res += " " + k + ": " + self.descdict[k].encode(encoder=nacl.encoding.HexEncoder).decode("ascii") + "\n"
  27. elif k == "sig":
  28. if withsig:
  29. res += " " + k + ": " + nacl.encoding.HexEncoder.encode(self.descdict[k]).decode("ascii") + "\n"
  30. else:
  31. res += " " + k + ": " + str(self.descdict[k]) + "\n"
  32. res += "]\n"
  33. return res
  34. def sign(self, signingkey, perfstats):
  35. serialized = self.__str__(False)
  36. signed = signingkey.sign(serialized.encode("ascii"))
  37. perfstats.sigs += 1
  38. self.descdict["sig"] = signed.signature
  39. @staticmethod
  40. def verify(desc, perfstats):
  41. assert(type(desc) is RelayDescriptor)
  42. serialized = desc.__str__(False)
  43. perfstats.verifs += 1
  44. desc.descdict["idkey"].verify(serialized.encode("ascii"), desc.descdict["sig"])
  45. # A SNIP is a dict containing:
  46. # epoch: epoch id
  47. # idkey: a public identity key
  48. # onionkey: a public onion key
  49. # addr: a network address
  50. # flags: relay flags
  51. # vrfkey: a VRF public key (Single-Pass Walking Onions only)
  52. # range: the (lo,hi) values for the index range (lo is inclusive, hi is
  53. # exclusive; that is, x is in the range if lo <= x < hi).
  54. # lo=hi denotes an empty range.
  55. # auth: either a signature from the authorities over the above
  56. # (Threshold signature case) or a Merkle path to the root
  57. # contained in the consensus (Merkle tree case)
  58. #
  59. # Note that the fields of the SNIP are the same as those of the
  60. # RelayDescriptor, except bw and sig are removed, and range and auth are
  61. # added.
  62. class SNIP:
  63. def __init__(self, snipdict):
  64. self.snipdict = snipdict
  65. def __str__(self, withauth = True):
  66. res = "SNIP [\n"
  67. for k in ["epoch", "idkey", "onionkey", "addr", "flags",
  68. "vrfkey", "range", "auth"]:
  69. if k in self.snipdict:
  70. if k == "idkey" or k == "onionkey":
  71. res += " " + k + ": " + self.snipdict[k].encode(encoder=nacl.encoding.HexEncoder).decode("ascii") + "\n"
  72. elif k == "auth":
  73. if withauth:
  74. if network.thenetwork.snipauthmode == \
  75. network.SNIPAuthMode.THRESHSIG:
  76. res += " " + k + ": " + nacl.encoding.HexEncoder.encode(self.snipdict[k]).decode("ascii") + "\n"
  77. else:
  78. raise NotImplementedError("Merkle auth not yet implemented")
  79. else:
  80. res += " " + k + ": " + str(self.snipdict[k]) + "\n"
  81. res += "]\n"
  82. return res
  83. def auth(self, signingkey, perfstats):
  84. if network.thenetwork.snipauthmode == network.SNIPAuthMode.THRESHSIG:
  85. serialized = self.__str__(False)
  86. signed = signingkey.sign(serialized.encode("ascii"))
  87. perfstats.sigs += 1
  88. self.snipdict["auth"] = signed.signature
  89. else:
  90. raise NotImplementedError("Merkle auth not yet implemented")
  91. @staticmethod
  92. def verify(snip, consensus, verifykey, perfstats):
  93. if network.thenetwork.snipauthmode == network.SNIPAuthMode.THRESHSIG:
  94. assert(type(snip) is SNIP and type(consensus) is Consensus)
  95. assert(consensus.consdict["epoch"] == snip.snipdict["epoch"])
  96. serialized = snip.__str__(False)
  97. perfstats.verifs += 1
  98. verifykey.verify(serialized.encode("ascii"),
  99. snip.snipdict["auth"])
  100. else:
  101. raise NotImplementedError("Merkle auth not yet implemented")
  102. # A consensus is a dict containing:
  103. # epoch: epoch id
  104. # numrelays: total number of relays
  105. # totbw: total bandwidth of relays
  106. # merkleroot: the root of the SNIP Merkle tree (Merkle tree auth only)
  107. # relays: list of relay descriptors (Vanilla Onion Routing only)
  108. # sigs: list of signatures from the dirauths
  109. class Consensus:
  110. def __init__(self, epoch, relays):
  111. relays = [ d for d in relays if d.descdict['epoch'] == epoch ]
  112. self.consdict = dict()
  113. self.consdict['epoch'] = epoch
  114. self.consdict['numrelays'] = len(relays)
  115. if network.thenetwork.womode == network.WOMode.VANILLA:
  116. self.consdict['totbw'] = sum([ d.descdict['bw'] for d in relays ])
  117. self.consdict['relays'] = relays
  118. else:
  119. self.consdict['totbw'] = 1<<32
  120. def __str__(self, withsigs = True):
  121. res = "Consensus [\n"
  122. for k in ["epoch", "numrelays", "totbw"]:
  123. if k in self.consdict:
  124. res += " " + k + ": " + str(self.consdict[k]) + "\n"
  125. if network.thenetwork.womode == network.WOMode.VANILLA:
  126. for r in self.consdict['relays']:
  127. res += str(r)
  128. if withsigs and ('sigs' in self.consdict):
  129. for s in self.consdict['sigs']:
  130. res += " sig: " + nacl.encoding.HexEncoder.encode(s).decode("ascii") + "\n"
  131. res += "]\n"
  132. return res
  133. def sign(self, signingkey, index, perfstats):
  134. """Use the given signing key to sign the consensus, storing the
  135. result in the sigs list at the given index."""
  136. serialized = self.__str__(False)
  137. signed = signingkey.sign(serialized.encode("ascii"))
  138. perfstats.sigs += 1
  139. if 'sigs' not in self.consdict:
  140. self.consdict['sigs'] = []
  141. if index >= len(self.consdict['sigs']):
  142. self.consdict['sigs'].extend([None] * (index+1-len(self.consdict['sigs'])))
  143. self.consdict['sigs'][index] = signed.signature
  144. @staticmethod
  145. def verify(consensus, verifkeylist, perfstats):
  146. """Use the given list of verification keys to check the
  147. signatures on the consensus. Return the RelayPicker if
  148. successful, or raise an exception otherwise."""
  149. assert(type(consensus) is Consensus)
  150. serialized = consensus.__str__(False)
  151. for i, vk in enumerate(verifkeylist):
  152. perfstats.verifs += 1
  153. vk.verify(serialized.encode("ascii"), consensus.consdict['sigs'][i])
  154. # If we got this far, all is well. Return the RelayPicker.
  155. return RelayPicker.get(consensus)
  156. # An ENDIVE is a dict containing:
  157. # epoch: epoch id
  158. # snips: list of SNIPS (in THRESHSIG mode, these include the auth
  159. # signatures; in MERKLE mode, these do _not_ include auth)
  160. # sigs: list of signatures from the dirauths
  161. class ENDIVE:
  162. def __init__(self, epoch, snips):
  163. snips = [ s for s in snips if s.snipdict['epoch'] == epoch ]
  164. self.enddict = dict()
  165. self.enddict['epoch'] = epoch
  166. self.enddict['snips'] = snips
  167. def __str__(self, withsigs = True):
  168. res = "ENDIVE [\n"
  169. for k in ["epoch"]:
  170. if k in self.enddict:
  171. res += " " + k + ": " + str(self.enddict[k]) + "\n"
  172. for s in self.enddict['snips']:
  173. res += str(s)
  174. if withsigs and ('sigs' in self.enddict):
  175. for s in self.enddict['sigs']:
  176. res += " sig: " + nacl.encoding.HexEncoder.encode(s).decode("ascii") + "\n"
  177. res += "]\n"
  178. return res
  179. def sign(self, signingkey, index, perfstats):
  180. """Use the given signing key to sign the ENDIVE, storing the
  181. result in the sigs list at the given index."""
  182. serialized = self.__str__(False)
  183. signed = signingkey.sign(serialized.encode("ascii"))
  184. perfstats.sigs += 1
  185. if 'sigs' not in self.enddict:
  186. self.enddict['sigs'] = []
  187. if index >= len(self.enddict['sigs']):
  188. self.enddict['sigs'].extend([None] * (index+1-len(self.enddict['sigs'])))
  189. self.enddict['sigs'][index] = signed.signature
  190. @staticmethod
  191. def verify(endive, consensus, verifkeylist, perfstats):
  192. """Use the given list of verification keys to check the
  193. signatures on the ENDIVE and consensus. Return the RelayPicker
  194. if successful, or raise an exception otherwise."""
  195. assert(type(endive) is ENDIVE and type(consensus) is Consensus)
  196. serializedcons = consensus.__str__(False)
  197. for i, vk in enumerate(verifkeylist):
  198. perfstats.verifs += 1
  199. vk.verify(serializedcons.encode("ascii"), consensus.consdict['sigs'][i])
  200. serializedend = endive.__str__(False)
  201. for i, vk in enumerate(verifkeylist):
  202. perfstats.verifs += 1
  203. vk.verify(serializedend.encode("ascii"), endive.enddict['sigs'][i])
  204. # If we got this far, all is well. Return the RelayPicker.
  205. return RelayPicker.get(consensus, endive)
  206. class RelayPicker:
  207. """An instance of this class (which may be a singleton in the
  208. simulation) is returned by the Consensus.verify() and
  209. ENDIVE.verify() methods. It does any necessary precomputation
  210. and/or caching, and exposes a method to select a random bw-weighted
  211. relay, either explicitly specifying a uniform random value, or
  212. letting the choice be done internally."""
  213. # The singleton instance
  214. relaypicker = None
  215. def __init__(self, consensus, endive = None):
  216. self.epoch = consensus.consdict["epoch"]
  217. self.totbw = consensus.consdict["totbw"]
  218. self.consensus = consensus
  219. self.endive = endive
  220. assert(endive is None or endive.enddict["epoch"] == self.epoch)
  221. if network.thenetwork.womode == network.WOMode.VANILLA:
  222. # Create the array of cumulative bandwidth values from a
  223. # consensus. The array (cdf) will have the same length as
  224. # the number of relays in the consensus. cdf[0] = 0, and
  225. # cdf[i] = cdf[i-1] + relay[i-1].bw.
  226. self.cdf = [0]
  227. for r in consensus.consdict['relays']:
  228. self.cdf.append(self.cdf[-1]+r.descdict['bw'])
  229. # Remove the last item, which should be the sum of all the bws
  230. self.cdf.pop()
  231. print('cdf=', self.cdf)
  232. elif network.thenetwork.snipauthmode == \
  233. network.SNIPAuthMode.THRESHSIG:
  234. # Note that clients will call this with endive = None
  235. if self.endive is not None:
  236. self.cdf = [ s.snipdict['range'][0] \
  237. for s in self.endive.enddict['snips'] ]
  238. else:
  239. self.cdf = None
  240. print('cdf=', self.cdf)
  241. else:
  242. raise NotImplementedError("Merkle auth not yet implemented")
  243. @staticmethod
  244. def get(consensus, endive = None):
  245. # Return the singleton instance, if it exists for this epoch
  246. # However, don't use the cached instance if that one has
  247. # endive=None, but we were passed a real ENDIVE
  248. if RelayPicker.relaypicker is not None and \
  249. (RelayPicker.relaypicker.endive is not None or \
  250. endive is None) and \
  251. RelayPicker.relaypicker.epoch == consensus.consdict["epoch"]:
  252. return RelayPicker.relaypicker
  253. # Create it otherwise, storing the result as the singleton
  254. RelayPicker.relaypicker = RelayPicker(consensus, endive)
  255. return RelayPicker.relaypicker
  256. def pick_relay_by_uniform_index(self, idx):
  257. """Pass in a uniform random index random(0,totbw-1) to get a
  258. relay selected weighted by bw."""
  259. if network.thenetwork.womode == network.WOMode.VANILLA:
  260. relays = self.consensus.consdict['relays']
  261. else:
  262. relays = self.endive.enddict['snips']
  263. # Find the rightmost entry less than or equal to idx
  264. i = bisect.bisect_right(self.cdf, idx)
  265. return relays[i-1]
  266. def pick_weighted_relay(self):
  267. """Select a random relay with probability proportional to its bw
  268. weight."""
  269. idx = self.pick_weighted_relay_index()
  270. return self.pick_relay_by_uniform_index(idx)
  271. def pick_weighted_relay_index(self):
  272. """Select a random relay index (for use in Walking Onions)
  273. uniformly, which will results in picking a relay with
  274. probability proportional to its bw weight."""
  275. totbw = self.totbw
  276. if totbw < 1:
  277. raise ValueError("No relays to choose from")
  278. return random.randint(0, totbw-1)
  279. class DirAuthNetMsg(network.NetMsg):
  280. """The subclass of NetMsg for messages to and from directory
  281. authorities."""
  282. class DirAuthUploadDescMsg(DirAuthNetMsg):
  283. """The subclass of DirAuthNetMsg for uploading a relay
  284. descriptor."""
  285. def __init__(self, desc):
  286. self.desc = desc
  287. class DirAuthDelDescMsg(DirAuthNetMsg):
  288. """The subclass of DirAuthNetMsg for deleting a relay
  289. descriptor."""
  290. def __init__(self, desc):
  291. self.desc = desc
  292. class DirAuthGetConsensusMsg(DirAuthNetMsg):
  293. """The subclass of DirAuthNetMsg for fetching the consensus."""
  294. class DirAuthConsensusMsg(DirAuthNetMsg):
  295. """The subclass of DirAuthNetMsg for returning the consensus."""
  296. def __init__(self, consensus):
  297. self.consensus = consensus
  298. class DirAuthGetConsensusDiffMsg(DirAuthNetMsg):
  299. """The subclass of DirAuthNetMsg for fetching the consensus, if the
  300. requestor already has the previous consensus."""
  301. class DirAuthConsensusDiffMsg(DirAuthNetMsg):
  302. """The subclass of DirAuthNetMsg for returning the consensus, if the
  303. requestor already has the previous consensus. We don't _actually_
  304. produce the diff at this time; we just charge fewer bytes for this
  305. message."""
  306. def __init__(self, consensus):
  307. self.consensus = consensus
  308. def size(self):
  309. if network.symbolic_byte_counters:
  310. return super().size()
  311. return math.ceil(DirAuthConsensusMsg(self.consensus).size() \
  312. * network.P_Delta)
  313. class DirAuthGetENDIVEMsg(DirAuthNetMsg):
  314. """The subclass of DirAuthNetMsg for fetching the ENDIVE."""
  315. class DirAuthENDIVEMsg(DirAuthNetMsg):
  316. """The subclass of DirAuthNetMsg for returning the ENDIVE."""
  317. def __init__(self, endive):
  318. self.endive = endive
  319. class DirAuthGetENDIVEDiffMsg(DirAuthNetMsg):
  320. """The subclass of DirAuthNetMsg for fetching the ENDIVE, if the
  321. requestor already has the previous ENDIVE."""
  322. class DirAuthENDIVEDiffMsg(DirAuthNetMsg):
  323. """The subclass of DirAuthNetMsg for returning the ENDIVE, if the
  324. requestor already has the previous consensus. We don't _actually_
  325. produce the diff at this time; we just charge fewer bytes for this
  326. message in Merkle mode. In threshold signature mode, we would still
  327. need to download at least the new signatures for every SNIP in the
  328. ENDIVE, so for now, just assume there's no gain from ENDIVE diffs in
  329. threshold signature mode."""
  330. def __init__(self, endive):
  331. self.endive = endive
  332. def size(self):
  333. if network.symbolic_byte_counters:
  334. return super().size()
  335. if network.thenetwork.snipauthmode == \
  336. network.SNIPAuthMode.THRESHSIG:
  337. return DirAuthENDIVEMsg(self.endive).size()
  338. return math.ceil(DirAuthENDIVEMsg(self.endive).size() \
  339. * network.P_Delta)
  340. class DirAuthConnection(network.ClientConnection):
  341. """The subclass of Connection for connections to directory
  342. authorities."""
  343. def __init__(self, peer):
  344. super().__init__(peer)
  345. def uploaddesc(self, desc):
  346. """Upload our RelayDescriptor to the DirAuth."""
  347. self.sendmsg(DirAuthUploadDescMeg(desc))
  348. def getconsensus(self):
  349. self.consensus = None
  350. self.sendmsg(DirAuthGetConsensusMsg())
  351. return self.consensus
  352. def getconsensusdiff(self):
  353. self.consensus = None
  354. self.sendmsg(DirAuthGetConsensusDiffMsg())
  355. return self.consensus
  356. def getendive(self):
  357. self.endive = None
  358. self.sendmsg(DirAuthGetENDIVEMsg())
  359. return self.endive
  360. def getendivediff(self):
  361. self.endive = None
  362. self.sendmsg(DirAuthGetENDIVEDiffMsg())
  363. return self.endive
  364. def receivedfromserver(self, msg):
  365. if isinstance(msg, DirAuthConsensusMsg):
  366. self.consensus = msg.consensus
  367. elif isinstance(msg, DirAuthConsensusDiffMsg):
  368. self.consensus = msg.consensus
  369. elif isinstance(msg, DirAuthENDIVEMsg):
  370. self.endive = msg.endive
  371. elif isinstance(msg, DirAuthENDIVEDiffMsg):
  372. self.endive = msg.endive
  373. else:
  374. raise TypeError('Not a server-originating DirAuthNetMsg', msg)
  375. class DirAuth(network.Server):
  376. """The class representing directory authorities."""
  377. # We simulate the act of computing the consensus by keeping a
  378. # class-static dict that's accessible to all of the dirauths
  379. # This dict is indexed by epoch, and the value is itself a dict
  380. # indexed by the stringified descriptor, with value a pair of (the
  381. # number of dirauths that saw that descriptor, the descriptor
  382. # itself).
  383. uploadeddescs = dict()
  384. consensus = None
  385. endive = None
  386. def __init__(self, me, tot):
  387. """Create a new directory authority. me is the index of which
  388. dirauth this one is (starting from 0), and tot is the total
  389. number of dirauths."""
  390. self.me = me
  391. self.tot = tot
  392. self.name = "Dirauth %d of %d" % (me+1, tot)
  393. self.perfstats = network.PerfStats(network.EntType.DIRAUTH)
  394. self.perfstats.is_bootstrapping = True
  395. # Create the dirauth signature keypair
  396. self.sigkey = nacl.signing.SigningKey.generate()
  397. self.perfstats.keygens += 1
  398. self.netaddr = network.thenetwork.bind(self)
  399. self.perfstats.name = "DirAuth at %s" % self.netaddr
  400. network.thenetwork.setdirauthkey(me, self.sigkey.verify_key)
  401. network.thenetwork.wantepochticks(self, True, True)
  402. def connected(self, client):
  403. """Callback invoked when a client connects to us. This callback
  404. creates the DirAuthConnection that will be passed to the
  405. client."""
  406. # We don't actually need to keep per-connection state at
  407. # dirauths, even in long-lived connections, so this is
  408. # particularly simple.
  409. return DirAuthConnection(self)
  410. def generate_consensus(self, epoch):
  411. """Generate the consensus (and ENDIVE, if using Walking Onions)
  412. for the given epoch, which should be the one after the one
  413. that's currently about to end."""
  414. threshold = int(self.tot/2)+1
  415. consensusdescs = []
  416. for numseen, desc in DirAuth.uploadeddescs[epoch].values():
  417. if numseen >= threshold:
  418. consensusdescs.append(desc)
  419. DirAuth.consensus = Consensus(epoch, consensusdescs)
  420. if network.thenetwork.womode != network.WOMode.VANILLA:
  421. totbw = sum([ d.descdict["bw"] for d in consensusdescs ])
  422. hi = 0
  423. cumbw = 0
  424. snips = []
  425. for d in consensusdescs:
  426. cumbw += d.descdict["bw"]
  427. lo = hi
  428. hi = int((cumbw<<32)/totbw)
  429. snipdict = dict(d.descdict)
  430. del snipdict["bw"]
  431. snipdict["range"] = (lo,hi)
  432. snips.append(SNIP(snipdict))
  433. DirAuth.endive = ENDIVE(epoch, snips)
  434. def epoch_ending(self, epoch):
  435. # Only dirauth 0 actually needs to generate the consensus
  436. # because of the shared class-static state, but everyone has to
  437. # sign it. Note that this code relies on dirauth 0's
  438. # epoch_ending callback being called before any of the other
  439. # dirauths'.
  440. if (epoch+1) not in DirAuth.uploadeddescs:
  441. DirAuth.uploadeddescs[epoch+1] = dict()
  442. if self.me == 0:
  443. self.generate_consensus(epoch+1)
  444. del DirAuth.uploadeddescs[epoch+1]
  445. if network.thenetwork.snipauthmode == \
  446. network.SNIPAuthMode.THRESHSIG:
  447. for s in DirAuth.endive.enddict['snips']:
  448. s.auth(self.sigkey, self.perfstats)
  449. else:
  450. if network.thenetwork.snipauthmode == \
  451. network.SNIPAuthMode.THRESHSIG:
  452. for s in DirAuth.endive.enddict['snips']:
  453. # We're just simulating threshold sigs by having
  454. # only the first dirauth sign, but in reality each
  455. # dirauth would contribute to the signature (at the
  456. # same cost as each one signing), so we'll charge
  457. # their perfstats as well
  458. self.perfstats.sigs += 1
  459. DirAuth.consensus.sign(self.sigkey, self.me, self.perfstats)
  460. if network.thenetwork.womode != network.WOMode.VANILLA:
  461. DirAuth.endive.sign(self.sigkey, self.me, self.perfstats)
  462. def received(self, client, msg):
  463. self.perfstats.bytes_received += msg.size()
  464. if isinstance(msg, DirAuthUploadDescMsg):
  465. # Check the uploaded descriptor for sanity
  466. epoch = msg.desc.descdict['epoch']
  467. if epoch != network.thenetwork.getepoch() + 1:
  468. return
  469. # Store it in the class-static dict
  470. if epoch not in DirAuth.uploadeddescs:
  471. DirAuth.uploadeddescs[epoch] = dict()
  472. descstr = str(msg.desc)
  473. if descstr not in DirAuth.uploadeddescs[epoch]:
  474. DirAuth.uploadeddescs[epoch][descstr] = (1, msg.desc)
  475. else:
  476. DirAuth.uploadeddescs[epoch][descstr] = \
  477. (DirAuth.uploadeddescs[epoch][descstr][0]+1,
  478. DirAuth.uploadeddescs[epoch][descstr][1])
  479. elif isinstance(msg, DirAuthDelDescMsg):
  480. # Check the uploaded descriptor for sanity
  481. epoch = msg.desc.descdict['epoch']
  482. if epoch != network.thenetwork.getepoch() + 1:
  483. return
  484. # Remove it from the class-static dict
  485. if epoch not in DirAuth.uploadeddescs:
  486. return
  487. descstr = str(msg.desc)
  488. if descstr not in DirAuth.uploadeddescs[epoch]:
  489. return
  490. elif DirAuth.uploadeddescs[epoch][descstr][0] == 1:
  491. del DirAuth.uploadeddescs[epoch][descstr]
  492. else:
  493. DirAuth.uploadeddescs[epoch][descstr] = \
  494. (DirAuth.uploadeddescs[epoch][descstr][0]-1,
  495. DirAuth.uploadeddescs[epoch][descstr][1])
  496. elif isinstance(msg, DirAuthGetConsensusMsg):
  497. replymsg = DirAuthConsensusMsg(DirAuth.consensus)
  498. msgsize = replymsg.size()
  499. self.perfstats.bytes_sent += msgsize
  500. client.reply(replymsg)
  501. elif isinstance(msg, DirAuthGetConsensusDiffMsg):
  502. replymsg = DirAuthConsensusDiffMsg(DirAuth.consensus)
  503. msgsize = replymsg.size()
  504. self.perfstats.bytes_sent += msgsize
  505. client.reply(replymsg)
  506. elif isinstance(msg, DirAuthGetENDIVEMsg):
  507. replymsg = DirAuthENDIVEMsg(DirAuth.endive)
  508. msgsize = replymsg.size()
  509. self.perfstats.bytes_sent += msgsize
  510. client.reply(replymsg)
  511. elif isinstance(msg, DirAuthGetENDIVEDiffMsg):
  512. replymsg = DirAuthENDIVEDiffMsg(DirAuth.endive)
  513. msgsize = replymsg.size()
  514. self.perfstats.bytes_sent += msgsize
  515. client.reply(replymsg)
  516. else:
  517. raise TypeError('Not a client-originating DirAuthNetMsg', msg)
  518. def closed(self):
  519. pass
  520. if __name__ == '__main__':
  521. # Start some dirauths
  522. numdirauths = 9
  523. dirauthaddrs = []
  524. for i in range(numdirauths):
  525. dirauth = DirAuth(i, numdirauths)
  526. dirauthaddrs.append(dirauth.netaddr)
  527. for a in dirauthaddrs:
  528. print(a,end=' ')
  529. print()
  530. network.thenetwork.nextepoch()