Browse Source

Simulate consensus diffs

We don't actually compute a diff. If the requestor already has the
previous epoch's diff, they request a consensus diff instead of
a consensus.  They will get a message (DirAuthConsensusDiffMsg or
RelayConsensusDiffMsg) that actually contains the whole consensus, but
we only charge a number of bytes equal to network.P_Delta times the
actual size of the consensus.
Ian Goldberg 4 years ago
parent
commit
aca5053d85
4 changed files with 101 additions and 16 deletions
  1. 35 15
      client.py
  2. 32 0
      dirauth.py
  3. 6 0
      network.py
  4. 28 1
      relay.py

+ 35 - 15
client.py

@@ -27,11 +27,11 @@ class VanillaCreatedExtendedHandler:
         circhandler.add_crypt_layer(enckey, deckey)
         if len(circhandler.circuit_descs) == 0:
             # This was a VanillaCreatedCircuitCell
-            circhandler.replace_celltype_handler( \
+            circhandler.replace_celltype_handler(
                     relay.VanillaCreatedCircuitCell, None)
         else:
             # This was a VanillaExtendedCircuitCell
-            circhandler.replace_celltype_handler( \
+            circhandler.replace_celltype_handler(
                     relay.VanillaExtendedCircuitCell, None)
         circhandler.circuit_descs.append(self.expecteddesc)
 
@@ -42,7 +42,7 @@ class VanillaCreatedExtendedHandler:
 
         nexthop = None
         while nexthop is None:
-            nexthop = self.channelmgr.consensus.select_weighted_relay( \
+            nexthop = self.channelmgr.consensus.select_weighted_relay(
                     self.channelmgr.consensus_cdf)
             if nexthop.descdict['addr'] in \
                     [ desc.descdict['addr'] \
@@ -52,12 +52,12 @@ class VanillaCreatedExtendedHandler:
         # Construct the VanillaExtendCircuitCell
         ntor = relay.NTor(self.channelmgr.perfstats)
         ntor_request = ntor.request()
-        circextendmsg = relay.VanillaExtendCircuitCell( \
+        circextendmsg = relay.VanillaExtendCircuitCell(
                 nexthop.descdict['addr'], ntor_request)
 
         # Set up the reply handler
-        circhandler.replace_celltype_handler( \
-                relay.VanillaExtendedCircuitCell, \
+        circhandler.replace_celltype_handler(
+                relay.VanillaExtendedCircuitCell,
                 VanillaCreatedExtendedHandler(self.channelmgr, ntor, nexthop))
 
         # Send the cell
@@ -122,8 +122,8 @@ class ClientChannelManager(relay.ChannelManager):
         circcreatemsg = relay.VanillaCreateCircuitMsg(circid, ntor_request)
 
         # Set up the reply handler
-        circhandler.replace_celltype_handler( \
-                relay.VanillaCreatedCircuitCell, \
+        circhandler.replace_celltype_handler(
+                relay.VanillaCreatedCircuitCell,
                 VanillaCreatedExtendedHandler(self, ntor, self.guard))
 
         # Send the message
@@ -140,8 +140,9 @@ class ClientChannelManager(relay.ChannelManager):
         """Callback when a NetMsg not specific to a circuit is
         received."""
         print("Client %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
-        if isinstance(msg, relay.RelayConsensusMsg):
-            dirauth.Consensus.verify(msg.consensus, \
+        if isinstance(msg, relay.RelayConsensusMsg) or \
+                instance(msg, relay.RelayConsensusDiffMsg):
+            dirauth.Consensus.verify(msg.consensus,
                     network.thenetwork.dirauthkeys(), self.perfstats)
             self.consensus = msg.consensus
             if network.thenetwork.womode == network.WOMode.VANILLA:
@@ -188,7 +189,23 @@ class Client:
         # we'll need a consensus (uh, oh; in that case, fetch the
         # consensus from a fallback relay).
 
-        self.channelmgr.get_consensus_from_fallbackrelay()
+        guardaddr = self.channelmgr.guardaddr
+        guardchannel = None
+        if guardaddr is not None:
+            try:
+                guardchannel = self.channelmgr.get_channel_to(guardaddr)
+            except network.NetNoServer:
+                guardaddr = None
+
+        if guardchannel is None:
+            self.channelmgr.get_consensus_from_fallbackrelay()
+        else:
+            if self.channelmgr.consensus is not None and \
+                    len(self.channelmgr.consensus.consdict['relays']) > 0:
+                guardchannel.send_msg(relay.RelayGetConsensusDiffMsg())
+            else:
+                guardchannel.send_msg(relay.RelayGetConsensusMsg())
+
         print('client consensus=', self.channelmgr.consensus)
 
     def newepoch(self, epoch):
@@ -288,10 +305,10 @@ if __name__ == '__main__':
         r = clients[0].channelmgr.consensus.select_weighted_relay(clients[0].channelmgr.consensus_cdf)
         print("relay",r.descdict["addr"])
 
-    relays[3].terminate()
-    relaysent += relays[3].perfstats.bytes_sent
-    relayrecv += relays[3].perfstats.bytes_received
-    del relays[3]
+    #relays[3].terminate()
+    #relaysent += relays[3].perfstats.bytes_sent
+    #relayrecv += relays[3].perfstats.bytes_received
+    #del relays[3]
 
     # Tick the epoch
     network.thenetwork.nextepoch()
@@ -302,6 +319,9 @@ if __name__ == '__main__':
         circs.append(circ)
         circ.send_cell(relay.StringCell("hello world circuit %d" % i))
 
+    # Tick the epoch
+    network.thenetwork.nextepoch()
+
     # See what channels exist and do a consistency check
     for r in relays:
         print("%s: %s" % (r.netaddr, [ str(k) + str([ck for ck in r.channelmgr.channels[k].circuithandlers.keys()]) for k in r.channelmgr.channels.keys()]))

+ 32 - 0
dirauth.py

@@ -2,6 +2,7 @@
 
 import random # For simulation, not cryptography!
 import bisect
+import math
 
 import nacl.encoding
 import nacl.signing
@@ -150,6 +151,25 @@ class DirAuthConsensusMsg(DirAuthNetMsg):
     def __init__(self, consensus):
         self.consensus = consensus
 
+class DirAuthGetConsensusDiffMsg(DirAuthNetMsg):
+    """The subclass of DirAuthNetMsg for fetching the consensus, if the
+    requestor already has the previous consensus."""
+
+class DirAuthConsensusDiffMsg(DirAuthNetMsg):
+    """The subclass of DirAuthNetMsg for returning the consensus, if the
+    requestor already has the previous consensus.  We don't _actually_
+    produce the diff at this time; we just charge fewer bytes for this
+    message."""
+
+    def __init__(self, consensus):
+        self.consensus = consensus
+
+    def size(self):
+        if network.symbolic_byte_counters:
+            return super().size()
+        return math.ceil(DirAuthConsensusMsg(self.consensus).size() \
+                            * network.P_Delta)
+
 class DirAuthGetENDIVEMsg(DirAuthNetMsg):
     """The subclass of DirAuthNetMsg for fetching the ENDIVE."""
 
@@ -176,6 +196,11 @@ class DirAuthConnection(network.ClientConnection):
         self.sendmsg(DirAuthGetConsensusMsg())
         return self.consensus
 
+    def getconsensusdiff(self):
+        self.consensus = None
+        self.sendmsg(DirAuthGetConsensusDiffMsg())
+        return self.consensus
+
     def getENDIVE(self):
         self.endive = None
         self.sendmsg(DirAuthGetENDIVEMsg())
@@ -184,6 +209,8 @@ class DirAuthConnection(network.ClientConnection):
     def receivedfromserver(self, msg):
         if isinstance(msg, DirAuthConsensusMsg):
             self.consensus = msg.consensus
+        elif isinstance(msg, DirAuthConsensusDiffMsg):
+            self.consensus = msg.consensus
         elif isinstance(msg, DirAuthENDIVEMsg):
             self.endive = msg.endive
         else:
@@ -295,6 +322,11 @@ class DirAuth(network.Server):
             msgsize = replymsg.size()
             self.perfstats.bytes_sent += msgsize
             client.reply(replymsg)
+        elif isinstance(msg, DirAuthGetConsensusDiffMsg):
+            replymsg = DirAuthConsensusDiffMsg(DirAuth.consensus)
+            msgsize = replymsg.size()
+            self.perfstats.bytes_sent += msgsize
+            client.reply(replymsg)
         elif isinstance(msg, DirAuthGetENDIVEMsg):
             replymsg = DirAuthENDIVEMsg(DirAuth.endive)
             msgsize = replymsg.size()

+ 6 - 0
network.py

@@ -12,6 +12,12 @@ symbolic_byte_counters = False
 if symbolic_byte_counters:
     import sympy
 
+# Network parameters
+
+# On average, how large is a consensus diff as compared to a full
+# consensus?
+P_Delta = 0.019
+
 class WOMode(Enum):
     """The different Walking Onion modes"""
     VANILLA     = 0  # No Walking Onions

+ 28 - 1
relay.py

@@ -27,6 +27,27 @@ class RelayConsensusMsg(RelayNetMsg):
         self.consensus = consensus
 
 
+class RelayGetConsensusDiffMsg(RelayNetMsg):
+    """The subclass of RelayNetMsg for fetching the consensus, if the
+    requestor already has the previous consensus."""
+
+
+class RelayConsensusDiffMsg(RelayNetMsg):
+    """The subclass of RelayNetMsg for returning the consensus, if the
+    requestor already has the previous consensus.  We don't _actually_
+    produce the diff at this time; we just charge fewer bytes for this
+    message."""
+
+    def __init__(self, consensus):
+        self.consensus = consensus
+
+    def size(self):
+        if network.symbolic_byte_counters:
+            return super().size()
+        return math.ceil(RelayConsensusMsg(self.consensus).size() \
+                            * network.P_Delta)
+
+
 class RelayRandomHopMsg(RelayNetMsg):
     """A message used for testing, that hops from relay to relay
     randomly until its TTL expires."""
@@ -481,8 +502,10 @@ class ChannelManager:
             return self.channels[addr]
 
         # Create the new channel
+        print('getting channel from',self.myaddr,'to',addr)
         newchannel = network.thenetwork.connect(self.myaddr, addr,
                 self.perfstats)
+        print('got channel from',self.myaddr,'to',addr)
         self.channels[addr] = newchannel
         newchannel.closer = lambda: self.channels.pop(addr)
         newchannel.channelmgr = self
@@ -522,7 +545,11 @@ class RelayChannelManager(ChannelManager):
         """Download a fresh consensus from a random dirauth."""
         a = random.choice(self.dirauthaddrs)
         c = network.thenetwork.connect(self, a, self.perfstats)
-        self.consensus = c.getconsensus()
+        if self.consensus is not None and \
+                len(self.consensus.consdict['relays']) > 0:
+            self.consensus = c.getconsensusdiff()
+        else:
+            self.consensus = c.getconsensus()
         dirauth.Consensus.verify(self.consensus,
                 network.thenetwork.dirauthkeys(), self.perfstats)
         c.close()