diff --git a/src/keri/app/cli/commands/delegate/confirm.py b/src/keri/app/cli/commands/delegate/confirm.py index a595630e4..374f017ac 100644 --- a/src/keri/app/cli/commands/delegate/confirm.py +++ b/src/keri/app/cli/commands/delegate/confirm.py @@ -130,7 +130,7 @@ def confirmDo(self, tymth, tock=0.0): if isinstance(hab, GroupHab): aids = hab.smids - #seqner = coring.Seqner(sn=eserder.sn) + anchor = dict(i=eserder.ked["i"], s=eserder.snh, d=eserder.said) if self.interact: msg = hab.interact(data=[anchor]) @@ -166,7 +166,7 @@ def confirmDo(self, tymth, tock=0.0): else: cur = hab.kever.sner.num - #seqner = coring.Seqner(sn=eserder.sn) + anchor = dict(i=eserder.ked["i"], s=eserder.snh, d=eserder.said) if self.interact: hab.interact(data=[anchor]) diff --git a/src/keri/core/eventing.py b/src/keri/core/eventing.py index 3e809dd6d..53a59ea1b 100644 --- a/src/keri/core/eventing.py +++ b/src/keri/core/eventing.py @@ -5714,6 +5714,124 @@ def processEscrowUnverNonTrans(self): break key = ekey # setup next while iteration, with key after ekey + def processEscrowDelegables(self): + """ + Process events escrowed by Kever that require delegation. + + Escrowed items are indexed in database table keyed by prefix and + sn with duplicates given by different dig inserted in insertion order. + This allows FIFO processing of events with same prefix and sn but different + digest. + + Uses .db.delegables.add(key, val) which is IOVal with dups. + + Value is dgkey for event stored in .Evt where .Evt has serder.raw of event. + + Steps: + Each pass (walk index table) + For each prefix,sn + For each escrow item dup at prefix,sn: + Get Event + Get and Attach Signatures + Process event as if it came in over the wire + If successful then remove from escrow table + """ + + for (pre, sn), dig in self.db.delegables.getItemIter(): + try: + edig = dig.encode("utf-8") + dgkey = dgKey(pre.encode("utf-8"), edig) + if not (esr := self.db.esrs.get(keys=dgkey)): # get event source, otherwise error + # no local sourde so raise ValidationError which unescrows below + raise ValidationError("Missing escrowed event source " + "at dig = {}.".format(bytes(edig))) + + # check date if expired then remove escrow. + dtb = self.db.getDts(dgkey) + if dtb is None: # othewise is a datetime as bytes + # no date time so raise ValidationError which unescrows below + logger.info("Kevery unescrow error: Missing event datetime" + " at dig = %s", bytes(edig)) + + raise ValidationError("Missing escrowed event datetime " + "at dig = {}.".format(bytes(edig))) + + # do date math here and discard if stale nowIso8601() bytes + dtnow = helping.nowUTC() + dte = helping.fromIso8601(bytes(dtb)) + if (dtnow - dte) > datetime.timedelta(seconds=self.TimeoutOOE): + # escrow stale so raise ValidationError which unescrows below + logger.info("Kevery unescrow error: Stale event escrow " + " at dig = %s", bytes(edig)) + + raise ValidationError("Stale event escrow " + "at dig = {}.".format(bytes(edig))) + + # get the escrowed event using edig + eraw = self.db.getEvt(dgKey(pre, bytes(edig))) + if eraw is None: + # no event so raise ValidationError which unescrows below + logger.info("Kevery unescrow error: Missing event at." + "dig = %s", bytes(edig)) + + raise ValidationError("Missing escrowed evt at dig = {}." + "".format(bytes(edig))) + + eserder = serdering.SerderKERI(raw=bytes(eraw)) # escrowed event + + # get sigs and attach + sigs = self.db.getSigs(dgKey(pre, bytes(edig))) + if not sigs: # otherwise its a list of sigs + # no sigs so raise ValidationError which unescrows below + logger.info("Kevery unescrow error: Missing event sigs at." + "dig = %s", bytes(edig)) + + raise ValidationError("Missing escrowed evt sigs at " + "dig = {}.".format(bytes(edig))) + + sigers = [Siger(qb64b=bytes(sig)) for sig in sigs] + + # get wigs + wigs = self.db.getWigs(dgKey(pre, bytes(edig))) # list of wigs + wigers = [Siger(qb64b=bytes(wig)) for wig in wigs] + + # get delgate seal + couple = self.db.getAes(dgkey) + if couple is not None: # Only try to parse the event if we have the del seal + raw = bytearray(couple) + seqner = coring.Seqner(qb64b=raw, strip=True) + saider = coring.Saider(qb64b=raw) + + # process event + self.processEvent(serder=eserder, sigers=sigers, wigers=wigers, delseqner=seqner, + delsaider=saider, local=esr.local) + else: + raise MissingDelegableApprovalError() + + except MissingDelegableApprovalError as ex: + # still waiting on missing delegation approval + if logger.isEnabledFor(logging.DEBUG): + logger.exception("Kevery unescrow failed: %s", ex.args[0]) + else: + logger.error("Kevery unescrow failed: %s", ex.args[0]) + + except Exception as ex: # log diagnostics errors etc + # error other than out of order so remove from OO escrow + self.db.delegables.rem(keys=(pre, sn,), val=edig) # removes one escrow at key val + if logger.isEnabledFor(logging.DEBUG): + logger.exception("Kevery unescrowed: %s", ex.args[0]) + else: + logger.error("Kevery unescrowed: %s", ex.args[0]) + + else: # unescrow succeeded, remove from escrow + # We don't remove all escrows at pre,sn because some might be + # duplicitous so we process remaining escrows in spite of found + # valid event escrow. + self.db.delegables.rem(keys=(pre, sn,), val=edig) # removes one escrow at key val + logger.info("Kevery unescrow succeeded in valid event: " + "event=%s", eserder.said) + logger.debug(f"event=\n{eserder.pretty()}\n") + def processQueryNotFound(self): """ Process qry events escrowed by Kevery for KELs that have not yet met the criteria of the query. diff --git a/tests/core/test_delegating.py b/tests/core/test_delegating.py index 5d659f5ce..59f4cbecd 100644 --- a/tests/core/test_delegating.py +++ b/tests/core/test_delegating.py @@ -13,6 +13,7 @@ from keri.app import keeping, habbing from keri.db import dbing, basing +from keri.db.dbing import snKey logger = help.ogler.getLogger() @@ -723,6 +724,56 @@ def test_load_event(mockHelpingNowUTC): """End Test""" +def test_delegables_escrow(): + gateSalt = core.Salter(raw=b'0123456789abcdef').qb64 + torSalt = core.Salter(raw=b'0123456789defabc').raw + + with habbing.openHby(name="delegate", temp=True, salt=gateSalt) as gateHby, \ + habbing.openHab(name="delegator", temp=True, salt=torSalt) as (torHby, torHab): + + gateHab = gateHby.makeHab(name="repTest", transferable=True, delpre=torHab.pre) + assert gateHab.pre == "EFqw1EgGdd2B6MgNLJaNO13_JoQpxAtasIjySDzGm9pd" + + gateIcp = gateHab.makeOwnEvent(sn=0) + torKvy = eventing.Kevery(db=torHab.db, lax=False, local=False) + parsing.Parser().parse(ims=bytearray(gateIcp), kvy=torKvy, local=True) + assert gateHab.pre not in torKvy.kevers + assert len(torHab.db.delegables.get(keys=snKey(gateHab.kever.serder.preb, gateHab.kever.serder.sn))) == 1 + + # Now create delegating interaction event + seal = eventing.SealEvent(i=gateHab.pre, + s="0", + d=gateHab.pre) + ixn = torHab.interact(data=[seal._asdict()]) + assert ixn == (b'{"v":"KERI10JSON00013a_","t":"ixn","d":"EPUCIjCibL-VeT3n6PYIkbyP' + b'qpioIFT79NRqxboFv0Os","i":"EJTtW40aDl0aKDZ09v-o6uDz_VwLJGplp6WTI' + b'BGCoVog","s":"1","p":"EJTtW40aDl0aKDZ09v-o6uDz_VwLJGplp6WTIBGCoV' + b'og","a":[{"i":"EFqw1EgGdd2B6MgNLJaNO13_JoQpxAtasIjySDzGm9pd","s"' + b':"0","d":"EFqw1EgGdd2B6MgNLJaNO13_JoQpxAtasIjySDzGm9pd"}]}-AABAA' + b'BRR9HDRx_7KdWJ7uokLzREP3c1Hg7Grq5fwoGl_EXA-reR05aYPjDdZ4CIZTnqDo' + b'EN2hqNbHfq4zMaDlR8Ja4D') + + # Make sure that our anchoring ixn event is in our own KEL + assert torHab.kever.sn == 1 + + # Place the anchor seal in the database... this will be retrieved from the fully committed delegate event + serder = torHab.kever.serder + seqner = coring.Seqner(sn=serder.sn) + couple = seqner.qb64b + serder.saidb + dgkey = dbing.dgKey(gateHab.kever.prefixer.qb64b, gateHab.kever.serder.saidb) + torHab.db.setAes(dgkey, couple) # authorizer event seal (delegator/issuer) + + # delegate still not kevers + assert gateHab.pre not in torKvy.kevers + assert len(torHab.db.delegables.get(keys=snKey(gateHab.kever.serder.preb, gateHab.kever.serder.sn))) == 1 + + # run the delegables escrow processor to make get delegate in our Kevers + torKvy.processEscrowDelegables() + assert len(torHab.db.delegables.get(keys=snKey(gateHab.kever.serder.preb, gateHab.kever.serder.sn))) == 0 + assert gateHab.pre in torKvy.kevers + + if __name__ == "__main__": test_delegation() test_delegation_supersede() +