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)
         circhandler.add_crypt_layer(enckey, deckey)
         if len(circhandler.circuit_descs) == 0:
         if len(circhandler.circuit_descs) == 0:
             # This was a VanillaCreatedCircuitCell
             # This was a VanillaCreatedCircuitCell
-            circhandler.replace_celltype_handler( \
+            circhandler.replace_celltype_handler(
                     relay.VanillaCreatedCircuitCell, None)
                     relay.VanillaCreatedCircuitCell, None)
         else:
         else:
             # This was a VanillaExtendedCircuitCell
             # This was a VanillaExtendedCircuitCell
-            circhandler.replace_celltype_handler( \
+            circhandler.replace_celltype_handler(
                     relay.VanillaExtendedCircuitCell, None)
                     relay.VanillaExtendedCircuitCell, None)
         circhandler.circuit_descs.append(self.expecteddesc)
         circhandler.circuit_descs.append(self.expecteddesc)
 
 
@@ -42,7 +42,7 @@ class VanillaCreatedExtendedHandler:
 
 
         nexthop = None
         nexthop = None
         while nexthop is None:
         while nexthop is None:
-            nexthop = self.channelmgr.consensus.select_weighted_relay( \
+            nexthop = self.channelmgr.consensus.select_weighted_relay(
                     self.channelmgr.consensus_cdf)
                     self.channelmgr.consensus_cdf)
             if nexthop.descdict['addr'] in \
             if nexthop.descdict['addr'] in \
                     [ desc.descdict['addr'] \
                     [ desc.descdict['addr'] \
@@ -52,12 +52,12 @@ class VanillaCreatedExtendedHandler:
         # Construct the VanillaExtendCircuitCell
         # Construct the VanillaExtendCircuitCell
         ntor = relay.NTor(self.channelmgr.perfstats)
         ntor = relay.NTor(self.channelmgr.perfstats)
         ntor_request = ntor.request()
         ntor_request = ntor.request()
-        circextendmsg = relay.VanillaExtendCircuitCell( \
+        circextendmsg = relay.VanillaExtendCircuitCell(
                 nexthop.descdict['addr'], ntor_request)
                 nexthop.descdict['addr'], ntor_request)
 
 
         # Set up the reply handler
         # Set up the reply handler
-        circhandler.replace_celltype_handler( \
-                relay.VanillaExtendedCircuitCell, \
+        circhandler.replace_celltype_handler(
+                relay.VanillaExtendedCircuitCell,
                 VanillaCreatedExtendedHandler(self.channelmgr, ntor, nexthop))
                 VanillaCreatedExtendedHandler(self.channelmgr, ntor, nexthop))
 
 
         # Send the cell
         # Send the cell
@@ -122,8 +122,8 @@ class ClientChannelManager(relay.ChannelManager):
         circcreatemsg = relay.VanillaCreateCircuitMsg(circid, ntor_request)
         circcreatemsg = relay.VanillaCreateCircuitMsg(circid, ntor_request)
 
 
         # Set up the reply handler
         # Set up the reply handler
-        circhandler.replace_celltype_handler( \
-                relay.VanillaCreatedCircuitCell, \
+        circhandler.replace_celltype_handler(
+                relay.VanillaCreatedCircuitCell,
                 VanillaCreatedExtendedHandler(self, ntor, self.guard))
                 VanillaCreatedExtendedHandler(self, ntor, self.guard))
 
 
         # Send the message
         # Send the message
@@ -140,8 +140,9 @@ class ClientChannelManager(relay.ChannelManager):
         """Callback when a NetMsg not specific to a circuit is
         """Callback when a NetMsg not specific to a circuit is
         received."""
         received."""
         print("Client %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
         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)
                     network.thenetwork.dirauthkeys(), self.perfstats)
             self.consensus = msg.consensus
             self.consensus = msg.consensus
             if network.thenetwork.womode == network.WOMode.VANILLA:
             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
         # we'll need a consensus (uh, oh; in that case, fetch the
         # consensus from a fallback relay).
         # 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)
         print('client consensus=', self.channelmgr.consensus)
 
 
     def newepoch(self, epoch):
     def newepoch(self, epoch):
@@ -288,10 +305,10 @@ if __name__ == '__main__':
         r = clients[0].channelmgr.consensus.select_weighted_relay(clients[0].channelmgr.consensus_cdf)
         r = clients[0].channelmgr.consensus.select_weighted_relay(clients[0].channelmgr.consensus_cdf)
         print("relay",r.descdict["addr"])
         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
     # Tick the epoch
     network.thenetwork.nextepoch()
     network.thenetwork.nextepoch()
@@ -302,6 +319,9 @@ if __name__ == '__main__':
         circs.append(circ)
         circs.append(circ)
         circ.send_cell(relay.StringCell("hello world circuit %d" % i))
         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
     # See what channels exist and do a consistency check
     for r in relays:
     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()]))
         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 random # For simulation, not cryptography!
 import bisect
 import bisect
+import math
 
 
 import nacl.encoding
 import nacl.encoding
 import nacl.signing
 import nacl.signing
@@ -150,6 +151,25 @@ class DirAuthConsensusMsg(DirAuthNetMsg):
     def __init__(self, consensus):
     def __init__(self, consensus):
         self.consensus = 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):
 class DirAuthGetENDIVEMsg(DirAuthNetMsg):
     """The subclass of DirAuthNetMsg for fetching the ENDIVE."""
     """The subclass of DirAuthNetMsg for fetching the ENDIVE."""
 
 
@@ -176,6 +196,11 @@ class DirAuthConnection(network.ClientConnection):
         self.sendmsg(DirAuthGetConsensusMsg())
         self.sendmsg(DirAuthGetConsensusMsg())
         return self.consensus
         return self.consensus
 
 
+    def getconsensusdiff(self):
+        self.consensus = None
+        self.sendmsg(DirAuthGetConsensusDiffMsg())
+        return self.consensus
+
     def getENDIVE(self):
     def getENDIVE(self):
         self.endive = None
         self.endive = None
         self.sendmsg(DirAuthGetENDIVEMsg())
         self.sendmsg(DirAuthGetENDIVEMsg())
@@ -184,6 +209,8 @@ class DirAuthConnection(network.ClientConnection):
     def receivedfromserver(self, msg):
     def receivedfromserver(self, msg):
         if isinstance(msg, DirAuthConsensusMsg):
         if isinstance(msg, DirAuthConsensusMsg):
             self.consensus = msg.consensus
             self.consensus = msg.consensus
+        elif isinstance(msg, DirAuthConsensusDiffMsg):
+            self.consensus = msg.consensus
         elif isinstance(msg, DirAuthENDIVEMsg):
         elif isinstance(msg, DirAuthENDIVEMsg):
             self.endive = msg.endive
             self.endive = msg.endive
         else:
         else:
@@ -295,6 +322,11 @@ class DirAuth(network.Server):
             msgsize = replymsg.size()
             msgsize = replymsg.size()
             self.perfstats.bytes_sent += msgsize
             self.perfstats.bytes_sent += msgsize
             client.reply(replymsg)
             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):
         elif isinstance(msg, DirAuthGetENDIVEMsg):
             replymsg = DirAuthENDIVEMsg(DirAuth.endive)
             replymsg = DirAuthENDIVEMsg(DirAuth.endive)
             msgsize = replymsg.size()
             msgsize = replymsg.size()

+ 6 - 0
network.py

@@ -12,6 +12,12 @@ symbolic_byte_counters = False
 if symbolic_byte_counters:
 if symbolic_byte_counters:
     import sympy
     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):
 class WOMode(Enum):
     """The different Walking Onion modes"""
     """The different Walking Onion modes"""
     VANILLA     = 0  # No Walking Onions
     VANILLA     = 0  # No Walking Onions

+ 28 - 1
relay.py

@@ -27,6 +27,27 @@ class RelayConsensusMsg(RelayNetMsg):
         self.consensus = consensus
         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):
 class RelayRandomHopMsg(RelayNetMsg):
     """A message used for testing, that hops from relay to relay
     """A message used for testing, that hops from relay to relay
     randomly until its TTL expires."""
     randomly until its TTL expires."""
@@ -481,8 +502,10 @@ class ChannelManager:
             return self.channels[addr]
             return self.channels[addr]
 
 
         # Create the new channel
         # Create the new channel
+        print('getting channel from',self.myaddr,'to',addr)
         newchannel = network.thenetwork.connect(self.myaddr, addr,
         newchannel = network.thenetwork.connect(self.myaddr, addr,
                 self.perfstats)
                 self.perfstats)
+        print('got channel from',self.myaddr,'to',addr)
         self.channels[addr] = newchannel
         self.channels[addr] = newchannel
         newchannel.closer = lambda: self.channels.pop(addr)
         newchannel.closer = lambda: self.channels.pop(addr)
         newchannel.channelmgr = self
         newchannel.channelmgr = self
@@ -522,7 +545,11 @@ class RelayChannelManager(ChannelManager):
         """Download a fresh consensus from a random dirauth."""
         """Download a fresh consensus from a random dirauth."""
         a = random.choice(self.dirauthaddrs)
         a = random.choice(self.dirauthaddrs)
         c = network.thenetwork.connect(self, a, self.perfstats)
         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,
         dirauth.Consensus.verify(self.consensus,
                 network.thenetwork.dirauthkeys(), self.perfstats)
                 network.thenetwork.dirauthkeys(), self.perfstats)
         c.close()
         c.close()