introducer.client: use integer seqnums, not time-based. Closes #1767.

This stores the sequence number in BASEDIR/announcement-seqnum, and
increments it each time any service is published (every service
announcement is regenerated with the new sequence number). As everyone
knows, time is an illusion, and occasionally goes backwards, so a
counter is generally safer (and reveals less information about the
node).

Later, we'll improve the introducer client to tolerate rollbacks (where,
perhaps due to a VM being restarted from an earlier checkpoint, the
stored sequence number reverts to an earlier version).
This commit is contained in:
Brian Warner
2013-03-18 17:40:56 -07:00
parent ff64a0fef5
commit 3e26c78ee3
4 changed files with 146 additions and 55 deletions

View File

@ -163,13 +163,24 @@ class Client(node.Node, pollmixin.PollMixin):
if webport: if webport:
self.init_web(webport) # strports string self.init_web(webport) # strports string
def _sequencer(self):
seqnum_s = self.get_config_from_file("announcement-seqnum")
if not seqnum_s:
seqnum_s = "0"
seqnum = int(seqnum_s.strip())
seqnum += 1 # increment
self.write_config("announcement-seqnum", "%d\n" % seqnum)
nonce = _make_secret().strip()
return seqnum, nonce
def init_introducer_client(self): def init_introducer_client(self):
self.introducer_furl = self.get_config("client", "introducer.furl") self.introducer_furl = self.get_config("client", "introducer.furl")
ic = IntroducerClient(self.tub, self.introducer_furl, ic = IntroducerClient(self.tub, self.introducer_furl,
self.nickname, self.nickname,
str(allmydata.__full_version__), str(allmydata.__full_version__),
str(self.OLDEST_SUPPORTED_VERSION), str(self.OLDEST_SUPPORTED_VERSION),
self.get_app_versions()) self.get_app_versions(),
self._sequencer)
self.introducer_client = ic self.introducer_client = ic
# hold off on starting the IntroducerClient until our tub has been # hold off on starting the IntroducerClient until our tub has been
# started, so we'll have a useful address on our RemoteReference, so # started, so we'll have a useful address on our RemoteReference, so

View File

@ -50,7 +50,7 @@ class IntroducerClient(service.Service, Referenceable):
def __init__(self, tub, introducer_furl, def __init__(self, tub, introducer_furl,
nickname, my_version, oldest_supported, nickname, my_version, oldest_supported,
app_versions): app_versions, sequencer):
self._tub = tub self._tub = tub
self.introducer_furl = introducer_furl self.introducer_furl = introducer_furl
@ -59,6 +59,7 @@ class IntroducerClient(service.Service, Referenceable):
self._my_version = my_version self._my_version = my_version
self._oldest_supported = oldest_supported self._oldest_supported = oldest_supported
self._app_versions = app_versions self._app_versions = app_versions
self._sequencer = sequencer
self._my_subscriber_info = { "version": 0, self._my_subscriber_info = { "version": 0,
"nickname": self._nickname, "nickname": self._nickname,
@ -69,7 +70,8 @@ class IntroducerClient(service.Service, Referenceable):
self._stub_client = None # for_v1 self._stub_client = None # for_v1
self._stub_client_furl = None self._stub_client_furl = None
self._published_announcements = {} self._outbound_announcements = {} # not signed
self._published_announcements = {} # signed
self._canary = Referenceable() self._canary = Referenceable()
self._publisher = None self._publisher = None
@ -78,14 +80,14 @@ class IntroducerClient(service.Service, Referenceable):
self._subscribed_service_names = set() self._subscribed_service_names = set()
self._subscriptions = set() # requests we've actually sent self._subscriptions = set() # requests we've actually sent
# _current_announcements remembers one announcement per # _inbound_announcements remembers one announcement per
# (servicename,serverid) pair. Anything that arrives with the same # (servicename,serverid) pair. Anything that arrives with the same
# pair will displace the previous one. This stores tuples of # pair will displace the previous one. This stores tuples of
# (unpacked announcement dictionary, verifyingkey, rxtime). The ann # (unpacked announcement dictionary, verifyingkey, rxtime). The ann
# dicts can be compared for equality to distinguish re-announcement # dicts can be compared for equality to distinguish re-announcement
# from updates. It also provides memory for clients who subscribe # from updates. It also provides memory for clients who subscribe
# after startup. # after startup.
self._current_announcements = {} self._inbound_announcements = {}
self.encoding_parameters = None self.encoding_parameters = None
@ -154,7 +156,7 @@ class IntroducerClient(service.Service, Referenceable):
self._local_subscribers.append( (service_name,cb,args,kwargs) ) self._local_subscribers.append( (service_name,cb,args,kwargs) )
self._subscribed_service_names.add(service_name) self._subscribed_service_names.add(service_name)
self._maybe_subscribe() self._maybe_subscribe()
for index,(ann,key_s,when) in self._current_announcements.items(): for index,(ann,key_s,when) in self._inbound_announcements.items():
servicename = index[0] servicename = index[0]
if servicename == service_name: if servicename == service_name:
eventually(cb, key_s, ann, *args, **kwargs) eventually(cb, key_s, ann, *args, **kwargs)
@ -201,9 +203,10 @@ class IntroducerClient(service.Service, Referenceable):
d.addCallback(_publish_stub_client) d.addCallback(_publish_stub_client)
return d return d
def create_announcement(self, service_name, ann, signing_key, _mod=None): def create_announcement_dict(self, service_name, ann):
full_ann = { "version": 0, ann_d = { "version": 0,
"seqnum": time.time(), # "seqnum" and "nonce" will be populated with new values in
# publish(), each time we make a change
"nickname": self._nickname, "nickname": self._nickname,
"app-versions": self._app_versions, "app-versions": self._app_versions,
"my-version": self._my_version, "my-version": self._my_version,
@ -211,13 +214,21 @@ class IntroducerClient(service.Service, Referenceable):
"service-name": service_name, "service-name": service_name,
} }
full_ann.update(ann) ann_d.update(ann)
if _mod: return ann_d
full_ann = _mod(full_ann) # for unit tests
return sign_to_foolscap(full_ann, signing_key)
def publish(self, service_name, ann, signing_key=None): def publish(self, service_name, ann, signing_key=None):
ann_t = self.create_announcement(service_name, ann, signing_key) # we increment the seqnum every time we publish something new
current_seqnum, current_nonce = self._sequencer()
ann_d = self.create_announcement_dict(service_name, ann)
self._outbound_announcements[service_name] = ann_d
# publish all announcements with the new seqnum and nonce
for service_name,ann_d in self._outbound_announcements.items():
ann_d["seqnum"] = current_seqnum
ann_d["nonce"] = current_nonce
ann_t = sign_to_foolscap(ann_d, signing_key)
self._published_announcements[service_name] = ann_t self._published_announcements[service_name] = ann_t
self._maybe_publish() self._maybe_publish()
@ -299,8 +310,8 @@ class IntroducerClient(service.Service, Referenceable):
index = make_index(ann, key_s) index = make_index(ann, key_s)
# is this announcement a duplicate? # is this announcement a duplicate?
if (index in self._current_announcements if (index in self._inbound_announcements
and self._current_announcements[index][0] == ann): and self._inbound_announcements[index][0] == ann):
self.log(format="reannouncement for [%(service)s]:%(description)s, ignoring", self.log(format="reannouncement for [%(service)s]:%(description)s, ignoring",
service=service_name, description=description, service=service_name, description=description,
parent=lp2, level=log.UNUSUAL, umid="B1MIdA") parent=lp2, level=log.UNUSUAL, umid="B1MIdA")
@ -308,12 +319,13 @@ class IntroducerClient(service.Service, Referenceable):
return return
# does it update an existing one? # does it update an existing one?
if index in self._current_announcements: if index in self._inbound_announcements:
old,_,_ = self._current_announcements[index] old,_,_ = self._inbound_announcements[index]
if "seqnum" in old: if "seqnum" in old:
# must beat previous sequence number to replace # must beat previous sequence number to replace
if "seqnum" not in ann: if ("seqnum" not in ann
self.log("not replacing old announcement, no seqnum: %s" or not isinstance(ann["seqnum"], (int,long))):
self.log("not replacing old announcement, no valid seqnum: %s"
% (ann,), % (ann,),
parent=lp2, level=log.NOISY, umid="zFGH3Q") parent=lp2, level=log.NOISY, umid="zFGH3Q")
return return
@ -335,7 +347,7 @@ class IntroducerClient(service.Service, Referenceable):
self.log("new announcement[%s]" % service_name, self.log("new announcement[%s]" % service_name,
parent=lp2, level=log.NOISY) parent=lp2, level=log.NOISY)
self._current_announcements[index] = (ann, key_s, time.time()) self._inbound_announcements[index] = (ann, key_s, time.time())
# note: we never forget an index, but we might update its value # note: we never forget an index, but we might update its value
for (service_name2,cb,args,kwargs) in self._local_subscribers: for (service_name2,cb,args,kwargs) in self._local_subscribers:

View File

@ -220,8 +220,9 @@ class IntroducerService(service.MultiService, Referenceable):
else: else:
if "seqnum" in old_ann: if "seqnum" in old_ann:
# must beat previous sequence number to replace # must beat previous sequence number to replace
if "seqnum" not in ann: if ("seqnum" not in ann
self.log("not replacing old ann, no seqnum", or not isinstance(ann["seqnum"], (int,long))):
self.log("not replacing old ann, no valid seqnum",
level=log.NOISY, umid="ySbaVw") level=log.NOISY, umid="ySbaVw")
self._debug_counts["inbound_no_seqnum"] += 1 self._debug_counts["inbound_no_seqnum"] += 1
return return

View File

@ -1,5 +1,5 @@
import os, re import os, re, itertools
from base64 import b32decode from base64 import b32decode
import simplejson import simplejson
@ -20,7 +20,8 @@ from allmydata.introducer import old
# test compatibility with old introducer .tac files # test compatibility with old introducer .tac files
from allmydata.introducer import IntroducerNode from allmydata.introducer import IntroducerNode
from allmydata.web import introweb from allmydata.web import introweb
from allmydata.util import pollmixin, keyutil, idlib from allmydata.client import Client as TahoeClient
from allmydata.util import pollmixin, keyutil, idlib, fileutil
import allmydata.test.common_util as testutil import allmydata.test.common_util as testutil
class LoggingMultiService(service.MultiService): class LoggingMultiService(service.MultiService):
@ -54,7 +55,7 @@ class Introducer(ServiceMixin, unittest.TestCase, pollmixin.PollMixin):
def test_create(self): def test_create(self):
ic = IntroducerClient(None, "introducer.furl", u"my_nickname", ic = IntroducerClient(None, "introducer.furl", u"my_nickname",
"my_version", "oldest_version", {}) "my_version", "oldest_version", {}, fakeseq)
self.failUnless(isinstance(ic, IntroducerClient)) self.failUnless(isinstance(ic, IntroducerClient))
def test_listen(self): def test_listen(self):
@ -86,12 +87,12 @@ class Introducer(ServiceMixin, unittest.TestCase, pollmixin.PollMixin):
i = IntroducerService() i = IntroducerService()
ic = IntroducerClient(None, ic = IntroducerClient(None,
"introducer.furl", u"my_nickname", "introducer.furl", u"my_nickname",
"my_version", "oldest_version", {}) "my_version", "oldest_version", {}, fakeseq)
sk_s, vk_s = keyutil.make_keypair() sk_s, vk_s = keyutil.make_keypair()
sk, _ignored = keyutil.parse_privkey(sk_s) sk, _ignored = keyutil.parse_privkey(sk_s)
keyid = keyutil.remove_prefix(vk_s, "pub-v0-") keyid = keyutil.remove_prefix(vk_s, "pub-v0-")
furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short") furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short")
ann_t = ic.create_announcement("storage", make_ann(furl1), sk) ann_t = make_ann_t(ic, furl1, sk, 1)
i.remote_publish_v2(ann_t, Referenceable()) i.remote_publish_v2(ann_t, Referenceable())
announcements = i.get_announcements() announcements = i.get_announcements()
self.failUnlessEqual(len(announcements), 1) self.failUnlessEqual(len(announcements), 1)
@ -112,24 +113,30 @@ class Introducer(ServiceMixin, unittest.TestCase, pollmixin.PollMixin):
self.failUnlessEqual(ann2_out["anonymous-storage-FURL"], furl2) self.failUnlessEqual(ann2_out["anonymous-storage-FURL"], furl2)
def fakeseq():
return 1, "nonce"
seqnum_counter = itertools.count(1)
def realseq():
return seqnum_counter.next(), str(os.randint(1,100000))
def make_ann(furl): def make_ann(furl):
ann = { "anonymous-storage-FURL": furl, ann = { "anonymous-storage-FURL": furl,
"permutation-seed-base32": get_tubid_string(furl) } "permutation-seed-base32": get_tubid_string(furl) }
return ann return ann
def make_ann_t(ic, furl, privkey, seqnum): def make_ann_t(ic, furl, privkey, seqnum):
def mod(ann): ann_d = ic.create_announcement_dict("storage", make_ann(furl))
ann["seqnum"] = seqnum ann_d["seqnum"] = seqnum
if seqnum is None: ann_d["nonce"] = "nonce"
del ann["seqnum"] ann_t = sign_to_foolscap(ann_d, privkey)
return ann return ann_t
return ic.create_announcement("storage", make_ann(furl), privkey, mod)
class Client(unittest.TestCase): class Client(unittest.TestCase):
def test_duplicate_receive_v1(self): def test_duplicate_receive_v1(self):
ic = IntroducerClient(None, ic = IntroducerClient(None,
"introducer.furl", u"my_nickname", "introducer.furl", u"my_nickname",
"my_version", "oldest_version", {}) "my_version", "oldest_version", {}, fakeseq)
announcements = [] announcements = []
ic.subscribe_to("storage", ic.subscribe_to("storage",
lambda key_s,ann: announcements.append(ann)) lambda key_s,ann: announcements.append(ann))
@ -178,12 +185,12 @@ class Client(unittest.TestCase):
def test_duplicate_receive_v2(self): def test_duplicate_receive_v2(self):
ic1 = IntroducerClient(None, ic1 = IntroducerClient(None,
"introducer.furl", u"my_nickname", "introducer.furl", u"my_nickname",
"ver23", "oldest_version", {}) "ver23", "oldest_version", {}, fakeseq)
# we use a second client just to create a different-looking # we use a second client just to create a different-looking
# announcement # announcement
ic2 = IntroducerClient(None, ic2 = IntroducerClient(None,
"introducer.furl", u"my_nickname", "introducer.furl", u"my_nickname",
"ver24","oldest_version",{}) "ver24","oldest_version",{}, fakeseq)
announcements = [] announcements = []
def _received(key_s, ann): def _received(key_s, ann):
announcements.append( (key_s, ann) ) announcements.append( (key_s, ann) )
@ -286,7 +293,7 @@ class Client(unittest.TestCase):
# not replace the other) # not replace the other)
ic = IntroducerClient(None, ic = IntroducerClient(None,
"introducer.furl", u"my_nickname", "introducer.furl", u"my_nickname",
"my_version", "oldest_version", {}) "my_version", "oldest_version", {}, fakeseq)
announcements = [] announcements = []
ic.subscribe_to("storage", ic.subscribe_to("storage",
lambda key_s,ann: announcements.append(ann)) lambda key_s,ann: announcements.append(ann))
@ -295,7 +302,7 @@ class Client(unittest.TestCase):
keyid = keyutil.remove_prefix(vk_s, "pub-v0-") keyid = keyutil.remove_prefix(vk_s, "pub-v0-")
furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short") furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short")
furl2 = "pb://%s@127.0.0.1:36106/swissnum" % keyid furl2 = "pb://%s@127.0.0.1:36106/swissnum" % keyid
ann_t = ic.create_announcement("storage", make_ann(furl1), sk) ann_t = make_ann_t(ic, furl1, sk, 1)
ic.remote_announce_v2([ann_t]) ic.remote_announce_v2([ann_t])
d = fireEventually() d = fireEventually()
def _then(ign): def _then(ign):
@ -324,7 +331,7 @@ class Server(unittest.TestCase):
i = IntroducerService() i = IntroducerService()
ic1 = IntroducerClient(None, ic1 = IntroducerClient(None,
"introducer.furl", u"my_nickname", "introducer.furl", u"my_nickname",
"ver23", "oldest_version", {}) "ver23", "oldest_version", {}, realseq)
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:36106/gydnp" furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:36106/gydnp"
privkey_s, _ = keyutil.make_keypair() privkey_s, _ = keyutil.make_keypair()
@ -334,6 +341,7 @@ class Server(unittest.TestCase):
ann1_old = make_ann_t(ic1, furl1, privkey, seqnum=9) ann1_old = make_ann_t(ic1, furl1, privkey, seqnum=9)
ann1_new = make_ann_t(ic1, furl1, privkey, seqnum=11) ann1_new = make_ann_t(ic1, furl1, privkey, seqnum=11)
ann1_noseqnum = make_ann_t(ic1, furl1, privkey, seqnum=None) ann1_noseqnum = make_ann_t(ic1, furl1, privkey, seqnum=None)
ann1_badseqnum = make_ann_t(ic1, furl1, privkey, seqnum="not an int")
i.remote_publish_v2(ann1, None) i.remote_publish_v2(ann1, None)
all = i.get_announcements() all = i.get_announcements()
@ -385,6 +393,16 @@ class Server(unittest.TestCase):
self.failUnlessEqual(i._debug_counts["inbound_old_replay"], 1) self.failUnlessEqual(i._debug_counts["inbound_old_replay"], 1)
self.failUnlessEqual(i._debug_counts["inbound_update"], 1) self.failUnlessEqual(i._debug_counts["inbound_update"], 1)
i.remote_publish_v2(ann1_badseqnum, None)
all = i.get_announcements()
self.failUnlessEqual(len(all), 1)
self.failUnlessEqual(all[0].announcement["seqnum"], 11)
self.failUnlessEqual(i._debug_counts["inbound_message"], 6)
self.failUnlessEqual(i._debug_counts["inbound_duplicate"], 1)
self.failUnlessEqual(i._debug_counts["inbound_no_seqnum"], 2)
self.failUnlessEqual(i._debug_counts["inbound_old_replay"], 1)
self.failUnlessEqual(i._debug_counts["inbound_update"], 1)
NICKNAME = u"n\u00EDickname-%s" # LATIN SMALL LETTER I WITH ACUTE NICKNAME = u"n\u00EDickname-%s" # LATIN SMALL LETTER I WITH ACUTE
@ -415,7 +433,7 @@ class Queue(SystemTestMixin, unittest.TestCase):
tub2 = Tub() tub2 = Tub()
tub2.setServiceParent(self.parent) tub2.setServiceParent(self.parent)
c = IntroducerClient(tub2, ifurl, c = IntroducerClient(tub2, ifurl,
u"nickname", "version", "oldest", {}) u"nickname", "version", "oldest", {}, fakeseq)
furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short") furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short")
sk_s, vk_s = keyutil.make_keypair() sk_s, vk_s = keyutil.make_keypair()
sk, _ignored = keyutil.parse_privkey(sk_s) sk, _ignored = keyutil.parse_privkey(sk_s)
@ -503,7 +521,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
c = IntroducerClient(tub, self.introducer_furl, c = IntroducerClient(tub, self.introducer_furl,
NICKNAME % str(i), NICKNAME % str(i),
"version", "oldest", "version", "oldest",
{"component": "component-v1"}) {"component": "component-v1"}, fakeseq)
received_announcements[c] = {} received_announcements[c] = {}
def got(key_s_or_tubid, ann, announcements, i): def got(key_s_or_tubid, ann, announcements, i):
if i == 0: if i == 0:
@ -822,7 +840,8 @@ class ClientInfo(unittest.TestCase):
tub = introducer_furl = None tub = introducer_furl = None
app_versions = {"whizzy": "fizzy"} app_versions = {"whizzy": "fizzy"}
client_v2 = IntroducerClient(tub, introducer_furl, NICKNAME % u"v2", client_v2 = IntroducerClient(tub, introducer_furl, NICKNAME % u"v2",
"my_version", "oldest", app_versions) "my_version", "oldest", app_versions,
fakeseq)
#furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum" #furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
#ann_s = make_ann_t(client_v2, furl1, None, 10) #ann_s = make_ann_t(client_v2, furl1, None, 10)
#introducer.remote_publish_v2(ann_s, Referenceable()) #introducer.remote_publish_v2(ann_s, Referenceable())
@ -883,10 +902,11 @@ class Announcements(unittest.TestCase):
tub = introducer_furl = None tub = introducer_furl = None
app_versions = {"whizzy": "fizzy"} app_versions = {"whizzy": "fizzy"}
client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2", client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2",
"my_version", "oldest", app_versions) "my_version", "oldest", app_versions,
fakeseq)
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum" furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
tubid = "62ubehyunnyhzs7r6vdonnm2hpi52w6y" tubid = "62ubehyunnyhzs7r6vdonnm2hpi52w6y"
ann_s0 = make_ann_t(client_v2, furl1, None, 10.0) ann_s0 = make_ann_t(client_v2, furl1, None, 10)
canary0 = Referenceable() canary0 = Referenceable()
introducer.remote_publish_v2(ann_s0, canary0) introducer.remote_publish_v2(ann_s0, canary0)
a = introducer.get_announcements() a = introducer.get_announcements()
@ -904,12 +924,13 @@ class Announcements(unittest.TestCase):
tub = introducer_furl = None tub = introducer_furl = None
app_versions = {"whizzy": "fizzy"} app_versions = {"whizzy": "fizzy"}
client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2", client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2",
"my_version", "oldest", app_versions) "my_version", "oldest", app_versions,
fakeseq)
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum" furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
sk_s, vk_s = keyutil.make_keypair() sk_s, vk_s = keyutil.make_keypair()
sk, _ignored = keyutil.parse_privkey(sk_s) sk, _ignored = keyutil.parse_privkey(sk_s)
pks = keyutil.remove_prefix(vk_s, "pub-") pks = keyutil.remove_prefix(vk_s, "pub-")
ann_t0 = make_ann_t(client_v2, furl1, sk, 10.0) ann_t0 = make_ann_t(client_v2, furl1, sk, 10)
canary0 = Referenceable() canary0 = Referenceable()
introducer.remote_publish_v2(ann_t0, canary0) introducer.remote_publish_v2(ann_t0, canary0)
a = introducer.get_announcements() a = introducer.get_announcements()
@ -941,6 +962,51 @@ class Announcements(unittest.TestCase):
self.failUnlessEqual(a[0].version, "my_version") self.failUnlessEqual(a[0].version, "my_version")
self.failUnlessEqual(a[0].announcement["anonymous-storage-FURL"], furl1) self.failUnlessEqual(a[0].announcement["anonymous-storage-FURL"], furl1)
class ClientSeqnums(unittest.TestCase):
def test_client(self):
basedir = "introducer/ClientSeqnums/test_client"
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, "tahoe.cfg"), "w")
f.write("[client]\n")
f.write("introducer.furl = nope\n")
f.close()
c = TahoeClient(basedir)
ic = c.introducer_client
outbound = ic._outbound_announcements
published = ic._published_announcements
def read_seqnum():
f = open(os.path.join(basedir, "announcement-seqnum"))
seqnum = f.read().strip()
f.close()
return int(seqnum)
ic.publish("sA", {"key": "value1"}, c._server_key)
self.failUnlessEqual(read_seqnum(), 1)
self.failUnless("sA" in outbound)
self.failUnlessEqual(outbound["sA"]["seqnum"], 1)
nonce1 = outbound["sA"]["nonce"]
self.failUnless(isinstance(nonce1, str))
self.failUnlessEqual(simplejson.loads(published["sA"][0]),
outbound["sA"])
# [1] is the signature, [2] is the pubkey
# publishing a second service causes both services to be
# re-published, with the next higher sequence number
ic.publish("sB", {"key": "value2"}, c._server_key)
self.failUnlessEqual(read_seqnum(), 2)
self.failUnless("sB" in outbound)
self.failUnlessEqual(outbound["sB"]["seqnum"], 2)
self.failUnless("sA" in outbound)
self.failUnlessEqual(outbound["sA"]["seqnum"], 2)
nonce2 = outbound["sA"]["nonce"]
self.failUnless(isinstance(nonce2, str))
self.failIfEqual(nonce1, nonce2)
self.failUnlessEqual(simplejson.loads(published["sA"][0]),
outbound["sA"])
self.failUnlessEqual(simplejson.loads(published["sB"][0]),
outbound["sB"])
class TooNewServer(IntroducerService): class TooNewServer(IntroducerService):
VERSION = { "http://allmydata.org/tahoe/protocols/introducer/v999": VERSION = { "http://allmydata.org/tahoe/protocols/introducer/v999":
@ -969,7 +1035,8 @@ class NonV1Server(SystemTestMixin, unittest.TestCase):
tub.setLocation("localhost:%d" % portnum) tub.setLocation("localhost:%d" % portnum)
c = IntroducerClient(tub, self.introducer_furl, c = IntroducerClient(tub, self.introducer_furl,
u"nickname-client", "version", "oldest", {}) u"nickname-client", "version", "oldest", {},
fakeseq)
announcements = {} announcements = {}
def got(key_s, ann): def got(key_s, ann):
announcements[key_s] = ann announcements[key_s] = ann