From d05049927fc7b90ca3b269110de482af7c6fda64 Mon Sep 17 00:00:00 2001 From: Philip Feairheller Date: Mon, 15 Jul 2024 14:19:20 -0700 Subject: [PATCH] - A new kli command to send an `/introduce` message with an AID and OOBI. (#817) - Bug fix to watching to check against the correct key state. - Logging additions, fixes and typo fixes. Signed-off-by: pfeairheller --- src/keri/app/cli/commands/introduce.py | 137 ++++++++++++++++++ .../app/cli/commands/watcher/adjudicate.py | 5 +- src/keri/app/forwarding.py | 2 +- src/keri/app/oobiing.py | 2 +- src/keri/app/watching.py | 2 +- src/keri/core/eventing.py | 7 +- src/keri/core/parsing.py | 6 +- src/keri/core/routing.py | 4 + 8 files changed, 154 insertions(+), 11 deletions(-) create mode 100644 src/keri/app/cli/commands/introduce.py diff --git a/src/keri/app/cli/commands/introduce.py b/src/keri/app/cli/commands/introduce.py new file mode 100644 index 000000000..f5f5a2ae7 --- /dev/null +++ b/src/keri/app/cli/commands/introduce.py @@ -0,0 +1,137 @@ +# -*- encoding: utf-8 -*- +""" +keri.kli.commands module + +""" +import argparse + +from hio.base import doing + +from keri.core import serdering +from ..common import existing +from ... import habbing, connecting, forwarding + +parser = argparse.ArgumentParser(description='Send an rpy /introduce message to recipient with OOBI') +parser.set_defaults(handler=lambda args: introduce(args)) +parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) +parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', + required=False, default="") +parser.add_argument('--alias', '-a', help='human readable alias for the new identifier prefix', required=True) +parser.add_argument('--passcode', '-p', help='21 character encryption passcode for keystore (is not saved)', + dest="bran", default=None) # passcode => bran +parser.add_argument('--recipient', help='alias or contact to send the introduction to', required=True) +parser.add_argument('--introducee', help='alias or contact of the OOBI to send', required=True) +parser.add_argument("--role", "-r", help="role of oobi to send", required=True) + + +def introduce(args): + """ + Performs a rotation of the identifier of the environment represented by the provided name parameter + + args (parseargs): Command line argument + + """ + name = args.name + alias = args.alias + base = args.base + bran = args.bran + introducee = args.introducee + recipient = args.recipient + role = args.role + + ixnDoer = IntroduceDoer(name=name, base=base, alias=alias, bran=bran, introducee=introducee, recipient=recipient, + role=role,) + + return [ixnDoer] + + +class IntroduceDoer(doing.DoDoer): + """ + + DoDoer that launches Doers needed to introduce one controller of an AID to another + + """ + + def __init__(self, name, base, bran, alias, introducee, recipient, role): + """ + Returns DoDoer with all registered Doers needed to perform an introduction. + + Parameters: + name is human readable str of identifier + """ + + self.alias = alias + self.introducee = introducee + self.recipient = recipient + self.role = role + + self.hby = existing.setupHby(name=name, base=base, bran=bran) + self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer + self.org = connecting.Organizer(hby=self.hby) + self.postman = forwarding.Poster(hby=self.hby) + doers = [self.hbyDoer, self.postman, doing.doify(self.introduceDo)] + + super(IntroduceDoer, self).__init__(doers=doers) + + def introduceDo(self, tymth, tock=0.0, **opts): + """ + Returns: doifiable Doist compatible generator method + Usage: + add result of doify on this method to doers list + """ + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + hab = self.hby.habByName(name=self.alias) + + if self.recipient in self.hby.kevers: + recp = self.recipient + else: + recp = self.org.find("alias", self.recipient) + if len(recp) != 1: + raise ValueError(f"invalid recipient {self.recipient}") + recp = recp[0]['id'] + + if (ihab := self.hby.habByName(self.introducee)) is not None: + introducee = ihab.pre + elif self.introducee in self.hby.habs: + introducee = self.introducee + elif self.introducee in self.hby.kevers: + introducee = self.introducee + else: + introducee = None + results = self.org.find("alias", self.introducee) + for result in results: + if result["alias"] == self.introducee: + introducee = result['id'] + if not introducee: + raise ValueError(f"invalid introducee {self.introducee}") + + oobi = None + for (key,), obr in self.hby.db.roobi.getItemIter(): + if obr.cid == introducee and obr.role == self.role: + oobi = key + + if oobi is None: + raise ValueError(f"Unable to find OOBI with role {self.role} for {introducee}") + + msg = hab.reply(route="/introduce", data=dict( + cid=hab.pre, + oobi=oobi + )) + + serder = serdering.SerderKERI(raw=msg) + atc = msg[serder.size:] + self.postman.send(src=hab.pre, dest=recp, topic="credential", serder=serder, + attachment=atc) + + while not len(self.postman.cues) == 1: + yield self.tock + + print(f"Introduction with OOBI {oobi} sent for {self.introducee} sent to {self.recipient}") + + toRemove = [self.hbyDoer, self.postman] + self.remove(toRemove) + + return diff --git a/src/keri/app/cli/commands/watcher/adjudicate.py b/src/keri/app/cli/commands/watcher/adjudicate.py index 82e368e83..41c9efc9a 100644 --- a/src/keri/app/cli/commands/watcher/adjudicate.py +++ b/src/keri/app/cli/commands/watcher/adjudicate.py @@ -133,10 +133,11 @@ def adjudicate(self, tymth, tock=0.0, **opts): case "keyStateUpdate": ahds = cue["aheads"] - logger.info(f"Threshold ({self.toad}) satisfying number of watchers ({len(ahds)}) are ahead") + print(f"Threshold ({self.toad}) satisfying number of watchers ({len(ahds)}) are ahead") for state in ahds: - logger.info(f"\tWatcher {state.wit} at Seq No. {state.sn} with digest: {state.dig}") + print(f"\tWatcher {state.wit} at Seq No. {state.sn} with digest: {state.dig}") + print("Submitting query to update local copy of latest events.") state = random.choice(ahds) querier = querying.SeqNoQuerier(hby=self.hby, hab=hab, pre=self.watched, sn=state.sn, wits=[state.wit]) diff --git a/src/keri/app/forwarding.py b/src/keri/app/forwarding.py index 2356ebc19..db5818f09 100644 --- a/src/keri/app/forwarding.py +++ b/src/keri/app/forwarding.py @@ -85,7 +85,7 @@ def deliverDo(self, tymth=None, tock=0.0): elif Roles.witness in ends: yield from self.forwardToWitness(hab, ends[Roles.witness], recp=recp, serder=srdr, atc=atc, topic=tpc) else: - logger.info(f"No end roles for {recp} to send evt={recp}") + logger.info(f"No end roles for {recp} to send evt={srdr.said}") continue except kering.ConfigurationError as e: logger.error(f"Error sending to {recp} with ends={ends}. Err={e}") diff --git a/src/keri/app/oobiing.py b/src/keri/app/oobiing.py index 89f87a526..cb8b41d3a 100644 --- a/src/keri/app/oobiing.py +++ b/src/keri/app/oobiing.py @@ -382,7 +382,7 @@ def processReply(self, *, serder, saider, route, cigars=None, tsgs=None, **kwarg aid=aid, osaider=None, cigars=cigars, tsgs=tsgs) if not accepted: - raise UnverifiedReplyError(f"Unverified introduciton reply. {serder.ked}") + raise UnverifiedReplyError(f"Unverified introduction reply. {serder.ked}") obr = basing.OobiRecord(cid=cid, date=dt) self.hby.db.oobis.put(keys=(oobi,), val=obr) diff --git a/src/keri/app/watching.py b/src/keri/app/watching.py index 1016383a4..677ee63f5 100644 --- a/src/keri/app/watching.py +++ b/src/keri/app/watching.py @@ -101,7 +101,7 @@ def adjudicate(self, watched, toad=None): raise ValueError(f"Threshold of {toad} is greater than number watchers {len(watchers)}") states = [] - mystate = self.hab.kever.state() + mystate = self.hab.kevers[watched].state() for watcher in watchers: saider = self.hab.db.knas.get(keys=(watched, watcher)) if saider is None: diff --git a/src/keri/core/eventing.py b/src/keri/core/eventing.py index 7b552b072..9580e8d9b 100644 --- a/src/keri/core/eventing.py +++ b/src/keri/core/eventing.py @@ -2130,8 +2130,8 @@ def deriveBacks(self, serder): Parameters: serder (SerderKeri): instance of current event """ - if serder.ilk not in (Ilks.rot, Ilks.drt): # no changes - return (self.wits, self.cuts, self.adds) + if serder.ilk not in (Ilks.rot, Ilks.drt) or self.sn >= serder.sn: # no changes + return self.wits, self.cuts, self.adds witset = oset(self.wits) cuts = serder.cuts @@ -4170,7 +4170,8 @@ def processReplyEndRole(self, *, serder, saider, route, cigars=None, tsgs=None, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: - raise UnverifiedReplyError(f"Unverified end role reply. {serder.ked}") + logger.debug(f"Unverified end role reply ked={serder.ked}") + raise UnverifiedReplyError(f"Unverified end role reply. {serder.said}") self.updateEnd(keys=keys, saider=saider, allowed=allowed) # update .eans and .ends diff --git a/src/keri/core/parsing.py b/src/keri/core/parsing.py index cd160c235..1cd9b4cd4 100644 --- a/src/keri/core/parsing.py +++ b/src/keri/core/parsing.py @@ -445,7 +445,7 @@ def allParsator(self, ims=None, framed=None, pipeline=None, kvy=None, if logger.isEnabledFor(logging.DEBUG): logger.exception("Parser msg non-extraction error: %s", ex) else: - logger.exception("Parser msg non-extraction error: %s", ex) + logger.error("Parser msg non-extraction error: %s", ex) yield return True @@ -530,7 +530,7 @@ def onceParsator(self, ims=None, framed=None, pipeline=None, kvy=None, if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery msg non-extraction error: %s", ex) else: - logger.exception("Kevery msg non-extraction error: %s", ex) + logger.error("Kevery msg non-extraction error: %s", ex) finally: done = True @@ -620,7 +620,7 @@ def parsator(self, ims=None, framed=None, pipeline=None, kvy=None, tvy=None, if logger.isEnabledFor(logging.DEBUG): logger.exception("Parser msg non-extraction error: %s", ex.args[0]) else: - logger.exception("Parser msg non-extraction error: %s", ex.args[0]) + logger.error("Parser msg non-extraction error: %s", ex.args[0]) yield return True # should never return diff --git a/src/keri/core/routing.py b/src/keri/core/routing.py index 3601529f0..57d1346a6 100644 --- a/src/keri/core/routing.py +++ b/src/keri/core/routing.py @@ -261,6 +261,8 @@ def acceptReply(self, serder, saider, route, aid, osaider=None, for cigar in cigars: # process each couple to verify sig and write to db if cigar.verfer.transferable: # ignore invalid transferable verfers + logger.info("Kevery process: skipped invalid transferable verfers" + " on reply said=", serder.said) continue # skip invalid transferable if not self.lax and cigar.verfer.qb64 in self.prefixes: # own cig @@ -338,6 +340,8 @@ def acceptReply(self, serder, saider, route, aid, osaider=None, if sdig is None: # create cue here to request key state for sprefixer signer # signer's est event not yet in signer's KEL + logger.info("Kevery process: escrowing without key state for signer" + " on reply said=", serder.said) self.escrowReply(serder=serder, saider=saider, dater=dater, route=route, prefixer=prefixer, seqner=seqner, ssaider=ssaider, sigers=sigers)